MediaWiki
master
|
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 'query' => 'ApiQuery', 00055 'expandtemplates' => 'ApiExpandTemplates', 00056 'parse' => 'ApiParse', 00057 'opensearch' => 'ApiOpenSearch', 00058 'feedcontributions' => 'ApiFeedContributions', 00059 'feedwatchlist' => 'ApiFeedWatchlist', 00060 'help' => 'ApiHelp', 00061 'paraminfo' => 'ApiParamInfo', 00062 'rsd' => 'ApiRsd', 00063 'compare' => 'ApiComparePages', 00064 'tokens' => 'ApiTokens', 00065 00066 // Write modules 00067 'purge' => 'ApiPurge', 00068 'setnotificationtimestamp' => 'ApiSetNotificationTimestamp', 00069 'rollback' => 'ApiRollback', 00070 'delete' => 'ApiDelete', 00071 'undelete' => 'ApiUndelete', 00072 'protect' => 'ApiProtect', 00073 'block' => 'ApiBlock', 00074 'unblock' => 'ApiUnblock', 00075 'move' => 'ApiMove', 00076 'edit' => 'ApiEditPage', 00077 'upload' => 'ApiUpload', 00078 'filerevert' => 'ApiFileRevert', 00079 'emailuser' => 'ApiEmailUser', 00080 'watch' => 'ApiWatch', 00081 'patrol' => 'ApiPatrol', 00082 'import' => 'ApiImport', 00083 'userrights' => 'ApiUserrights', 00084 'options' => 'ApiOptions', 00085 ); 00086 00090 private static $Formats = array( 00091 'json' => 'ApiFormatJson', 00092 'jsonfm' => 'ApiFormatJson', 00093 'php' => 'ApiFormatPhp', 00094 'phpfm' => 'ApiFormatPhp', 00095 'wddx' => 'ApiFormatWddx', 00096 'wddxfm' => 'ApiFormatWddx', 00097 'xml' => 'ApiFormatXml', 00098 'xmlfm' => 'ApiFormatXml', 00099 'yaml' => 'ApiFormatYaml', 00100 'yamlfm' => 'ApiFormatYaml', 00101 'rawfm' => 'ApiFormatJson', 00102 'txt' => 'ApiFormatTxt', 00103 'txtfm' => 'ApiFormatTxt', 00104 'dbg' => 'ApiFormatDbg', 00105 'dbgfm' => 'ApiFormatDbg', 00106 'dump' => 'ApiFormatDump', 00107 'dumpfm' => 'ApiFormatDump', 00108 'none' => 'ApiFormatNone', 00109 ); 00110 00117 private static $mRights = array( 00118 'writeapi' => array( 00119 'msg' => 'Use of the write API', 00120 'params' => array() 00121 ), 00122 'apihighlimits' => array( 00123 '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.', 00124 'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 ) 00125 ) 00126 ); 00127 00131 private $mPrinter; 00132 00133 private $mModules, $mModuleNames, $mFormats, $mFormatNames; 00134 private $mResult, $mAction, $mShowVersions, $mEnableWrite; 00135 private $mInternalMode, $mSquidMaxage, $mModule; 00136 00137 private $mCacheMode = 'private'; 00138 private $mCacheControl = array(); 00139 private $mParamsUsed = array(); 00140 00147 public function __construct( $context = null, $enableWrite = false ) { 00148 if ( $context === null ) { 00149 $context = RequestContext::getMain(); 00150 } elseif ( $context instanceof WebRequest ) { 00151 // BC for pre-1.19 00152 $request = $context; 00153 $context = RequestContext::getMain(); 00154 } 00155 // We set a derivative context so we can change stuff later 00156 $this->setContext( new DerivativeContext( $context ) ); 00157 00158 if ( isset( $request ) ) { 00159 $this->getContext()->setRequest( $request ); 00160 } 00161 00162 $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest ); 00163 00164 // Special handling for the main module: $parent === $this 00165 parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' ); 00166 00167 if ( !$this->mInternalMode ) { 00168 // Impose module restrictions. 00169 // If the current user cannot read, 00170 // Remove all modules other than login 00171 global $wgUser; 00172 00173 if ( $this->getVal( 'callback' ) !== null ) { 00174 // JSON callback allows cross-site reads. 00175 // For safety, strip user credentials. 00176 wfDebug( "API: stripping user credentials for JSON callback\n" ); 00177 $wgUser = new User(); 00178 $this->getContext()->setUser( $wgUser ); 00179 } 00180 } 00181 00182 global $wgAPIModules; // extension modules 00183 $this->mModules = $wgAPIModules + self::$Modules; 00184 00185 $this->mModuleNames = array_keys( $this->mModules ); 00186 $this->mFormats = self::$Formats; 00187 $this->mFormatNames = array_keys( $this->mFormats ); 00188 00189 $this->mResult = new ApiResult( $this ); 00190 $this->mShowVersions = false; 00191 $this->mEnableWrite = $enableWrite; 00192 00193 $this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling() 00194 $this->mCommit = false; 00195 } 00196 00201 public function isInternalMode() { 00202 return $this->mInternalMode; 00203 } 00204 00210 public function getResult() { 00211 return $this->mResult; 00212 } 00213 00219 public function getModule() { 00220 return $this->mModule; 00221 } 00222 00228 public function getPrinter() { 00229 return $this->mPrinter; 00230 } 00231 00237 public function setCacheMaxAge( $maxage ) { 00238 $this->setCacheControl( array( 00239 'max-age' => $maxage, 00240 's-maxage' => $maxage 00241 ) ); 00242 } 00243 00269 public function setCacheMode( $mode ) { 00270 if ( !in_array( $mode, array( 'private', 'public', 'anon-public-user-private' ) ) ) { 00271 wfDebug( __METHOD__ . ": unrecognised cache mode \"$mode\"\n" ); 00272 // Ignore for forwards-compatibility 00273 return; 00274 } 00275 00276 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) { 00277 // Private wiki, only private headers 00278 if ( $mode !== 'private' ) { 00279 wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" ); 00280 return; 00281 } 00282 } 00283 00284 wfDebug( __METHOD__ . ": setting cache mode $mode\n" ); 00285 $this->mCacheMode = $mode; 00286 } 00287 00293 public function setCachePrivate() { 00294 wfDeprecated( __METHOD__, '1.17' ); 00295 $this->setCacheMode( 'private' ); 00296 } 00297 00308 public function setCacheControl( $directives ) { 00309 $this->mCacheControl = $directives + $this->mCacheControl; 00310 } 00311 00322 public function setVaryCookie() { 00323 wfDeprecated( __METHOD__, '1.17' ); 00324 $this->setCacheMode( 'anon-public-user-private' ); 00325 } 00326 00334 public function createPrinterByName( $format ) { 00335 if ( !isset( $this->mFormats[$format] ) ) { 00336 $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); 00337 } 00338 return new $this->mFormats[$format] ( $this, $format ); 00339 } 00340 00344 public function execute() { 00345 $this->profileIn(); 00346 if ( $this->mInternalMode ) { 00347 $this->executeAction(); 00348 } else { 00349 $this->executeActionWithErrorHandling(); 00350 } 00351 00352 $this->profileOut(); 00353 } 00354 00359 protected function executeActionWithErrorHandling() { 00360 // Verify the CORS header before executing the action 00361 if ( !$this->handleCORS() ) { 00362 // handleCORS() has sent a 403, abort 00363 return; 00364 } 00365 00366 // In case an error occurs during data output, 00367 // clear the output buffer and print just the error information 00368 ob_start(); 00369 00370 $t = microtime( true ); 00371 try { 00372 $this->executeAction(); 00373 } catch ( Exception $e ) { 00374 // Allow extra cleanup and logging 00375 wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); 00376 00377 // Log it 00378 if ( !( $e instanceof UsageException ) ) { 00379 global $wgLogExceptionBacktrace; 00380 if ( $wgLogExceptionBacktrace ) { 00381 wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" ); 00382 } else { 00383 wfDebugLog( 'exception', $e->getLogMessage() ); 00384 } 00385 } 00386 00387 // Handle any kind of exception by outputing properly formatted error message. 00388 // If this fails, an unhandled exception should be thrown so that global error 00389 // handler will process and log it. 00390 00391 $errCode = $this->substituteResultWithError( $e ); 00392 00393 // Error results should not be cached 00394 $this->setCacheMode( 'private' ); 00395 00396 $response = $this->getRequest()->response(); 00397 $headerStr = 'MediaWiki-API-Error: ' . $errCode; 00398 if ( $e->getCode() === 0 ) { 00399 $response->header( $headerStr ); 00400 } else { 00401 $response->header( $headerStr, true, $e->getCode() ); 00402 } 00403 00404 // Reset and print just the error message 00405 ob_clean(); 00406 00407 // If the error occurred during printing, do a printer->profileOut() 00408 $this->mPrinter->safeProfileOut(); 00409 $this->printResult( true ); 00410 } 00411 00412 // Log the request whether or not there was an error 00413 $this->logRequest( microtime( true ) - $t); 00414 00415 // Send cache headers after any code which might generate an error, to 00416 // avoid sending public cache headers for errors. 00417 $this->sendCacheHeaders(); 00418 00419 if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) { 00420 echo wfReportTime(); 00421 } 00422 00423 ob_end_flush(); 00424 } 00425 00438 protected function handleCORS() { 00439 global $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions; 00440 00441 $originParam = $this->getParameter( 'origin' ); // defaults to null 00442 if ( $originParam === null ) { 00443 // No origin parameter, nothing to do 00444 return true; 00445 } 00446 00447 $request = $this->getRequest(); 00448 $response = $request->response(); 00449 // Origin: header is a space-separated list of origins, check all of them 00450 $originHeader = $request->getHeader( 'Origin' ); 00451 if ( $originHeader === false ) { 00452 $origins = array(); 00453 } else { 00454 $origins = explode( ' ', $originHeader ); 00455 } 00456 if ( !in_array( $originParam, $origins ) ) { 00457 // origin parameter set but incorrect 00458 // Send a 403 response 00459 $message = HttpStatus::getMessage( 403 ); 00460 $response->header( "HTTP/1.1 403 $message", true, 403 ); 00461 $response->header( 'Cache-Control: no-cache' ); 00462 echo "'origin' parameter does not match Origin header\n"; 00463 return false; 00464 } 00465 if ( self::matchOrigin( $originParam, $wgCrossSiteAJAXdomains, $wgCrossSiteAJAXdomainExceptions ) ) { 00466 $response->header( "Access-Control-Allow-Origin: $originParam" ); 00467 $response->header( 'Access-Control-Allow-Credentials: true' ); 00468 $this->getOutput()->addVaryHeader( 'Origin' ); 00469 } 00470 return true; 00471 } 00472 00480 protected static function matchOrigin( $value, $rules, $exceptions ) { 00481 foreach ( $rules as $rule ) { 00482 if ( preg_match( self::wildcardToRegex( $rule ), $value ) ) { 00483 // Rule matches, check exceptions 00484 foreach ( $exceptions as $exc ) { 00485 if ( preg_match( self::wildcardToRegex( $exc ), $value ) ) { 00486 return false; 00487 } 00488 } 00489 return true; 00490 } 00491 } 00492 return false; 00493 } 00494 00503 protected static function wildcardToRegex( $wildcard ) { 00504 $wildcard = preg_quote( $wildcard, '/' ); 00505 $wildcard = str_replace( 00506 array( '\*', '\?' ), 00507 array( '.*?', '.' ), 00508 $wildcard 00509 ); 00510 return "/https?:\/\/$wildcard/"; 00511 } 00512 00513 protected function sendCacheHeaders() { 00514 global $wgUseXVO, $wgVaryOnXFP; 00515 $response = $this->getRequest()->response(); 00516 $out = $this->getOutput(); 00517 00518 if ( $wgVaryOnXFP ) { 00519 $out->addVaryHeader( 'X-Forwarded-Proto' ); 00520 } 00521 00522 if ( $this->mCacheMode == 'private' ) { 00523 $response->header( 'Cache-Control: private' ); 00524 return; 00525 } 00526 00527 if ( $this->mCacheMode == 'anon-public-user-private' ) { 00528 $out->addVaryHeader( 'Cookie' ); 00529 $response->header( $out->getVaryHeader() ); 00530 if ( $wgUseXVO ) { 00531 $response->header( $out->getXVO() ); 00532 if ( $out->haveCacheVaryCookies() ) { 00533 // Logged in, mark this request private 00534 $response->header( 'Cache-Control: private' ); 00535 return; 00536 } 00537 // Logged out, send normal public headers below 00538 } elseif ( session_id() != '' ) { 00539 // Logged in or otherwise has session (e.g. anonymous users who have edited) 00540 // Mark request private 00541 $response->header( 'Cache-Control: private' ); 00542 return; 00543 } // else no XVO and anonymous, send public headers below 00544 } 00545 00546 // Send public headers 00547 $response->header( $out->getVaryHeader() ); 00548 if ( $wgUseXVO ) { 00549 $response->header( $out->getXVO() ); 00550 } 00551 00552 // If nobody called setCacheMaxAge(), use the (s)maxage parameters 00553 if ( !isset( $this->mCacheControl['s-maxage'] ) ) { 00554 $this->mCacheControl['s-maxage'] = $this->getParameter( 'smaxage' ); 00555 } 00556 if ( !isset( $this->mCacheControl['max-age'] ) ) { 00557 $this->mCacheControl['max-age'] = $this->getParameter( 'maxage' ); 00558 } 00559 00560 if ( !$this->mCacheControl['s-maxage'] && !$this->mCacheControl['max-age'] ) { 00561 // Public cache not requested 00562 // Sending a Vary header in this case is harmless, and protects us 00563 // against conditional calls of setCacheMaxAge(). 00564 $response->header( 'Cache-Control: private' ); 00565 return; 00566 } 00567 00568 $this->mCacheControl['public'] = true; 00569 00570 // Send an Expires header 00571 $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] ); 00572 $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge ); 00573 $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); 00574 00575 // Construct the Cache-Control header 00576 $ccHeader = ''; 00577 $separator = ''; 00578 foreach ( $this->mCacheControl as $name => $value ) { 00579 if ( is_bool( $value ) ) { 00580 if ( $value ) { 00581 $ccHeader .= $separator . $name; 00582 $separator = ', '; 00583 } 00584 } else { 00585 $ccHeader .= $separator . "$name=$value"; 00586 $separator = ', '; 00587 } 00588 } 00589 00590 $response->header( "Cache-Control: $ccHeader" ); 00591 } 00592 00599 protected function substituteResultWithError( $e ) { 00600 global $wgShowHostnames; 00601 00602 $result = $this->getResult(); 00603 // Printer may not be initialized if the extractRequestParams() fails for the main module 00604 if ( !isset ( $this->mPrinter ) ) { 00605 // The printer has not been created yet. Try to manually get formatter value. 00606 $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); 00607 if ( !in_array( $value, $this->mFormatNames ) ) { 00608 $value = self::API_DEFAULT_FORMAT; 00609 } 00610 00611 $this->mPrinter = $this->createPrinterByName( $value ); 00612 if ( $this->mPrinter->getNeedsRawData() ) { 00613 $result->setRawMode(); 00614 } 00615 } 00616 00617 if ( $e instanceof UsageException ) { 00618 // User entered incorrect parameters - print usage screen 00619 $errMessage = $e->getMessageArray(); 00620 00621 // Only print the help message when this is for the developer, not runtime 00622 if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) { 00623 ApiResult::setContent( $errMessage, $this->makeHelpMsg() ); 00624 } 00625 00626 } else { 00627 global $wgShowSQLErrors, $wgShowExceptionDetails; 00628 // Something is seriously wrong 00629 if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) { 00630 $info = 'Database query error'; 00631 } else { 00632 $info = "Exception Caught: {$e->getMessage()}"; 00633 } 00634 00635 $errMessage = array( 00636 'code' => 'internal_api_error_' . get_class( $e ), 00637 'info' => $info, 00638 ); 00639 ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); 00640 } 00641 00642 $result->reset(); 00643 $result->disableSizeCheck(); 00644 // Re-add the id 00645 $requestid = $this->getParameter( 'requestid' ); 00646 if ( !is_null( $requestid ) ) { 00647 $result->addValue( null, 'requestid', $requestid ); 00648 } 00649 00650 if ( $wgShowHostnames ) { 00651 // servedby is especially useful when debugging errors 00652 $result->addValue( null, 'servedby', wfHostName() ); 00653 } 00654 00655 $result->addValue( null, 'error', $errMessage ); 00656 00657 return $errMessage['code']; 00658 } 00659 00664 protected function setupExecuteAction() { 00665 global $wgShowHostnames; 00666 00667 // First add the id to the top element 00668 $result = $this->getResult(); 00669 $requestid = $this->getParameter( 'requestid' ); 00670 if ( !is_null( $requestid ) ) { 00671 $result->addValue( null, 'requestid', $requestid ); 00672 } 00673 00674 if ( $wgShowHostnames ) { 00675 $servedby = $this->getParameter( 'servedby' ); 00676 if ( $servedby ) { 00677 $result->addValue( null, 'servedby', wfHostName() ); 00678 } 00679 } 00680 00681 $params = $this->extractRequestParams(); 00682 00683 $this->mShowVersions = $params['version']; 00684 $this->mAction = $params['action']; 00685 00686 if ( !is_string( $this->mAction ) ) { 00687 $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); 00688 } 00689 00690 return $params; 00691 } 00692 00697 protected function setupModule() { 00698 // Instantiate the module requested by the user 00699 $module = new $this->mModules[$this->mAction] ( $this, $this->mAction ); 00700 $this->mModule = $module; 00701 00702 $moduleParams = $module->extractRequestParams(); 00703 00704 // Die if token required, but not provided (unless there is a gettoken parameter) 00705 if ( isset( $moduleParams['gettoken'] ) ) { 00706 $gettoken = $moduleParams['gettoken']; 00707 } else { 00708 $gettoken = false; 00709 } 00710 00711 $salt = $module->getTokenSalt(); 00712 if ( $salt !== false && !$gettoken ) { 00713 if ( !isset( $moduleParams['token'] ) ) { 00714 $this->dieUsageMsg( array( 'missingparam', 'token' ) ); 00715 } else { 00716 if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) { 00717 $this->dieUsageMsg( 'sessionfailure' ); 00718 } 00719 } 00720 } 00721 return $module; 00722 } 00723 00730 protected function checkMaxLag( $module, $params ) { 00731 if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) { 00732 // Check for maxlag 00733 global $wgShowHostnames; 00734 $maxLag = $params['maxlag']; 00735 list( $host, $lag ) = wfGetLB()->getMaxLag(); 00736 if ( $lag > $maxLag ) { 00737 $response = $this->getRequest()->response(); 00738 00739 $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); 00740 $response->header( 'X-Database-Lag: ' . intval( $lag ) ); 00741 00742 if ( $wgShowHostnames ) { 00743 $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); 00744 } else { 00745 $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); 00746 } 00747 return false; 00748 } 00749 } 00750 return true; 00751 } 00752 00757 protected function checkExecutePermissions( $module ) { 00758 $user = $this->getUser(); 00759 if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && 00760 !$user->isAllowed( 'read' ) ) 00761 { 00762 $this->dieUsageMsg( 'readrequired' ); 00763 } 00764 if ( $module->isWriteMode() ) { 00765 if ( !$this->mEnableWrite ) { 00766 $this->dieUsageMsg( 'writedisabled' ); 00767 } 00768 if ( !$user->isAllowed( 'writeapi' ) ) { 00769 $this->dieUsageMsg( 'writerequired' ); 00770 } 00771 if ( wfReadOnly() ) { 00772 $this->dieReadOnly(); 00773 } 00774 } 00775 00776 // Allow extensions to stop execution for arbitrary reasons. 00777 $message = false; 00778 if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { 00779 $this->dieUsageMsg( $message ); 00780 } 00781 } 00782 00788 protected function setupExternalResponse( $module, $params ) { 00789 // Ignore mustBePosted() for internal calls 00790 if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) { 00791 $this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) ); 00792 } 00793 00794 // See if custom printer is used 00795 $this->mPrinter = $module->getCustomPrinter(); 00796 if ( is_null( $this->mPrinter ) ) { 00797 // Create an appropriate printer 00798 $this->mPrinter = $this->createPrinterByName( $params['format'] ); 00799 } 00800 00801 if ( $this->mPrinter->getNeedsRawData() ) { 00802 $this->getResult()->setRawMode(); 00803 } 00804 } 00805 00809 protected function executeAction() { 00810 $params = $this->setupExecuteAction(); 00811 $module = $this->setupModule(); 00812 00813 $this->checkExecutePermissions( $module ); 00814 00815 if ( !$this->checkMaxLag( $module, $params ) ) { 00816 return; 00817 } 00818 00819 if ( !$this->mInternalMode ) { 00820 $this->setupExternalResponse( $module, $params ); 00821 } 00822 00823 // Execute 00824 $module->profileIn(); 00825 $module->execute(); 00826 wfRunHooks( 'APIAfterExecute', array( &$module ) ); 00827 $module->profileOut(); 00828 00829 if ( !$this->mInternalMode ) { 00830 // Report unused params 00831 $this->reportUnusedParams(); 00832 00833 //append Debug information 00834 MWDebug::appendDebugInfoToApiResult( $this->getContext(), $this->getResult() ); 00835 00836 // Print result data 00837 $this->printResult( false ); 00838 } 00839 } 00840 00845 protected function logRequest( $time ) { 00846 $request = $this->getRequest(); 00847 $milliseconds = $time === null ? '?' : round( $time * 1000 ); 00848 $s = 'API' . 00849 ' ' . $request->getMethod() . 00850 ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . 00851 ' ' . $request->getIP() . 00852 ' T=' . $milliseconds .'ms'; 00853 foreach ( $this->getParamsUsed() as $name ) { 00854 $value = $request->getVal( $name ); 00855 if ( $value === null ) { 00856 continue; 00857 } 00858 $s .= ' ' . $name . '='; 00859 if ( strlen( $value ) > 256 ) { 00860 $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) ); 00861 $s .= $encValue . '[...]'; 00862 } else { 00863 $s .= $this->encodeRequestLogValue( $value ); 00864 } 00865 } 00866 $s .= "\n"; 00867 wfDebugLog( 'api', $s, false ); 00868 } 00869 00873 protected function encodeRequestLogValue( $s ) { 00874 static $table; 00875 if ( !$table ) { 00876 $chars = ';@$!*(),/:'; 00877 for ( $i = 0; $i < strlen( $chars ); $i++ ) { 00878 $table[ rawurlencode( $chars[$i] ) ] = $chars[$i]; 00879 } 00880 } 00881 return strtr( rawurlencode( $s ), $table ); 00882 } 00883 00887 protected function getParamsUsed() { 00888 return array_keys( $this->mParamsUsed ); 00889 } 00890 00894 public function getVal( $name, $default = null ) { 00895 $this->mParamsUsed[$name] = true; 00896 return $this->getRequest()->getVal( $name, $default ); 00897 } 00898 00903 public function getCheck( $name ) { 00904 $this->mParamsUsed[$name] = true; 00905 return $this->getRequest()->getCheck( $name ); 00906 } 00907 00912 protected function reportUnusedParams() { 00913 $paramsUsed = $this->getParamsUsed(); 00914 $allParams = $this->getRequest()->getValueNames(); 00915 00916 $unusedParams = array_diff( $allParams, $paramsUsed ); 00917 if( count( $unusedParams ) ) { 00918 $s = count( $unusedParams ) > 1 ? 's' : ''; 00919 $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" ); 00920 } 00921 } 00922 00928 protected function printResult( $isError ) { 00929 $this->getResult()->cleanUpUTF8(); 00930 $printer = $this->mPrinter; 00931 $printer->profileIn(); 00932 00938 $printer->setUnescapeAmps( ( $this->mAction == 'help' || $isError ) 00939 && $printer->getFormat() == 'XML' && $printer->getIsHtml() ); 00940 00941 $printer->initPrinter( $isError ); 00942 00943 $printer->execute(); 00944 $printer->closePrinter(); 00945 $printer->profileOut(); 00946 } 00947 00951 public function isReadMode() { 00952 return false; 00953 } 00954 00960 public function getAllowedParams() { 00961 return array( 00962 'format' => array( 00963 ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, 00964 ApiBase::PARAM_TYPE => $this->mFormatNames 00965 ), 00966 'action' => array( 00967 ApiBase::PARAM_DFLT => 'help', 00968 ApiBase::PARAM_TYPE => $this->mModuleNames 00969 ), 00970 'version' => false, 00971 'maxlag' => array( 00972 ApiBase::PARAM_TYPE => 'integer' 00973 ), 00974 'smaxage' => array( 00975 ApiBase::PARAM_TYPE => 'integer', 00976 ApiBase::PARAM_DFLT => 0 00977 ), 00978 'maxage' => array( 00979 ApiBase::PARAM_TYPE => 'integer', 00980 ApiBase::PARAM_DFLT => 0 00981 ), 00982 'requestid' => null, 00983 'servedby' => false, 00984 'origin' => null, 00985 ); 00986 } 00987 00993 public function getParamDescription() { 00994 return array( 00995 'format' => 'The format of the output', 00996 'action' => 'What action you would like to perform. See below for module help', 00997 'version' => 'When showing help, include version for each module', 00998 'maxlag' => array( 00999 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', 01000 'To save actions causing any more site replication lag, this parameter can make the client', 01001 'wait until the replication lag is less than the specified value.', 01002 'In case of a replag error, a HTTP 503 error is returned, with the message like', 01003 '"Waiting for $host: $lag seconds lagged\n".', 01004 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', 01005 ), 01006 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 01007 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', 01008 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 01009 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error', 01010 'origin' => array( 01011 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.', 01012 '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 .', 01013 'If this parameter does not match the Origin: header, a 403 response will be returned.', 01014 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.', 01015 ), 01016 ); 01017 } 01018 01024 public function getDescription() { 01025 return array( 01026 '', 01027 '', 01028 '**********************************************************************************************************', 01029 '** **', 01030 '** This is an auto-generated MediaWiki API documentation page **', 01031 '** **', 01032 '** Documentation and Examples: **', 01033 '** https://www.mediawiki.org/wiki/API **', 01034 '** **', 01035 '**********************************************************************************************************', 01036 '', 01037 'Status: All features shown on this page should be working, but the API', 01038 ' is still in active development, and may change at any time.', 01039 ' Make sure to monitor our mailing list for any updates', 01040 '', 01041 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent', 01042 ' with the key "MediaWiki-API-Error" and then both the value of the', 01043 ' header and the error code sent back will be set to the same value', 01044 '', 01045 ' In the case of an invalid action being passed, these will have a value', 01046 ' of "unknown_action"', 01047 '', 01048 ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings', 01049 '', 01050 'Documentation: https://www.mediawiki.org/wiki/API:Main_page', 01051 'FAQ https://www.mediawiki.org/wiki/API:FAQ', 01052 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api', 01053 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', 01054 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', 01055 '', 01056 '', 01057 '', 01058 '', 01059 '', 01060 ); 01061 } 01062 01066 public function getPossibleErrors() { 01067 return array_merge( parent::getPossibleErrors(), array( 01068 array( 'readonlytext' ), 01069 array( 'code' => 'unknown_format', 'info' => 'Unrecognized format: format' ), 01070 array( 'code' => 'unknown_action', 'info' => 'The API requires a valid action parameter' ), 01071 array( 'code' => 'maxlag', 'info' => 'Waiting for host: x seconds lagged' ), 01072 array( 'code' => 'maxlag', 'info' => 'Waiting for a database server: x seconds lagged' ), 01073 ) ); 01074 } 01075 01080 protected function getCredits() { 01081 return array( 01082 'API developers:', 01083 ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-present)', 01084 ' Victor Vasiliev - vasilvv at gee mail dot com', 01085 ' Bryan Tong Minh - bryan . tongminh @ gmail . com', 01086 ' Sam Reed - sam @ reedyboy . net', 01087 ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007)', 01088 '', 01089 'Please send your comments, suggestions and questions to [email protected]', 01090 'or file a bug report at https://bugzilla.wikimedia.org/' 01091 ); 01092 } 01093 01099 public function setHelp( $help = true ) { 01100 $this->mPrinter->setHelp( $help ); 01101 } 01102 01108 public function makeHelpMsg() { 01109 global $wgMemc, $wgAPICacheHelpTimeout; 01110 $this->setHelp(); 01111 // Get help text from cache if present 01112 $key = wfMemcKey( 'apihelp', $this->getModuleName(), 01113 SpecialVersion::getVersion( 'nodb' ) . 01114 $this->getShowVersions() ); 01115 if ( $wgAPICacheHelpTimeout > 0 ) { 01116 $cached = $wgMemc->get( $key ); 01117 if ( $cached ) { 01118 return $cached; 01119 } 01120 } 01121 $retval = $this->reallyMakeHelpMsg(); 01122 if ( $wgAPICacheHelpTimeout > 0 ) { 01123 $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout ); 01124 } 01125 return $retval; 01126 } 01127 01131 public function reallyMakeHelpMsg() { 01132 $this->setHelp(); 01133 01134 // Use parent to make default message for the main module 01135 $msg = parent::makeHelpMsg(); 01136 01137 $astriks = str_repeat( '*** ', 14 ); 01138 $msg .= "\n\n$astriks Modules $astriks\n\n"; 01139 foreach ( array_keys( $this->mModules ) as $moduleName ) { 01140 $module = new $this->mModules[$moduleName] ( $this, $moduleName ); 01141 $msg .= self::makeHelpMsgHeader( $module, 'action' ); 01142 $msg2 = $module->makeHelpMsg(); 01143 if ( $msg2 !== false ) { 01144 $msg .= $msg2; 01145 } 01146 $msg .= "\n"; 01147 } 01148 01149 $msg .= "\n$astriks Permissions $astriks\n\n"; 01150 foreach ( self::$mRights as $right => $rightMsg ) { 01151 $groups = User::getGroupsWithPermission( $right ); 01152 $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg[ 'msg' ], $rightMsg[ 'params' ] ) . 01153 "\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n"; 01154 01155 } 01156 01157 $msg .= "\n$astriks Formats $astriks\n\n"; 01158 foreach ( array_keys( $this->mFormats ) as $formatName ) { 01159 $module = $this->createPrinterByName( $formatName ); 01160 $msg .= self::makeHelpMsgHeader( $module, 'format' ); 01161 $msg2 = $module->makeHelpMsg(); 01162 if ( $msg2 !== false ) { 01163 $msg .= $msg2; 01164 } 01165 $msg .= "\n"; 01166 } 01167 01168 $msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n"; 01169 01170 return $msg; 01171 } 01172 01178 public static function makeHelpMsgHeader( $module, $paramName ) { 01179 $modulePrefix = $module->getModulePrefix(); 01180 if ( strval( $modulePrefix ) !== '' ) { 01181 $modulePrefix = "($modulePrefix) "; 01182 } 01183 01184 return "* $paramName={$module->getModuleName()} $modulePrefix*"; 01185 } 01186 01187 private $mCanApiHighLimits = null; 01188 01193 public function canApiHighLimits() { 01194 if ( !isset( $this->mCanApiHighLimits ) ) { 01195 $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' ); 01196 } 01197 01198 return $this->mCanApiHighLimits; 01199 } 01200 01205 public function getShowVersions() { 01206 return $this->mShowVersions; 01207 } 01208 01215 public function getVersion() { 01216 $vers = array(); 01217 $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; 01218 $vers[] = __CLASS__ . ': $Id$'; 01219 $vers[] = ApiBase::getBaseVersion(); 01220 $vers[] = ApiFormatBase::getBaseVersion(); 01221 $vers[] = ApiQueryBase::getBaseVersion(); 01222 return $vers; 01223 } 01224 01233 protected function addModule( $mdlName, $mdlClass ) { 01234 $this->mModules[$mdlName] = $mdlClass; 01235 } 01236 01244 protected function addFormat( $fmtName, $fmtClass ) { 01245 $this->mFormats[$fmtName] = $fmtClass; 01246 } 01247 01252 function getModules() { 01253 return $this->mModules; 01254 } 01255 01262 public function getFormats() { 01263 return $this->mFormats; 01264 } 01265 } 01266 01273 class UsageException extends MWException { 01274 01275 private $mCodestr; 01276 01280 private $mExtraData; 01281 01288 public function __construct( $message, $codestr, $code = 0, $extradata = null ) { 01289 parent::__construct( $message, $code ); 01290 $this->mCodestr = $codestr; 01291 $this->mExtraData = $extradata; 01292 } 01293 01297 public function getCodeString() { 01298 return $this->mCodestr; 01299 } 01300 01304 public function getMessageArray() { 01305 $result = array( 01306 'code' => $this->mCodestr, 01307 'info' => $this->getMessage() 01308 ); 01309 if ( is_array( $this->mExtraData ) ) { 01310 $result = array_merge( $result, $this->mExtraData ); 01311 } 01312 return $result; 01313 } 01314 01318 public function __toString() { 01319 return "{$this->getCodeString()}: {$this->getMessage()}"; 01320 } 01321 }