diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 2dddcb5a2e2..0189be255eb 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -9675,6 +9675,8 @@ $wgHTTPProxy = ''; * Local virtual hosts. * * This lists domains that are configured as virtual hosts on the same machine. + * It is expected that each domain can be identified by its hostname alone, + * without any ports. * * This affects the following: * - MWHttpRequest: If a request is to be made to a domain listed here, or any @@ -9687,12 +9689,16 @@ $wgHTTPProxy = ''; $wgLocalVirtualHosts = []; /** - * Proxy to use to requests to domains in $wgLocalVirtualHosts + * Reverse proxy to use for requests to domains in $wgLocalVirtualHosts * - * If set to false, no proxy will be used for local requests + * When used, any port in the request URL will be dropped. The behavior of + * redirects and cookies is dependent upon the reverse proxy actually in use, + * as MediaWiki doesn't implement any special handling for them. + * + * If set to false, no reverse proxy will be used for local requests. * * @var string|bool - * @since 1.37 + * @since 1.38 */ $wgLocalHTTPProxy = false; diff --git a/includes/http/MWHttpRequest.php b/includes/http/MWHttpRequest.php index b75905e0d3a..ef1025accf8 100644 --- a/includes/http/MWHttpRequest.php +++ b/includes/http/MWHttpRequest.php @@ -239,12 +239,47 @@ abstract class MWHttpRequest implements LoggerAwareInterface { // Otherwise, fallback to $wgLocalHTTPProxy for local URLs // or $wgHTTPProxy for everything else if ( self::isLocalURL( $this->url ) ) { - $this->proxy = (string)$wgLocalHTTPProxy; + if ( $wgLocalHTTPProxy !== false ) { + $this->setReverseProxy( $wgLocalHTTPProxy ); + } } else { $this->proxy = (string)$wgHTTPProxy; } } + /** + * Enable use of a reverse proxy in which the hostname is + * passed as a "Host" header, and the request is sent to the + * proxy's host:port instead. + * + * Note that any custom port in the request URL will be lost + * and cookies and redirects may not work properly. + * + * @param string $proxy URL of proxy + */ + protected function setReverseProxy( string $proxy ) { + $parsedProxy = wfParseUrl( $proxy ); + if ( $parsedProxy === false ) { + throw new Exception( "Invalid reverseProxy configured: $proxy" ); + } + // Set the current host in the Host header + $this->setHeader( 'Host', $this->parsedUrl['host'] ); + // Set current protocol in X-Forwarded-Proto + // TODO: consider supporting the standardized "Forwarded" header too + $this->setHeader( 'X-Forwarded-Proto', $this->parsedUrl['scheme'] ); + // Replace scheme, host and port in the request + $this->parsedUrl['scheme'] = $parsedProxy['scheme']; + $this->parsedUrl['host'] = $parsedProxy['host']; + if ( isset( $parsedProxy['port'] ) ) { + $this->parsedUrl['port'] = $parsedProxy['port']; + } else { + unset( $this->parsedUrl['port'] ); + } + $this->url = wfAssembleUrl( $this->parsedUrl ); + // Mark that we're already using a proxy + $this->noProxy = true; + } + /** * Check if the URL can be served by localhost * diff --git a/tests/phpunit/includes/http/MWHttpRequestTest.php b/tests/phpunit/includes/http/MWHttpRequestTest.php index 714a4dc5d62..26e9596fe5c 100644 --- a/tests/phpunit/includes/http/MWHttpRequestTest.php +++ b/tests/phpunit/includes/http/MWHttpRequestTest.php @@ -1,5 +1,7 @@ assertSame( $expect, MWHttpRequest::isValidURI( $uri ), $message ); } + public function testSetReverseProxy() { + $req = TestingAccessWrapper::newFromObject( + MWHttpRequest::factory( 'https://example.org/path?query=string' ) + ); + $req->setReverseProxy( 'http://localhost:1234' ); + $this->assertSame( $req->url, 'http://localhost:1234/path?query=string' ); + $this->assertSame( $req->reqHeaders['Host'], 'example.org' ); + $this->assertSame( $req->reqHeaders['X-Forwarded-Proto'], 'https' ); + } + }