X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fapi%2FApiMain.php;h=f17b8741bb2bd7ee0a5ef2d8bbba008337d58730;hb=68aab7ca8bd2bf7271a2f69aff1e7a60ab88078b;hp=7600066a23eda66918f71e341e534247d830dc4d;hpb=4770a3a93d1b95dc481af93e1fdc3f49c87dd66f;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 7600066a23..f17b8741bb 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', @@ -86,6 +87,7 @@ class ApiMain extends ApiBase { 'options' => 'ApiOptions', 'imagerotate' => 'ApiImageRotate', 'revisiondelete' => 'ApiRevisionDelete', + 'managetags' => 'ApiManageTags', ); /** @@ -189,19 +191,20 @@ class ApiMain extends ApiBase { $uselang = $this->getParameter( 'uselang' ); if ( $uselang === 'user' ) { - $uselang = $this->getUser()->getOption( 'language' ); - $uselang = RequestContext::sanitizeLangCode( $uselang ); - wfRunHooks( 'UserGetLanguageObject', array( $this->getUser(), &$uselang, $this ) ); - } elseif ( $uselang === 'content' ) { - global $wgContLang; - $uselang = $wgContLang->getCode(); - } - $code = RequestContext::sanitizeLangCode( $uselang ); - $this->getContext()->setLanguage( $code ); - if ( !$this->mInternalMode ) { - global $wgLang; - $wgLang = $this->getContext()->getLanguage(); - RequestContext::getMain()->setLanguage( $wgLang ); + // Assume the parent context is going to return the user language + // for uselang=user (see T85635). + } else { + if ( $uselang === 'content' ) { + global $wgContLang; + $uselang = $wgContLang->getCode(); + } + $code = RequestContext::sanitizeLangCode( $uselang ); + $this->getContext()->setLanguage( $code ); + if ( !$this->mInternalMode ) { + global $wgLang; + $wgLang = $this->getContext()->getLanguage(); + RequestContext::getMain()->setLanguage( $wgLang ); + } } $config = $this->getConfig(); @@ -417,7 +420,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 ) ) { @@ -457,6 +460,7 @@ class ApiMain extends ApiBase { * * @since 1.23 * @param Exception $e + * @throws Exception */ public static function handleApiBeforeMainException( Exception $e ) { ob_start(); @@ -485,6 +489,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) */ @@ -497,12 +503,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 ) ) { @@ -517,19 +525,44 @@ 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' ); - $response->header( 'Access-Control-Allow-Headers: Api-User-Agent' ); - $this->getOutput()->addVaryHeader( 'Origin' ); + $response->header( "Timing-Allow-Origin: $originHeader" ); # http://www.w3.org/TR/resource-timing/#timing-allow-origin + + if ( !$preflight ) { + $response->header( 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag' ); + } } + $this->getOutput()->addVaryHeader( 'Origin' ); return true; } @@ -558,6 +591,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 * '*' => '.*?' @@ -574,7 +642,7 @@ class ApiMain extends ApiBase { $wildcard ); - return "/https?:\/\/$wildcard/"; + return "/^https?:\/\/$wildcard$/"; } protected function sendCacheHeaders() { @@ -708,12 +776,14 @@ class ApiMain extends ApiBase { $errMessage = array( 'code' => 'internal_api_error_' . get_class( $e ), - 'info' => $info, - ); - ApiResult::setContent( - $errMessage, - $config->get( 'ShowExceptionDetails' ) ? "\n\n{$e->getTraceAsString()}\n\n" : '' + 'info' => '[' . MWExceptionHandler::getLogId( $e ) . '] ' . $info, ); + if ( $config->get( 'ShowExceptionDetails' ) ) { + ApiResult::setContent( + $errMessage, + MWExceptionHandler::getRedactedTraceAsString( $e ) + ); + } } // Remember all the warnings to re-add them later @@ -777,6 +847,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 @@ -877,7 +949,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 ); } } @@ -951,7 +1023,7 @@ class ApiMain extends ApiBase { // Execute $module->profileIn(); $module->execute(); - wfRunHooks( 'APIAfterExecute', array( &$module ) ); + Hooks::run( 'APIAfterExecute', array( &$module ) ); $module->profileOut(); $this->reportUnusedParams();