MediaWiki
REL1_21
|
00001 <?php 00041 class ApiMain extends ApiBase { 00042 00046 const API_DEFAULT_FORMAT = 'xmlfm'; 00047 00051 private static $Modules = array( 00052 'login' => 'ApiLogin', 00053 'logout' => 'ApiLogout', 00054 'createaccount' => 'ApiCreateAccount', 00055 'query' => 'ApiQuery', 00056 'expandtemplates' => 'ApiExpandTemplates', 00057 'parse' => 'ApiParse', 00058 'opensearch' => 'ApiOpenSearch', 00059 'feedcontributions' => 'ApiFeedContributions', 00060 'feedwatchlist' => 'ApiFeedWatchlist', 00061 'help' => 'ApiHelp', 00062 'paraminfo' => 'ApiParamInfo', 00063 'rsd' => 'ApiRsd', 00064 'compare' => 'ApiComparePages', 00065 'tokens' => 'ApiTokens', 00066 00067 // Write modules 00068 'purge' => 'ApiPurge', 00069 'setnotificationtimestamp' => 'ApiSetNotificationTimestamp', 00070 'rollback' => 'ApiRollback', 00071 'delete' => 'ApiDelete', 00072 'undelete' => 'ApiUndelete', 00073 'protect' => 'ApiProtect', 00074 'block' => 'ApiBlock', 00075 'unblock' => 'ApiUnblock', 00076 'move' => 'ApiMove', 00077 'edit' => 'ApiEditPage', 00078 'upload' => 'ApiUpload', 00079 'filerevert' => 'ApiFileRevert', 00080 'emailuser' => 'ApiEmailUser', 00081 'watch' => 'ApiWatch', 00082 'patrol' => 'ApiPatrol', 00083 'import' => 'ApiImport', 00084 'userrights' => 'ApiUserrights', 00085 'options' => 'ApiOptions', 00086 'imagerotate' =>'ApiImageRotate', 00087 ); 00088 00092 private static $Formats = array( 00093 'json' => 'ApiFormatJson', 00094 'jsonfm' => 'ApiFormatJson', 00095 'php' => 'ApiFormatPhp', 00096 'phpfm' => 'ApiFormatPhp', 00097 'wddx' => 'ApiFormatWddx', 00098 'wddxfm' => 'ApiFormatWddx', 00099 'xml' => 'ApiFormatXml', 00100 'xmlfm' => 'ApiFormatXml', 00101 'yaml' => 'ApiFormatYaml', 00102 'yamlfm' => 'ApiFormatYaml', 00103 'rawfm' => 'ApiFormatJson', 00104 'txt' => 'ApiFormatTxt', 00105 'txtfm' => 'ApiFormatTxt', 00106 'dbg' => 'ApiFormatDbg', 00107 'dbgfm' => 'ApiFormatDbg', 00108 'dump' => 'ApiFormatDump', 00109 'dumpfm' => 'ApiFormatDump', 00110 'none' => 'ApiFormatNone', 00111 ); 00112 00119 private static $mRights = array( 00120 'writeapi' => array( 00121 'msg' => 'Use of the write API', 00122 'params' => array() 00123 ), 00124 'apihighlimits' => array( 00125 '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.', 00126 'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 ) 00127 ) 00128 ); 00129 00133 private $mPrinter; 00134 00135 private $mModuleMgr, $mResult; 00136 private $mAction; 00137 private $mEnableWrite; 00138 private $mInternalMode, $mSquidMaxage, $mModule; 00139 00140 private $mCacheMode = 'private'; 00141 private $mCacheControl = array(); 00142 private $mParamsUsed = array(); 00143 00150 public function __construct( $context = null, $enableWrite = false ) { 00151 if ( $context === null ) { 00152 $context = RequestContext::getMain(); 00153 } elseif ( $context instanceof WebRequest ) { 00154 // BC for pre-1.19 00155 $request = $context; 00156 $context = RequestContext::getMain(); 00157 } 00158 // We set a derivative context so we can change stuff later 00159 $this->setContext( new DerivativeContext( $context ) ); 00160 00161 if ( isset( $request ) ) { 00162 $this->getContext()->setRequest( $request ); 00163 } 00164 00165 $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest ); 00166 00167 // Special handling for the main module: $parent === $this 00168 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' ); 00169 00170 if ( !$this->mInternalMode ) { 00171 // Impose module restrictions. 00172 // If the current user cannot read, 00173 // Remove all modules other than login 00174 global $wgUser; 00175 00176 if ( $this->getVal( 'callback' ) !== null ) { 00177 // JSON callback allows cross-site reads. 00178 // For safety, strip user credentials. 00179 wfDebug( "API: stripping user credentials for JSON callback\n" ); 00180 $wgUser = new User(); 00181 $this->getContext()->setUser( $wgUser ); 00182 } 00183 } 00184 00185 global $wgAPIModules; 00186 $this->mModuleMgr = new ApiModuleManager( $this ); 00187 $this->mModuleMgr->addModules( self::$Modules, 'action' ); 00188 $this->mModuleMgr->addModules( $wgAPIModules, 'action' ); 00189 $this->mModuleMgr->addModules( self::$Formats, 'format' ); 00190 00191 $this->mResult = new ApiResult( $this ); 00192 $this->mEnableWrite = $enableWrite; 00193 00194 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling() 00195 $this->mCommit = false; 00196 } 00197 00202 public function isInternalMode() { 00203 return $this->mInternalMode; 00204 } 00205 00211 public function getResult() { 00212 return $this->mResult; 00213 } 00214 00220 public function getModule() { 00221 return $this->mModule; 00222 } 00223 00229 public function getPrinter() { 00230 return $this->mPrinter; 00231 } 00232 00238 public function setCacheMaxAge( $maxage ) { 00239 $this->setCacheControl( array( 00240 'max-age' => $maxage, 00241 's-maxage' => $maxage 00242 ) ); 00243 } 00244 00270 public function setCacheMode( $mode ) { 00271 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) { 00272 wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" ); 00273 // Ignore for forwards-compatibility 00274 return; 00275 } 00276 00277 if ( !User::groupHasPermission( '*', 'read' ) ) { 00278 // Private wiki, only private headers 00279 if ( $mode !== 'private' ) { 00280 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" ); 00281 return; 00282 } 00283 } 00284 00285 wfDebug( __METHOD__ . ": setting cache mode $mode\n" ); 00286 $this->mCacheMode = $mode; 00287 } 00288 00294 public function setCachePrivate() { 00295 wfDeprecated( __METHOD__, '1.17' ); 00296 $this->setCacheMode( 'private' ); 00297 } 00298 00309 public function setCacheControl( $directives ) { 00310 $this->mCacheControl = $directives + $this->mCacheControl; 00311 } 00312 00323 public function setVaryCookie() { 00324 wfDeprecated( __METHOD__, '1.17' ); 00325 $this->setCacheMode( 'anon-public-user-private' ); 00326 } 00327 00335 public function createPrinterByName( $format ) { 00336 $printer = $this->mModuleMgr->getModule( $format, 'format' ); 00337 if ( $printer === null ) { 00338 $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); 00339 } 00340 return $printer; 00341 } 00342 00346 public function execute() { 00347 $this->profileIn(); 00348 if ( $this->mInternalMode ) { 00349 $this->executeAction(); 00350 } else { 00351 $this->executeActionWithErrorHandling(); 00352 } 00353 00354 $this->profileOut(); 00355 } 00356 00361 protected function executeActionWithErrorHandling() { 00362 // Verify the CORS header before executing the action 00363 if ( !$this->handleCORS() ) { 00364 // handleCORS() has sent a 403, abort 00365 return; 00366 } 00367 00368 // Exit here if the request method was OPTIONS 00369 // (assume there will be a followup GET or POST) 00370 if ( $this->getRequest()->getMethod() === 'OPTIONS' ) { 00371 return; 00372 } 00373 00374 // In case an error occurs during data output, 00375 // clear the output buffer and print just the error information 00376 ob_start(); 00377 00378 $t = microtime( true ); 00379 try { 00380 $this->executeAction(); 00381 } catch ( Exception $e ) { 00382 // Allow extra cleanup and logging 00383 wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); 00384 00385 // Log it 00386 if ( $e instanceof MWException && !( $e instanceof UsageException ) ) { 00387 global $wgLogExceptionBacktrace; 00388 if ( $wgLogExceptionBacktrace ) { 00389 wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" ); 00390 } else { 00391 wfDebugLog( 'exception', $e->getLogMessage() ); 00392 } 00393 } 00394 00395 // Handle any kind of exception by outputting properly formatted error message. 00396 // If this fails, an unhandled exception should be thrown so that global error 00397 // handler will process and log it. 00398 00399 $errCode = $this->substituteResultWithError( $e ); 00400 00401 // Error results should not be cached 00402 $this->setCacheMode( 'private' ); 00403 00404 $response = $this->getRequest()->response(); 00405 $headerStr = 'MediaWiki-API-Error: ' . $errCode; 00406 if ( $e->getCode() === 0 ) { 00407 $response->header( $headerStr ); 00408 } else { 00409 $response->header( $headerStr, true, $e->getCode() ); 00410 } 00411 00412 // Reset and print just the error message 00413 ob_clean(); 00414 00415 // If the error occurred during printing, do a printer->profileOut() 00416 $this->mPrinter->safeProfileOut(); 00417 $this->printResult( true ); 00418 } 00419 00420 // Log the request whether or not there was an error 00421 $this->logRequest( microtime( true ) - $t); 00422 00423 // Send cache headers after any code which might generate an error, to 00424 // avoid sending public cache headers for errors. 00425 $this->sendCacheHeaders(); 00426 00427 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) { 00428 echo wfReportTime(); 00429 } 00430 00431 ob_end_flush(); 00432 } 00433 00446 protected function handleCORS() { 00447 global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions; 00448 00449 $originParam = $this->getParameter( 'origin' ); // defaults to null 00450 if ( $originParam === null ) { 00451 // No origin parameter, nothing to do 00452 return true; 00453 } 00454 00455 $request = $this->getRequest(); 00456 $response = $request->response(); 00457 // Origin: header is a space-separated list of origins, check all of them 00458 $originHeader = $request->getHeader( 'Origin' ); 00459 if ( $originHeader === false ) { 00460 $origins = array(); 00461 } else { 00462 $origins = explode( ' ', $originHeader ); 00463 } 00464 if ( !in_array( $originParam, $origins ) ) { 00465 // origin parameter set but incorrect 00466 // Send a 403 response 00467 $message = HttpStatus::getMessage( 403 ); 00468 $response->header( "HTTP/1.1 403 $message", true, 403 ); 00469 $response->header( 'Cache-Control: no-cache' ); 00470 echo "'origin' parameter does not match Origin header\n"; 00471 return false; 00472 } 00473 if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) { 00474 $response->header( "Access-Control-Allow-Origin: $originParam" ); 00475 $response->header( 'Access-Control-Allow-Credentials: true' ); 00476 $this->getOutput()->addVaryHeader( 'Origin' ); 00477 } 00478 return true; 00479 } 00480 00488 protected static function matchOrigin( $value, $rules, $exceptions ) { 00489 foreach ( $rules as $rule ) { 00490 if ( preg_match( self::wildcardToRegex( $rule ), $value ) ) { 00491 // Rule matches, check exceptions 00492 foreach ( $exceptions as $exc ) { 00493 if ( preg_match( self::wildcardToRegex( $exc ), $value ) ) { 00494 return false; 00495 } 00496 } 00497 return true; 00498 } 00499 } 00500 return false; 00501 } 00502 00511 protected static function wildcardToRegex( $wildcard ) { 00512 $wildcard = preg_quote( $wildcard, '/' ); 00513 $wildcard = str_replace( 00514 array( '\*', '\?' ), 00515 array( '.*?', '.' ), 00516 $wildcard 00517 ); 00518 return "/https?:\/\/$wildcard/"; 00519 } 00520 00521 protected function sendCacheHeaders() { 00522 global $wgUseXVO, $wgVaryOnXFP; 00523 $response = $this->getRequest()->response(); 00524 $out = $this->getOutput(); 00525 00526 if ( $wgVaryOnXFP ) { 00527 $out->addVaryHeader( 'X-Forwarded-Proto' ); 00528 } 00529 00530 if ( $this->mCacheMode == 'private' ) { 00531 $response->header( 'Cache-Control: private' ); 00532 return; 00533 } 00534 00535 if ( $this->mCacheMode == 'anon-public-user-private' ) { 00536 $out->addVaryHeader( 'Cookie' ); 00537 $response->header( $out->getVaryHeader() ); 00538 if ( $wgUseXVO ) { 00539 $response->header( $out->getXVO() ); 00540 if ( $out->haveCacheVaryCookies() ) { 00541 // Logged in, mark this request private 00542 $response->header( 'Cache-Control: private' ); 00543 return; 00544 } 00545 // Logged out, send normal public headers below 00546 } elseif ( session_id() != '' ) { 00547 // Logged in or otherwise has session (e.g. anonymous users who have edited) 00548 // Mark request private 00549 $response->header( 'Cache-Control: private' ); 00550 return; 00551 } // else no XVO and anonymous, send public headers below 00552 } 00553 00554 // Send public headers 00555 $response->header( $out->getVaryHeader() ); 00556 if ( $wgUseXVO ) { 00557 $response->header( $out->getXVO() ); 00558 } 00559 00560 // If nobody called setCacheMaxAge(), use the (s)maxage parameters 00561 if ( !isset( $this->mCacheControl['s-maxage'] ) ) { 00562 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' ); 00563 } 00564 if ( !isset( $this->mCacheControl['max-age'] ) ) { 00565 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' ); 00566 } 00567 00568 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) { 00569 // Public cache not requested 00570 // Sending a Vary header in this case is harmless, and protects us 00571 // against conditional calls of setCacheMaxAge(). 00572 $response->header( 'Cache-Control: private' ); 00573 return; 00574 } 00575 00576 $this->mCacheControl['public'] = true; 00577 00578 // Send an Expires header 00579 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] ); 00580 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge ); 00581 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); 00582 00583 // Construct the Cache-Control header 00584 $ccHeader = ''; 00585 $separator = ''; 00586 foreach ( $this->mCacheControl as $name => $value ) { 00587 if ( is_bool( $value ) ) { 00588 if ( $value ) { 00589 $ccHeader .= $separator . $name; 00590 $separator = ', '; 00591 } 00592 } else { 00593 $ccHeader .= $separator . "$name=$value"; 00594 $separator = ', '; 00595 } 00596 } 00597 00598 $response->header( "Cache-Control: $ccHeader" ); 00599 } 00600 00607 protected function substituteResultWithError( $e ) { 00608 global $wgShowHostnames; 00609 00610 $result = $this->getResult(); 00611 // Printer may not be initialized if the extractRequestParams() fails for the main module 00612 if ( !isset ( $this->mPrinter ) ) { 00613 // The printer has not been created yet. Try to manually get formatter value. 00614 $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); 00615 if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) { 00616 $value = self::API_DEFAULT_FORMAT; 00617 } 00618 00619 $this->mPrinter = $this->createPrinterByName( $value ); 00620 if ( $this->mPrinter->getNeedsRawData() ) { 00621 $result->setRawMode(); 00622 } 00623 } 00624 00625 if ( $e instanceof UsageException ) { 00626 // User entered incorrect parameters - print usage screen 00627 $errMessage = $e->getMessageArray(); 00628 00629 // Only print the help message when this is for the developer, not runtime 00630 if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) { 00631 ApiResult::setContent( $errMessage, $this->makeHelpMsg() ); 00632 } 00633 } else { 00634 global $wgShowSQLErrors, $wgShowExceptionDetails; 00635 // Something is seriously wrong 00636 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) { 00637 $info = 'Database query error'; 00638 } else { 00639 $info = "Exception Caught: {$e->getMessage()}"; 00640 } 00641 00642 $errMessage = array( 00643 'code' => 'internal_api_error_' . get_class( $e ), 00644 'info' => $info, 00645 ); 00646 ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); 00647 } 00648 00649 // Remember all the warnings to re-add them later 00650 $oldResult = $result->getData(); 00651 $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null; 00652 00653 $result->reset(); 00654 $result->disableSizeCheck(); 00655 // Re-add the id 00656 $requestid = $this->getParameter( 'requestid' ); 00657 if ( !is_null( $requestid ) ) { 00658 $result->addValue( null, 'requestid', $requestid ); 00659 } 00660 if ( $wgShowHostnames ) { 00661 // servedby is especially useful when debugging errors 00662 $result->addValue( null, 'servedby', wfHostName() ); 00663 } 00664 if ( $warnings !== null ) { 00665 $result->addValue( null, 'warnings', $warnings ); 00666 } 00667 00668 $result->addValue( null, 'error', $errMessage ); 00669 00670 return $errMessage['code']; 00671 } 00672 00677 protected function setupExecuteAction() { 00678 global $wgShowHostnames; 00679 00680 // First add the id to the top element 00681 $result = $this->getResult(); 00682 $requestid = $this->getParameter( 'requestid' ); 00683 if ( !is_null( $requestid ) ) { 00684 $result->addValue( null, 'requestid', $requestid ); 00685 } 00686 00687 if ( $wgShowHostnames ) { 00688 $servedby = $this->getParameter( 'servedby' ); 00689 if ( $servedby ) { 00690 $result->addValue( null, 'servedby', wfHostName() ); 00691 } 00692 } 00693 00694 $params = $this->extractRequestParams(); 00695 00696 $this->mAction = $params['action']; 00697 00698 if ( !is_string( $this->mAction ) ) { 00699 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00700 } 00701 00702 return $params; 00703 } 00704 00709 protected function setupModule() { 00710 // Instantiate the module requested by the user 00711 $module = $this->mModuleMgr->getModule( $this->mAction, 'action' ); 00712 if ( $module === null ) { 00713 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00714 } 00715 $moduleParams = $module->extractRequestParams(); 00716 00717 // Die if token required, but not provided 00718 $salt = $module->getTokenSalt(); 00719 if ( $salt !== false ) { 00720 if ( !isset( $moduleParams['token'] ) ) { 00721 $this->dieUsageMsg( array( 'missingparam', 'token' ) ); 00722 } else { 00723 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) { 00724 $this->dieUsageMsg( 'sessionfailure' ); 00725 } 00726 } 00727 } 00728 return $module; 00729 } 00730 00737 protected function checkMaxLag( $module, $params ) { 00738 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { 00739 // Check for maxlag 00740 global $wgShowHostnames; 00741 $maxLag = $params['maxlag']; 00742 list( $host, $lag ) = wfGetLB()->getMaxLag(); 00743 if ( $lag > $maxLag ) { 00744 $response = $this->getRequest()->response(); 00745 00746 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); 00747 $response->header( 'X-Database-Lag: ' . intval( $lag ) ); 00748 00749 if ( $wgShowHostnames ) { 00750 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); 00751 } else { 00752 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); 00753 } 00754 return false; 00755 } 00756 } 00757 return true; 00758 } 00759 00764 protected function checkExecutePermissions( $module ) { 00765 $user = $this->getUser(); 00766 if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) && 00767 !$user->isAllowed( 'read' ) ) 00768 { 00769 $this->dieUsageMsg( 'readrequired' ); 00770 } 00771 if ( $module->isWriteMode() ) { 00772 if ( !$this->mEnableWrite ) { 00773 $this->dieUsageMsg( 'writedisabled' ); 00774 } 00775 if ( !$user->isAllowed( 'writeapi' ) ) { 00776 $this->dieUsageMsg( 'writerequired' ); 00777 } 00778 if ( wfReadOnly() ) { 00779 $this->dieReadOnly(); 00780 } 00781 } 00782 00783 // Allow extensions to stop execution for arbitrary reasons. 00784 $message = false; 00785 if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { 00786 $this->dieUsageMsg( $message ); 00787 } 00788 } 00789 00795 protected function setupExternalResponse( $module, $params ) { 00796 if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) { 00797 // Module requires POST. GET request might still be allowed 00798 // if $wgDebugApi is true, otherwise fail. 00799 $this->dieUsageMsgOrDebug( array( 'mustbeposted', $this->mAction ) ); 00800 } 00801 00802 // See if custom printer is used 00803 $this->mPrinter = $module->getCustomPrinter(); 00804 if ( is_null( $this->mPrinter ) ) { 00805 // Create an appropriate printer 00806 $this->mPrinter = $this->createPrinterByName( $params['format'] ); 00807 } 00808 00809 if ( $this->mPrinter->getNeedsRawData() ) { 00810 $this->getResult()->setRawMode(); 00811 } 00812 } 00813 00817 protected function executeAction() { 00818 $params = $this->setupExecuteAction(); 00819 $module = $this->setupModule(); 00820 $this->mModule = $module; 00821 00822 $this->checkExecutePermissions( $module ); 00823 00824 if ( !$this->checkMaxLag( $module, $params ) ) { 00825 return; 00826 } 00827 00828 if ( !$this->mInternalMode ) { 00829 $this->setupExternalResponse( $module, $params ); 00830 } 00831 00832 // Execute 00833 $module->profileIn(); 00834 $module->execute(); 00835 wfRunHooks( 'APIAfterExecute', array( &$module ) ); 00836 $module->profileOut(); 00837 00838 $this->reportUnusedParams(); 00839 00840 if ( !$this->mInternalMode ) { 00841 //append Debug information 00842 MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() ); 00843 00844 // Print result data 00845 $this->printResult( false ); 00846 } 00847 } 00848 00853 protected function logRequest( $time ) { 00854 $request = $this->getRequest(); 00855 $milliseconds = $time === null ? '?' : round( $time * 1000 ); 00856 $s = 'API' . 00857 ' ' . $request->getMethod() . 00858 ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . 00859 ' ' . $request->getIP() . 00860 ' T=' . $milliseconds .'ms'; 00861 foreach ( $this->getParamsUsed() as $name ) { 00862 $value = $request->getVal( $name ); 00863 if ( $value === null ) { 00864 continue; 00865 } 00866 $s .= ' ' . $name . '='; 00867 if ( strlen( $value ) > 256 ) { 00868 $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) ); 00869 $s .= $encValue . '[...]'; 00870 } else { 00871 $s .= $this->encodeRequestLogValue( $value ); 00872 } 00873 } 00874 $s .= "\n"; 00875 wfDebugLog( 'api', $s, false ); 00876 } 00877 00881 protected function encodeRequestLogValue( $s ) { 00882 static $table; 00883 if ( !$table ) { 00884 $chars = ';@$!*(),/:'; 00885 for ( $i = 0; $i < strlen( $chars ); $i++ ) { 00886 $table[rawurlencode( $chars[$i] )] = $chars[$i]; 00887 } 00888 } 00889 return strtr( rawurlencode( $s ), $table ); 00890 } 00891 00895 protected function getParamsUsed() { 00896 return array_keys( $this->mParamsUsed ); 00897 } 00898 00902 public function getVal( $name, $default = null ) { 00903 $this->mParamsUsed[$name] = true; 00904 return $this->getRequest()->getVal( $name, $default ); 00905 } 00906 00911 public function getCheck( $name ) { 00912 $this->mParamsUsed[$name] = true; 00913 return $this->getRequest()->getCheck( $name ); 00914 } 00915 00923 public function getUpload( $name ) { 00924 $this->mParamsUsed[$name] = true; 00925 return $this->getRequest()->getUpload( $name ); 00926 } 00927 00932 protected function reportUnusedParams() { 00933 $paramsUsed = $this->getParamsUsed(); 00934 $allParams = $this->getRequest()->getValueNames(); 00935 00936 if ( !$this->mInternalMode ) { 00937 // Printer has not yet executed; don't warn that its parameters are unused 00938 $printerParams = array_map( 00939 array( $this->mPrinter, 'encodeParamName' ), 00940 array_keys( $this->mPrinter->getFinalParams() ?: array() ) 00941 ); 00942 $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams ); 00943 } else { 00944 $unusedParams = array_diff( $allParams, $paramsUsed ); 00945 } 00946 00947 if( count( $unusedParams ) ) { 00948 $s = count( $unusedParams ) > 1 ? 's' : ''; 00949 $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" ); 00950 } 00951 } 00952 00958 protected function printResult( $isError ) { 00959 global $wgDebugAPI; 00960 if( $wgDebugAPI !== false ) { 00961 $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); 00962 } 00963 00964 $this->getResult()->cleanUpUTF8(); 00965 $printer = $this->mPrinter; 00966 $printer->profileIn(); 00967 00973 $isHelp = $isError || $this->mAction == 'help'; 00974 $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); 00975 00976 $printer->initPrinter( $isHelp ); 00977 00978 $printer->execute(); 00979 $printer->closePrinter(); 00980 $printer->profileOut(); 00981 } 00982 00986 public function isReadMode() { 00987 return false; 00988 } 00989 00995 public function getAllowedParams() { 00996 return array( 00997 'format' => array( 00998 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, 00999 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' ) 01000 ), 01001 'action' => array( 01002 ApiBase::PARAM_DFLT => 'help', 01003 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' ) 01004 ), 01005 'maxlag' => array( 01006 ApiBase::PARAM_TYPE => 'integer' 01007 ), 01008 'smaxage' => array( 01009 ApiBase::PARAM_TYPE => 'integer', 01010 ApiBase::PARAM_DFLT => 0 01011 ), 01012 'maxage' => array( 01013 ApiBase::PARAM_TYPE => 'integer', 01014 ApiBase::PARAM_DFLT => 0 01015 ), 01016 'requestid' => null, 01017 'servedby' => false, 01018 'origin' => null, 01019 ); 01020 } 01021 01027 public function getParamDescription() { 01028 return array( 01029 'format' => 'The format of the output', 01030 'action' => 'What action you would like to perform. See below for module help', 01031 'maxlag' => array( 01032 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', 01033 'To save actions causing any more site replication lag, this parameter can make the client', 01034 'wait until the replication lag is less than the specified value.', 01035 'In case of a replag error, error code "maxlag" is returned, with the message like', 01036 '"Waiting for $host: $lag seconds lagged\n".', 01037 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', 01038 ), 01039 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 01040 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', 01041 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 01042 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error', 01043 'origin' => array( 01044 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.', 01045 'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .', 01046 'If this parameter does not match the Origin: header, a 403 response will be returned.', 01047 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.', 01048 ), 01049 ); 01050 } 01051 01057 public function getDescription() { 01058 return array( 01059 '', 01060 '', 01061 '**********************************************************************************************************', 01062 '** **', 01063 '** This is an auto-generated MediaWiki API documentation page **', 01064 '** **', 01065 '** Documentation and Examples: **', 01066 '** https://www.mediawiki.org/wiki/API **', 01067 '** **', 01068 '**********************************************************************************************************', 01069 '', 01070 'Status: All features shown on this page should be working, but the API', 01071 ' is still in active development, and may change at any time.', 01072 ' Make sure to monitor our mailing list for any updates', 01073 '', 01074 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent', 01075 ' with the key "MediaWiki-API-Error" and then both the value of the', 01076 ' header and the error code sent back will be set to the same value', 01077 '', 01078 ' In the case of an invalid action being passed, these will have a value', 01079 ' of "unknown_action"', 01080 '', 01081 ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings', 01082 '', 01083 'Documentation: https://www.mediawiki.org/wiki/API:Main_page', 01084 'FAQ https://www.mediawiki.org/wiki/API:FAQ', 01085 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api', 01086 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', 01087 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', 01088 '', 01089 '', 01090 '', 01091 '', 01092 '', 01093 ); 01094 } 01095 01099 public function getPossibleErrors() { 01100 return array_merge( parent::getPossibleErrors(), array( 01101 array( 'readonlytext' ), 01102 array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ), 01103 array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ), 01104 array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ), 01105 array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ), 01106 ) ); 01107 } 01108 01113 protected function getCredits() { 01114 return array( 01115 'API developers:', 01116 ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)', 01117 ' Victor Vasiliev - vasilvv at gee mail dot com', 01118 ' Bryan Tong Minh - bryan . tongminh @ gmail . com', 01119 ' Sam Reed - sam @ reedyboy . net', 01120 ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)', 01121 '', 01122 'Please send your comments, suggestions and questions to [email protected]', 01123 'or file a bug report at https://bugzilla.wikimedia.org/' 01124 ); 01125 } 01126 01132 public function setHelp( $help = true ) { 01133 $this->mPrinter->setHelp( $help ); 01134 } 01135 01141 public function makeHelpMsg() { 01142 global $wgMemc, $wgAPICacheHelpTimeout; 01143 $this->setHelp(); 01144 // Get help text from cache if present 01145 $key = wfMemcKey( 'apihelp', $this->getModuleName(), 01146 SpecialVersion::getVersion( 'nodb' ) ); 01147 if ( $wgAPICacheHelpTimeout > 0 ) { 01148 $cached = $wgMemc->get( $key ); 01149 if ( $cached ) { 01150 return $cached; 01151 } 01152 } 01153 $retval = $this->reallyMakeHelpMsg(); 01154 if ( $wgAPICacheHelpTimeout > 0 ) { 01155 $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout ); 01156 } 01157 return $retval; 01158 } 01159 01163 public function reallyMakeHelpMsg() { 01164 $this->setHelp(); 01165 01166 // Use parent to make default message for the main module 01167 $msg = parent::makeHelpMsg(); 01168 01169 $astriks = str_repeat( '*** ', 14 ); 01170 $msg .= "\n\n$astriks Modules $astriks\n\n"; 01171 01172 foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) { 01173 $module = $this->mModuleMgr->getModule( $name ); 01174 $msg .= self::makeHelpMsgHeader( $module, 'action' ); 01175 01176 $msg2 = $module->makeHelpMsg(); 01177 if ( $msg2 !== false ) { 01178 $msg .= $msg2; 01179 } 01180 $msg .= "\n"; 01181 } 01182 01183 $msg .= "\n$astriks Permissions $astriks\n\n"; 01184 foreach ( self::$mRights as $right => $rightMsg ) { 01185 $groups = User::getGroupsWithPermission( $right ); 01186 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) . 01187 "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n"; 01188 } 01189 01190 $msg .= "\n$astriks Formats $astriks\n\n"; 01191 foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) { 01192 $module = $this->mModuleMgr->getModule( $name ); 01193 $msg .= self::makeHelpMsgHeader( $module, 'format' ); 01194 $msg2 = $module->makeHelpMsg(); 01195 if ( $msg2 !== false ) { 01196 $msg .= $msg2; 01197 } 01198 $msg .= "\n"; 01199 } 01200 01201 $msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n"; 01202 01203 return $msg; 01204 } 01205 01211 public static function makeHelpMsgHeader( $module, $paramName ) { 01212 $modulePrefix = $module->getModulePrefix(); 01213 if ( strval( $modulePrefix ) !== '' ) { 01214 $modulePrefix = "($modulePrefix) "; 01215 } 01216 01217 return "* $paramName={$module->getModuleName()} $modulePrefix*"; 01218 } 01219 01220 private $mCanApiHighLimits = null; 01221 01226 public function canApiHighLimits() { 01227 if ( !isset( $this->mCanApiHighLimits ) ) { 01228 $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' ); 01229 } 01230 01231 return $this->mCanApiHighLimits; 01232 } 01233 01239 public function getShowVersions() { 01240 wfDeprecated( __METHOD__, '1.21' ); 01241 return false; 01242 } 01243 01248 public function getModuleManager() { 01249 return $this->mModuleMgr; 01250 } 01251 01261 protected function addModule( $name, $class ) { 01262 $this->getModuleManager()->addModule( $name, 'action', $class ); 01263 } 01264 01273 protected function addFormat( $name, $class ) { 01274 $this->getModuleManager()->addModule( $name, 'format', $class ); 01275 } 01276 01282 function getModules() { 01283 return $this->getModuleManager()->getNamesWithClasses( 'action' ); 01284 } 01285 01293 public function getFormats() { 01294 return $this->getModuleManager()->getNamesWithClasses( 'format' ); 01295 } 01296 } 01297 01304 class UsageException extends MWException { 01305 01306 private $mCodestr; 01307 01311 private $mExtraData; 01312 01319 public function __construct( $message, $codestr, $code = 0, $extradata = null ) { 01320 parent::__construct( $message, $code ); 01321 $this->mCodestr = $codestr; 01322 $this->mExtraData = $extradata; 01323 } 01324 01328 public function getCodeString() { 01329 return $this->mCodestr; 01330 } 01331 01335 public function getMessageArray() { 01336 $result = array( 01337 'code' => $this->mCodestr, 01338 'info' => $this->getMessage() 01339 ); 01340 if ( is_array( $this->mExtraData ) ) { 01341 $result = array_merge( $result, $this->mExtraData ); 01342 } 01343 return $result; 01344 } 01345 01349 public function __toString() { 01350 return "{$this->getCodeString()}: {$this->getMessage()}"; 01351 } 01352 }