WebRequest & RequestFromGlobals: get HTTP headers in one way

apache_request_headers() is a vendor-specific function - it got used
when present and alternative code paths were exercised otherwise.
These preserved certain "special" headers, e.g. Content-Type, only
inconsistently.

The function getallheaders() is an alias[1] for apache_request_headers()
on systems where the latter is present. Alternatively, there is a
polyfill (ralouphie/getallheaders) which is already installed in
mediawiki-vendor[2] (by virtue of guzzle).

Using getallheaders() exclusively, will make sure these "special"
headers are consistently available alongside their "regular"[3] peers
and helps MediaWiki code focus on its domain.

The dependency to ralouphie/getallheaders is made explicit in the same
version in which it is currently locked in mediawiki-vendor[4].

This surfaced because the deprecation warning for API POST requests
without a Content-Type header, introduced in bba1a0f, appeared in my
development system (somewhat dated addshore/mediawiki-docker-dev/) even
though the client did a fine job.

Interesting implementation detail: While WebRequest keeps track of
headers using keys in all upper case, REST RequestFromGlobals does so in
all lower case - but both use retrieval logic complementary to their
respective approach however. In case of REST RequestFromGlobals this is
encapsulated inside of HeaderContainer (setting and retrieving), while
WebRequest does all of this by itself. Cf. [5] and [6]

[1]: https://www.php.net/manual/en/function.getallheaders.php
[2]: https://github.com/wikimedia/mediawiki-vendor/tree/8f2967d/ralouphie/getallheaders
[3]: https://www.php.net/manual/en/reserved.variables.server.php#110763
[4]: https://github.com/wikimedia/mediawiki-vendor/blob/8f2967d/composer.lock#L3250
[5]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
[6]: https://www.php.net/manual/en/function.apache-request-headers.php#124236

Bug: T245535
Change-Id: Iba52f152e15928473b729a2588c2462e76e85634
This commit is contained in:
Pablo Grass 2020-03-18 09:49:15 +01:00
parent 7c74e448aa
commit ff40270448
No known key found for this signature in database
GPG key ID: 75E133A5B48B4CE0
5 changed files with 46 additions and 35 deletions

View file

@ -36,6 +36,7 @@
"php": ">=7.2.22",
"psr/container": "1.0.0",
"psr/log": "1.1.3",
"ralouphie/getallheaders": "3.0.3",
"wikimedia/assert": "0.5.0",
"wikimedia/at-ease": "2.0.0",
"wikimedia/base-convert": "2.0.1",

View file

@ -54,22 +54,7 @@ class RequestFromGlobals extends RequestBase {
}
protected function initHeaders() {
if ( function_exists( 'apache_request_headers' ) ) {
$this->setHeaders( apache_request_headers() );
} else {
$headers = [];
foreach ( $_SERVER as $name => $value ) {
if ( substr( $name, 0, 5 ) === 'HTTP_' ) {
$name = strtolower( str_replace( '_', '-', substr( $name, 5 ) ) );
$headers[$name] = $value;
} elseif ( $name === 'CONTENT_LENGTH' ) {
$headers['content-length'] = $value;
} elseif ( $name === 'CONTENT_TYPE' ) {
$headers['content-type'] = $value;
}
}
$this->setHeaders( $headers );
}
$this->setHeaders( getallheaders() );
}
public function getBody() {

View file

@ -1089,21 +1089,7 @@ class WebRequest {
return;
}
$apacheHeaders = function_exists( 'apache_request_headers' ) ? apache_request_headers() : false;
if ( $apacheHeaders ) {
foreach ( $apacheHeaders as $tempName => $tempValue ) {
$this->headers[strtoupper( $tempName )] = $tempValue;
}
} else {
foreach ( $_SERVER as $name => $value ) {
if ( substr( $name, 0, 5 ) === 'HTTP_' ) {
$name = str_replace( '_', '-', substr( $name, 5 ) );
$this->headers[$name] = $value;
} elseif ( $name === 'CONTENT_LENGTH' ) {
$this->headers['CONTENT-LENGTH'] = $value;
}
}
}
$this->headers = array_change_key_case( getallheaders(), CASE_UPPER );
}
/**

View file

@ -77,16 +77,26 @@ class RequestFromGlobalsTest extends MediaWikiTestCase {
$this->setServerVars( [
'HTTP_HOST' => '[::1]',
'CONTENT_LENGTH' => 6,
'CONTENT_TYPE' => 'application/json'
'CONTENT_TYPE' => 'application/json',
'CONTENT_MD5' => 'rL0Y20zC+Fzt72VPzMSk2A==',
] );
$this->assertEquals( $this->reqFromGlobals->getHeaders(), [
'host' => [ '[::1]' ],
'content-length' => [ 6 ],
'content-type' => [ 'application/json' ]
'Host' => [ '[::1]' ],
'Content-Length' => [ 6 ],
'Content-Type' => [ 'application/json' ],
'Content-Md5' => [ 'rL0Y20zC+Fzt72VPzMSk2A==' ],
] );
}
public function testGetHeaderKeyIsCaseInsensitive() {
$cacheControl = 'private, must-revalidate, max-age=0';
$this->setServerVars( [ 'HTTP_CACHE_CONTROL' => $cacheControl ] );
$this->assertSame( $this->reqFromGlobals->getHeader( 'Cache-Control' ), [ $cacheControl ] );
$this->assertSame( $this->reqFromGlobals->getHeader( 'cache-control' ), [ $cacheControl ] );
}
public function testGetBody() {
$this->setServerVars( [
'REQUEST_METHOD' => 'POST',

View file

@ -644,6 +644,35 @@ class WebRequestTest extends MediaWikiTestCase {
$this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description );
}
/**
* @covers WebRequest::getHeader
*/
public function testGetHeaderCanYieldSpecialCgiHeaders() {
$contentType = 'application/json; charset=utf-8';
$contentLength = '4711';
$contentMd5 = 'rL0Y20zC+Fzt72VPzMSk2A==';
$this->setServerVars( [
'HTTP_CONTENT_TYPE' => $contentType,
'HTTP_CONTENT_LENGTH' => $contentLength,
'HTTP_CONTENT_MD5' => $contentMd5,
] );
$request = new WebRequest();
$this->assertSame( $request->getHeader( 'Content-Type' ), $contentType );
$this->assertSame( $request->getHeader( 'Content-Length' ), $contentLength );
$this->assertSame( $request->getHeader( 'Content-Md5' ), $contentMd5 );
}
/**
* @covers WebRequest::getHeader
*/
public function testGetHeaderKeyIsCaseInsensitive() {
$cacheControl = 'private, must-revalidate, max-age=0';
$this->setServerVars( [ 'HTTP_CACHE_CONTROL' => $cacheControl ] );
$request = new WebRequest();
$this->assertSame( $request->getHeader( 'Cache-Control' ), $cacheControl );
$this->assertSame( $request->getHeader( 'cache-control' ), $cacheControl );
}
protected function setServerVars( $vars ) {
// Don't remove vars which should be available in all SAPI.
if ( !isset( $vars['REQUEST_TIME_FLOAT'] ) ) {