Open Monograph Press  3.3.0
PKPComponentRouter.inc.php
1 <?php
2 
48 // The string to be found in the URL to mark this request as a component request
49 define('COMPONENT_ROUTER_PATHINFO_MARKER', '$$$call$$$');
50 
51 // The parameter to be found in the query string for servers with path info disabled
52 define('COMPONENT_ROUTER_PARAMETER_MARKER', 'component');
53 
54 // This is the maximum directory depth allowed within the component directory. Set
55 // it to something reasonable to avoid DoS or overflow attacks
56 define ('COMPONENT_ROUTER_PARTS_MAXDEPTH', 9);
57 
58 // This is the maximum/minimum length of the name of a sub-directory or
59 // handler class name.
60 define ('COMPONENT_ROUTER_PARTS_MAXLENGTH', 50);
61 define ('COMPONENT_ROUTER_PARTS_MINLENGTH', 2);
62 
63 import('lib.pkp.classes.core.PKPRouter');
64 import('classes.core.Request');
65 
67  //
68  // Internal state cache variables
69  // NB: Please do not access directly but
70  // only via their respective getters/setters
71  //
73  var $_component;
75  var $_op;
77  var $_rpcServiceEndpointParts = false;
79  var $_rpcServiceEndpoint = false;
80 
81 
87  function supports($request) {
88  // See whether this looks like a component router request.
89  // NOTE: this is prone to false positives i.e. when a class
90  // name cannot be matched, but this laxity permits plugins to
91  // extend the system by registering against the
92  // LoadComponentHandler hook.
93  return $this->_retrieveServiceEndpointParts($request) !== null;
94  }
95 
106  function getRequestedComponent($request) {
107  if (is_null($this->_component)) {
108  $this->_component = '';
109 
110  // Retrieve the service endpoint parts from the request.
111  if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) {
112  // Endpoint parts cannot be found in the request
113  return '';
114  }
115 
116  // Pop off the operation part
117  array_pop($rpcServiceEndpointParts);
118 
119  // Construct the fully qualified component class name from the rest of it.
120  $handlerClassName = PKPString::camelize(array_pop($rpcServiceEndpointParts), CAMEL_CASE_HEAD_UP).'Handler';
121 
122  // camelize remaining endpoint parts
123  $camelizedRpcServiceEndpointParts = array();
124  foreach ( $rpcServiceEndpointParts as $part) {
125  $camelizedRpcServiceEndpointParts[] = PKPString::camelize($part, CAMEL_CASE_HEAD_DOWN);
126  }
127  $handlerPackage = implode('.', $camelizedRpcServiceEndpointParts);
128 
129  $this->_component = $handlerPackage.'.'.$handlerClassName;
130  }
131 
132  return $this->_component;
133  }
134 
145  function getRequestedOp($request) {
146  if (is_null($this->_op)) {
147  $this->_op = '';
148 
149  // Retrieve the service endpoint parts from the request.
150  if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) {
151  // Endpoint parts cannot be found in the request
152  return '';
153  }
154 
155  // Pop off the operation part
156  $this->_op = PKPString::camelize(array_pop($rpcServiceEndpointParts), CAMEL_CASE_HEAD_DOWN);
157  }
158 
159  return $this->_op;
160  }
161 
170  function &getRpcServiceEndpoint($request) {
171  if ($this->_rpcServiceEndpoint === false) {
172  // We have not yet resolved this request. Mark the
173  // state variable so that we don't try again next
174  // time.
175  $this->_rpcServiceEndpoint = $nullVar = null;
176 
177  // Retrieve requested component operation
178  $op = $this->getRequestedOp($request);
179  assert(!empty($op));
180 
181  //
182  // Component Handler
183  //
184  // Retrieve requested component handler
185  $component = $this->getRequestedComponent($request);
186 
187  $allowedPackages = null;
188 
189  // Give plugins a chance to intervene
190  if (!HookRegistry::call('LoadComponentHandler', array(&$component, &$op))) {
191 
192  if (empty($component)) return $nullVar;
193 
194  // Construct the component handler file name and test its existence.
195  $component = 'controllers.'.$component;
196  $componentFileName = str_replace('.', DIRECTORY_SEPARATOR, $component).'.inc.php';
197  switch (true) {
198  case file_exists($componentFileName):
199  break;
200 
201  case file_exists(PKP_LIB_PATH . DIRECTORY_SEPARATOR . $componentFileName):
202  $component = 'lib.pkp.'.$component;
203  break;
204 
205  default:
206  // Request to non-existent handler
207  return $nullVar;
208  }
209 
210  // We expect the handler to be part of one
211  // of the following packages:
212  $allowedPackages = array(
213  'controllers',
214  'lib.pkp.controllers'
215  );
216  }
217 
218  // A handler at least needs to implement the
219  // following methods:
220  $requiredMethods = array(
221  $op, 'authorize', 'validate', 'initialize'
222  );
223 
224  $componentInstance =& instantiate($component, 'PKPHandler', $allowedPackages, $requiredMethods);
225  if (!is_object($componentInstance)) return $nullVar;
226  $this->setHandler($componentInstance);
227 
228  //
229  // Callable service endpoint
230  //
231  // Construct the callable array
232  $this->_rpcServiceEndpoint = array($componentInstance, $op);
233  }
234 
236  }
237 
238 
239  //
240  // Implement template methods from PKPRouter
241  //
245  function route($request) {
246  // Determine the requested service endpoint.
247  $rpcServiceEndpoint =& $this->getRpcServiceEndpoint($request);
248 
249  // Retrieve RPC arguments from the request.
250  $args = $request->getUserVars();
251  assert(is_array($args));
252 
253  // Remove the caller-parameter (if present)
254  if (isset($args[COMPONENT_ROUTER_PARAMETER_MARKER])) unset($args[COMPONENT_ROUTER_PARAMETER_MARKER]);
255 
256  // Authorize, validate and initialize the request
257  $this->_authorizeInitializeAndCallRequest($rpcServiceEndpoint, $request, $args);
258  }
259 
263  function url($request, $newContext = null, $component = null, $op = null, $path = null,
264  $params = null, $anchor = null, $escape = false) {
265  if (!is_null($path)) {
266  throw new Exception('Path must be null when calling PKPComponentRouter::url()');
267  }
268  $pathInfoEnabled = $request->isPathInfoEnabled();
269 
270  //
271  // Base URL and Context
272  //
273  $baseUrlAndContext = $this->_urlGetBaseAndContext(
274  $request, $this->_urlCanonicalizeNewContext($newContext));
275  $baseUrl = array_shift($baseUrlAndContext);
276  $context = $baseUrlAndContext;
277 
278  //
279  // Component and Operation
280  //
281  // We only support component/op retrieval from the request
282  // if this request is a component request.
283  $currentRequestIsAComponentRequest = is_a($request->getRouter(), 'PKPComponentRouter');
284  if($currentRequestIsAComponentRequest) {
285  if (empty($component)) $component = $this->getRequestedComponent($request);
286  if (empty($op)) $op = $this->getRequestedOp($request);
287  }
288  assert(!empty($component) && !empty($op));
289 
290  // Encode the component and operation
291  $componentParts = explode('.', $component);
292  $componentName = array_pop($componentParts);
293  assert(substr($componentName, -7) == 'Handler');
294  $componentName = PKPString::uncamelize(substr($componentName, 0, -7));
295 
296  // uncamelize the component parts
297  $uncamelizedComponentParts = array();
298  foreach ($componentParts as $part) {
299  $uncamelizedComponentParts[] = PKPString::uncamelize($part);
300  }
301  array_push($uncamelizedComponentParts, $componentName);
302  $opName = PKPString::uncamelize($op);
303 
304  //
305  // Additional query parameters
306  //
307  $additionalParameters = $this->_urlGetAdditionalParameters($request, $params, $escape);
308 
309  //
310  // Anchor
311  //
312  $anchor = (empty($anchor) ? '' : '#'.rawurlencode($anchor));
313 
314  //
315  // Assemble URL
316  //
317  if ($pathInfoEnabled) {
318  // If path info is enabled then context, page,
319  // operation and additional path go into the
320  // path info.
321  $pathInfoArray = array_merge(
322  $context,
323  array(COMPONENT_ROUTER_PATHINFO_MARKER),
324  $uncamelizedComponentParts,
325  array($opName)
326  );
327 
328  // Query parameters
329  $queryParametersArray = $additionalParameters;
330  } else {
331  // If path info is disabled then context, page,
332  // operation and additional path are encoded as
333  // query parameters.
334  $pathInfoArray = array();
335 
336  // Query parameters
337  $queryParametersArray = array_merge(
338  $context,
339  array(
340  COMPONENT_ROUTER_PARAMETER_MARKER.'='.implode('.', $uncamelizedComponentParts),
341  "op=$opName"
342  ),
343  $additionalParameters
344  );
345  }
346 
347  return $this->_urlFromParts($baseUrl, $pathInfoArray, $queryParametersArray, $anchor, $escape);
348  }
349 
353  function handleAuthorizationFailure($request, $authorizationMessage) {
354  // Translate the authorization error message.
355  if (defined('LOCALE_COMPONENT_APP_COMMON')) {
356  AppLocale::requireComponents(LOCALE_COMPONENT_APP_COMMON);
357  }
358  AppLocale::requireComponents(LOCALE_COMPONENT_PKP_USER);
359  $translatedAuthorizationMessage = __($authorizationMessage);
360 
361  // Add the router name and operation if show_stacktrace is enabled.
362  if (Config::getVar('debug', 'show_stacktrace')) {
363  $url = $request->getRequestUrl();
364  $queryString = $request->getQueryString();
365  if ($queryString) $queryString = '?'.$queryString;
366  $translatedAuthorizationMessage .= ' ['.$url.$queryString.']';
367  }
368  // Return a JSON error message.
369  import('lib.pkp.classes.core.JSONMessage');
370  return new JSONMessage(false, $translatedAuthorizationMessage);
371  }
372 
373 
374  //
375  // Private helper methods
376  //
385  function _getValidatedServiceEndpointParts($request) {
386  if ($this->_rpcServiceEndpointParts === false) {
387  // Mark the internal state variable so this
388  // will not be called again.
389  $this->_rpcServiceEndpointParts = null;
390 
391  // Retrieve service endpoint parts from the request.
392  if (is_null($rpcServiceEndpointParts = $this->_retrieveServiceEndpointParts($request))) {
393  // This is not an RPC request
394  return null;
395  }
396 
397  // Validate the service endpoint parts.
398  if (is_null($rpcServiceEndpointParts = $this->_validateServiceEndpointParts($rpcServiceEndpointParts))) {
399  // Invalid request
400  return null;
401  }
402 
403  // Assign the validated service endpoint parts
404  $this->_rpcServiceEndpointParts = $rpcServiceEndpointParts;
405  }
406 
408  }
409 
418  function _retrieveServiceEndpointParts($request) {
419  // URL pattern depends on whether the server has path info
420  // enabled or not. See classdoc for details.
421  if ($request->isPathInfoEnabled()) {
422  if (!isset($_SERVER['PATH_INFO'])) return null;
423 
424  $pathInfoParts = explode('/', trim($_SERVER['PATH_INFO'], '/'));
425 
426  // We expect at least the context + the component
427  // router marker + 3 component parts (path, handler, operation)
428  $application = $this->getApplication();
429  $contextDepth = $application->getContextDepth();
430  if (count($pathInfoParts) < $contextDepth + 4) {
431  // This path info is too short to be an RPC request
432  return null;
433  }
434 
435  // Check the component router marker
436  if ($pathInfoParts[$contextDepth] != COMPONENT_ROUTER_PATHINFO_MARKER) {
437  // This is not an RPC request
438  return null;
439  }
440 
441  // Remove context and component marker from the array
442  $rpcServiceEndpointParts = array_slice($pathInfoParts, $contextDepth + 1);
443  } else {
444  $componentParameter = $request->getUserVar(COMPONENT_ROUTER_PARAMETER_MARKER);
445  $operationParameter = $request->getUserVar('op');
446  if (is_null($componentParameter) || is_null($operationParameter)) {
447  // This is not an RPC request
448  return null;
449  }
450 
451  // Expand the router parameter
452  $rpcServiceEndpointParts = explode('.', $componentParameter);
453 
454  // Add the operation
455  array_push($rpcServiceEndpointParts, $operationParameter);
456  }
457 
458  return $rpcServiceEndpointParts;
459  }
460 
469  function _validateServiceEndpointParts($rpcServiceEndpointParts) {
470  // Do we have data at all?
471  if (is_null($rpcServiceEndpointParts) || empty($rpcServiceEndpointParts)
472  || !is_array($rpcServiceEndpointParts)) return null;
473 
474  // We require at least three parts: component directory, handler
475  // and method name.
476  if (count($rpcServiceEndpointParts) < 3) return null;
477 
478  // Check that the array dimensions remain within sane limits.
479  if (count($rpcServiceEndpointParts) > COMPONENT_ROUTER_PARTS_MAXDEPTH) return null;
480 
481  // Validate the individual endpoint parts.
482  foreach($rpcServiceEndpointParts as $key => $rpcServiceEndpointPart) {
483  // Make sure that none of the elements exceeds the length limit.
484  $partLen = strlen($rpcServiceEndpointPart);
485  if ($partLen > COMPONENT_ROUTER_PARTS_MAXLENGTH
486  || $partLen < COMPONENT_ROUTER_PARTS_MINLENGTH) return null;
487 
488  // Service endpoint URLs are case insensitive.
489  $rpcServiceEndpointParts[$key] = strtolower_codesafe($rpcServiceEndpointPart);
490 
491  // We only allow letters, numbers and the hyphen.
492  if (!PKPString::regexp_match('/^[a-z0-9-]*$/', $rpcServiceEndpointPart)) return null;
493  }
494 
495  return $rpcServiceEndpointParts;
496  }
497 }
498 
PKPComponentRouter\_getValidatedServiceEndpointParts
_getValidatedServiceEndpointParts($request)
Definition: PKPComponentRouter.inc.php:397
PKPRouter\_authorizeInitializeAndCallRequest
_authorizeInitializeAndCallRequest(&$serviceEndpoint, $request, &$args, $validate=true)
Definition: PKPRouter.inc.php:393
$op
$op
Definition: lib/pkp/pages/help/index.php:18
PKPRouter\setHandler
setHandler($handler)
Definition: PKPRouter.inc.php:153
PKPComponentRouter
Class mapping an HTTP request to a component handler operation.
Definition: PKPComponentRouter.inc.php:66
AppLocale\requireComponents
static requireComponents()
Definition: env1/MockAppLocale.inc.php:56
instantiate
& instantiate($fullyQualifiedClassName, $expectedTypes=null, $expectedPackages=null, $expectedMethods=null, $constructorArg=null)
Definition: functions.inc.php:165
$application
$application
Definition: index.php:61
PKPComponentRouter\route
route($request)
Definition: PKPComponentRouter.inc.php:257
PKPComponentRouter\_validateServiceEndpointParts
_validateServiceEndpointParts($rpcServiceEndpointParts)
Definition: PKPComponentRouter.inc.php:481
PKPString\uncamelize
static uncamelize($string)
Definition: PKPString.inc.php:455
PKPRouter\_urlCanonicalizeNewContext
_urlCanonicalizeNewContext($newContext)
Definition: PKPRouter.inc.php:452
PKPComponentRouter\getRequestedOp
getRequestedOp($request)
Definition: PKPComponentRouter.inc.php:157
PKPRouter\_urlGetBaseAndContext
_urlGetBaseAndContext($request, $newContext=array())
Definition: PKPRouter.inc.php:487
PKPComponentRouter\$_op
$_op
Definition: PKPComponentRouter.inc.php:81
JSONMessage
Class to represent a JSON (Javascript Object Notation) message.
Definition: JSONMessage.inc.php:18
Config\getVar
static getVar($section, $key, $default=null)
Definition: Config.inc.php:35
PKPComponentRouter\$_rpcServiceEndpointParts
$_rpcServiceEndpointParts
Definition: PKPComponentRouter.inc.php:86
PKPComponentRouter\getRequestedComponent
getRequestedComponent($request)
Definition: PKPComponentRouter.inc.php:118
PKPComponentRouter\getRpcServiceEndpoint
& getRpcServiceEndpoint($request)
Definition: PKPComponentRouter.inc.php:182
PKPComponentRouter\$_rpcServiceEndpoint
$_rpcServiceEndpoint
Definition: PKPComponentRouter.inc.php:91
PKPRouter\getApplication
& getApplication()
Definition: PKPRouter.inc.php:114
PKPComponentRouter\$_component
$_component
Definition: PKPComponentRouter.inc.php:76
strtolower_codesafe
strtolower_codesafe($str)
Definition: functions.inc.php:280
PKPRouter
Basic router class that has functionality common to all routers.
Definition: PKPRouter.inc.php:57
PKPString\camelize
static camelize($string, $type=CAMEL_CASE_HEAD_UP)
Definition: PKPString.inc.php:435
PKPRouter\_urlFromParts
_urlFromParts($baseUrl, $pathInfoArray=array(), $queryParametersArray=array(), $anchor='', $escape=false)
Definition: PKPRouter.inc.php:582
PKPComponentRouter\_retrieveServiceEndpointParts
_retrieveServiceEndpointParts($request)
Definition: PKPComponentRouter.inc.php:430
PKPComponentRouter\url
url($request, $newContext=null, $component=null, $op=null, $path=null, $params=null, $anchor=null, $escape=false)
Definition: PKPComponentRouter.inc.php:275
PKPString\regexp_match
static regexp_match($pattern, $subject)
Definition: PKPString.inc.php:245
HookRegistry\call
static call($hookName, $args=null)
Definition: HookRegistry.inc.php:86
PKPRouter\_urlGetAdditionalParameters
_urlGetAdditionalParameters($request, $params=null, $escape=true)
Definition: PKPRouter.inc.php:555
PKPComponentRouter\supports
supports($request)
Definition: PKPComponentRouter.inc.php:99
PKPComponentRouter\handleAuthorizationFailure
handleAuthorizationFailure($request, $authorizationMessage)
Definition: PKPComponentRouter.inc.php:365