<?php
/**
- *
- *
* Created on Sep 4, 2006
*
* Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use Wikimedia\Timestamp\TimestampException;
+use Wikimedia\Rdbms\DBQueryError;
+use Wikimedia\Rdbms\DBError;
/**
* This is the main API class, used for both external and internal processing.
wfDebug( "API: stripping user credentials when the same-origin policy is not applied\n" );
$wgUser = new User();
$this->getContext()->setUser( $wgUser );
+ $request->response()->header( 'MediaWiki-Login-Suppressed: true' );
}
}
$request = $this->getRequest();
$response = $request->response();
- $matchOrigin = false;
+ $matchedOrigin = false;
$allowTiming = false;
$varyOrigin = true;
if ( $originParam === '*' ) {
// Request for anonymous CORS
- $matchOrigin = true;
+ // Technically we should check for the presence of an Origin header
+ // and not process it as CORS if it's not set, but that would
+ // require us to vary on Origin for all 'origin=*' requests which
+ // we don't want to do.
+ $matchedOrigin = true;
$allowOrigin = '*';
$allowCredentials = 'false';
$varyOrigin = false; // No need to vary
}
$config = $this->getConfig();
- $matchOrigin = count( $origins ) === 1 && self::matchOrigin(
+ $matchedOrigin = count( $origins ) === 1 && self::matchOrigin(
$originParam,
$config->get( 'CrossSiteAJAXdomains' ),
$config->get( 'CrossSiteAJAXdomainExceptions' )
$allowTiming = $originHeader;
}
- if ( $matchOrigin ) {
+ if ( $matchedOrigin ) {
$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.
+ $response->header( 'MediaWiki-CORS-Rejection: Unsupported method requested in preflight' );
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 ) ) {
+ $response->header( 'MediaWiki-CORS-Rejection: Unsupported header requested in preflight' );
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' );
+ } elseif ( $request->getMethod() !== 'POST' && $request->getMethod() !== 'GET' ) {
+ // Unsupported non-preflight method, don't handle it as CORS
+ $response->header(
+ 'MediaWiki-CORS-Rejection: Unsupported method for simple request or actual request'
+ );
+ return true;
}
$response->header( "Access-Control-Allow-Origin: $allowOrigin" );
if ( !$preflight ) {
$response->header(
- 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag'
+ 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag, '
+ . 'MediaWiki-Login-Suppressed'
);
}
+ } else {
+ $response->header( 'MediaWiki-CORS-Rejection: Origin mismatch' );
}
if ( $varyOrigin ) {
} else {
// Something is seriously wrong
$config = $this->getConfig();
- $code = 'internal_api_error_' . get_class( $e );
+ $class = preg_replace( '#^Wikimedia\\\Rdbms\\\#', '', get_class( $e ) );
+ $code = 'internal_api_error_' . $class;
if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
$params = [ 'apierror-databaseerror', WebRequest::getRequestId() ];
} else {
return $module;
}
+ /**
+ * @return array
+ */
+ private function getMaxLag() {
+ $dbLag = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaxLag();
+ $lagInfo = [
+ 'host' => $dbLag[0],
+ 'lag' => $dbLag[1],
+ 'type' => 'db'
+ ];
+
+ $jobQueueLagFactor = $this->getConfig()->get( 'JobQueueIncludeInMaxLagFactor' );
+ if ( $jobQueueLagFactor ) {
+ // Turn total number of jobs into seconds by using the configured value
+ $totalJobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
+ $jobQueueLag = $totalJobs / (float)$jobQueueLagFactor;
+ if ( $jobQueueLag > $lagInfo['lag'] ) {
+ $lagInfo = [
+ 'host' => wfHostname(), // XXX: Is there a better value that could be used?
+ 'lag' => $jobQueueLag,
+ 'type' => 'jobqueue',
+ 'jobs' => $totalJobs,
+ ];
+ }
+ }
+
+ return $lagInfo;
+ }
+
/**
* Check the max lag if necessary
* @param ApiBase $module Api module being used
protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
$maxLag = $params['maxlag'];
- list( $host, $lag ) = wfGetLB()->getMaxLag();
- if ( $lag > $maxLag ) {
+ $lagInfo = $this->getMaxLag();
+ if ( $lagInfo['lag'] > $maxLag ) {
$response = $this->getRequest()->response();
$response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
- $response->header( 'X-Database-Lag: ' . intval( $lag ) );
+ $response->header( 'X-Database-Lag: ' . intval( $lagInfo['lag'] ) );
if ( $this->getConfig()->get( 'ShowHostnames' ) ) {
- $this->dieWithError( [ 'apierror-maxlag', $lag, $host ] );
+ $this->dieWithError(
+ [ 'apierror-maxlag', $lagInfo['lag'], $lagInfo['host'] ],
+ 'maxlag',
+ $lagInfo
+ );
}
- $this->dieWithError( [ 'apierror-maxlag-generic', $lag ], 'maxlag' );
+ $this->dieWithError( [ 'apierror-maxlag-generic', $lagInfo['lag'] ], 'maxlag', $lagInfo );
}
}