30 use Wikimedia\Timestamp\TimestampException;
63 'login' => ApiLogin::class,
64 'clientlogin' => ApiClientLogin::class,
65 'logout' => ApiLogout::class,
66 'createaccount' => ApiAMCreateAccount::class,
67 'linkaccount' => ApiLinkAccount::class,
68 'unlinkaccount' => ApiRemoveAuthenticationData::class,
69 'changeauthenticationdata' => ApiChangeAuthenticationData::class,
70 'removeauthenticationdata' => ApiRemoveAuthenticationData::class,
72 'class' => ApiResetPassword::class,
77 'query' => ApiQuery::class,
78 'expandtemplates' => ApiExpandTemplates::class,
79 'parse' => ApiParse::class,
81 'class' => ApiStashEdit::class,
83 'ContentHandlerFactory',
89 'opensearch' => ApiOpenSearch::class,
90 'feedcontributions' => ApiFeedContributions::class,
91 'feedrecentchanges' => [
92 'class' => ApiFeedRecentChanges::class,
97 'feedwatchlist' => ApiFeedWatchlist::class,
98 'help' => ApiHelp::class,
99 'paraminfo' => ApiParamInfo::class,
100 'rsd' => ApiRsd::class,
101 'compare' => ApiComparePages::class,
102 'tokens' => ApiTokens::class,
103 'checktoken' => ApiCheckToken::class,
104 'cspreport' => ApiCSPReport::class,
105 'validatepassword' => [
106 'class' => ApiValidatePassword::class,
114 'purge' => ApiPurge::class,
115 'setnotificationtimestamp' => [
116 'class' => ApiSetNotificationTimestamp::class,
123 'rollback' => ApiRollback::class,
124 'delete' => ApiDelete::class,
125 'undelete' => ApiUndelete::class,
126 'protect' => ApiProtect::class,
128 'class' => ApiBlock::class,
130 'BlockPermissionCheckerFactory',
138 'class' => ApiUnblock::class,
140 'BlockPermissionCheckerFactory',
141 'UnblockUserFactory',
147 'class' => ApiMove::class,
152 'edit' => ApiEditPage::class,
153 'upload' => ApiUpload::class,
154 'filerevert' => ApiFileRevert::class,
155 'emailuser' => ApiEmailUser::class,
156 'watch' => ApiWatch::class,
157 'patrol' => ApiPatrol::class,
158 'import' => ApiImport::class,
160 'class' => ApiClearHasMsg::class,
162 'TalkPageNotificationManager',
165 'userrights' => ApiUserrights::class,
166 'options' => ApiOptions::class,
167 'imagerotate' => ApiImageRotate::class,
168 'revisiondelete' => ApiRevisionDelete::class,
169 'managetags' => ApiManageTags::class,
170 'tag' => ApiTag::class,
171 'mergehistory' => ApiMergeHistory::class,
172 'setpagelanguage' => ApiSetPageLanguage::class,
173 'changecontentmodel' => [
174 'class' => ApiChangeContentModel::class,
176 'ContentHandlerFactory',
177 'ContentModelChangeFactory',
186 'json' => ApiFormatJson::class,
187 'jsonfm' => ApiFormatJson::class,
188 'php' => ApiFormatPhp::class,
189 'phpfm' => ApiFormatPhp::class,
190 'xml' => ApiFormatXml::class,
191 'xmlfm' => ApiFormatXml::class,
192 'rawfm' => ApiFormatJson::class,
193 'none' => ApiFormatNone::class,
204 'msg' =>
'right-writeapi',
208 'msg' =>
'api-help-right-apihighlimits',
256 if ( isset( $request ) ) {
257 $derivativeContext->setRequest( $request );
262 $this->mInternalMode = ( $request instanceof
FauxRequest );
265 parent::__construct( $this, $this->mInternalMode ?
'main_int' :
'main' );
269 if ( !$this->mInternalMode ) {
274 wfDebug(
"API: stripping user credentials when the same-origin policy is not applied" );
277 $derivativeContext->setUser( $user );
278 $request->response()->header(
'MediaWiki-Login-Suppressed: true' );
283 $this, MediaWikiServices::getInstance()->getObjectFactory()
291 $uselang = $request->getRawVal(
'uselang', self::API_DEFAULT_USELANG );
292 if ( $uselang ===
'user' ) {
296 if ( $uselang ===
'content' ) {
297 $uselang = MediaWikiServices::getInstance()->getContentLanguage()->getCode();
300 $derivativeContext->setLanguage( $code );
301 if ( !$this->mInternalMode ) {
303 $wgLang = $derivativeContext->getLanguage();
311 $errorFormat = $request->getRawVal(
'errorformat',
'bc' );
312 $errorLangCode = $request->getRawVal(
'errorlang',
'uselang' );
313 $errorsUseDB = $request->getCheck(
'errorsuselocal' );
314 if ( in_array( $errorFormat, [
'plaintext',
'wikitext',
'html',
'raw',
'none' ],
true ) ) {
315 if ( $errorLangCode ===
'uselang' ) {
317 } elseif ( $errorLangCode ===
'content' ) {
318 $errorLang = MediaWikiServices::getInstance()->getContentLanguage();
321 $errorLang = MediaWikiServices::getInstance()->getLanguageFactory()
322 ->getLanguage( $errorLangCode );
325 $this->mResult, $errorLang, $errorFormat, $errorsUseDB
334 MediaWikiServices::getInstance()->getObjectFactory()
336 $this->mModuleMgr->addModules( self::MODULES,
'action' );
337 $this->mModuleMgr->addModules( $config->get(
'APIModules' ),
'action' );
338 $this->mModuleMgr->addModules( self::FORMATS,
'format' );
339 $this->mModuleMgr->addModules( $config->get(
'APIFormatModules' ),
'format' );
341 $this->
getHookRunner()->onApiMain__moduleManager( $this->mModuleMgr );
343 $this->mContinuationManager =
null;
344 $this->mEnableWrite = $enableWrite;
346 $this->mCdnMaxAge = -1;
378 if ( $request->getCheck(
'callback' ) ) {
384 if ( $request->getVal(
'origin' ) ===
'*' ) {
391 if ( $request->getHeader(
'Treat-as-Untrusted' ) !==
false ) {
398 ->onRequestHasSameOriginSecurity( $request );
421 if ( $manager !==
null && $this->mContinuationManager !==
null ) {
422 throw new UnexpectedValueException(
423 __METHOD__ .
': tried to set manager from ' . $manager->getSource() .
424 ' when a manager is already set from ' . $this->mContinuationManager->getSource()
427 $this->mContinuationManager = $manager;
463 'max-age' => $maxage,
464 's-maxage' => $maxage
494 if ( !in_array( $mode, [
'private',
'public',
'anon-public-user-private' ] ) ) {
495 wfDebug( __METHOD__ .
": unrecognised cache mode \"$mode\"" );
503 if ( $mode !==
'private' ) {
504 wfDebug( __METHOD__ .
": ignoring request for $mode cache mode, private wiki" );
510 if ( $mode ===
'public' && $this->
getParameter(
'uselang' ) ===
'user' ) {
515 wfDebug( __METHOD__ .
": downgrading cache mode 'public' to " .
516 "'anon-public-user-private' due to uselang=user" );
517 $mode =
'anon-public-user-private';
520 wfDebug( __METHOD__ .
": setting cache mode $mode" );
521 $this->mCacheMode = $mode;
546 $printer = $this->mModuleMgr->getModule( $format,
'format',
true );
547 if ( $printer ===
null ) {
560 if ( $this->mInternalMode ) {
580 if ( $this->
getRequest()->getMethod() ===
'OPTIONS' ) {
586 $obLevel = ob_get_level();
589 $t = microtime(
true );
593 $runTime = microtime(
true ) -
$t;
595 MediaWikiServices::getInstance()->getStatsdDataFactory()->timing(
596 'api.' . $this->mModule->getModuleName() .
'.executeTiming', 1000 * $runTime
598 }
catch ( Throwable $e ) {
607 $this->mCacheMode ===
'private'
609 $this->mCacheMode ===
'anon-public-user-private'
610 && SessionManager::getGlobalSession()->isPersistent()
613 $this->
getContext()->getOutput()->enableClientCache(
false );
614 $this->
getContext()->getOutput()->considerCacheSettingsFinal();
626 while ( ob_get_level() > $obLevel ) {
643 MWExceptionHandler::CAUGHT_BY_ENTRYPOINT
660 $headerStr =
'MediaWiki-API-Error: ' . implode(
', ', $errCodes );
661 $response->header( $headerStr );
680 $this->
addWarning(
'apiwarn-errorprinterfailed' );
683 $this->mPrinter->addWarning( $error );
684 }
catch ( Throwable $ex2 ) {
691 $this->mPrinter =
null;
693 $this->mPrinter->forceDefaultParams();
695 $response->statusHeader( 200 );
716 $main->handleException( $e );
717 $main->logRequest( 0, $e );
718 }
catch ( Throwable $e2 ) {
724 $main->sendCacheHeaders(
true );
745 if ( $originParam ===
null ) {
751 $response = $request->response();
753 $allowTiming =
false;
756 if ( $originParam ===
'*' ) {
762 $matchedOrigin =
true;
764 $allowCredentials =
'false';
770 $originHeader = $request->getHeader(
'Origin' );
771 if ( $originHeader ===
false ) {
774 $originHeader = trim( $originHeader );
775 $origins = preg_split(
'/\s+/', $originHeader );
778 if ( !in_array( $originParam, $origins ) ) {
781 $response->statusHeader( 403 );
782 $response->header(
'Cache-Control: no-cache' );
783 echo
"'origin' parameter does not match Origin header\n";
789 $origin = Origin::parseHeaderList( $origins );
790 $matchedOrigin = $origin->match(
791 $config->get(
'CrossSiteAJAXdomains' ),
792 $config->get(
'CrossSiteAJAXdomainExceptions' )
795 $allowOrigin = $originHeader;
796 $allowCredentials =
'true';
797 $allowTiming = $originHeader;
800 if ( $matchedOrigin ) {
801 $requestedMethod = $request->getHeader(
'Access-Control-Request-Method' );
802 $preflight = $request->getMethod() ===
'OPTIONS' && $requestedMethod !==
false;
805 if ( $requestedMethod !==
'POST' && $requestedMethod !==
'GET' ) {
807 $response->header(
'MediaWiki-CORS-Rejection: Unsupported method requested in preflight' );
811 $requestedHeaders = $request->getHeader(
'Access-Control-Request-Headers' );
812 $allowedHeaders = $this->
getConfig()->get(
'AllowedCorsHeaders' );
813 if ( $requestedHeaders !==
false ) {
814 if ( !self::matchRequestedHeaders( $requestedHeaders, $allowedHeaders ) ) {
815 $response->header(
'MediaWiki-CORS-Rejection: Unsupported header requested in preflight' );
818 $response->header(
'Access-Control-Allow-Headers: ' . $requestedHeaders );
822 $response->header(
'Access-Control-Allow-Methods: POST, GET' );
823 } elseif ( $request->getMethod() !==
'POST' && $request->getMethod() !==
'GET' ) {
826 'MediaWiki-CORS-Rejection: Unsupported method for simple request or actual request'
831 $response->header(
"Access-Control-Allow-Origin: $allowOrigin" );
832 $response->header(
"Access-Control-Allow-Credentials: $allowCredentials" );
834 if ( $allowTiming !==
false ) {
835 $response->header(
"Timing-Allow-Origin: $allowTiming" );
840 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag, '
841 .
'MediaWiki-Login-Suppressed'
845 $response->header(
'MediaWiki-CORS-Rejection: Origin mismatch' );
849 $this->
getOutput()->addVaryHeader(
'Origin' );
864 if ( trim( $requestedHeaders ) ===
'' ) {
867 $requestedHeaders = explode(
',', $requestedHeaders );
868 $allowedHeaders = array_change_key_case( array_flip( $allowedHeaders ), CASE_LOWER );
869 foreach ( $requestedHeaders as $rHeader ) {
870 $rHeader = strtolower( trim( $rHeader ) );
871 if ( !isset( $allowedHeaders[$rHeader] ) ) {
872 LoggerFactory::getInstance(
'api-warning' )->warning(
873 'CORS preflight failed on requested header: {header}', [
892 $out->addVaryHeader(
'Treat-as-Untrusted' );
896 if ( $config->get(
'VaryOnXFP' ) ) {
897 $out->addVaryHeader(
'X-Forwarded-Proto' );
900 if ( !$isError && $this->mModule &&
903 $etag = $this->mModule->getConditionalRequestData(
'etag' );
904 if ( $etag !==
null ) {
905 $response->header(
"ETag: $etag" );
907 $lastMod = $this->mModule->getConditionalRequestData(
'last-modified' );
908 if ( $lastMod !==
null ) {
909 $response->header(
'Last-Modified: ' .
wfTimestamp( TS_RFC2822, $lastMod ) );
920 if ( isset( $this->mCacheControl[
'max-age'] ) ) {
921 $maxage = $this->mCacheControl[
'max-age'];
922 } elseif ( ( $this->mModule && !$this->mModule->isWriteMode() ) ||
923 $this->mCacheMode !==
'private'
927 $privateCache =
'private, must-revalidate, max-age=' . $maxage;
929 if ( $this->mCacheMode ==
'private' ) {
930 $response->header(
"Cache-Control: $privateCache" );
934 if ( $this->mCacheMode ==
'anon-public-user-private' ) {
935 $out->addVaryHeader(
'Cookie' );
936 $response->header( $out->getVaryHeader() );
937 if ( SessionManager::getGlobalSession()->isPersistent() ) {
940 $response->header(
"Cache-Control: $privateCache" );
947 $response->header( $out->getVaryHeader() );
950 if ( !isset( $this->mCacheControl[
's-maxage'] ) ) {
951 $this->mCacheControl[
's-maxage'] = $this->
getParameter(
'smaxage' );
953 if ( !isset( $this->mCacheControl[
'max-age'] ) ) {
954 $this->mCacheControl[
'max-age'] = $this->
getParameter(
'maxage' );
957 if ( !$this->mCacheControl[
's-maxage'] && !$this->mCacheControl[
'max-age'] ) {
961 $response->header(
"Cache-Control: $privateCache" );
966 $this->mCacheControl[
'public'] =
true;
969 $maxAge = min( $this->mCacheControl[
's-maxage'], $this->mCacheControl[
'max-age'] );
970 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
971 $response->header(
'Expires: ' .
wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
976 foreach ( $this->mCacheControl as $name => $value ) {
977 if ( is_bool( $value ) ) {
979 $ccHeader .= $separator . $name;
983 $ccHeader .= $separator .
"$name=$value";
988 $response->header(
"Cache-Control: $ccHeader" );
995 if ( !isset( $this->mPrinter ) ) {
996 $value = $this->
getRequest()->getVal(
'format', self::API_DEFAULT_FORMAT );
997 if ( !$this->mModuleMgr->isDefined( $value,
'format' ) ) {
1005 if ( !$this->mPrinter->canPrintErrors() ) {
1028 foreach ( $e->getStatusValue()->getErrorsByType(
$type ) as $error ) {
1031 } elseif (
$type !==
'error' ) {
1037 $class = preg_replace(
'#^Wikimedia\\\Rdbms\\\#',
'', get_class( $e ) );
1038 $code =
'internal_api_error_' . $class;
1039 $data = [
'errorclass' => get_class( $e ) ];
1040 if ( $config->get(
'ShowExceptionDetails' ) ) {
1042 $msg = $e->getMessageObject();
1070 $errors = $result->getResultData( [
'errors' ] );
1071 $warnings = $result->getResultData( [
'warnings' ] );
1073 if ( $warnings !==
null ) {
1076 if ( $errors !==
null ) {
1080 foreach ( $errors as $error ) {
1081 if ( isset( $error[
'code'] ) ) {
1082 $errorCodes[$error[
'code']] =
true;
1091 $errorCodes[$msg->getApiCode()] =
true;
1093 LoggerFactory::getInstance(
'api-warning' )->error(
'Invalid API error code "{code}"', [
1094 'code' => $msg->getApiCode(),
1097 $errorCodes[
'<invalid-code>'] =
true;
1099 $formatter->addError( $modulePath, $msg );
1102 $formatter->addWarning( $modulePath, $msg );
1108 $path = [
'error' ];
1114 $result->addContentValue(
1118 $this->
msg(
'api-usage-docref', $link )->inLanguage( $formatter->getLanguage() )->text()
1120 . $this->msg(
'api-usage-mailinglist-ref' )->inLanguage( $formatter->getLanguage() )->text()
1123 } elseif ( $config->get(
'ShowExceptionDetails' ) ) {
1124 $result->addContentValue(
1127 $this->
msg(
'api-exception-trace',
1132 )->inLanguage( $formatter->getLanguage() )->text()
1139 return array_keys( $errorCodes );
1151 if ( $requestid !==
null ) {
1155 if ( $this->
getConfig()->
get(
'ShowHostnames' ) && (
1156 in_array(
'servedby', $force,
true ) || $this->
getParameter(
'servedby' )
1166 $result->addValue(
null,
'uselang', $this->
getLanguage()->getCode(),
1181 $this->mAction = $params[
'action'];
1194 $module = $this->mModuleMgr->getModule( $this->mAction,
'action' );
1195 if ( $module ===
null ) {
1199 [
'apierror-unknownaction',
wfEscapeWikiText( $this->mAction ) ],
'unknown_action'
1203 $moduleParams = $module->extractRequestParams();
1206 if ( $module->needsToken() ===
true ) {
1208 "Module '{$module->getModuleName()}' must be updated for the new token handling. " .
1209 'See documentation for ApiBase::needsToken for details.'
1212 if ( $module->needsToken() ) {
1213 if ( !$module->mustBePosted() ) {
1215 "Module '{$module->getModuleName()}' must require POST to use tokens."
1219 if ( !isset( $moduleParams[
'token'] ) ) {
1222 $module->dieWithError( [
'apierror-missingparam',
'token' ] );
1226 $module->requirePostedParameters( [
'token' ] );
1228 if ( !$module->validateToken( $moduleParams[
'token'], $moduleParams ) ) {
1229 $module->dieWithError(
'apierror-badtoken' );
1240 $dbLag = MediaWikiServices::getInstance()->getDBLoadBalancer()->getMaxLag();
1242 'host' => $dbLag[0],
1247 $jobQueueLagFactor = $this->
getConfig()->get(
'JobQueueIncludeInMaxLagFactor' );
1248 if ( $jobQueueLagFactor ) {
1251 $jobQueueLag = $totalJobs / (float)$jobQueueLagFactor;
1252 if ( $jobQueueLag > $lagInfo[
'lag'] ) {
1255 'lag' => $jobQueueLag,
1256 'type' =>
'jobqueue',
1257 'jobs' => $totalJobs,
1274 if ( $module->shouldCheckMaxlag() && isset( $params[
'maxlag'] ) ) {
1275 $maxLag = $params[
'maxlag'];
1277 if ( $lagInfo[
'lag'] > $maxLag ) {
1280 $response->header(
'Retry-After: ' . max( (
int)$maxLag, 5 ) );
1281 $response->header(
'X-Database-Lag: ' . (
int)$lagInfo[
'lag'] );
1283 if ( $this->
getConfig()->
get(
'ShowHostnames' ) ) {
1285 [
'apierror-maxlag', $lagInfo[
'lag'], $lagInfo[
'host'] ],
1291 $this->
dieWithError( [
'apierror-maxlag-generic', $lagInfo[
'lag'] ],
'maxlag', $lagInfo );
1320 if ( $this->mInternalMode ) {
1325 if ( $this->
getRequest()->getMethod() !==
'GET' && $this->
getRequest()->getMethod() !==
'HEAD' ) {
1332 $ifNoneMatch = array_diff(
1336 if ( $ifNoneMatch ) {
1338 if ( $ifNoneMatch === [
'*' ] ) {
1342 $etag = $module->getConditionalRequestData(
'etag' );
1345 if ( $ifNoneMatch && $etag !==
null ) {
1346 $test = substr( $etag, 0, 2 ) ===
'W/' ? substr( $etag, 2 ) : $etag;
1347 $match = array_map(
function (
$s ) {
1348 return substr(
$s, 0, 2 ) ===
'W/' ? substr(
$s, 2 ) :
$s;
1350 $return304 = in_array( $test, $match,
true );
1352 $value = trim( $this->
getRequest()->getHeader(
'If-Modified-Since' ) );
1357 $i = strpos( $value,
';' );
1358 if ( $i !==
false ) {
1359 $value = trim( substr( $value, 0, $i ) );
1362 if ( $value !==
'' ) {
1367 $ts->getTimestamp( TS_RFC2822 ) === $value ||
1369 $ts->format(
'l, d-M-y H:i:s' ) .
' GMT' === $value ||
1371 $ts->format(
'D M j H:i:s Y' ) === $value ||
1372 $ts->format(
'D M j H:i:s Y' ) === $value
1375 $lastMod = $module->getConditionalRequestData(
'last-modified' );
1376 if ( $lastMod !==
null ) {
1380 'user' => $this->
getUser()->getTouched(),
1381 'epoch' => $config->get(
'CacheEpoch' ),
1384 if ( $config->get(
'UseCdn' ) ) {
1387 TS_MW, time() - $config->get(
'CdnMaxAge' )
1391 $lastMod = max( $modifiedTimes );
1392 $return304 =
wfTimestamp( TS_MW, $lastMod ) <= $ts->getTimestamp( TS_MW );
1395 }
catch ( TimestampException $e ) {
1402 $this->
getRequest()->response()->statusHeader( 304 );
1405 Wikimedia\suppressWarnings();
1406 ini_set(
'zlib.output_compression', 0 );
1407 Wikimedia\restoreWarnings();
1422 if ( $module->isReadMode() && !$this->getPermissionManager()->isEveryoneAllowed(
'read' ) &&
1423 !$this->getPermissionManager()->userHasRight( $user,
'read' )
1428 if ( $module->isWriteMode() ) {
1429 if ( !$this->mEnableWrite ) {
1433 } elseif ( $this->
getRequest()->getHeader(
'Promise-Non-Write-API-Action' ) ) {
1434 $this->
dieWithError(
'apierror-promised-nonwrite-api' );
1441 $message =
'hookaborted';
1442 if ( !$this->
getHookRunner()->onApiCheckCanExecute( $module, $user, $message ) ) {
1456 if ( $module->isWriteMode()
1457 && $this->getUser()->isBot()
1458 && MediaWikiServices::getInstance()->getDBLoadBalancer()->getServerCount() > 1
1470 $lagLimit = $this->
getConfig()->get(
'APIMaxLagThreshold' );
1471 $laggedServers = [];
1472 $loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
1473 foreach ( $loadBalancer->getLagTimes() as $serverIndex => $lag ) {
1474 if ( $lag > $lagLimit ) {
1476 $laggedServers[] = $loadBalancer->getServerName( $serverIndex ) .
" ({$lag}s)";
1481 $replicaCount = $loadBalancer->getServerCount() - 1;
1482 if ( $numLagged >= ceil( $replicaCount / 2 ) ) {
1483 $laggedServers = implode(
', ', $laggedServers );
1486 "Api request failed as read only because the following DBs are lagged: $laggedServers"
1488 LoggerFactory::getInstance(
'api-warning' )->warning(
1489 "Api request failed as read only because the following DBs are lagged: {laggeddbs}", [
1490 'laggeddbs' => $laggedServers,
1497 [
'readonlyreason' =>
"Waiting for $numLagged lagged database(s)" ]
1507 if ( isset( $params[
'assert'] ) ) {
1509 switch ( $params[
'assert'] ) {
1511 if ( !$user->isAnon() ) {
1516 if ( $user->isAnon() ) {
1527 if ( isset( $params[
'assertuser'] ) ) {
1529 if ( !$assertUser || !$this->
getUser()->equals( $assertUser ) ) {
1531 [
'apierror-assertnameduserfailed',
wfEscapeWikiText( $params[
'assertuser'] ) ]
1543 $validMethods = [
'GET',
'HEAD',
'POST',
'OPTIONS' ];
1546 if ( !in_array( $request->getMethod(), $validMethods ) ) {
1547 $this->
dieWithError(
'apierror-invalidmethod',
null,
null, 405 );
1550 if ( !$request->wasPosted() && $module->mustBePosted() ) {
1556 if ( $request->wasPosted() && !$request->getHeader(
'Content-Type' ) ) {
1558 'apiwarn-deprecation-post-without-content-type',
'post-without-content-type'
1563 $this->mPrinter = $module->getCustomPrinter();
1564 if ( $this->mPrinter ===
null ) {
1569 if ( $request->getProtocol() ===
'http' &&
1571 $this->getConfig()->get(
'ForceHTTPS' ) ||
1572 $request->getSession()->shouldForceHTTPS() ||
1573 ( $this->getUser()->isRegistered() &&
1574 $this->getUser()->requiresHTTPS() )
1577 $this->
addDeprecation(
'apiwarn-deprecation-httpsexpected',
'https-expected' );
1592 $this->mModule = $module;
1594 if ( !$this->mInternalMode ) {
1608 if ( !$this->mInternalMode ) {
1617 if ( !$this->mInternalMode ) {
1629 $limits = $this->
getConfig()->get(
'TrxProfilerLimits' );
1631 $trxProfiler->setLogger( LoggerFactory::getInstance(
'DBPerformance' ) );
1632 if ( $this->
getRequest()->hasSafeMethod() ) {
1633 $trxProfiler->setExpectations( $limits[
'GET'], __METHOD__ );
1635 $trxProfiler->setExpectations( $limits[
'POST-nonwrite'], __METHOD__ );
1638 $trxProfiler->setExpectations( $limits[
'POST'], __METHOD__ );
1652 '$schema' =>
'/mediawiki/api/request/0.0.1',
1655 'id' => MediaWikiServices::getInstance()
1656 ->getGlobalIdGenerator()->newUUIDv4(),
1658 'domain' => $this->
getConfig()->get(
'ServerName' ),
1661 'stream' =>
'mediawiki.api-request'
1664 'method' => $request->getMethod(),
1665 'client_ip' => $request->getIP()
1668 'backend_time_ms' => (int)round( $time * 1000 ),
1672 $httpRequestHeadersToLog = [
'accept-language',
'referer',
'user-agent' ];
1673 foreach ( $httpRequestHeadersToLog as
$header ) {
1674 if ( $request->getHeader(
$header ) ) {
1676 $logCtx[
'http'][
'request_headers'][
$header] = $request->getHeader(
$header );
1681 $logCtx[
'api_error_codes'] = [];
1683 $logCtx[
'api_error_codes'][] = $msg->getApiCode();
1688 $msg =
"API {$request->getMethod()} " .
1690 " {$logCtx['http']['client_ip']} " .
1691 "T={$logCtx['backend_time_ms']}ms";
1695 $value = $request->getVal( $name );
1696 if ( $value ===
null ) {
1700 if ( isset( $sensitive[$name] ) ) {
1701 $value =
'[redacted]';
1702 $encValue =
'[redacted]';
1703 } elseif ( strlen( $value ) > 256 ) {
1704 $value = substr( $value, 0, 256 );
1710 $logCtx[
'params'][$name] = $value;
1711 $msg .=
" {$name}={$encValue}";
1718 wfDebugLog(
'api-request',
'',
'private', $logCtx );
1729 $chars =
';@$!*(),/:';
1730 $numChars = strlen( $chars );
1731 for ( $i = 0; $i < $numChars; $i++ ) {
1732 $table[rawurlencode( $chars[$i] )] = $chars[$i];
1736 return strtr( rawurlencode(
$s ), $table );
1744 return array_keys( $this->mParamsUsed );
1752 $this->mParamsUsed += array_fill_keys( (array)$params,
true );
1761 return array_keys( $this->mParamsSensitive );
1770 $this->mParamsSensitive += array_fill_keys( (array)$params,
true );
1779 public function getVal( $name, $default =
null ) {
1780 $this->mParamsUsed[$name] =
true;
1783 if ( $ret ===
null ) {
1784 if ( $this->
getRequest()->getArray( $name ) !==
null ) {
1787 $this->
addWarning( [
'apiwarn-unsupportedarray', $name ] );
1801 $this->mParamsUsed[$name] =
true;
1802 return $this->
getRequest()->getCheck( $name );
1813 $this->mParamsUsed[$name] =
true;
1815 return $this->
getRequest()->getUpload( $name );
1824 $allParams = $this->
getRequest()->getValueNames();
1826 if ( !$this->mInternalMode ) {
1828 $printerParams = $this->mPrinter->encodeParamName(
1829 array_keys( $this->mPrinter->getFinalParams() ?: [] )
1831 $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams );
1833 $unusedParams = array_diff( $allParams, $paramsUsed );
1836 if ( count( $unusedParams ) ) {
1838 'apierror-unrecognizedparams',
1840 count( $unusedParams )
1851 if ( $this->
getConfig()->
get(
'DebugAPI' ) !==
false ) {
1858 $printer->setHttpStatus( $httpCode );
1860 $printer->execute();
1861 $printer->closePrinter();
1902 UserDef::PARAM_ALLOWED_USER_TYPES => [
'name' ],
1904 'requestid' =>
null,
1905 'servedby' =>
false,
1906 'curtimestamp' =>
false,
1907 'responselanginfo' =>
false,
1919 'errorsuselocal' => [
1929 =>
'apihelp-help-example-main',
1930 'action=help&recursivesubmodules=1'
1931 =>
'apihelp-help-example-recursive',
1944 foreach ( $oldHelp as $k => $v ) {
1945 if ( $k ===
'submodules' ) {
1946 $help[
'permissions'] =
'';
1950 $help[
'datatypes'] =
'';
1951 $help[
'templatedparams'] =
'';
1952 $help[
'credits'] =
'';
1956 [
'class' =>
'apihelp-block apihelp-permissions' ] );
1957 $m = $this->
msg(
'api-help-permissions' );
1958 if ( !$m->isDisabled() ) {
1960 $m->numParams( count( self::RIGHTS_MAP ) )->parse()
1964 foreach ( self::RIGHTS_MAP as $right => $rightMsg ) {
1967 $rightMsg = $this->
msg( $rightMsg[
'msg'], $rightMsg[
'params'] )->parse();
1970 $groups = array_map(
function ( $group ) {
1971 return $group ==
'*' ?
'all' : $group;
1975 $this->
msg(
'api-help-permissions-granted-to' )
1976 ->numParams( count( $groups ) )
1985 if ( empty( $options[
'nolead'] ) ) {
1986 $level = $options[
'headerlevel'];
1987 $tocnumber = &$options[
'tocnumber'];
1989 $header = $this->
msg(
'api-help-datatypes-header' )->parse();
1994 ' class="apihelp-header">',
2001 if ( $id !==
'main/datatypes' && $idFallback !==
'main/datatypes' ) {
2002 $headline =
'<div id="main/datatypes"></div>' . $headline;
2004 $help[
'datatypes'] .= $headline;
2005 $help[
'datatypes'] .= $this->
msg(
'api-help-datatypes-top' )->parseAsBlock();
2006 $help[
'datatypes'] .=
'<dl>';
2008 $m = $this->
msg(
"api-help-datatype-$type" );
2009 if ( !$m->isDisabled() ) {
2010 $id =
"main/datatype/$type";
2011 $help[
'datatypes'] .=
'<dt id="' . htmlspecialchars( $id ) .
'">';
2013 if ( $encId !== $id ) {
2014 $help[
'datatypes'] .=
'<span id="' . htmlspecialchars( $encId ) .
'"></span>';
2017 if ( $encId2 !== $id && $encId2 !== $encId ) {
2018 $help[
'datatypes'] .=
'<span id="' . htmlspecialchars( $encId2 ) .
'"></span>';
2020 $help[
'datatypes'] .= htmlspecialchars(
$type ) .
'</dt><dd>' . $m->parseAsBlock() .
"</dd>";
2023 $help[
'datatypes'] .=
'</dl>';
2024 if ( !isset( $tocData[
'main/datatypes'] ) ) {
2025 $tocnumber[$level]++;
2026 $tocData[
'main/datatypes'] = [
2027 'toclevel' => count( $tocnumber ),
2029 'anchor' =>
'main/datatypes',
2031 'number' => implode(
'.', $tocnumber ),
2036 $header = $this->
msg(
'api-help-templatedparams-header' )->parse();
2041 ' class="apihelp-header">',
2048 if ( $id !==
'main/templatedparams' && $idFallback !==
'main/templatedparams' ) {
2049 $headline =
'<div id="main/templatedparams"></div>' . $headline;
2051 $help[
'templatedparams'] .= $headline;
2052 $help[
'templatedparams'] .= $this->
msg(
'api-help-templatedparams' )->parseAsBlock();
2053 if ( !isset( $tocData[
'main/templatedparams'] ) ) {
2054 $tocnumber[$level]++;
2055 $tocData[
'main/templatedparams'] = [
2056 'toclevel' => count( $tocnumber ),
2058 'anchor' =>
'main/templatedparams',
2060 'number' => implode(
'.', $tocnumber ),
2065 $header = $this->
msg(
'api-credits-header' )->parse();
2069 ' class="apihelp-header">',
2076 if ( $id !==
'main/credits' && $idFallback !==
'main/credits' ) {
2077 $headline =
'<div id="main/credits"></div>' . $headline;
2079 $help[
'credits'] .= $headline;
2080 $help[
'credits'] .= $this->
msg(
'api-credits' )->useDatabase(
false )->parseAsBlock();
2081 if ( !isset( $tocData[
'main/credits'] ) ) {
2082 $tocnumber[$level]++;
2083 $tocData[
'main/credits'] = [
2084 'toclevel' => count( $tocnumber ),
2086 'anchor' =>
'main/credits',
2088 'number' => implode(
'.', $tocnumber ),
2102 if ( !isset( $this->mCanApiHighLimits ) ) {
2104 ->userHasRight( $this->
getUser(),
'apihighlimits' );
2128 $this->
getRequest()->getHeader(
'Api-user-agent' ) .
' ' .
2129 $this->
getRequest()->getHeader(
'User-agent' )