X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FMediaWiki.php;h=45a13853e45e63f0394470afd0f7e5276c19708e;hb=72e757c01d184a23988c1f0731632375a35a6dfe;hp=fe1bb8ec4dc82eedc047f08d4cb8acfcdc556edf;hpb=4e1e42e43046ba222f9f931cae40b9593b0a36a2;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index fe1bb8ec4d..45a13853e4 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -36,6 +36,11 @@ class MediaWiki { */ private $config; + /** + * @var String Cache what action this request is + */ + private $action; + /** * @param IContextSource|null $context */ @@ -141,13 +146,11 @@ class MediaWiki { * @return string Action */ public function getAction() { - static $action = null; - - if ( $action === null ) { - $action = Action::getActionName( $this->context ); + if ( $this->action === null ) { + $this->action = Action::getActionName( $this->context ); } - return $action; + return $this->action; } /** @@ -175,7 +178,7 @@ class MediaWiki { } $unused = null; // To pass it by reference - Hooks::run( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) ); + Hooks::run( 'BeforeInitialize', [ &$title, &$unused, &$output, &$user, $request, $this ] ); // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty. if ( is_null( $title ) || ( $title->getDBkey() == '' && !$title->isExternal() ) @@ -194,7 +197,7 @@ class MediaWiki { // We have to check here to catch special pages etc. // We will check again in Article::view(). $permErrors = $title->isSpecial( 'RunJobs' ) - ? array() // relies on HMAC key signature alone + ? [] // relies on HMAC key signature alone : $title->getUserPermissionsErrors( 'read', $user ); if ( count( $permErrors ) ) { // Bug 32276: allowing the skin to generate output with $wgTitle or @@ -217,7 +220,7 @@ class MediaWiki { if ( $title->isExternal() ) { $rdfrom = $request->getVal( 'rdfrom' ); if ( $rdfrom ) { - $url = $title->getFullURL( array( 'rdfrom' => $rdfrom ) ); + $url = $title->getFullURL( [ 'rdfrom' => $rdfrom ] ); } else { $query = $request->getValues(); unset( $query['title'] ); @@ -241,8 +244,39 @@ class MediaWiki { // Handle any other redirects. // Redirect loops, titleless URL, $wgUsePathInfo URLs, and URLs with a variant } elseif ( !$this->tryNormaliseRedirect( $title ) ) { + // Prevent information leak via Special:MyPage et al (T109724) + if ( $title->isSpecialPage() ) { + $specialPage = SpecialPageFactory::getPage( $title->getDBKey() ); + if ( $specialPage instanceof RedirectSpecialPage ) { + $specialPage->setContext( $this->context ); + if ( $this->config->get( 'HideIdentifiableRedirects' ) + && $specialPage->personallyIdentifiableTarget() + ) { + list( , $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBKey() ); + $target = $specialPage->getRedirect( $subpage ); + // target can also be true. We let that case fall through to normal processing. + if ( $target instanceof Title ) { + $query = $specialPage->getRedirectQuery() ?: []; + $request = new DerivativeRequest( $this->context->getRequest(), $query ); + $request->setRequestURL( $this->context->getRequest()->getRequestURL() ); + $this->context->setRequest( $request ); + // Do not varnish cache these. May vary even for anons + $this->context->getOutput()->lowerCdnMaxage( 0 ); + $this->context->setTitle( $target ); + $wgTitle = $target; + // Reset action type cache. (Special pages have only view) + $this->action = null; + $title = $target; + $output->addJsConfigVars( [ + 'wgInternalRedirectTargetUrl' => $target->getFullURL( $query ), + ] ); + $output->addModules( 'mediawiki.action.view.redirect' ); + } + } + } + } - // Special pages + // Special pages ($title may have changed since if statement above) if ( NS_SPECIAL == $title->getNamespace() ) { // Actions that need to be made when we have a special pages SpecialPageFactory::executePath( $title, $this->context ); @@ -292,8 +326,8 @@ class MediaWiki { if ( $request->getVal( 'action', 'view' ) != 'view' || $request->wasPosted() - || count( $request->getValueNames( array( 'action', 'title' ) ) ) - || !Hooks::run( 'TestCanonicalRedirect', array( $request, $title, $output ) ) + || count( $request->getValueNames( [ 'action', 'title' ] ) ) + || !Hooks::run( 'TestCanonicalRedirect', [ $request, $title, $output ] ) ) { return false; } @@ -347,23 +381,26 @@ class MediaWiki { * Initialize the main Article object for "standard" actions (view, etc) * Create an Article object for the page, following redirects if needed. * - * @return mixed An Article, or a string to redirect to another URL + * @return Article|string An Article, or a string to redirect to another URL */ private function initializeArticle() { - $title = $this->context->getTitle(); if ( $this->context->canUseWikiPage() ) { // Try to use request context wiki page, as there // is already data from db saved in per process // cache there from this->getAction() call. $page = $this->context->getWikiPage(); - $article = Article::newFromWikiPage( $page, $this->context ); } else { // This case should not happen, but just in case. - $article = Article::newFromTitle( $title, $this->context ); - $this->context->setWikiPage( $article->getPage() ); + // @TODO: remove this or use an exception + $page = WikiPage::factory( $title ); + $this->context->setWikiPage( $page ); + wfWarn( "RequestContext::canUseWikiPage() returned false" ); } + // Make GUI wrapper for the WikiPage + $article = Article::newFromWikiPage( $page, $this->context ); + // Skip some unnecessary code if the content model doesn't support redirects if ( !ContentHandler::getForTitle( $title )->supportsRedirects() ) { return $article; @@ -374,7 +411,7 @@ class MediaWiki { // Namespace might change when using redirects // Check for redirects ... $action = $request->getVal( 'action', 'view' ); - $file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null; + $file = ( $page instanceof WikiFilePage ) ? $page->getFile() : null; if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content && !$request->getVal( 'oldid' ) // ... and are not old revisions && !$request->getVal( 'diff' ) // ... and not when showing diff @@ -386,13 +423,14 @@ class MediaWiki { $ignoreRedirect = $target = false; Hooks::run( 'InitializeArticleMaybeRedirect', - array( &$title, &$request, &$ignoreRedirect, &$target, &$article ) ); + [ &$title, &$request, &$ignoreRedirect, &$target, &$article ] ); + $page = $article->getPage(); // reflect any hook changes // Follow redirects only for... redirects. // If $target is set, then a hook wanted to redirect. - if ( !$ignoreRedirect && ( $target || $article->isRedirect() ) ) { + if ( !$ignoreRedirect && ( $target || $page->isRedirect() ) ) { // Is the target already set by an extension? - $target = $target ? $target : $article->followRedirect(); + $target = $target ? $target : $page->followRedirect(); if ( is_string( $target ) ) { if ( !$this->config->get( 'DisableHardRedirects' ) ) { // we'll need to redirect @@ -401,16 +439,19 @@ class MediaWiki { } if ( is_object( $target ) ) { // Rewrite environment to redirected article - $rarticle = Article::newFromTitle( $target, $this->context ); - $rarticle->loadPageData(); - if ( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) { + $rpage = WikiPage::factory( $target ); + $rpage->loadPageData(); + if ( $rpage->exists() || ( is_object( $file ) && !$file->isLocal() ) ) { + $rarticle = Article::newFromWikiPage( $rpage, $this->context ); $rarticle->setRedirectedFrom( $title ); + $article = $rarticle; $this->context->setTitle( $target ); $this->context->setWikiPage( $article->getPage() ); } } } else { + // Article may have been changed by hook $this->context->setTitle( $article->getTitle() ); $this->context->setWikiPage( $article->getPage() ); } @@ -426,23 +467,28 @@ class MediaWiki { * @param Title $requestTitle The original title, before any redirects were applied */ private function performAction( Page $page, Title $requestTitle ) { - $request = $this->context->getRequest(); $output = $this->context->getOutput(); $title = $this->context->getTitle(); $user = $this->context->getUser(); if ( !Hooks::run( 'MediaWikiPerformAction', - array( $output, $page, $title, $user, $request, $this ) ) + [ $output, $page, $title, $user, $request, $this ] ) ) { return; } $act = $this->getAction(); - $action = Action::factory( $act, $page, $this->context ); if ( $action instanceof Action ) { + // Narrow DB query expectations for this HTTP request + $trxLimits = $this->config->get( 'TrxProfilerLimits' ); + $trxProfiler = Profiler::instance()->getTransactionProfiler(); + if ( $request->wasPosted() && !$action->doesWrites() ) { + $trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ ); + } + # Let CDN cache things if we can purge them. if ( $this->config->get( 'UseSquid' ) && in_array( @@ -458,11 +504,10 @@ class MediaWiki { return; } - if ( Hooks::run( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) { + if ( Hooks::run( 'UnknownAction', [ $request->getVal( 'action', 'view' ), $page ] ) ) { $output->setStatusCode( 404 ); $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); } - } /** @@ -508,21 +553,12 @@ class MediaWiki { $config = $context->getConfig(); $factory = wfGetLBFactory(); - // Check if any transaction was too big - $limit = $config->get( 'MaxUserDBWriteDuration' ); - $factory->forEachLB( function ( LoadBalancer $lb ) use ( $limit ) { - $lb->forEachOpenConnection( function ( IDatabase $db ) use ( $limit ) { - $time = $db->pendingWriteQueryDuration(); - if ( $limit > 0 && $time > $limit ) { - throw new DBTransactionError( - $db, - wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->plain() - ); - } - } ); - } ); // Commit all changes - $factory->commitMasterChanges(); + $factory->commitMasterChanges( + __METHOD__, + // Abort if any transaction was too big + [ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ] + ); // Record ChronologyProtector positions $factory->shutdown(); wfDebug( __METHOD__ . ': all transactions committed' ); @@ -530,15 +566,19 @@ class MediaWiki { DeferredUpdates::doUpdates( 'enqueue', DeferredUpdates::PRESEND ); wfDebug( __METHOD__ . ': pre-send deferred updates completed' ); - // Set a cookie to tell all CDN edge nodes to "stick" the user to the - // DC that handles this POST request (e.g. the "master" data center) + // Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this + // POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so + // ChronologyProtector works for cacheable URLs. $request = $context->getRequest(); if ( $request->wasPosted() && $factory->hasOrMadeRecentMasterChanges() ) { $expires = time() + $config->get( 'DataCenterUpdateStickTTL' ); - $request->response()->setCookie( 'UseDC', 'master', $expires, array( 'prefix' => '' ) ); + $options = [ 'prefix' => '' ]; + $request->response()->setCookie( 'UseDC', 'master', $expires, $options ); + $request->response()->setCookie( 'UseCDNCache', 'false', $expires, $options ); } - // Avoid letting a few seconds of slave lag cause a month of stale data + // Avoid letting a few seconds of slave lag cause a month of stale data. This logic is + // also intimately related to the value of $wgCdnReboundPurgeDelay. if ( $factory->laggedSlaveUsed() ) { $maxAge = $config->get( 'CdnMaxageLagged' ); $context->getOutput()->lowerCdnMaxage( $maxAge ); @@ -591,7 +631,7 @@ class MediaWiki { } private function main() { - global $wgTitle, $wgTrxProfilerLimits; + global $wgTitle; $request = $this->context->getRequest(); @@ -615,13 +655,14 @@ class MediaWiki { $action = $this->getAction(); $wgTitle = $title; + // Set DB query expectations for this HTTP request + $trxLimits = $this->config->get( 'TrxProfilerLimits' ); $trxProfiler = Profiler::instance()->getTransactionProfiler(); $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) ); - if ( $request->wasPosted() ) { - $trxProfiler->setExpectations( $wgTrxProfilerLimits['POST'], __METHOD__ ); + $trxProfiler->setExpectations( $trxLimits['POST'], __METHOD__ ); } else { - $trxProfiler->setExpectations( $wgTrxProfilerLimits['GET'], __METHOD__ ); + $trxProfiler->setExpectations( $trxLimits['GET'], __METHOD__ ); } // If the user has forceHTTPS set to true, or if the user @@ -632,8 +673,10 @@ class MediaWiki { if ( $request->getProtocol() == 'http' && ( + $request->getSession()->shouldForceHTTPS() || + // Check the cookie manually, for paranoia $request->getCookie( 'forceHTTPS', '' ) || - // check for prefixed version for currently logged in users + // check for prefixed version that was used for a time in older MW versions $request->getCookie( 'forceHTTPS' ) || // Avoid checking the user and groups unless it's enabled. ( @@ -646,7 +689,7 @@ class MediaWiki { $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl ); // ATTENTION: This hook is likely to be removed soon due to overall design of the system. - if ( Hooks::run( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) { + if ( Hooks::run( 'BeforeHttpsRedirect', [ $this->context, &$redirUrl ] ) ) { if ( $request->wasPosted() ) { // This is weird and we'd hope it almost never happens. This @@ -709,7 +752,7 @@ class MediaWiki { */ public function restInPeace( $mode = 'fast' ) { // Assure deferred updates are not in the main transaction - wfGetLBFactory()->commitMasterChanges(); + wfGetLBFactory()->commitMasterChanges( __METHOD__ ); // Ignore things like master queries/connections on GET requests // as long as they are in deferred updates (which catch errors). @@ -732,7 +775,7 @@ class MediaWiki { // Commit and close up! $factory = wfGetLBFactory(); - $factory->commitMasterChanges(); + $factory->commitMasterChanges( __METHOD__ ); $factory->shutdown( LBFactory::SHUTDOWN_NO_CHRONPROT ); wfDebug( "Request ended normally\n" ); @@ -766,7 +809,7 @@ class MediaWiki { if ( !$this->config->get( 'RunJobsAsync' ) ) { // Fall back to running the job here while the user waits $runner = new JobRunner( $runJobsLogger ); - $runner->run( array( 'maxJobs' => $n ) ); + $runner->run( [ 'maxJobs' => $n ] ); return; } @@ -779,8 +822,8 @@ class MediaWiki { return; // do not make the site unavailable } - $query = array( 'title' => 'Special:RunJobs', - 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ); + $query = [ 'title' => 'Special:RunJobs', + 'tasks' => 'jobs', 'maxjobs' => $n, 'sigexpiry' => time() + 5 ]; $query['signature'] = SpecialRunJobs::getQuerySignature( $query, $this->config->get( 'SecretKey' ) ); @@ -801,7 +844,7 @@ class MediaWiki { $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" ); // Fall back to running the job here while the user waits $runner = new JobRunner( $runJobsLogger ); - $runner->run( array( 'maxJobs' => $n ) ); + $runner->run( [ 'maxJobs' => $n ] ); return; }