* (bug 28798) Set $wgServer in the default LocalSettings.php
authorTim Starling <tstarling@users.mediawiki.org>
Wed, 15 Jun 2011 07:35:47 +0000 (07:35 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Wed, 15 Jun 2011 07:35:47 +0000 (07:35 +0000)
* (bug 14977) When detecting $wgServer, treat IPv6 addresses in $_SERVER['SERVER_NAME'] etc. in a sensible way.
* Tests for the new functions in IP.php and Installer.php

includes/IP.php
includes/installer/Installer.i18n.php
includes/installer/Installer.php
includes/installer/LocalSettingsGenerator.php
includes/installer/WebInstallerPage.php
tests/phpunit/includes/IPTest.php
tests/phpunit/includes/installer/InstallerTest.php [new file with mode: 0644]

index 9b55ad2..055fe85 100644 (file)
@@ -185,6 +185,76 @@ class IP {
                return $ip;
        }
 
+       /**
+        * Given a host/port string, like one might find in the host part of a URL 
+        * per RFC 2732, split the hostname part and the port part and return an 
+        * array with an element for each. If there is no port part, the array will 
+        * have false in place of the port. If the string was invalid in some way, 
+        * false is returned.
+        *
+        * This was easy with IPv4 and was generally done in an ad-hoc way, but 
+        * with IPv6 it's somewhat more complicated due to the need to parse the 
+        * square brackets and colons.
+        *
+        * A bare IPv6 address is accepted despite the lack of square brackets.
+        *
+        * @param $both The string with the host and port
+        * @return array
+        */
+       public static function splitHostAndPort( $both ) {
+               if ( substr( $both, 0, 1 ) === '[' ) {
+                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
+                               if ( isset( $m['port'] ) ) {
+                                       return array( $m[1], intval( $m['port'] ) );
+                               } else {
+                                       return array( $m[1], false );
+                               }
+                       } else {
+                               // Square bracket found but no IPv6
+                               return false;
+                       }
+               }
+               $numColons = substr_count( $both, ':' );
+               if ( $numColons >= 2 ) {
+                       // Is it a bare IPv6 address?
+                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
+                               return array( $both, false );
+                       } else {
+                               // Not valid IPv6, but too many colons for anything else
+                               return false;
+                       }
+               }
+               if ( $numColons >= 1 ) {
+                       // Host:port?
+                       $bits = explode( ':', $both );
+                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
+                               return array( $bits[0], intval( $bits[1] ) );
+                       } else {
+                               // Not a valid port
+                               return false;
+                       }
+               }
+               // Plain hostname
+               return array( $both, false );
+       }
+
+       /**
+        * Given a host name and a port, combine them into host/port string like
+        * you might find in a URL. If the host contains a colon, wrap it in square
+        * brackets like in RFC 2732. If the port matches the default port, omit 
+        * the port specification
+        */
+       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
+               if ( strpos( $host, ':' ) !== false ) {
+                       $host = "[$host]";
+               }
+               if ( $defaultPort !== false && $port == $defaultPort ) {
+                       return $host;
+               } else {
+                       return "$host:$port";
+               }
+       }
+
        /**
         * Given an unsigned integer, returns an IPv6 address in octet notation
         *
index b2850bb..009d093 100644 (file)
@@ -147,6 +147,7 @@ Image thumbnailing will be enabled if you enable uploads.',
 Image thumbnailing will be disabled.',
        'config-no-uri'                   => "'''Error:''' Could not determine the current URI.
 Installation aborted.",
+       'config-using-server'             => 'Using server name "<nowiki>$1</nowiki>".',
        'config-uploads-not-safe'         => "'''Warning:''' Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.
 Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [http://www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
        'config-brokenlibxml'             => 'Your system has a combination of PHP and libxml2 versions which is buggy and can cause hidden data corruption in MediaWiki and other web applications.
index 55de557..2bee742 100644 (file)
@@ -99,6 +99,7 @@ abstract class Installer {
                'envCheckCache',
                'envCheckDiff3',
                'envCheckGraphics',
+               'envCheckServer',
                'envCheckPath',
                'envCheckExtension',
                'envCheckShellLocale',
@@ -131,6 +132,8 @@ abstract class Installer {
                'wgDiff3',
                'wgImageMagickConvertCommand',
                'IP',
+               'wgServer',
+               'wgProto',
                'wgScriptPath',
                'wgScriptExtension',
                'wgMetaNamespace',
@@ -837,6 +840,47 @@ abstract class Installer {
                }
        }
 
+       /**
+        * Environment check for the server hostname.
+        */
+       protected function envCheckServer() {
+               if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on') {
+                       $proto = 'https';
+                       $stdPort = 443;
+               } else {
+                       $proto = 'http';
+                       $stdPort = 80;
+               }
+
+               $varNames = array( 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' );
+               $host = 'localhost';
+               $port = $stdPort;
+               foreach ( $varNames as $varName ) {
+                       if ( !isset( $_SERVER[$varName] ) ) {
+                               continue;
+                       }
+                       $parts = IP::splitHostAndPort( $_SERVER[$varName] );
+                       if ( !$parts ) {
+                               // Invalid, do not use
+                               continue;
+                       }
+                       $host = $parts[0];
+                       if ( $parts[1] === false ) {
+                               if ( isset( $_SERVER['SERVER_PORT'] ) ) {
+                                       $port = $_SERVER['SERVER_PORT'];
+                               } // else leave it as $stdPort
+                       } else {
+                               $port = $parts[1];
+                       }
+                       break;
+               }
+
+               $server = $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort );
+               $this->showMessage( 'config-using-server', $server );
+               $this->setVar( 'wgServer', $server );
+               $this->setVar( 'wgProto', $proto );
+       }
+
        /**
         * Environment check for setting $IP and $wgScriptPath.
         */
