From 08aa4418c30cfc18ccc69a0f0f9cb9e17be6c196 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Mon, 12 Aug 2013 09:28:15 +0200 Subject: Update to MediaWiki 1.21.1 --- includes/api/ApiMain.php | 289 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 211 insertions(+), 78 deletions(-) (limited to 'includes/api/ApiMain.php') diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 35febd95..80bca2f6 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -51,6 +51,7 @@ class ApiMain extends ApiBase { private static $Modules = array( 'login' => 'ApiLogin', 'logout' => 'ApiLogout', + 'createaccount' => 'ApiCreateAccount', 'query' => 'ApiQuery', 'expandtemplates' => 'ApiExpandTemplates', 'parse' => 'ApiParse', @@ -82,6 +83,7 @@ class ApiMain extends ApiBase { 'import' => 'ApiImport', 'userrights' => 'ApiUserrights', 'options' => 'ApiOptions', + 'imagerotate' =>'ApiImageRotate', ); /** @@ -105,6 +107,7 @@ class ApiMain extends ApiBase { 'dbgfm' => 'ApiFormatDbg', 'dump' => 'ApiFormatDump', 'dumpfm' => 'ApiFormatDump', + 'none' => 'ApiFormatNone', ); /** @@ -118,7 +121,7 @@ class ApiMain extends ApiBase { 'msg' => 'Use of the write API', 'params' => array() ), - 'apihighlimits' => array( + 'apihighlimits' => array( 'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.', 'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 ) ) @@ -129,18 +132,20 @@ class ApiMain extends ApiBase { */ private $mPrinter; - private $mModules, $mModuleNames, $mFormats, $mFormatNames; - private $mResult, $mAction, $mShowVersions, $mEnableWrite; + private $mModuleMgr, $mResult; + private $mAction; + private $mEnableWrite; private $mInternalMode, $mSquidMaxage, $mModule; private $mCacheMode = 'private'; private $mCacheControl = array(); + private $mParamsUsed = array(); /** * Constructs an instance of ApiMain that utilizes the module and format specified by $request. * * @param $context IContextSource|WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs - * @param $enableWrite bool should be set to true if the api may modify data + * @param bool $enableWrite should be set to true if the api may modify data */ public function __construct( $context = null, $enableWrite = false ) { if ( $context === null ) { @@ -168,7 +173,7 @@ class ApiMain extends ApiBase { // Remove all modules other than login global $wgUser; - if ( $this->getRequest()->getVal( 'callback' ) !== null ) { + if ( $this->getVal( 'callback' ) !== null ) { // JSON callback allows cross-site reads. // For safety, strip user credentials. wfDebug( "API: stripping user credentials for JSON callback\n" ); @@ -177,15 +182,13 @@ class ApiMain extends ApiBase { } } - global $wgAPIModules; // extension modules - $this->mModules = $wgAPIModules + self::$Modules; - - $this->mModuleNames = array_keys( $this->mModules ); - $this->mFormats = self::$Formats; - $this->mFormatNames = array_keys( $this->mFormats ); + global $wgAPIModules; + $this->mModuleMgr = new ApiModuleManager( $this ); + $this->mModuleMgr->addModules( self::$Modules, 'action' ); + $this->mModuleMgr->addModules( $wgAPIModules, 'action' ); + $this->mModuleMgr->addModules( self::$Formats, 'format' ); $this->mResult = new ApiResult( $this ); - $this->mShowVersions = false; $this->mEnableWrite = $enableWrite; $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling() @@ -242,7 +245,7 @@ class ApiMain extends ApiBase { /** * Set the type of caching headers which will be sent. * - * @param $mode String One of: + * @param string $mode One of: * - 'public': Cache this object in public caches, if the maxage or smaxage * parameter is set, or if setCacheMaxAge() was called. If a maximum age is * not provided by any of these means, the object will be private. @@ -271,7 +274,7 @@ class ApiMain extends ApiBase { return; } - if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) { + if ( !User::groupHasPermission( '*', 'read' ) ) { // Private wiki, only private headers if ( $mode !== 'private' ) { wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" ); @@ -330,10 +333,11 @@ class ApiMain extends ApiBase { * @return ApiFormatBase */ public function createPrinterByName( $format ) { - if ( !isset( $this->mFormats[$format] ) ) { + $printer = $this->mModuleMgr->getModule( $format, 'format' ); + if ( $printer === null ) { $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); } - return new $this->mFormats[$format] ( $this, $format ); + return $printer; } /** @@ -361,10 +365,17 @@ class ApiMain extends ApiBase { return; } + // Exit here if the request method was OPTIONS + // (assume there will be a followup GET or POST) + if ( $this->getRequest()->getMethod() === 'OPTIONS' ) { + return; + } + // In case an error occurs during data output, // clear the output buffer and print just the error information ob_start(); + $t = microtime( true ); try { $this->executeAction(); } catch ( Exception $e ) { @@ -372,11 +383,16 @@ class ApiMain extends ApiBase { wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); // Log it - if ( !( $e instanceof UsageException ) ) { - wfDebugLog( 'exception', $e->getLogMessage() ); + if ( $e instanceof MWException && !( $e instanceof UsageException ) ) { + global $wgLogExceptionBacktrace; + if ( $wgLogExceptionBacktrace ) { + wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" ); + } else { + wfDebugLog( 'exception', $e->getLogMessage() ); + } } - // Handle any kind of exception by outputing properly formatted error message. + // Handle any kind of exception by outputting properly formatted error message. // If this fails, an unhandled exception should be thrown so that global error // handler will process and log it. @@ -401,6 +417,9 @@ class ApiMain extends ApiBase { $this->printResult( true ); } + // Log the request whether or not there was an error + $this->logRequest( microtime( true ) - $t); + // Send cache headers after any code which might generate an error, to // avoid sending public cache headers for errors. $this->sendCacheHeaders(); @@ -461,9 +480,9 @@ class ApiMain extends ApiBase { /** * Attempt to match an Origin header against a set of rules and a set of exceptions - * @param $value string Origin header - * @param $rules array Set of wildcard rules - * @param $exceptions array Set of wildcard rules + * @param string $value Origin header + * @param array $rules Set of wildcard rules + * @param array $exceptions Set of wildcard rules * @return bool True if $value matches a rule in $rules and doesn't match any rules in $exceptions, false otherwise */ protected static function matchOrigin( $value, $rules, $exceptions ) { @@ -486,7 +505,7 @@ class ApiMain extends ApiBase { * '*' => '.*?' * '?' => '.' * - * @param $wildcard string String with wildcards + * @param string $wildcard String with wildcards * @return string Regular expression */ protected static function wildcardToRegex( $wildcard ) { @@ -593,7 +612,7 @@ class ApiMain extends ApiBase { if ( !isset ( $this->mPrinter ) ) { // The printer has not been created yet. Try to manually get formatter value. $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); - if ( !in_array( $value, $this->mFormatNames ) ) { + if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) { $value = self::API_DEFAULT_FORMAT; } @@ -611,7 +630,6 @@ class ApiMain extends ApiBase { if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) { ApiResult::setContent( $errMessage, $this->makeHelpMsg() ); } - } else { global $wgShowSQLErrors, $wgShowExceptionDetails; // Something is seriously wrong @@ -628,6 +646,10 @@ class ApiMain extends ApiBase { ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); } + // Remember all the warnings to re-add them later + $oldResult = $result->getData(); + $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null; + $result->reset(); $result->disableSizeCheck(); // Re-add the id @@ -635,11 +657,13 @@ class ApiMain extends ApiBase { if ( !is_null( $requestid ) ) { $result->addValue( null, 'requestid', $requestid ); } - if ( $wgShowHostnames ) { // servedby is especially useful when debugging errors $result->addValue( null, 'servedby', wfHostName() ); } + if ( $warnings !== null ) { + $result->addValue( null, 'warnings', $warnings ); + } $result->addValue( null, 'error', $errMessage ); @@ -669,7 +693,6 @@ class ApiMain extends ApiBase { $params = $this->extractRequestParams(); - $this->mShowVersions = $params['version']; $this->mAction = $params['action']; if ( !is_string( $this->mAction ) ) { @@ -685,9 +708,10 @@ class ApiMain extends ApiBase { */ protected function setupModule() { // Instantiate the module requested by the user - $module = new $this->mModules[$this->mAction] ( $this, $this->mAction ); - $this->mModule = $module; - + $module = $this->mModuleMgr->getModule( $this->mAction, 'action' ); + if ( $module === null ) { + $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); + } $moduleParams = $module->extractRequestParams(); // Die if token required, but not provided (unless there is a gettoken parameter) @@ -713,7 +737,7 @@ class ApiMain extends ApiBase { /** * Check the max lag if necessary * @param $module ApiBase object: Api module being used - * @param $params Array an array containing the request parameters. + * @param array $params an array containing the request parameters. * @return boolean True on success, false should exit immediately */ protected function checkMaxLag( $module, $params ) { @@ -745,7 +769,7 @@ class ApiMain extends ApiBase { */ protected function checkExecutePermissions( $module ) { $user = $this->getUser(); - if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && + if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) && !$user->isAllowed( 'read' ) ) { $this->dieUsageMsg( 'readrequired' ); @@ -772,12 +796,13 @@ class ApiMain extends ApiBase { /** * Check POST for external response and setup result printer * @param $module ApiBase An Api module - * @param $params Array an array with the request parameters + * @param array $params an array with the request parameters */ protected function setupExternalResponse( $module, $params ) { - // Ignore mustBePosted() for internal calls - if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) { - $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) ); + if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) { + // Module requires POST. GET request might still be allowed + // if $wgDebugApi is true, otherwise fail. + $this->dieUsageMsgOrDebug( array( 'mustbeposted', $this->mAction ) ); } // See if custom printer is used @@ -798,6 +823,7 @@ class ApiMain extends ApiBase { protected function executeAction() { $params = $this->setupExecuteAction(); $module = $this->setupModule(); + $this->mModule = $module; $this->checkExecutePermissions( $module ); @@ -815,6 +841,8 @@ class ApiMain extends ApiBase { wfRunHooks( 'APIAfterExecute', array( &$module ) ); $module->profileOut(); + $this->reportUnusedParams(); + if ( !$this->mInternalMode ) { //append Debug information MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() ); @@ -824,12 +852,121 @@ class ApiMain extends ApiBase { } } + /** + * Log the preceding request + * @param $time Time in seconds + */ + protected function logRequest( $time ) { + $request = $this->getRequest(); + $milliseconds = $time === null ? '?' : round( $time * 1000 ); + $s = 'API' . + ' ' . $request->getMethod() . + ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . + ' ' . $request->getIP() . + ' T=' . $milliseconds .'ms'; + foreach ( $this->getParamsUsed() as $name ) { + $value = $request->getVal( $name ); + if ( $value === null ) { + continue; + } + $s .= ' ' . $name . '='; + if ( strlen( $value ) > 256 ) { + $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) ); + $s .= $encValue . '[...]'; + } else { + $s .= $this->encodeRequestLogValue( $value ); + } + } + $s .= "\n"; + wfDebugLog( 'api', $s, false ); + } + + /** + * Encode a value in a format suitable for a space-separated log line. + */ + protected function encodeRequestLogValue( $s ) { + static $table; + if ( !$table ) { + $chars = ';@$!*(),/:'; + for ( $i = 0; $i < strlen( $chars ); $i++ ) { + $table[rawurlencode( $chars[$i] )] = $chars[$i]; + } + } + return strtr( rawurlencode( $s ), $table ); + } + + /** + * Get the request parameters used in the course of the preceding execute() request + */ + protected function getParamsUsed() { + return array_keys( $this->mParamsUsed ); + } + + /** + * Get a request value, and register the fact that it was used, for logging. + */ + public function getVal( $name, $default = null ) { + $this->mParamsUsed[$name] = true; + return $this->getRequest()->getVal( $name, $default ); + } + + /** + * Get a boolean request value, and register the fact that the parameter + * was used, for logging. + */ + public function getCheck( $name ) { + $this->mParamsUsed[$name] = true; + return $this->getRequest()->getCheck( $name ); + } + + /** + * Get a request upload, and register the fact that it was used, for logging. + * + * @since 1.21 + * @param string $name Parameter name + * @return WebRequestUpload + */ + public function getUpload( $name ) { + $this->mParamsUsed[$name] = true; + return $this->getRequest()->getUpload( $name ); + } + + /** + * Report unused parameters, so the client gets a hint in case it gave us parameters we don't know, + * for example in case of spelling mistakes or a missing 'g' prefix for generators. + */ + protected function reportUnusedParams() { + $paramsUsed = $this->getParamsUsed(); + $allParams = $this->getRequest()->getValueNames(); + + if ( !$this->mInternalMode ) { + // Printer has not yet executed; don't warn that its parameters are unused + $printerParams = array_map( + array( $this->mPrinter, 'encodeParamName' ), + array_keys( $this->mPrinter->getFinalParams() ?: array() ) + ); + $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams ); + } else { + $unusedParams = array_diff( $allParams, $paramsUsed ); + } + + if( count( $unusedParams ) ) { + $s = count( $unusedParams ) > 1 ? 's' : ''; + $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" ); + } + } + /** * Print results using the current printer * * @param $isError bool */ protected function printResult( $isError ) { + global $wgDebugAPI; + if( $wgDebugAPI !== false ) { + $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); + } + $this->getResult()->cleanUpUTF8(); $printer = $this->mPrinter; $printer->profileIn(); @@ -839,10 +976,10 @@ class ApiMain extends ApiBase { * tell the printer not to escape ampersands so that our links do * not break. */ - $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError ) - && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); + $isHelp = $isError || $this->mAction == 'help'; + $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); - $printer->initPrinter( $isError ); + $printer->initPrinter( $isHelp ); $printer->execute(); $printer->closePrinter(); @@ -865,13 +1002,12 @@ class ApiMain extends ApiBase { return array( 'format' => array( ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, - ApiBase::PARAM_TYPE => $this->mFormatNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' ) ), 'action' => array( ApiBase::PARAM_DFLT => 'help', - ApiBase::PARAM_TYPE => $this->mModuleNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' ) ), - 'version' => false, 'maxlag' => array( ApiBase::PARAM_TYPE => 'integer' ), @@ -898,12 +1034,11 @@ class ApiMain extends ApiBase { return array( 'format' => 'The format of the output', 'action' => 'What action you would like to perform. See below for module help', - 'version' => 'When showing help, include version for each module', 'maxlag' => array( 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', 'To save actions causing any more site replication lag, this parameter can make the client', 'wait until the replication lag is less than the specified value.', - 'In case of a replag error, a HTTP 503 error is returned, with the message like', + 'In case of a replag error, error code "maxlag" is returned, with the message like', '"Waiting for $host: $lag seconds lagged\n".', 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', ), @@ -984,11 +1119,11 @@ class ApiMain extends ApiBase { protected function getCredits() { return array( 'API developers:', - ' Roan Kattouw ".@gmail.com" (lead developer Sep 2007-present)', + ' Roan Kattouw ".@gmail.com" (lead developer Sep 2007-2009)', ' Victor Vasiliev - vasilvv at gee mail dot com', ' Bryan Tong Minh - bryan . tongminh @ gmail . com', ' Sam Reed - sam @ reedyboy . net', - ' Yuri Astrakhan "@gmail.com" (creator, lead developer Sep 2006-Sep 2007)', + ' Yuri Astrakhan "@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)', '', 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org', 'or file a bug report at https://bugzilla.wikimedia.org/' @@ -1014,8 +1149,7 @@ class ApiMain extends ApiBase { $this->setHelp(); // Get help text from cache if present $key = wfMemcKey( 'apihelp', $this->getModuleName(), - SpecialVersion::getVersion( 'nodb' ) . - $this->getShowVersions() ); + SpecialVersion::getVersion( 'nodb' ) ); if ( $wgAPICacheHelpTimeout > 0 ) { $cached = $wgMemc->get( $key ); if ( $cached ) { @@ -1040,9 +1174,11 @@ class ApiMain extends ApiBase { $astriks = str_repeat( '*** ', 14 ); $msg .= "\n\n$astriks Modules $astriks\n\n"; - foreach ( array_keys( $this->mModules ) as $moduleName ) { - $module = new $this->mModules[$moduleName] ( $this, $moduleName ); + + foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) { + $module = $this->mModuleMgr->getModule( $name ); $msg .= self::makeHelpMsgHeader( $module, 'action' ); + $msg2 = $module->makeHelpMsg(); if ( $msg2 !== false ) { $msg .= $msg2; @@ -1053,14 +1189,13 @@ class ApiMain extends ApiBase { $msg .= "\n$astriks Permissions $astriks\n\n"; foreach ( self::$mRights as $right => $rightMsg ) { $groups = User::getGroupsWithPermission( $right ); - $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) . + $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) . "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n"; - } $msg .= "\n$astriks Formats $astriks\n\n"; - foreach ( array_keys( $this->mFormats ) as $formatName ) { - $module = $this->createPrinterByName( $formatName ); + foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) { + $module = $this->mModuleMgr->getModule( $name ); $msg .= self::makeHelpMsgHeader( $module, 'format' ); $msg2 = $module->makeHelpMsg(); if ( $msg2 !== false ) { @@ -1076,7 +1211,7 @@ class ApiMain extends ApiBase { /** * @param $module ApiBase - * @param $paramName String What type of request is this? e.g. action, query, list, prop, meta, format + * @param string $paramName What type of request is this? e.g. action, query, list, prop, meta, format * @return string */ public static function makeHelpMsgHeader( $module, $paramName ) { @@ -1105,25 +1240,19 @@ class ApiMain extends ApiBase { /** * Check whether the user wants us to show version information in the API help * @return bool + * @deprecated since 1.21, always returns false */ public function getShowVersions() { - return $this->mShowVersions; + wfDeprecated( __METHOD__, '1.21' ); + return false; } /** - * Returns the version information of this file, plus it includes - * the versions for all files that are not callable proper API modules - * - * @return array + * Overrides to return this instance's module manager. + * @return ApiModuleManager */ - public function getVersion() { - $vers = array(); - $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; - $vers[] = __CLASS__ . ': $Id$'; - $vers[] = ApiBase::getBaseVersion(); - $vers[] = ApiFormatBase::getBaseVersion(); - $vers[] = ApiQueryBase::getBaseVersion(); - return $vers; + public function getModuleManager() { + return $this->mModuleMgr; } /** @@ -1131,40 +1260,44 @@ class ApiMain extends ApiBase { * classes who wish to add their own modules to their lexicon or override the * behavior of inherent ones. * - * @param $mdlName String The identifier for this module. - * @param $mdlClass String The class where this module is implemented. + * @deprecated since 1.21, Use getModuleManager()->addModule() instead. + * @param string $name The identifier for this module. + * @param $class ApiBase The class where this module is implemented. */ - protected function addModule( $mdlName, $mdlClass ) { - $this->mModules[$mdlName] = $mdlClass; + protected function addModule( $name, $class ) { + $this->getModuleManager()->addModule( $name, 'action', $class ); } /** * Add or overwrite an output format for this ApiMain. Intended for use by extending * classes who wish to add to or modify current formatters. * - * @param $fmtName string The identifier for this format. - * @param $fmtClass ApiFormatBase The class implementing this format. + * @deprecated since 1.21, Use getModuleManager()->addModule() instead. + * @param string $name The identifier for this format. + * @param $class ApiFormatBase The class implementing this format. */ - protected function addFormat( $fmtName, $fmtClass ) { - $this->mFormats[$fmtName] = $fmtClass; + protected function addFormat( $name, $class ) { + $this->getModuleManager()->addModule( $name, 'format', $class ); } /** * Get the array mapping module names to class names + * @deprecated since 1.21, Use getModuleManager()'s methods instead. * @return array */ function getModules() { - return $this->mModules; + return $this->getModuleManager()->getNamesWithClasses( 'action' ); } /** * Returns the list of supported formats in form ( 'format' => 'ClassName' ) * * @since 1.18 + * @deprecated since 1.21, Use getModuleManager()'s methods instead. * @return array */ public function getFormats() { - return $this->mFormats; + return $this->getModuleManager()->getNamesWithClasses( 'format' ); } } -- cgit v1.2.3-54-g00ecf