[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
v1
/
lib
/
pkp
/
classes
/
core
/
[
Home
]
File: PKPComponentRouter.php
<?php /** * @file classes/core/PKPComponentRouter.php * * Copyright (c) 2014-2021 Simon Fraser University * Copyright (c) 2000-2021 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class PKPComponentRouter * * @ingroup core * * @brief Class mapping an HTTP request to a component handler operation. * * We are using an RPC style URL-to-endpoint mapping. Our approach follows * a simple "convention-over-configuration" paradigm. If necessary the * router can be subclassed to implement more complex URL-to-endpoint mappings. * * For servers with path info enabled the component URL has the following elements: * * .../index.php/context1/context2/$$$call$$$/path/to/handler-class/operation-name?arg1=...&arg2=... * * where "$$$call$$$" is a non-mutable literal string and "path/to" is * by convention the directory path below the "controllers" folder leading to the * component. The next element ("handler-class" in this example) will be mapped to a * component class file by "camelizing" the string to "HandlerClassHandler" and adding * ".php" to the end. The "operation-name" is transformed to "operationName" * and represents the name of the handler method to be called. Finally "arg1", "arg2", * etc. are parameters to be passed along to the handler method. * * For servers with path info disabled the component URL looks like this: * * .../index.php?component=path.to.handler-class&op=operation-name&arg1=...&arg2=... * * The router will sanitize the request URL to a certain amount to make sure that * random code inclusions are prevented. User authorization and parameter validation * are however not the router's concern. These must be implemented on handler level. * * NB: Component and operation names may only contain a-z, 0-9 and hyphens. Numbers * are not allowed at the beginning of a name or after a hyphen. * * NB: Component handlers must implement an initialize() method that will be called * before the request is routed. The initialization method must enforce authorization * and request validation. */ namespace PKP\core; use Exception; use PKP\config\Config; use PKP\plugins\Hook; // The string to be found in the URL to mark this request as a component request define('COMPONENT_ROUTER_PATHINFO_MARKER', '$$$call$$$'); // The parameter to be found in the query string for servers with path info disabled define('COMPONENT_ROUTER_PARAMETER_MARKER', 'component'); // This is the maximum directory depth allowed within the component directory. Set // it to something reasonable to avoid DoS or overflow attacks define('COMPONENT_ROUTER_PARTS_MAXDEPTH', 9); // This is the maximum/minimum length of the name of a sub-directory or // handler class name. define('COMPONENT_ROUTER_PARTS_MAXLENGTH', 50); define('COMPONENT_ROUTER_PARTS_MINLENGTH', 2); class PKPComponentRouter extends PKPRouter { // // Internal state cache variables // NB: Please do not access directly but // only via their respective getters/setters // /** @var string the requested component handler */ public $_component; /** @var string the requested operation */ public $_op; /** @var array the rpc service endpoint parts from the request */ public $_rpcServiceEndpointParts = false; /** @var callable the rpc service endpoint the request was routed to */ public $_rpcServiceEndpoint = false; /** * Determines whether this router can route the given request. * * @param PKPRequest $request * * @return bool true, if the router supports this request, otherwise false */ public function supports($request): bool { // See whether this looks like a component router request. // NOTE: this is prone to false positives i.e. when a class // name cannot be matched, but this laxity permits plugins to // extend the system by registering against the // LoadComponentHandler hook. return $this->_retrieveServiceEndpointParts($request) !== null; } /** * Retrieve the requested component from the request. * * NB: This can be a component that not actually exists * in the code base. * * @param PKPRequest $request * * @return string the requested component or an empty string * if none can be found. */ public function getRequestedComponent($request) { if (is_null($this->_component)) { $this->_component = ''; // Retrieve the service endpoint parts from the request. if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) { // Endpoint parts cannot be found in the request return ''; } // Pop off the operation part array_pop($rpcServiceEndpointParts); // Construct the fully qualified component class name from the rest of it. $handlerClassName = PKPString::camelize(array_pop($rpcServiceEndpointParts), PKPString::CAMEL_CASE_HEAD_UP) . 'Handler'; // camelize remaining endpoint parts $camelizedRpcServiceEndpointParts = []; foreach ($rpcServiceEndpointParts as $part) { $camelizedRpcServiceEndpointParts[] = PKPString::camelize($part, PKPString::CAMEL_CASE_HEAD_DOWN); } $handlerPackage = implode('.', $camelizedRpcServiceEndpointParts); $this->_component = $handlerPackage . '.' . $handlerClassName; } return $this->_component; } /** * Retrieve the requested operation from the request * * NB: This can be an operation that not actually * exists in the requested component. * * @param PKPRequest $request * * @return string the requested operation or an empty string * if none can be found. */ public function getRequestedOp($request) { if (is_null($this->_op)) { $this->_op = ''; // Retrieve the service endpoint parts from the request. if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) { // Endpoint parts cannot be found in the request return ''; } // Pop off the operation part $this->_op = PKPString::camelize(array_pop($rpcServiceEndpointParts), PKPString::CAMEL_CASE_HEAD_DOWN); } return $this->_op; } /** * Get the (validated) RPC service endpoint from the request. * If no such RPC service endpoint can be constructed then the method * returns null. * * @param PKPRequest $request the request to be routed * * @return callable|array|null an array with the handler instance * and the handler operation to be called by call_user_func(). */ public function &getRpcServiceEndpoint($request) { if ($this->_rpcServiceEndpoint === false) { // We have not yet resolved this request. Mark the // state variable so that we don't try again next // time. $this->_rpcServiceEndpoint = $nullVar = null; // Retrieve requested component operation $op = $this->getRequestedOp($request); assert(!empty($op)); // // Component Handler // // Retrieve requested component handler $component = $this->getRequestedComponent($request); $componentInstance = null; $allowedPackages = null; // Give plugins a chance to intervene if (!Hook::call('LoadComponentHandler', [&$component, &$op, &$componentInstance])) { if (empty($component)) { return $nullVar; } // Construct the component handler file name and test its existence. $component = 'controllers.' . $component; $componentFileNamePart = str_replace('.', '/', $component); switch (true) { case file_exists("{$componentFileNamePart}.php"): $className = 'APP\\' . strtr($componentFileNamePart, '/', '\\'); $componentInstance = new $className(); break; case file_exists("{$componentFileNamePart}.inc.php"): // This behaviour is DEPRECATED as of 3.4.0. break; case file_exists(PKP_LIB_PATH . "/{$componentFileNamePart}.php"): $className = 'PKP\\' . strtr($componentFileNamePart, '/', '\\'); $componentInstance = new $className(); break; case file_exists(PKP_LIB_PATH . "/{$componentFileNamePart}.inc.php"): // This behaviour is DEPRECATED as of 3.4.0. $component = 'lib.pkp.' . $component; break; default: // Request to non-existent handler return $nullVar; } // We expect the handler to be part of one // of the following packages: $allowedPackages = [ 'controllers', 'lib.pkp.controllers' ]; } // A handler at least needs to implement the // following methods: $requiredMethods = [ $op, 'authorize', 'validate', 'initialize' ]; if (!$componentInstance) { $componentInstance = instantiate($component, 'PKPHandler', $allowedPackages, $requiredMethods); } if (!is_object($componentInstance)) { return $nullVar; } $this->setHandler($componentInstance); // // Callable service endpoint // // Construct the callable array $this->_rpcServiceEndpoint = [$componentInstance, $op]; } return $this->_rpcServiceEndpoint; } // // Implement template methods from PKPRouter // /** * @copydoc PKPRouter::route() */ public function route($request) { // Determine the requested service endpoint. $rpcServiceEndpoint = & $this->getRpcServiceEndpoint($request); // Retrieve RPC arguments from the request. $args = $request->getUserVars(); assert(is_array($args)); // Remove the caller-parameter (if present) if (isset($args[COMPONENT_ROUTER_PARAMETER_MARKER])) { unset($args[COMPONENT_ROUTER_PARAMETER_MARKER]); } // Authorize, validate and initialize the request $this->_authorizeInitializeAndCallRequest($rpcServiceEndpoint, $request, $args); } /** * @copydoc PKPRouter::url() * * @param null|mixed $newContext * @param null|mixed $component * @param null|mixed $op * @param null|mixed $path * @param null|mixed $params * @param null|mixed $anchor */ public function url( $request, $newContext = null, $component = null, $op = null, $path = null, $params = null, $anchor = null, $escape = false ) { if (!is_null($path)) { throw new Exception('Path must be null when calling PKPComponentRouter::url()'); } // // Base URL and Context // [$baseUrl, $context] = $this->_urlGetBaseAndContext($request, $newContext); // // Component and Operation // // We only support component/op retrieval from the request // if this request is a component request. $currentRequestIsAComponentRequest = $request->getRouter() instanceof self; if ($currentRequestIsAComponentRequest) { if (empty($component)) { $component = $this->getRequestedComponent($request); } if (empty($op)) { $op = $this->getRequestedOp($request); } } assert(!empty($component) && !empty($op)); // Encode the component and operation $componentParts = explode('.', $component); $componentName = array_pop($componentParts); assert(substr($componentName, -7) == 'Handler'); $componentName = PKPString::uncamelize(substr($componentName, 0, -7)); // uncamelize the component parts $uncamelizedComponentParts = []; foreach ($componentParts as $part) { $uncamelizedComponentParts[] = PKPString::uncamelize($part); } array_push($uncamelizedComponentParts, $componentName); $opName = PKPString::uncamelize($op); // // Additional query parameters // $additionalParameters = $this->_urlGetAdditionalParameters($request, $params, $escape); // // Anchor // $anchor = (empty($anchor) ? '' : '#' . rawurlencode($anchor)); // // Assemble URL // // Context, page, operation and additional path go into the path info. $pathInfoArray = array_merge( $context, [COMPONENT_ROUTER_PATHINFO_MARKER], $uncamelizedComponentParts, [$opName] ); // Query parameters $queryParametersArray = $additionalParameters; return $this->_urlFromParts($baseUrl, $pathInfoArray, $queryParametersArray, $anchor, $escape); } /** * @copydoc PKPRouter::handleAuthorizationFailure() */ public function handleAuthorizationFailure( $request, $authorizationMessage, array $messageParams = [] ) { $translatedAuthorizationMessage = __($authorizationMessage, $messageParams); // Add the router name and operation if show_stacktrace is enabled. if (Config::getVar('debug', 'show_stacktrace')) { $url = $request->getRequestUrl(); $queryString = $request->getQueryString(); if ($queryString) { $queryString = '?' . $queryString; } $translatedAuthorizationMessage .= ' [' . $url . $queryString . ']'; } // Return a JSON error message. return new JSONMessage(false, $translatedAuthorizationMessage); } // // Private helper methods // /** * Get the (validated) RPC service endpoint parts from the request. * If no such RPC service endpoint parts can be retrieved * then the method returns null. * * @param PKPRequest $request the request to be routed * * @return ?array a string array with the RPC service endpoint * parts as values. */ public function _getValidatedServiceEndpointParts($request) { if ($this->_rpcServiceEndpointParts === false) { // Mark the internal state variable so this // will not be called again. $this->_rpcServiceEndpointParts = null; // Retrieve service endpoint parts from the request. if (is_null($rpcServiceEndpointParts = $this->_retrieveServiceEndpointParts($request))) { // This is not an RPC request return null; } // Validate the service endpoint parts. if (is_null($rpcServiceEndpointParts = $this->_validateServiceEndpointParts($rpcServiceEndpointParts))) { // Invalid request return null; } // Assign the validated service endpoint parts $this->_rpcServiceEndpointParts = $rpcServiceEndpointParts; } return $this->_rpcServiceEndpointParts; } /** * Try to retrieve a (non-validated) array with the service * endpoint parts from the request. See the classdoc for the * URL patterns supported here. * * @param PKPRequest $request the request to be routed * * @return ?array an array of (non-validated) service endpoint * parts or null if the request is not an RPC request. */ public function _retrieveServiceEndpointParts($request) { if (!isset($_SERVER['PATH_INFO'])) { return null; } $pathInfoParts = explode('/', trim($_SERVER['PATH_INFO'], '/')); // We expect at least the context + the component // router marker + 3 component parts (path, handler, operation) $application = $this->getApplication(); if (count($pathInfoParts) < 5) { // This path info is too short to be an RPC request return null; } // Check the component router marker if ($pathInfoParts[1] != COMPONENT_ROUTER_PATHINFO_MARKER) { // This is not an RPC request return null; } // Remove context and component marker from the array $rpcServiceEndpointParts = array_slice($pathInfoParts, 2); return $rpcServiceEndpointParts; } /** * This method pre-validates the service endpoint parts before * we try to convert them to a file/method name. This also * converts all parts to lower case. * * @param array $rpcServiceEndpointParts * * @return ?array the validated service endpoint parts or null if validation * does not succeed. */ public function _validateServiceEndpointParts($rpcServiceEndpointParts) { // Do we have data at all? if (is_null($rpcServiceEndpointParts) || empty($rpcServiceEndpointParts) || !is_array($rpcServiceEndpointParts)) { return null; } // We require at least three parts: component directory, handler // and method name. if (count($rpcServiceEndpointParts) < 3) { return null; } // Check that the array dimensions remain within sane limits. if (count($rpcServiceEndpointParts) > COMPONENT_ROUTER_PARTS_MAXDEPTH) { return null; } // Validate the individual endpoint parts. foreach ($rpcServiceEndpointParts as $key => $rpcServiceEndpointPart) { // Make sure that none of the elements exceeds the length limit. $partLen = strlen($rpcServiceEndpointPart); if ($partLen > COMPONENT_ROUTER_PARTS_MAXLENGTH || $partLen < COMPONENT_ROUTER_PARTS_MINLENGTH) { return null; } // Service endpoint URLs are case insensitive. $rpcServiceEndpointParts[$key] = strtolower_codesafe($rpcServiceEndpointPart); // We only allow letters, numbers and the hyphen. if (!PKPString::regexp_match('/^[a-z0-9-]*$/', $rpcServiceEndpointPart)) { return null; } } return $rpcServiceEndpointParts; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\core\PKPComponentRouter', '\PKPComponentRouter'); }