MediaWiki
REL1_22
|
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::isEveryoneAllowed( '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 UsageException ) ) { 00387 MWExceptionHandler::logException( $e ); 00388 } 00389 00390 // Handle any kind of exception by outputting properly formatted error message. 00391 // If this fails, an unhandled exception should be thrown so that global error 00392 // handler will process and log it. 00393 00394 $errCode = $this->substituteResultWithError( $e ); 00395 00396 // Error results should not be cached 00397 $this->setCacheMode( 'private' ); 00398 00399 $response = $this->getRequest()->response(); 00400 $headerStr = 'MediaWiki-API-Error: ' . $errCode; 00401 if ( $e->getCode() === 0 ) { 00402 $response->header( $headerStr ); 00403 } else { 00404 $response->header( $headerStr, true, $e->getCode() ); 00405 } 00406 00407 // Reset and print just the error message 00408 ob_clean(); 00409 00410 // If the error occurred during printing, do a printer->profileOut() 00411 $this->mPrinter->safeProfileOut(); 00412 $this->printResult( true ); 00413 } 00414 00415 // Log the request whether or not there was an error 00416 $this->logRequest( microtime( true ) - $t ); 00417 00418 // Send cache headers after any code which might generate an error, to 00419 // avoid sending public cache headers for errors. 00420 $this->sendCacheHeaders(); 00421 00422 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) { 00423 echo wfReportTime(); 00424 } 00425 00426 ob_end_flush(); 00427 } 00428 00441 protected function handleCORS() { 00442 global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions; 00443 00444 $originParam = $this->getParameter( 'origin' ); // defaults to null 00445 if ( $originParam === null ) { 00446 // No origin parameter, nothing to do 00447 return true; 00448 } 00449 00450 $request = $this->getRequest(); 00451 $response = $request->response(); 00452 // Origin: header is a space-separated list of origins, check all of them 00453 $originHeader = $request->getHeader( 'Origin' ); 00454 if ( $originHeader === false ) { 00455 $origins = array(); 00456 } else { 00457 $origins = explode( ' ', $originHeader ); 00458 } 00459 if ( !in_array( $originParam, $origins ) ) { 00460 // origin parameter set but incorrect 00461 // Send a 403 response 00462 $message = HttpStatus::getMessage( 403 ); 00463 $response->header( "HTTP/1.1 403 $message", true, 403 ); 00464 $response->header( 'Cache-Control: no-cache' ); 00465 echo "'origin' parameter does not match Origin header\n"; 00466 return false; 00467 } 00468 if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) { 00469 $response->header( "Access-Control-Allow-Origin: $originParam" ); 00470 $response->header( 'Access-Control-Allow-Credentials: true' ); 00471 $this->getOutput()->addVaryHeader( 'Origin' ); 00472 } 00473 return true; 00474 } 00475 00483 protected static function matchOrigin( $value, $rules, $exceptions ) { 00484 foreach ( $rules as $rule ) { 00485 if ( preg_match( self::wildcardToRegex( $rule ), $value ) ) { 00486 // Rule matches, check exceptions 00487 foreach ( $exceptions as $exc ) { 00488 if ( preg_match( self::wildcardToRegex( $exc ), $value ) ) { 00489 return false; 00490 } 00491 } 00492 return true; 00493 } 00494 } 00495 return false; 00496 } 00497 00506 protected static function wildcardToRegex( $wildcard ) { 00507 $wildcard = preg_quote( $wildcard, '/' ); 00508 $wildcard = str_replace( 00509 array( '\*', '\?' ), 00510 array( '.*?', '.' ), 00511 $wildcard 00512 ); 00513 return "/https?:\/\/$wildcard/"; 00514 } 00515 00516 protected function sendCacheHeaders() { 00517 global $wgUseXVO, $wgVaryOnXFP; 00518 $response = $this->getRequest()->response(); 00519 $out = $this->getOutput(); 00520 00521 if ( $wgVaryOnXFP ) { 00522 $out->addVaryHeader( 'X-Forwarded-Proto' ); 00523 } 00524 00525 if ( $this->mCacheMode == 'private' ) { 00526 $response->header( 'Cache-Control: private' ); 00527 return; 00528 } 00529 00530 if ( $this->mCacheMode == 'anon-public-user-private' ) { 00531 $out->addVaryHeader( 'Cookie' ); 00532 $response->header( $out->getVaryHeader() ); 00533 if ( $wgUseXVO ) { 00534 $response->header( $out->getXVO() ); 00535 if ( $out->haveCacheVaryCookies() ) { 00536 // Logged in, mark this request private 00537 $response->header( 'Cache-Control: private' ); 00538 return; 00539 } 00540 // Logged out, send normal public headers below 00541 } elseif ( session_id() != '' ) { 00542 // Logged in or otherwise has session (e.g. anonymous users who have edited) 00543 // Mark request private 00544 $response->header( 'Cache-Control: private' ); 00545 return; 00546 } // else no XVO and anonymous, send public headers below 00547 } 00548 00549 // Send public headers 00550 $response->header( $out->getVaryHeader() ); 00551 if ( $wgUseXVO ) { 00552 $response->header( $out->getXVO() ); 00553 } 00554 00555 // If nobody called setCacheMaxAge(), use the (s)maxage parameters 00556 if ( !isset( $this->mCacheControl['s-maxage'] ) ) { 00557 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' ); 00558 } 00559 if ( !isset( $this->mCacheControl['max-age'] ) ) { 00560 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' ); 00561 } 00562 00563 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) { 00564 // Public cache not requested 00565 // Sending a Vary header in this case is harmless, and protects us 00566 // against conditional calls of setCacheMaxAge(). 00567 $response->header( 'Cache-Control: private' ); 00568 return; 00569 } 00570 00571 $this->mCacheControl['public'] = true; 00572 00573 // Send an Expires header 00574 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] ); 00575 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge ); 00576 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); 00577 00578 // Construct the Cache-Control header 00579 $ccHeader = ''; 00580 $separator = ''; 00581 foreach ( $this->mCacheControl as $name => $value ) { 00582 if ( is_bool( $value ) ) { 00583 if ( $value ) { 00584 $ccHeader .= $separator . $name; 00585 $separator = ', '; 00586 } 00587 } else { 00588 $ccHeader .= $separator . "$name=$value"; 00589 $separator = ', '; 00590 } 00591 } 00592 00593 $response->header( "Cache-Control: $ccHeader" ); 00594 } 00595 00602 protected function substituteResultWithError( $e ) { 00603 global $wgShowHostnames; 00604 00605 $result = $this->getResult(); 00606 // Printer may not be initialized if the extractRequestParams() fails for the main module 00607 if ( !isset( $this->mPrinter ) ) { 00608 // The printer has not been created yet. Try to manually get formatter value. 00609 $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); 00610 if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) { 00611 $value = self::API_DEFAULT_FORMAT; 00612 } 00613 00614 $this->mPrinter = $this->createPrinterByName( $value ); 00615 if ( $this->mPrinter->getNeedsRawData() ) { 00616 $result->setRawMode(); 00617 } 00618 } 00619 00620 if ( $e instanceof UsageException ) { 00621 // User entered incorrect parameters - print usage screen 00622 $errMessage = $e->getMessageArray(); 00623 00624 // Only print the help message when this is for the developer, not runtime 00625 if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) { 00626 ApiResult::setContent( $errMessage, $this->makeHelpMsg() ); 00627 } 00628 } else { 00629 global $wgShowSQLErrors, $wgShowExceptionDetails; 00630 // Something is seriously wrong 00631 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) { 00632 $info = 'Database query error'; 00633 } else { 00634 $info = "Exception Caught: {$e->getMessage()}"; 00635 } 00636 00637 $errMessage = array( 00638 'code' => 'internal_api_error_' . get_class( $e ), 00639 'info' => $info, 00640 ); 00641 ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); 00642 } 00643 00644 // Remember all the warnings to re-add them later 00645 $oldResult = $result->getData(); 00646 $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null; 00647 00648 $result->reset(); 00649 $result->disableSizeCheck(); 00650 // Re-add the id 00651 $requestid = $this->getParameter( 'requestid' ); 00652 if ( !is_null( $requestid ) ) { 00653 $result->addValue( null, 'requestid', $requestid ); 00654 } 00655 if ( $wgShowHostnames ) { 00656 // servedby is especially useful when debugging errors 00657 $result->addValue( null, 'servedby', wfHostName() ); 00658 } 00659 if ( $warnings !== null ) { 00660 $result->addValue( null, 'warnings', $warnings ); 00661 } 00662 00663 $result->addValue( null, 'error', $errMessage ); 00664 00665 return $errMessage['code']; 00666 } 00667 00672 protected function setupExecuteAction() { 00673 global $wgShowHostnames; 00674 00675 // First add the id to the top element 00676 $result = $this->getResult(); 00677 $requestid = $this->getParameter( 'requestid' ); 00678 if ( !is_null( $requestid ) ) { 00679 $result->addValue( null, 'requestid', $requestid ); 00680 } 00681 00682 if ( $wgShowHostnames ) { 00683 $servedby = $this->getParameter( 'servedby' ); 00684 if ( $servedby ) { 00685 $result->addValue( null, 'servedby', wfHostName() ); 00686 } 00687 } 00688 00689 $params = $this->extractRequestParams(); 00690 00691 $this->mAction = $params['action']; 00692 00693 if ( !is_string( $this->mAction ) ) { 00694 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00695 } 00696 00697 return $params; 00698 } 00699 00704 protected function setupModule() { 00705 // Instantiate the module requested by the user 00706 $module = $this->mModuleMgr->getModule( $this->mAction, 'action' ); 00707 if ( $module === null ) { 00708 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00709 } 00710 $moduleParams = $module->extractRequestParams(); 00711 00712 // Die if token required, but not provided 00713 $salt = $module->getTokenSalt(); 00714 if ( $salt !== false ) { 00715 if ( !isset( $moduleParams['token'] ) ) { 00716 $this->dieUsageMsg( array( 'missingparam', 'token' ) ); 00717 } else { 00718 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) { 00719 $this->dieUsageMsg( 'sessionfailure' ); 00720 } 00721 } 00722 } 00723 return $module; 00724 } 00725 00732 protected function checkMaxLag( $module, $params ) { 00733 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { 00734 // Check for maxlag 00735 global $wgShowHostnames; 00736 $maxLag = $params['maxlag']; 00737 list( $host, $lag ) = wfGetLB()->getMaxLag(); 00738 if ( $lag > $maxLag ) { 00739 $response = $this->getRequest()->response(); 00740 00741 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); 00742 $response->header( 'X-Database-Lag: ' . intval( $lag ) ); 00743 00744 if ( $wgShowHostnames ) { 00745 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); 00746 } else { 00747 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); 00748 } 00749 return false; 00750 } 00751 } 00752 return true; 00753 } 00754 00759 protected function checkExecutePermissions( $module ) { 00760 $user = $this->getUser(); 00761 if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) && 00762 !$user->isAllowed( 'read' ) ) 00763 { 00764 $this->dieUsageMsg( 'readrequired' ); 00765 } 00766 if ( $module->isWriteMode() ) { 00767 if ( !$this->mEnableWrite ) { 00768 $this->dieUsageMsg( 'writedisabled' ); 00769 } 00770 if ( !$user->isAllowed( 'writeapi' ) ) { 00771 $this->dieUsageMsg( 'writerequired' ); 00772 } 00773 if ( wfReadOnly() ) { 00774 $this->dieReadOnly(); 00775 } 00776 } 00777 00778 // Allow extensions to stop execution for arbitrary reasons. 00779 $message = false; 00780 if ( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { 00781 $this->dieUsageMsg( $message ); 00782 } 00783 } 00784 00790 protected function setupExternalResponse( $module, $params ) { 00791 if ( !$this->getRequest()->wasPosted() && $module->mustBePosted() ) { 00792 // Module requires POST. GET request might still be allowed 00793 // if $wgDebugApi is true, otherwise fail. 00794 $this->dieUsageMsgOrDebug( array( 'mustbeposted', $this->mAction ) ); 00795 } 00796 00797 // See if custom printer is used 00798 $this->mPrinter = $module->getCustomPrinter(); 00799 if ( is_null( $this->mPrinter ) ) { 00800 // Create an appropriate printer 00801 $this->mPrinter = $this->createPrinterByName( $params['format'] ); 00802 } 00803 00804 if ( $this->mPrinter->getNeedsRawData() ) { 00805 $this->getResult()->setRawMode(); 00806 } 00807 } 00808 00812 protected function executeAction() { 00813 $params = $this->setupExecuteAction(); 00814 $module = $this->setupModule(); 00815 $this->mModule = $module; 00816 00817 $this->checkExecutePermissions( $module ); 00818 00819 if ( !$this->checkMaxLag( $module, $params ) ) { 00820 return; 00821 } 00822 00823 if ( !$this->mInternalMode ) { 00824 $this->setupExternalResponse( $module, $params ); 00825 } 00826 00827 // Execute 00828 $module->profileIn(); 00829 $module->execute(); 00830 wfRunHooks( 'APIAfterExecute', array( &$module ) ); 00831 $module->profileOut(); 00832 00833 $this->reportUnusedParams(); 00834 00835 if ( !$this->mInternalMode ) { 00836 //append Debug information 00837 MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() ); 00838 00839 // Print result data 00840 $this->printResult( false ); 00841 } 00842 } 00843 00848 protected function logRequest( $time ) { 00849 $request = $this->getRequest(); 00850 $milliseconds = $time === null ? '?' : round( $time * 1000 ); 00851 $s = 'API' . 00852 ' ' . $request->getMethod() . 00853 ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . 00854 ' ' . $request->getIP() . 00855 ' T=' . $milliseconds . 'ms'; 00856 foreach ( $this->getParamsUsed() as $name ) { 00857 $value = $request->getVal( $name ); 00858 if ( $value === null ) { 00859 continue; 00860 } 00861 $s .= ' ' . $name . '='; 00862 if ( strlen( $value ) > 256 ) { 00863 $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) ); 00864 $s .= $encValue . '[...]'; 00865 } else { 00866 $s .= $this->encodeRequestLogValue( $value ); 00867 } 00868 } 00869 $s .= "\n"; 00870 wfDebugLog( 'api', $s, false ); 00871 } 00872 00876 protected function encodeRequestLogValue( $s ) { 00877 static $table; 00878 if ( !$table ) { 00879 $chars = ';@$!*(),/:'; 00880 for ( $i = 0; $i < strlen( $chars ); $i++ ) { 00881 $table[rawurlencode( $chars[$i] )] = $chars[$i]; 00882 } 00883 } 00884 return strtr( rawurlencode( $s ), $table ); 00885 } 00886 00890 protected function getParamsUsed() { 00891 return array_keys( $this->mParamsUsed ); 00892 } 00893 00897 public function getVal( $name, $default = null ) { 00898 $this->mParamsUsed[$name] = true; 00899 return $this->getRequest()->getVal( $name, $default ); 00900 } 00901 00906 public function getCheck( $name ) { 00907 $this->mParamsUsed[$name] = true; 00908 return $this->getRequest()->getCheck( $name ); 00909 } 00910 00918 public function getUpload( $name ) { 00919 $this->mParamsUsed[$name] = true; 00920 return $this->getRequest()->getUpload( $name ); 00921 } 00922 00927 protected function reportUnusedParams() { 00928 $paramsUsed = $this->getParamsUsed(); 00929 $allParams = $this->getRequest()->getValueNames(); 00930 00931 if ( !$this->mInternalMode ) { 00932 // Printer has not yet executed; don't warn that its parameters are unused 00933 $printerParams = array_map( 00934 array( $this->mPrinter, 'encodeParamName' ), 00935 array_keys( $this->mPrinter->getFinalParams() ?: array() ) 00936 ); 00937 $unusedParams = array_diff( $allParams, $paramsUsed, $printerParams ); 00938 } else { 00939 $unusedParams = array_diff( $allParams, $paramsUsed ); 00940 } 00941 00942 if ( count( $unusedParams ) ) { 00943 $s = count( $unusedParams ) > 1 ? 's' : ''; 00944 $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" ); 00945 } 00946 } 00947 00953 protected function printResult( $isError ) { 00954 global $wgDebugAPI; 00955 if ( $wgDebugAPI !== false ) { 00956 $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); 00957 } 00958 00959 $this->getResult()->cleanUpUTF8(); 00960 $printer = $this->mPrinter; 00961 $printer->profileIn(); 00962 00968 $isHelp = $isError || $this->mAction == 'help'; 00969 $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); 00970 00971 $printer->initPrinter( $isHelp ); 00972 00973 $printer->execute(); 00974 $printer->closePrinter(); 00975 $printer->profileOut(); 00976 } 00977 00981 public function isReadMode() { 00982 return false; 00983 } 00984 00990 public function getAllowedParams() { 00991 return array( 00992 'format' => array( 00993 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, 00994 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' ) 00995 ), 00996 'action' => array( 00997 ApiBase::PARAM_DFLT => 'help', 00998 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' ) 00999 ), 01000 'maxlag' => array( 01001 ApiBase::PARAM_TYPE => 'integer' 01002 ), 01003 'smaxage' => array( 01004 ApiBase::PARAM_TYPE => 'integer', 01005 ApiBase::PARAM_DFLT => 0 01006 ), 01007 'maxage' => array( 01008 ApiBase::PARAM_TYPE => 'integer', 01009 ApiBase::PARAM_DFLT => 0 01010 ), 01011 'requestid' => null, 01012 'servedby' => false, 01013 'origin' => null, 01014 ); 01015 } 01016 01022 public function getParamDescription() { 01023 return array( 01024 'format' => 'The format of the output', 01025 'action' => 'What action you would like to perform. See below for module help', 01026 'maxlag' => array( 01027 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', 01028 'To save actions causing any more site replication lag, this parameter can make the client', 01029 'wait until the replication lag is less than the specified value.', 01030 'In case of a replag error, error code "maxlag" is returned, with the message like', 01031 '"Waiting for $host: $lag seconds lagged\n".', 01032 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', 01033 ), 01034 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 01035 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', 01036 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 01037 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error', 01038 'origin' => array( 01039 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.', 01040 'This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).', 01041 '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 .', 01042 'If this parameter does not match the Origin: header, a 403 response will be returned.', 01043 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.', 01044 ), 01045 ); 01046 } 01047 01053 public function getDescription() { 01054 return array( 01055 '', 01056 '', 01057 '**********************************************************************************************************', 01058 '** **', 01059 '** This is an auto-generated MediaWiki API documentation page **', 01060 '** **', 01061 '** Documentation and Examples: **', 01062 '** https://www.mediawiki.org/wiki/API **', 01063 '** **', 01064 '**********************************************************************************************************', 01065 '', 01066 'Status: All features shown on this page should be working, but the API', 01067 ' is still in active development, and may change at any time.', 01068 ' Make sure to monitor our mailing list for any updates', 01069 '', 01070 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent', 01071 ' with the key "MediaWiki-API-Error" and then both the value of the', 01072 ' header and the error code sent back will be set to the same value', 01073 '', 01074 ' In the case of an invalid action being passed, these will have a value', 01075 ' of "unknown_action"', 01076 '', 01077 ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings', 01078 '', 01079 'Documentation: https://www.mediawiki.org/wiki/API:Main_page', 01080 'FAQ https://www.mediawiki.org/wiki/API:FAQ', 01081 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api', 01082 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', 01083 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', 01084 '', 01085 '', 01086 '', 01087 '', 01088 '', 01089 ); 01090 } 01091 01095 public function getPossibleErrors() { 01096 return array_merge( parent::getPossibleErrors(), array( 01097 array( 'readonlytext' ), 01098 array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ), 01099 array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ), 01100 array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ), 01101 array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ), 01102 ) ); 01103 } 01104 01109 protected function getCredits() { 01110 return array( 01111 'API developers:', 01112 ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)', 01113 ' Victor Vasiliev - vasilvv @ gmail . com', 01114 ' Bryan Tong Minh - bryan . tongminh @ gmail . com', 01115 ' Sam Reed - sam @ reedyboy . net', 01116 ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)', 01117 '', 01118 'Please send your comments, suggestions and questions to [email protected]', 01119 'or file a bug report at https://bugzilla.wikimedia.org/' 01120 ); 01121 } 01122 01128 public function setHelp( $help = true ) { 01129 $this->mPrinter->setHelp( $help ); 01130 } 01131 01137 public function makeHelpMsg() { 01138 global $wgMemc, $wgAPICacheHelpTimeout; 01139 $this->setHelp(); 01140 // Get help text from cache if present 01141 $key = wfMemcKey( 'apihelp', $this->getModuleName(), 01142 str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) ); 01143 if ( $wgAPICacheHelpTimeout > 0 ) { 01144 $cached = $wgMemc->get( $key ); 01145 if ( $cached ) { 01146 return $cached; 01147 } 01148 } 01149 $retval = $this->reallyMakeHelpMsg(); 01150 if ( $wgAPICacheHelpTimeout > 0 ) { 01151 $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout ); 01152 } 01153 return $retval; 01154 } 01155 01159 public function reallyMakeHelpMsg() { 01160 $this->setHelp(); 01161 01162 // Use parent to make default message for the main module 01163 $msg = parent::makeHelpMsg(); 01164 01165 $astriks = str_repeat( '*** ', 14 ); 01166 $msg .= "\n\n$astriks Modules $astriks\n\n"; 01167 01168 foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) { 01169 $module = $this->mModuleMgr->getModule( $name ); 01170 $msg .= self::makeHelpMsgHeader( $module, 'action' ); 01171 01172 $msg2 = $module->makeHelpMsg(); 01173 if ( $msg2 !== false ) { 01174 $msg .= $msg2; 01175 } 01176 $msg .= "\n"; 01177 } 01178 01179 $msg .= "\n$astriks Permissions $astriks\n\n"; 01180 foreach ( self::$mRights as $right => $rightMsg ) { 01181 $groups = User::getGroupsWithPermission( $right ); 01182 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) . 01183 "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n"; 01184 } 01185 01186 $msg .= "\n$astriks Formats $astriks\n\n"; 01187 foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) { 01188 $module = $this->mModuleMgr->getModule( $name ); 01189 $msg .= self::makeHelpMsgHeader( $module, 'format' ); 01190 $msg2 = $module->makeHelpMsg(); 01191 if ( $msg2 !== false ) { 01192 $msg .= $msg2; 01193 } 01194 $msg .= "\n"; 01195 } 01196 01197 $msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n"; 01198 01199 return $msg; 01200 } 01201 01207 public static function makeHelpMsgHeader( $module, $paramName ) { 01208 $modulePrefix = $module->getModulePrefix(); 01209 if ( strval( $modulePrefix ) !== '' ) { 01210 $modulePrefix = "($modulePrefix) "; 01211 } 01212 01213 return "* $paramName={$module->getModuleName()} $modulePrefix*"; 01214 } 01215 01216 private $mCanApiHighLimits = null; 01217 01222 public function canApiHighLimits() { 01223 if ( !isset( $this->mCanApiHighLimits ) ) { 01224 $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' ); 01225 } 01226 01227 return $this->mCanApiHighLimits; 01228 } 01229 01235 public function getShowVersions() { 01236 wfDeprecated( __METHOD__, '1.21' ); 01237 return false; 01238 } 01239 01244 public function getModuleManager() { 01245 return $this->mModuleMgr; 01246 } 01247 01257 protected function addModule( $name, $class ) { 01258 $this->getModuleManager()->addModule( $name, 'action', $class ); 01259 } 01260 01269 protected function addFormat( $name, $class ) { 01270 $this->getModuleManager()->addModule( $name, 'format', $class ); 01271 } 01272 01278 function getModules() { 01279 return $this->getModuleManager()->getNamesWithClasses( 'action' ); 01280 } 01281 01289 public function getFormats() { 01290 return $this->getModuleManager()->getNamesWithClasses( 'format' ); 01291 } 01292 } 01293 01300 class UsageException extends MWException { 01301 01302 private $mCodestr; 01303 01307 private $mExtraData; 01308 01315 public function __construct( $message, $codestr, $code = 0, $extradata = null ) { 01316 parent::__construct( $message, $code ); 01317 $this->mCodestr = $codestr; 01318 $this->mExtraData = $extradata; 01319 } 01320 01324 public function getCodeString() { 01325 return $this->mCodestr; 01326 } 01327 01331 public function getMessageArray() { 01332 $result = array( 01333 'code' => $this->mCodestr, 01334 'info' => $this->getMessage() 01335 ); 01336 if ( is_array( $this->mExtraData ) ) { 01337 $result = array_merge( $result, $this->mExtraData ); 01338 } 01339 return $result; 01340 } 01341 01345 public function __toString() { 01346 return "{$this->getCodeString()}: {$this->getMessage()}"; 01347 } 01348 }