diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 904524896..677d34418 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -802,6 +802,13 @@ $config['no_save_sent_messages'] = false; // Note: Use assets_path to not prevent the browser from caching assets $config['use_secure_urls'] = false; +// Specify the $_SERVER field that contains the full path of the original HTTP request. +// This might be changed when Roundcube runs behind a reverse proxy using a subpath. +// The reverse proxy config can specify a custom header (e.g. X-Forwarded-Path) containing +// the path under which Roundcube is exposed to the outside world (e.g. /rcube/). +// This header vaue is then availbale in PHP with $_SERVER['HTTP_X_FORWARDED_PATH']. +$config['request_uri_field'] = 'REQUEST_URI'; + // Allows to define separate server/path for image/js/css files // Warning: If the domain is different cross-domain access to some // resources need to be allowed diff --git a/program/include/rcmail.php b/program/include/rcmail.php index f2ec6358b..b1aa809d2 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1117,7 +1117,11 @@ class rcmail extends rcube } $base_path = ''; - if (!empty($_SERVER['REDIRECT_SCRIPT_URL'])) { + $server_var = $this->get_request_uri_field(); + if ($server_var && !empty($_SERVER[$server_var])) { + $base_path = preg_replace('/[?&].*$/', '', $_SERVER[$server_var]); + } + else if (!empty($_SERVER['REDIRECT_SCRIPT_URL'])) { $base_path = $_SERVER['REDIRECT_SCRIPT_URL']; } else if (!empty($_SERVER['SCRIPT_NAME'])) { @@ -1152,17 +1156,25 @@ class rcmail extends rcube $prefix = rtrim($prefix, '/') . '/'; } else { - if (isset($_SERVER['REQUEST_URI'])) { - $prefix = preg_replace('/[?&].*$/', '', $_SERVER['REQUEST_URI']) ?: './'; - } - else { - $prefix = './'; - } + $prefix = $base_path ?: './'; } return $prefix . $url; } + /** + * Get the 'request_uri_field' config option + * with an additional check against the 'proxy_whitelist' config + */ + protected function get_request_uri_field() + { + $server_var = $this->config->get('request_uri_field'); + if (!empty($server_var) && (strpos($server_var, 'HTTP_') !== 0 || rcube_utils::check_proxy_whitelist_ip())) { + return $server_var; + } + return null; + } + /** * Function to be executed in script shutdown */ diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index c55ee5726..a641497d5 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -673,7 +673,7 @@ class rcube_utils if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' - && in_array($_SERVER['REMOTE_ADDR'], (array) rcube::get_instance()->config->get('proxy_whitelist', [])) + && self::check_proxy_whitelist_ip() ) { return true; } @@ -689,6 +689,13 @@ class rcube_utils return false; } + /** + * Check if the reported REMOTE_ADDR is in the 'proxy_whitelist' config option + */ + public static function check_proxy_whitelist_ip() { + return in_array($_SERVER['REMOTE_ADDR'], (array) rcube::get_instance()->config->get('proxy_whitelist', [])); + } + /** * Replaces hostname variables. * diff --git a/tests/Rcmail/OutputHtml.php b/tests/Rcmail/OutputHtml.php index 9aa294228..9c891831a 100644 --- a/tests/Rcmail/OutputHtml.php +++ b/tests/Rcmail/OutputHtml.php @@ -381,7 +381,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase $rcmail = rcube::get_instance(); $output = new rcmail_output_html(); - $this->assertSame('
test
', $output->form_tag([], 'test')); + $this->assertSame('
test
', $output->form_tag([], 'test')); } /** @@ -404,7 +404,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase $output = new rcmail_output_html(); $expected = '
' + . ' action="vendor/bin/phpunit?_task=cli" method="get">' . '
'; $this->assertSame($expected, $output->search_form([])); diff --git a/tests/Rcmail/Rcmail.php b/tests/Rcmail/Rcmail.php index 1f5587273..4c4de7c68 100644 --- a/tests/Rcmail/Rcmail.php +++ b/tests/Rcmail/Rcmail.php @@ -14,6 +14,7 @@ class Rcmail_Rcmail extends ActionTestCase $_SERVER['SERVER_PORT'] = '443'; $_SERVER['SCRIPT_NAME'] = '/sub/index.php'; $_SERVER['HTTPS'] = true; + $_SERVER['X_FORWARDED_PATH'] = '/proxied/'; rcmail::get_instance()->filename = ''; } @@ -126,27 +127,32 @@ class Rcmail_Rcmail extends ActionTestCase $rcmail = rcmail::get_instance(); $this->assertEquals( - './?_task=cli&_action=test', + '/sub/?_task=cli&_action=test', $rcmail->url('test'), "Action only" ); $this->assertEquals( - './?_task=cli&_action=test&_a=AA', + '/sub/?_task=cli&_action=test&_a=AA', $rcmail->url(['action' => 'test', 'a' => 'AA']), "Unprefixed parameters" ); $this->assertEquals( - './?_task=cli&_action=test&_b=BB', + '/sub/?_task=cli&_action=test&_b=BB', $rcmail->url(['_action' => 'test', '_b' => 'BB', '_c' => null]), "Prefixed parameters (skip empty)" ); - $this->assertEquals('./?_task=cli', $rcmail->url([]), "Empty input"); + $this->assertEquals('/sub/?_task=cli', $rcmail->url([]), "Empty input"); + $_SERVER['REQUEST_URI'] = '/rc/?_task=mail'; $this->assertEquals('/rc/?_task=cli', $rcmail->url([]), "Empty input with REQUEST_URI prefix"); + $rcmail->config->set('request_uri_field', 'X_FORWARDED_PATH'); + $this->assertEquals('/proxied/?_task=cli', $rcmail->url([]), "Consider request_uri_field config"); + $rcmail->config->set('request_uri_field', 'UNDEFINED'); + $this->assertEquals( '/sub/?_task=cli&_action=test&_mode=ABS', $rcmail->url(['_action' => 'test', '_mode' => 'ABS'], true),