diff options
Diffstat (limited to 'include/ws_core.inc.php')
-rw-r--r-- | include/ws_core.inc.php | 399 |
1 files changed, 253 insertions, 146 deletions
diff --git a/include/ws_core.inc.php b/include/ws_core.inc.php index 3e1db6647..a8c1b7f76 100644 --- a/include/ws_core.inc.php +++ b/include/ws_core.inc.php @@ -2,7 +2,7 @@ // +-----------------------------------------------------------------------+ // | Piwigo - a PHP based photo gallery | // +-----------------------------------------------------------------------+ -// | Copyright(C) 2008-2013 Piwigo Team http://piwigo.org | +// | Copyright(C) 2008-2014 Piwigo Team http://piwigo.org | // | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net | // | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick | // +-----------------------------------------------------------------------+ @@ -35,12 +35,18 @@ define( 'WS_PARAM_ACCEPT_ARRAY', 0x010000 ); define( 'WS_PARAM_FORCE_ARRAY', 0x030000 ); define( 'WS_PARAM_OPTIONAL', 0x040000 ); +define( 'WS_TYPE_BOOL', 0x01 ); +define( 'WS_TYPE_INT', 0x02 ); +define( 'WS_TYPE_FLOAT', 0x04 ); +define( 'WS_TYPE_POSITIVE', 0x10 ); +define( 'WS_TYPE_NOTNULL', 0x20 ); +define( 'WS_TYPE_ID', WS_TYPE_INT | WS_TYPE_POSITIVE | WS_TYPE_NOTNULL); + define( 'WS_ERR_INVALID_METHOD', 501 ); define( 'WS_ERR_MISSING_PARAM', 1002 ); define( 'WS_ERR_INVALID_PARAM', 1003 ); define( 'WS_XML_ATTRIBUTES', 'attributes_xml_'); -define( 'WS_XML_CONTENT', 'content_xml_'); /** * PwgError object can be returned from any web service function implementation. @@ -98,7 +104,6 @@ class PwgNamedArray class PwgNamedStruct { /*private*/ var $_content; - /*private*/ var $_name; /*private*/ var $_xmlAttributes; /** @@ -110,9 +115,8 @@ class PwgNamedStruct * encoded as xml attributes (if null - automatically prefer xml attributes * whenever possible) */ - function PwgNamedStruct($name, $content, $xmlAttributes=null, $xmlElements=null ) + function PwgNamedStruct($content, $xmlAttributes=null, $xmlElements=null ) { - $this->_name = $name; $this->_content = $content; if ( isset($xmlAttributes) ) { @@ -139,28 +143,28 @@ class PwgNamedStruct /** * Abstract base class for request handlers. */ -class PwgRequestHandler +abstract class PwgRequestHandler { /** Virtual abstract method. Decodes the request (GET or POST) handles the * method invocation as well as response sending. */ - function handleRequest(&$server) { assert(false); } + abstract function handleRequest(&$service); } /** * * Base class for web service response encoder. */ -class PwgResponseEncoder +abstract class PwgResponseEncoder { /** encodes the web service response to the appropriate output format * @param response mixed the unencoded result of a service method call */ - function encodeResponse($response) { assert(false); } + abstract function encodeResponse($response); /** default "Content-Type" http header for this kind of response format */ - function getContentType() { assert(false); } + abstract function getContentType(); /** * returns true if the parameter is a 'struct' (php array type whose keys are @@ -182,115 +186,42 @@ class PwgResponseEncoder * removes all XML formatting from $response (named array, named structs, etc) * usually called by every response encoder, except rest xml. */ - static function flattenResponse(&$response) + static function flattenResponse(&$value) { - PwgResponseEncoder::_mergeAttributesAndContent($response); - PwgResponseEncoder::_removeNamedArray($response); - PwgResponseEncoder::_removeNamedStruct($response); - if (is_array($response)) - { // need to call 2 times (first time might add new arrays) - array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') ); - array_walk_recursive($response, array('PwgResponseEncoder', '_remove_named_callback') ); - } -//print_r($response); - PwgResponseEncoder::_mergeAttributesAndContent($response); - } - - private static function _remove_named_callback(&$value, $key) - { - do - { - $changed = 0; - $changed += PwgResponseEncoder::_removeNamedArray($value); - $changed += PwgResponseEncoder::_removeNamedStruct($value); - // print_r('walk '.$key."<br>\n"); - } - while ($changed); + self::flatten($value); } - private static function _mergeAttributesAndContent(&$value) + private static function flatten(&$value) { - if ( !is_array($value) ) - return; -/* $first_key = ''; - if (count($value)) { $ak = array_keys($value); $first_key = $ak[0]; } - - print_r( '_mergeAttributesAndContent is_struct='.PwgResponseEncoder::is_struct($value) - .' count='.count($value) - .' first_key='.$first_key - ."<br>\n" - );*/ - $ret = 0; - if (PwgResponseEncoder::is_struct($value)) + if (is_object($value)) { - if ( isset($value[WS_XML_ATTRIBUTES]) ) + $class = strtolower( @get_class($value) ); + if ($class == 'pwgnamedarray') { - $value = array_merge( $value, $value[WS_XML_ATTRIBUTES] ); - unset( $value[WS_XML_ATTRIBUTES] ); - $ret=1; + $value = $value->_content; } - if ( isset($value[WS_XML_CONTENT]) ) + if ($class == 'pwgnamedstruct') { - $cont_processed = 0; - if ( count($value)==1 ) - { - $value = $value[WS_XML_CONTENT]; - $cont_processed=1; - } - else - { - if (PwgResponseEncoder::is_struct($value[WS_XML_CONTENT])) - { - $value = array_merge( $value, $value[WS_XML_CONTENT] ); - unset( $value[WS_XML_CONTENT] ); - $cont_processed=1; - } - } - $ret += $cont_processed; - if (!$cont_processed) - { - $value['_content'] = $value[WS_XML_CONTENT]; - unset( $value[WS_XML_CONTENT] ); - $ret++; - } + $value = $value->_content; } } - foreach ($value as $key=>$v) + if (!is_array($value)) + return; + + if (self::is_struct($value)) { - if ( PwgResponseEncoder::_mergeAttributesAndContent($v) ) + if ( isset($value[WS_XML_ATTRIBUTES]) ) { - $value[$key]=$v; - $ret++; + $value = array_merge( $value, $value[WS_XML_ATTRIBUTES] ); + unset( $value[WS_XML_ATTRIBUTES] ); } } - return $ret; - } - - private static function _removeNamedArray(&$value) - { - if ( strtolower( @get_class($value) ) =='pwgnamedarray') - { - $value = $value->_content; - return 1; - } - return 0; - } - private static function _removeNamedStruct(&$value) - { - if ( strtolower( @get_class($value) ) =='pwgnamedstruct') + foreach ($value as $key=>&$v) { - if ( isset($value->_content['']) ) - { - $unknown = $value->_content['']; - unset( $value->_content[''] ); - $value->_content[$value->_name] = $unknown; - } - $value = $value->_content; - return 1; + self::flatten($v); } - return 0; } } @@ -349,12 +280,16 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF return; } - $this->addMethod('reflection.getMethodList', - array('PwgServer', 'ws_getMethodList'), - null, '' ); - $this->addMethod('reflection.getMethodDetails', + // add reflection methods + $this->addMethod( + 'reflection.getMethodList', + array('PwgServer', 'ws_getMethodList') + ); + $this->addMethod( + 'reflection.getMethodDetails', array('PwgServer', 'ws_getMethodDetails'), - array('methodName'),''); + array('methodName') + ); trigger_action('ws_add_methods', array(&$this) ); uksort( $this->_methods, 'strnatcmp' ); @@ -378,16 +313,22 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF * Registers a web service method. * @param methodName string - the name of the method as seen externally * @param callback mixed - php method to be invoked internally - * @param params array - map of allowed parameter names with optional default - * values and parameter flags. Example of $params: - * array( 'param1' => array('default'=>523, 'flags'=>WS_PARAM_FORCE_ARRAY) ) . - * Possible parameter flags are: - * WS_PARAM_ALLOW_ARRAY - this parameter can be an array - * WS_PARAM_FORCE_ARRAY - if this parameter is scalar, force it to an array - * before invoking the method + * @param params array - map of allowed parameter names with options + * @option mixed default (optional) + * @option int flags (optional) + * possible values: WS_PARAM_ALLOW_ARRAY, WS_PARAM_FORCE_ARRAY, WS_PARAM_OPTIONAL + * @option int type (optional) + * possible values: WS_TYPE_BOOL, WS_TYPE_INT, WS_TYPE_FLOAT, WS_TYPE_ID + * WS_TYPE_POSITIVE, WS_TYPE_NOTNULL + * @option int|float maxValue (optional) * @param description string - a description of the method. + * @param include_file string - a file to be included befaore the callback is executed + * @param options array + * @option bool hidden (optional) - if true, this method won't be visible by reflection.getMethodList + * @option bool admin_only (optional) + * @option bool post_only (optional) */ - function addMethod($methodName, $callback, $params=array(), $description, $include_file='') + function addMethod($methodName, $callback, $params=array(), $description='', $include_file='', $options=array()) { if (!is_array($params)) { @@ -399,25 +340,27 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF $params = array_flip($params); } - foreach( $params as $param=>$options) + foreach( $params as $param=>$data) { - if ( !is_array($options) ) + if ( !is_array($data) ) { - $params[$param] = array('flags'=>0); + $params[$param] = array('flags'=>0,'type'=>0); } else { - $flags = isset($options['flags']) ? $options['flags'] : 0; - if ( array_key_exists('default', $options) ) + if ( !isset($data['flags']) ) + { + $data['flags'] = 0; + } + if ( array_key_exists('default', $data) ) { - $flags |= WS_PARAM_OPTIONAL; + $data['flags'] |= WS_PARAM_OPTIONAL; } - if ( $flags & WS_PARAM_FORCE_ARRAY ) + if ( !isset($data['type']) ) { - $flags |= WS_PARAM_ACCEPT_ARRAY; + $data['type'] = 0; } - $options['flags'] = $flags; - $params[$param] = $options; + $params[$param] = $data; } } @@ -426,6 +369,7 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF 'description' => $description, 'signature' => $params, 'include' => $include_file, + 'options' => $options, ); } @@ -445,13 +389,22 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF $signature = @$this->_methods[$methodName]['signature']; return isset($signature) ? $signature : array(); } + + /** + * @since 2.6 + */ + function getMethodOptions($methodName) + { + $options = @$this->_methods[$methodName]['options']; + return isset($options) ? $options : array(); + } - /*static*/ function isPost() + static function isPost() { return isset($HTTP_RAW_POST_DATA) or !empty($_POST); } - /*static*/ function makeArrayParam(&$param) + static function makeArrayParam(&$param) { if ( $param==null ) { @@ -459,12 +412,100 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF } else { - if (! is_array($param) ) + if ( !is_array($param) ) { $param = array($param); } } } + + static function checkType(&$param, $type, $name) + { + $opts = array(); + $msg = ''; + if ( self::hasFlag($type, WS_TYPE_POSITIVE | WS_TYPE_NOTNULL) ) + { + $opts['options']['min_range'] = 1; + $msg = ' positive and not null'; + } + else if ( self::hasFlag($type, WS_TYPE_POSITIVE) ) + { + $opts['options']['min_range'] = 0; + $msg = ' positive'; + } + + if ( is_array($param) ) + { + if ( self::hasFlag($type, WS_TYPE_BOOL) ) + { + foreach ($param as &$value) + { + if ( ($value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) === null ) + { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must only contain booleans' ); + } + } + unset($value); + } + else if ( self::hasFlag($type, WS_TYPE_INT) ) + { + foreach ($param as &$value) + { + if ( ($value = filter_var($value, FILTER_VALIDATE_INT, $opts)) === false ) + { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must only contain'.$msg.' integers' ); + } + } + unset($value); + } + else if ( self::hasFlag($type, WS_TYPE_FLOAT) ) + { + foreach ($param as &$value) + { + if ( + ($value = filter_var($value, FILTER_VALIDATE_FLOAT)) === false + or ( isset($opts['options']['min_range']) and $value < $opts['options']['min_range'] ) + ) { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must only contain'.$msg.' floats' ); + } + } + unset($value); + } + } + else if ( $param !== '' ) + { + if ( self::hasFlag($type, WS_TYPE_BOOL) ) + { + if ( ($param = filter_var($param, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) === null ) + { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be a boolean' ); + } + } + else if ( self::hasFlag($type, WS_TYPE_INT) ) + { + if ( ($param = filter_var($param, FILTER_VALIDATE_INT, $opts)) === false ) + { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be an'.$msg.' integer' ); + } + } + else if ( self::hasFlag($type, WS_TYPE_FLOAT) ) + { + if ( + ($param = filter_var($param, FILTER_VALIDATE_FLOAT)) === false + or ( isset($opts['options']['min_range']) and $param < $opts['options']['min_range'] ) + ) { + return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be a'.$msg.' float' ); + } + } + } + + return null; + } + + static function hasFlag($val, $flag) + { + return ($val & $flag) == $flag; + } /** * Invokes a registered method. Returns the return of the method (or @@ -476,54 +517,87 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF { $method = @$this->_methods[$methodName]; - if ( $method==null ) + if ( $method == null ) { return new PwgError(WS_ERR_INVALID_METHOD, 'Method name is not valid'); } + + if ( isset($method['options']['post_only']) and $method['options']['post_only'] and !self::isPost() ) + { + return new PwgError(405, 'This method requires HTTP POST'); + } + + if ( isset($method['options']['admin_only']) and $method['options']['admin_only'] and !is_admin() ) + { + return new PwgError(401, 'Access denied'); + } - // parameter check and data coercion ! + // parameter check and data correction $signature = $method['signature']; $missing_params = array(); - foreach($signature as $name=>$options) + + foreach ($signature as $name => $options) { $flags = $options['flags']; + + // parameter not provided in the request if ( !array_key_exists($name, $params) ) - {// parameter not provided in the request - if ( !($flags&WS_PARAM_OPTIONAL) ) + { + if ( !self::hasFlag($flags, WS_PARAM_OPTIONAL) ) { $missing_params[] = $name; } - else if ( array_key_exists('default',$options) ) + else if ( array_key_exists('default', $options) ) { $params[$name] = $options['default']; - if ( ($flags&WS_PARAM_FORCE_ARRAY) ) + if ( self::hasFlag($flags, WS_PARAM_FORCE_ARRAY) ) { - $this->makeArrayParam( $params[$name] ); + self::makeArrayParam($params[$name]); } } } + // parameter provided but empty + else if ( $params[$name]==='' and !self::hasFlag($flags, WS_PARAM_OPTIONAL) ) + { + $missing_params[] = $name; + } + // parameter provided - do some basic checks else - {// parameter provided - do some basic checks + { $the_param = $params[$name]; - if ( is_array($the_param) and ($flags&WS_PARAM_ACCEPT_ARRAY)==0 ) + + if ( is_array($the_param) and !self::hasFlag($flags, WS_PARAM_ACCEPT_ARRAY) ) { return new PwgError(WS_ERR_INVALID_PARAM, $name.' must be scalar' ); } - if ( ($flags&WS_PARAM_FORCE_ARRAY) ) + + if ( self::hasFlag($flags, WS_PARAM_FORCE_ARRAY) ) + { + self::makeArrayParam($the_param); + } + + if ( $options['type'] > 0 ) { - $this->makeArrayParam( $the_param ); + if ( ($ret = self::checkType($the_param, $options['type'], $name)) !== null ) + { + return $ret; + } } + if ( isset($options['maxValue']) and $the_param>$options['maxValue']) { $the_param = $options['maxValue']; } + $params[$name] = $the_param; } } + if (count($missing_params)) { return new PwgError(WS_ERR_MISSING_PARAM, 'Missing parameters: '.implode(',',$missing_params)); } + $result = trigger_event('ws_invoke_allowed', true, $methodName, $params); if ( strtolower( @get_class($result) )!='pwgerror') { @@ -533,6 +607,7 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF } $result = call_user_func_array($method['callback'], array($params, &$this) ); } + return $result; } @@ -541,7 +616,9 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF */ static function ws_getMethodList($params, &$service) { - return array('methods' => new PwgNamedArray( array_keys($service->_methods),'method' ) ); + $methods = array_filter($service->_methods, + create_function('$m', 'return empty($m["options"]["hidden"]) || !$m["options"]["hidden"];')); + return array('methods' => new PwgNamedArray( array_keys($methods),'method' ) ); } /** @@ -550,32 +627,62 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF static function ws_getMethodDetails($params, &$service) { $methodName = $params['methodName']; + if (!$service->hasMethod($methodName)) { - return new PwgError(WS_ERR_INVALID_PARAM, - 'Requested method does not exist'); + return new PwgError(WS_ERR_INVALID_PARAM, 'Requested method does not exist'); } + $res = array( 'name' => $methodName, 'description' => $service->getMethodDescription($methodName), 'params' => array(), + 'options' => $service->getMethodOptions($methodName), ); - $signature = $service->getMethodSignature($methodName); - foreach ($signature as $name => $options) + + foreach ($service->getMethodSignature($methodName) as $name => $options) { $param_data = array( 'name' => $name, - 'optional' => ($options['flags']&WS_PARAM_OPTIONAL)?true:false, - 'acceptArray' => ($options['flags']&WS_PARAM_ACCEPT_ARRAY)?true:false, + 'optional' => self::hasFlag($options['flags'], WS_PARAM_OPTIONAL), + 'acceptArray' => self::hasFlag($options['flags'], WS_PARAM_ACCEPT_ARRAY), + 'type' => 'mixed', ); + if (isset($options['default'])) { $param_data['defaultValue'] = $options['default']; } + if (isset($options['maxValue'])) + { + $param_data['maxValue'] = $options['maxValue']; + } if (isset($options['info'])) { $param_data['info'] = $options['info']; } + + if ( self::hasFlag($options['type'], WS_TYPE_BOOL) ) + { + $param_data['type'] = 'bool'; + } + else if ( self::hasFlag($options['type'], WS_TYPE_INT) ) + { + $param_data['type'] = 'int'; + } + else if ( self::hasFlag($options['type'], WS_TYPE_FLOAT) ) + { + $param_data['type'] = 'float'; + } + if ( self::hasFlag($options['type'], WS_TYPE_POSITIVE) ) + { + $param_data['type'].= ' positive'; + } + if ( self::hasFlag($options['type'], WS_TYPE_NOTNULL) ) + { + $param_data['type'].= ' notnull'; + } + $res['params'][] = $param_data; } return $res; |