X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fapi%2FApiMain.php;h=ba22e394572c8a3d430b48f5df2ac4e1e23b7250;hb=3eacf0349fe7ff6f2c831831501037c92a2185c6;hp=bf26eeec0c44e61ad61fd72ebc0e94a6933f2d34;hpb=49d2aee5c7b9e00c32904a6691d31f9feb9efd97;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index bf26eeec0c..ba22e39457 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -54,6 +54,7 @@ class ApiMain extends ApiBase { 'query' => 'ApiQuery', 'expandtemplates' => 'ApiExpandTemplates', 'parse' => 'ApiParse', + 'stashedit' => 'ApiStashEdit', 'opensearch' => 'ApiOpenSearch', 'feedcontributions' => 'ApiFeedContributions', 'feedrecentchanges' => 'ApiFeedRecentChanges', @@ -191,7 +192,10 @@ class ApiMain extends ApiBase { if ( $uselang === 'user' ) { $uselang = $this->getUser()->getOption( 'language' ); $uselang = RequestContext::sanitizeLangCode( $uselang ); - wfRunHooks( 'UserGetLanguageObject', array( $this->getUser(), &$uselang, $this ) ); + Hooks::run( 'UserGetLanguageObject', array( $this->getUser(), &$uselang, $this ) ); + } elseif ( $uselang === 'content' ) { + global $wgContLang; + $uselang = $wgContLang->getCode(); } $code = RequestContext::sanitizeLangCode( $uselang ); $this->getContext()->setLanguage( $code ); @@ -310,7 +314,7 @@ class ApiMain extends ApiBase { // then there's an appropriate Vary header set by whatever set // their non-default language. wfDebug( __METHOD__ . ": downgrading cache mode 'public' to " . - "'anon-public-user-private' due to uselang=user\n" ); + "'anon-public-user-private' due to uselang=user\n" ); $mode = 'anon-public-user-private'; } @@ -414,7 +418,7 @@ class ApiMain extends ApiBase { } // Allow extra cleanup and logging - wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); + Hooks::run( 'ApiMain::onException', array( $this, $e ) ); // Log it if ( !( $e instanceof UsageException ) ) { @@ -454,6 +458,7 @@ class ApiMain extends ApiBase { * * @since 1.23 * @param Exception $e + * @throws Exception */ public static function handleApiBeforeMainException( Exception $e ) { ob_start(); @@ -482,6 +487,8 @@ class ApiMain extends ApiBase { * If the parameter and the header do match, the header is checked against $wgCrossSiteAJAXdomains * and $wgCrossSiteAJAXdomainExceptions, and if the origin qualifies, the appropriate CORS * headers are set. + * http://www.w3.org/TR/cors/#resource-requests + * http://www.w3.org/TR/cors/#resource-preflight-requests * * @return bool False if the caller should abort (403 case), true otherwise (all other cases) */ @@ -494,12 +501,14 @@ class ApiMain extends ApiBase { $request = $this->getRequest(); $response = $request->response(); + // Origin: header is a space-separated list of origins, check all of them $originHeader = $request->getHeader( 'Origin' ); if ( $originHeader === false ) { $origins = array(); } else { - $origins = explode( ' ', $originHeader ); + $originHeader = trim( $originHeader ); + $origins = preg_split( '/\s+/', $originHeader ); } if ( !in_array( $originParam, $origins ) ) { @@ -514,18 +523,43 @@ class ApiMain extends ApiBase { } $config = $this->getConfig(); - $matchOrigin = self::matchOrigin( + $matchOrigin = count( $origins ) === 1 && self::matchOrigin( $originParam, $config->get( 'CrossSiteAJAXdomains' ), $config->get( 'CrossSiteAJAXdomainExceptions' ) ); if ( $matchOrigin ) { - $response->header( "Access-Control-Allow-Origin: $originParam" ); + $requestedMethod = $request->getHeader( 'Access-Control-Request-Method' ); + $preflight = $request->getMethod() === 'OPTIONS' && $requestedMethod !== false; + if ( $preflight ) { + // This is a CORS preflight request + if ( $requestedMethod !== 'POST' && $requestedMethod !== 'GET' ) { + // If method is not a case-sensitive match, do not set any additional headers and terminate. + return true; + } + // We allow the actual request to send the following headers + $requestedHeaders = $request->getHeader( 'Access-Control-Request-Headers' ); + if ( $requestedHeaders !== false ) { + if ( !self::matchRequestedHeaders( $requestedHeaders ) ) { + return true; + } + $response->header( 'Access-Control-Allow-Headers: ' . $requestedHeaders ); + } + + // We only allow the actual request to be GET or POST + $response->header( 'Access-Control-Allow-Methods: POST, GET' ); + } + + $response->header( "Access-Control-Allow-Origin: $originHeader" ); $response->header( 'Access-Control-Allow-Credentials: true' ); - $this->getOutput()->addVaryHeader( 'Origin' ); + + if ( !$preflight ) { + $response->header( 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag' ); + } } + $this->getOutput()->addVaryHeader( 'Origin' ); return true; } @@ -554,6 +588,41 @@ class ApiMain extends ApiBase { return false; } + /** + * Attempt to validate the value of Access-Control-Request-Headers against a list + * of headers that we allow the follow up request to send. + * + * @param string $requestedHeaders Comma seperated list of HTTP headers + * @return bool True if all requested headers are in the list of allowed headers + */ + protected static function matchRequestedHeaders( $requestedHeaders ) { + if ( trim( $requestedHeaders ) === '' ) { + return true; + } + $requestedHeaders = explode( ',', $requestedHeaders ); + $allowedAuthorHeaders = array_flip( array( + /* simple headers (see spec) */ + 'accept', + 'accept-language', + 'content-language', + 'content-type', + /* non-authorable headers in XHR, which are however requested by some UAs */ + 'accept-encoding', + 'dnt', + 'origin', + /* MediaWiki whitelist */ + 'api-user-agent', + ) ); + foreach ( $requestedHeaders as $rHeader ) { + $rHeader = strtolower( trim( $rHeader ) ); + if ( !isset( $allowedAuthorHeaders[$rHeader] ) ) { + wfDebugLog( 'api', 'CORS preflight failed on requested header: ' . $rHeader ); + return false; + } + } + return true; + } + /** * Helper function to convert wildcard string into a regex * '*' => '.*?' @@ -570,7 +639,7 @@ class ApiMain extends ApiBase { $wildcard ); - return "/https?:\/\/$wildcard/"; + return "/^https?:\/\/$wildcard$/"; } protected function sendCacheHeaders() { @@ -773,6 +842,8 @@ class ApiMain extends ApiBase { /** * Set up the module for response * @return ApiBase The module that will handle this action + * @throws MWException + * @throws UsageException */ protected function setupModule() { // Instantiate the module requested by the user @@ -873,7 +944,7 @@ class ApiMain extends ApiBase { // Allow extensions to stop execution for arbitrary reasons. $message = false; - if ( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { + if ( !Hooks::run( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { $this->dieUsageMsg( $message ); } } @@ -947,7 +1018,7 @@ class ApiMain extends ApiBase { // Execute $module->profileIn(); $module->execute(); - wfRunHooks( 'APIAfterExecute', array( &$module ) ); + Hooks::run( 'APIAfterExecute', array( &$module ) ); $module->profileOut(); $this->reportUnusedParams(); @@ -1121,8 +1192,6 @@ class ApiMain extends ApiBase { * @return array */ public function getAllowedParams() { - global $wgContLang; - return array( 'action' => array( ApiBase::PARAM_DFLT => 'help', @@ -1151,13 +1220,13 @@ class ApiMain extends ApiBase { 'curtimestamp' => false, 'origin' => null, 'uselang' => array( - ApiBase::PARAM_DFLT => $wgContLang->getCode(), + ApiBase::PARAM_DFLT => 'user', ), ); } /** @see ApiBase::getExamplesMessages() */ - public function getExamplesMessages() { + protected function getExamplesMessages() { return array( 'action=help' => 'apihelp-help-example-main', @@ -1241,6 +1310,21 @@ class ApiMain extends ApiBase { return $this->mModuleMgr; } + /** + * Fetches the user agent used for this request + * + * The value will be the combination of the 'Api-User-Agent' header (if + * any) and the standard User-Agent header (if any). + * + * @return string + */ + public function getUserAgent() { + return trim( + $this->getRequest()->getHeader( 'Api-user-agent' ) . ' ' . + $this->getRequest()->getHeader( 'User-agent' ) + ); + } + /************************************************************************//** * @name Deprecated * @{