@@ -955,10 +999,10 @@ abstract class Installer {
         * TODO: document
         */
        protected function envCheckUploadsDirectory() {
-               global $IP, $wgServer;
+               global $IP;
 
                $dir = $IP . '/images/';
-               $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
+               $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
                $safe = !$this->dirIsExecutable( $dir, $url );
 
                if ( $safe ) {
index a08a697..7605674 100644 (file)
@@ -39,7 +39,7 @@ class LocalSettingsGenerator {
 
                $confItems = array_merge(
                        array(
-                               'wgScriptPath', 'wgScriptExtension',
+                               'wgServer', 'wgProto', 'wgScriptPath', 'wgScriptExtension',
                                'wgPasswordSender', 'wgImageMagickConvertCommand', 'wgShellLocale',
                                'wgLanguageCode', 'wgEnableEmail', 'wgEnableUserEmail', 'wgDiff3',
                                'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication',
@@ -249,6 +249,12 @@ if ( !defined( 'MEDIAWIKI' ) ) {
 \$wgScriptPath       = \"{$this->values['wgScriptPath']}\";
 \$wgScriptExtension  = \"{$this->values['wgScriptExtension']}\";
 
+## The server name to use in fully-qualified URLs
+\$wgServer           = \"{$this->values['wgServer']}\";
+
+## The URL protocol, may be http or https
+\$wgProto            = \"{$this->values['wgProto']}\";
+
 ## The relative URL path to the skins directory
 \$wgStylePath        = \"\$wgScriptPath/skins\";
 
index e56db83..9b303c8 100644 (file)
@@ -531,7 +531,7 @@ class WebInstaller_Upgrade extends WebInstallerPage {
                $this->addHTML(
                        $this->parent->getInfoBox(
                                wfMsgNoTrans( $msg,
-                                       $GLOBALS['wgServer'] .
+                                       $this->getVar( 'wgServer' ) .
                                                $this->getVar( 'wgScriptPath' ) . '/index' .
                                                $this->getVar( 'wgScriptExtension' )
                                ), 'tick-32.png'
@@ -934,8 +934,8 @@ class WebInstaller_Options extends WebInstallerPage {
         * @return string
         */
        public function getCCPartnerUrl() {
-               global $wgServer;
-               $exitUrl = $wgServer . $this->parent->getUrl( array(
+               $server = $this->getVar( 'wgServer' );
+               $exitUrl = $server . $this->parent->getUrl( array(
                        'page' => 'Options',
                        'SubmitCC' => 'indeed',
                        'config__LicenseCode' => 'cc',
@@ -943,7 +943,7 @@ class WebInstaller_Options extends WebInstallerPage {
                        'config_wgRightsText' => '[license_name]',
                        'config_wgRightsIcon' => '[license_button]',
                ) );
-               $styleUrl = $wgServer . dirname( dirname( $this->parent->getUrl() ) ) .
+               $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
                        '/skins/common/config-cc.css';
                $iframeUrl = 'http://creativecommons.org/license/?' .
                        wfArrayToCGI( array(
@@ -1147,7 +1147,7 @@ class WebInstaller_Complete extends WebInstallerPage {
        public function execute() {
                // Pop up a dialog box, to make it difficult for the user to forget
                // to download the file
-               $lsUrl = $GLOBALS['wgServer'] . $this->parent->getURL( array( 'localsettings' => 1 ) );
+               $lsUrl = $this->getVar( 'wgServer' ) . $this->parent->getURL( array( 'localsettings' => 1 ) );
                if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
                        // JS appears the only method that works consistently with IE7+
                        $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] . '">jQuery( document ).ready( function() { document.location='
@@ -1162,7 +1162,7 @@ class WebInstaller_Complete extends WebInstallerPage {
                        $this->parent->getInfoBox(
                                wfMsgNoTrans( 'config-install-done',
                                        $lsUrl,
-                                       $GLOBALS['wgServer'] .
+                                       $this->getVar( 'wgServer' ) .
                                                $this->getVar( 'wgScriptPath' ) . '/index' .
                                                $this->getVar( 'wgScriptExtension' ),
                                        '<downloadlink/>'
index 05deb40..6232d4b 100644 (file)
@@ -426,4 +426,56 @@ class IPTest extends MediaWikiTestCase {
                        array( false, '2001:0DB8:F::', '2001:DB8::/96' ),
                );
        }
+
+       /**
+        * Test for IP::splitHostAndPort().
+        * @dataProvider provideSplitHostAndPort
+        */
+       function testSplitHostAndPort( $expected, $input, $description ) {
+               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::splitHostAndPort()
+        */
+       function provideSplitHostAndPort() {
+               return array(
+                       array( false, '[', 'Unclosed square bracket' ),
+                       array( false, '[::', 'Unclosed square bracket 2' ),
+                       array( array( '::', false ), '::', 'Bare IPv6 0' ),
+                       array( array( '::1', false ), '::1', 'Bare IPv6 1' ),
+                       array( array( '::', false ), '[::]', 'Bracketed IPv6 0' ),
+                       array( array( '::1', false ), '[::1]', 'Bracketed IPv6 1' ),
+                       array( array( '::1', 80 ), '[::1]:80', 'Bracketed IPv6 with port' ),
+                       array( false, '::x', 'Double colon but no IPv6' ),
+                       array( array( 'x', 80 ), 'x:80', 'Hostname and port' ),
+                       array( false, 'x:x', 'Hostname and invalid port' ),
+                       array( array( 'x', false ), 'x', 'Plain hostname' )
+               );
+       }
+
+       /**
+        * Test for IP::combineHostAndPort()
+        * @dataProvider provideCombineHostAndPort
+        */
+       function testCombineHostAndPort( $expected, $input, $description ) {
+               list( $host, $port, $defaultPort ) = $input;
+               $this->assertEquals( 
+                       $expected, 
+                       IP::combineHostAndPort( $host, $port, $defaultPort ), 
+                       $description );
+       }
+
+       /**
+        * Provider for IP::combineHostAndPort()
+        */
+       function provideCombineHostAndPort() {
+               return array(
+                       array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ),
+                       array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ),
+                       array( 'x', array( 'x', 2, 2 ), 'Normal default port' ),
+                       array( 'x:2', array( 'x', 2, 3 ), 'Normal non-default port' ),
+               );
+       }
+
 }
diff --git a/tests/phpunit/includes/installer/InstallerTest.php b/tests/phpunit/includes/installer/InstallerTest.php
new file mode 100644 (file)
index 0000000..e38d1c1
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+
+class Installer_TestHelper extends Installer {
+       function showMessage( $msg ) {}
+       function showError( $msg ) {}
+       function showStatusMessage( Status $status ) {}
+
+       function __construct() {
+               $this->settings = array();
+       }
+
+}
+
+class InstallerTest extends MediaWikiTestCase {
+       /**
+        * @dataProvider provideEnvCheckServer
+        */
+       function testEnvCheckServer( $expected, $input, $description ) {
+               $installer = new Installer_TestHelper;
+               $oldServer = $_SERVER;
+               $_SERVER = $input;
+               $rm = new ReflectionMethod( 'Installer_TestHelper', 'envCheckServer' );
+               $rm->setAccessible( true );
+               $rm->invoke( $installer );
+               $_SERVER = $oldServer;
+               $this->assertEquals( $expected, $installer->getVar( 'wgServer' ), $description );
+       }
+
+       function provideEnvCheckServer() {
+               return array(
+                       array(
+                               'http://x',
+                               array(
+                                       'HTTP_HOST' => 'x'
+                               ),
+                               'Host header'
+                       ),
+                       array(
+                               'https://x',
+                               array(
+                                       'HTTP_HOST' => 'x',
+                                       'HTTPS' => 'on',
+                               ),
+                               'Host header with secure'
+                       ),
+                       array(
+                               'http://x',
+                               array(
+                                       'HTTP_HOST' => 'x',
+                                       'SERVER_PORT' => 80,
+                               ),
+                               'Default SERVER_PORT',
+                       ),
+                       array(
+                               'http://x',
+                               array(
+                                       'HTTP_HOST' => 'x',
+                                       'HTTPS' => 'off',
+                               ),
+                               'Secure off'
+                       ),
+                       array(
+                               'http://y',
+                               array(
+                                       'SERVER_NAME' => 'y',
+                               ),
+                               'Server name'
+                       ),
+                       array(
+                               'http://x',
+                               array(
+                                       'HTTP_HOST' => 'x',
+                                       'SERVER_NAME' => 'y',
+                               ),
+                               'Host server name precedence'
+                       ),
+                       array(
+                               'http://[::1]:81',
+                               array(
+                                       'HTTP_HOST' => '[::1]',
+                                       'SERVER_NAME' => '::1',
+                                       'SERVER_PORT' => '81',
+                               ),
+                               'Apache bug 26005'
+                       ),
+                       array(
+                               'http://localhost',
+                               array(
+                                       'SERVER_NAME' => '[2001'
+                               ),
+                               'Kind of like lighttpd per commit message in MW r83847',
+                       ),
+                       array(
+                               'http://[2a01:e35:2eb4:1::2]:777',
+                               array(
+                                       'SERVER_NAME' => '[2a01:e35:2eb4:1::2]:777'
+                               ),
+                               'Possible lighttpd environment per bug 14977 comment 13',
+                       ),
+               );
+       }
+}