X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/banques/?a=blobdiff_plain;f=includes%2Fapi%2FApiMain.php;h=ba22e394572c8a3d430b48f5df2ac4e1e23b7250;hb=3eacf0349fe7ff6f2c831831501037c92a2185c6;hp=a5287b6a1091bc1029d83ae63bb903f048cd6cf7;hpb=4d9143c7f55857377da353bb114ed44c491af2a6;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index a5287b6a10..ba22e39457 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -487,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) */ @@ -499,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 ) ) { @@ -519,19 +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' ); - $response->header( 'Access-Control-Allow-Headers: Api-User-Agent' ); - $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; } @@ -560,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 * '*' => '.*?'