From cbdb16da1d8699e87dade377a8271a96e0c178e1 Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Sat, 24 Oct 2015 23:45:02 -0700 Subject: [PATCH] Add header to flag API POST requests with no write intentions This performs sanity check that request *is* for a non-write module. By handling the validation, the CDN layer can simply use the presence of this header to route POST requests to the local datacenter. Without validation, users could cause strange traffic patterns and slow cross-DC database writes (which can involve many RTTs). This is useful for AJAX widgets that need to post a payload to get a response, but that don't actually change anything in the process. They should be able to use the local datacenter. Bug: T91820 Change-Id: I34248ddee33033e3d1d86c3391236259d917d4a7 --- includes/WebStart.php | 22 ++++++++++++++++++++++ includes/api/ApiMain.php | 12 ++++++++---- languages/i18n/en.json | 1 + languages/i18n/qqq.json | 1 + 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/includes/WebStart.php b/includes/WebStart.php index b095577c74..adce346b7f 100644 --- a/includes/WebStart.php +++ b/includes/WebStart.php @@ -141,3 +141,25 @@ if ( !defined( 'MW_NO_SETUP' ) ) { if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST' ) { ignore_user_abort( true ); } + +if ( !defined( 'MW_API' ) && + RequestContext::getMain()->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) +) { + header( 'Cache-Control: no-cache' ); + header( 'Content-Type: text/html; charset=utf-8' ); + HttpStatus::header( 400 ); + $error = wfMessage( 'nonwrite-api-promise-error' )->escaped(); + $content = << + + + +$error + + + +EOT; + header( 'Content-Length: ' . strlen( $content ) ); + echo $content; + die(); +} diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index c641c9513c..86a8a87a6b 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -1151,13 +1151,17 @@ class ApiMain extends ApiBase { if ( $module->isWriteMode() ) { if ( !$this->mEnableWrite ) { $this->dieUsageMsg( 'writedisabled' ); - } - if ( !$user->isAllowed( 'writeapi' ) ) { + } elseif ( !$user->isAllowed( 'writeapi' ) ) { $this->dieUsageMsg( 'writerequired' ); - } - if ( wfReadOnly() ) { + } elseif ( wfReadOnly() ) { $this->dieReadOnly(); } + if ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) { + $this->dieUsage( + "Promise-Non-Write-API-Action HTTP header cannot be sent to write API modules", + 'promised-nonwrite-api' + ); + } } // Allow extensions to stop execution for arbitrary reasons. diff --git a/languages/i18n/en.json b/languages/i18n/en.json index d62913ad1d..c4e7d92bba 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -323,6 +323,7 @@ "missingarticle-rev": "(revision#: $1)", "missingarticle-diff": "(Diff: $1, $2)", "readonly_lag": "The database has been automatically locked while the slave database servers catch up to the master", + "nonwrite-api-promise-error" : "The 'Promise-Non-Write-API-Action' HTTP header was sent but the request was to an API write module.", "internalerror": "Internal error", "internalerror_info": "Internal error: $1", "internalerror-fatal-exception": "Fatal exception of type \"$1\"", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 7298f4e87c..34240d44f2 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -497,6 +497,7 @@ "missingarticle-rev": "Used as $2 in {{msg-mw|Missing-article}}\n\nPreceded by the page title.\n\n[{{canonicalurl:Translating:Tasks|oldid=371789000}} Click here] to see an example of such an error message.\n\nParameters:\n* $1 - revision# of the requested ID", "missingarticle-diff": "Used as $2 in {{msg-mw|Missing-article}}\n\nPreceded by the page title.\n\n[{{canonicalurl:Translating:Tasks|diff=372398&oldid=371789000}} Click here] to see an example of such an error message.\n\nParameters:\n* $1 - the old revision ID\n* $2 - the revision ID to build the diff with", "readonly_lag": "Error message displayed when the database is locked.", + "nonwrite-api-promise-error": "Error message displayed when the 'Promise-Non-Write-API-Action' HTTP header is misused.", "internalerror": "{{Identical|Internal error}}", "internalerror_info": "Parameters:\n* $1 - error message", "internalerror-fatal-exception": "Error message displayed by MediaWiki itself when the request failed, inside an error box which also contains a code, a timestamp and a colon before this message.\nParameters:\n* $1 - proper name of the kind of error\n* $2 - alphanumeric code identifying the error in the server logs\n* $3 - URL which resulted in the error\n$2 and $3 are not used by default and only available for wiki customisations, because they are useful for communication to the wiki system administrator.", -- 2.20.1