Skip to content

Commit 16f5c4b

Browse files
authored
Merge pull request #6785 from kenjis/feat-add-allowedHostnames
feat: add $allowedHostnames for multiple domain support
2 parents 60f1e8e + 1f62c1c commit 16f5c4b

File tree

13 files changed

+227
-41
lines changed

13 files changed

+227
-41
lines changed

app/Config/App.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ class App extends BaseConfig
2424
*/
2525
public string $baseURL = 'http://localhost:8080/';
2626

27+
/**
28+
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
29+
* If you want to accept multiple Hostnames, set this.
30+
*
31+
* E.g. When your site URL ($baseURL) is 'http://example.com/', and your site
32+
* also accepts 'http://media.example.com/' and
33+
* 'http://accounts.example.com/':
34+
* ['media.example.com', 'accounts.example.com']
35+
*
36+
* @var string[]
37+
* @phpstan-var list<string>
38+
*/
39+
public array $allowedHostnames = [];
40+
2741
/**
2842
* --------------------------------------------------------------------------
2943
* Index File

system/HTTP/IncomingRequest.php

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,12 @@ public function detectLocale($config)
192192
* Sets up our URI object based on the information we have. This is
193193
* either provided by the user in the baseURL Config setting, or
194194
* determined from the environment as needed.
195+
*
196+
* @deprecated $protocol and $baseURL are deprecated. No longer used.
195197
*/
196198
protected function detectURI(string $protocol, string $baseURL)
197199
{
198-
// Passing the config is unnecessary but left for legacy purposes
199-
$config = clone $this->config;
200-
$config->baseURL = $baseURL;
201-
202-
$this->setPath($this->detectPath($protocol), $config);
200+
$this->setPath($this->detectPath($this->config->uriProtocol), $this->config);
203201
}
204202

205203
/**
@@ -270,7 +268,7 @@ protected function parseRequestURI(): string
270268
}
271269

272270
// This section ensures that even on servers that require the URI to contain the query string (Nginx) a correct
273-
// URI is found, and also fixes the QUERY_STRING getServer var and $_GET array.
271+
// URI is found, and also fixes the QUERY_STRING Server var and $_GET array.
274272
if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0) {
275273
$query = explode('?', $query, 2);
276274
$uri = $query[0];
@@ -400,19 +398,26 @@ public function setPath(string $path, ?App $config = null)
400398

401399
// It's possible the user forgot a trailing slash on their
402400
// baseURL, so let's help them out.
403-
$baseURL = $config->baseURL === '' ? $config->baseURL : rtrim($config->baseURL, '/ ') . '/';
401+
$baseURL = ($config->baseURL === '') ? $config->baseURL : rtrim($config->baseURL, '/ ') . '/';
404402

405-
// Based on our baseURL provided by the developer
406-
// set our current domain name, scheme
403+
// Based on our baseURL and allowedHostnames provided by the developer
404+
// and HTTP_HOST, set our current domain name, scheme.
407405
if ($baseURL !== '') {
406+
$host = $this->determineHost($config, $baseURL);
407+
408+
// Set URI::$baseURL
409+
$uri = new URI($baseURL);
410+
$currentBaseURL = (string) $uri->setHost($host);
411+
$this->uri->setBaseURL($currentBaseURL);
412+
408413
$this->uri->setScheme(parse_url($baseURL, PHP_URL_SCHEME));
409-
$this->uri->setHost(parse_url($baseURL, PHP_URL_HOST));
414+
$this->uri->setHost($host);
410415
$this->uri->setPort(parse_url($baseURL, PHP_URL_PORT));
411416

412417
// Ensure we have any query vars
413418
$this->uri->setQuery($_SERVER['QUERY_STRING'] ?? '');
414419

415-
// Check if the baseURL scheme needs to be coerced into its secure version
420+
// Check if the scheme needs to be coerced into its secure version
416421
if ($config->forceGlobalSecureRequests && $this->uri->getScheme() === 'http') {
417422
$this->uri->setScheme('https');
418423
}
@@ -425,6 +430,27 @@ public function setPath(string $path, ?App $config = null)
425430
return $this;
426431
}
427432

433+
private function determineHost(App $config, string $baseURL): string
434+
{
435+
$host = parse_url($baseURL, PHP_URL_HOST);
436+
437+
if (empty($config->allowedHostnames)) {
438+
return $host;
439+
}
440+
441+
// Update host if it is valid.
442+
$httpHostPort = $this->getServer('HTTP_HOST');
443+
if ($httpHostPort !== null) {
444+
[$httpHost] = explode(':', $httpHostPort, 2);
445+
446+
if (in_array($httpHost, $config->allowedHostnames, true)) {
447+
$host = $httpHost;
448+
}
449+
}
450+
451+
return $host;
452+
}
453+
428454
/**
429455
* Returns the path relative to SCRIPT_NAME,
430456
* running detection as necessary.

system/HTTP/URI.php

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
namespace CodeIgniter\HTTP;
1313

14+
use BadMethodCallException;
1415
use CodeIgniter\HTTP\Exceptions\HTTPException;
15-
use InvalidArgumentException;
1616

1717
/**
1818
* Abstraction for a uniform resource identifier (URI).
@@ -36,6 +36,11 @@ class URI
3636
*/
3737
protected $uriString;
3838

39+
/**
40+
* The Current baseURL.
41+
*/
42+
private ?string $baseURL = null;
43+
3944
/**
4045
* List of URI segments.
4146
*
@@ -83,6 +88,11 @@ class URI
8388
/**
8489
* URI path.
8590
*
91+
* Note: The constructor of the IncomingRequest class changes the path of
92+
* the URI object held by the IncomingRequest class to a path relative
93+
* to the SCRIPT_NAME. If the baseURL contains subfolders, this value
94+
* will be different from the current URI path.
95+
*
8696
* @var string
8797
*/
8898
protected $path;
@@ -232,9 +242,12 @@ public static function removeDotSegments(string $path): string
232242
/**
233243
* Constructor.
234244
*
235-
* @param string $uri
245+
* @param string|null $uri The URI to parse.
246+
*
247+
* @throws HTTPException
236248
*
237-
* @throws InvalidArgumentException
249+
* @TODO null for param $uri should be removed.
250+
* See https://www.php-fig.org/psr/psr-17/#26-urifactoryinterface
238251
*/
239252
public function __construct(?string $uri = null)
240253
{
@@ -273,6 +286,8 @@ public function useRawQueryString(bool $raw = true)
273286
* Sets and overwrites any current URI information.
274287
*
275288
* @return URI
289+
*
290+
* @throws HTTPException
276291
*/
277292
public function setURI(?string $uri = null)
278293
{
@@ -744,6 +759,30 @@ public function setPath(string $path)
744759
return $this;
745760
}
746761

762+
/**
763+
* Sets the current baseURL.
764+
*
765+
* @interal
766+
*/
767+
public function setBaseURL(string $baseURL): void
768+
{
769+
$this->baseURL = $baseURL;
770+
}
771+
772+
/**
773+
* Returns the current baseURL.
774+
*
775+
* @interal
776+
*/
777+
public function getBaseURL(): string
778+
{
779+
if ($this->baseURL === null) {
780+
throw new BadMethodCallException('The $baseURL is not set.');
781+
}
782+
783+
return $this->baseURL;
784+
}
785+
747786
/**
748787
* Sets the path portion of the URI based on segments.
749788
*

system/Helpers/url_helper.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* the LICENSE file that was distributed with this source code.
1010
*/
1111

12+
use CodeIgniter\HTTP\CLIRequest;
13+
use CodeIgniter\HTTP\Exceptions\HTTPException;
1214
use CodeIgniter\HTTP\IncomingRequest;
1315
use CodeIgniter\HTTP\URI;
1416
use CodeIgniter\Router\Exceptions\RouterException;
@@ -19,14 +21,15 @@
1921

2022
if (! function_exists('_get_uri')) {
2123
/**
22-
* Used by the other URL functions to build a
23-
* framework-specific URI based on the App config.
24+
* Used by the other URL functions to build a framework-specific URI
25+
* based on $request->getUri()->getBaseURL() and the App config.
2426
*
25-
* @internal Outside of the framework this should not be used directly.
27+
* @internal Outside the framework this should not be used directly.
2628
*
2729
* @param string $relativePath May include queries or fragments
2830
*
29-
* @throws InvalidArgumentException For invalid paths or config
31+
* @throws HTTPException For invalid paths.
32+
* @throws InvalidArgumentException For invalid config.
3033
*/
3134
function _get_uri(string $relativePath = '', ?App $config = null): URI
3235
{
@@ -37,7 +40,7 @@ function _get_uri(string $relativePath = '', ?App $config = null): URI
3740
}
3841

3942
// If a full URI was passed then convert it
40-
if (is_int(strpos($relativePath, '://'))) {
43+
if (strpos($relativePath, '://') !== false) {
4144
$full = new URI($relativePath);
4245
$relativePath = URI::createURIString(
4346
null,
@@ -51,7 +54,14 @@ function _get_uri(string $relativePath = '', ?App $config = null): URI
5154
$relativePath = URI::removeDotSegments($relativePath);
5255

5356
// Build the full URL based on $config and $relativePath
54-
$url = rtrim($config->baseURL, '/ ') . '/';
57+
$request = Services::request();
58+
59+
if ($request instanceof CLIRequest) {
60+
/** @var App $config */
61+
$url = rtrim($config->baseURL, '/ ') . '/';
62+
} else {
63+
$url = $request->getUri()->getBaseURL();
64+
}
5565

5666
// Check for an index page
5767
if ($config->indexPage !== '') {

system/Test/Mock/MockIncomingRequest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,4 @@
1515

1616
class MockIncomingRequest extends IncomingRequest
1717
{
18-
protected function detectURI($protocol, $baseURL)
19-
{
20-
// Do nothing...
21-
}
2218
}

tests/system/HTTP/RedirectResponseTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ protected function setUp(): void
4040
{
4141
parent::setUp();
4242

43+
$this->resetServices();
44+
4345
$_SERVER['REQUEST_METHOD'] = 'GET';
4446

4547
$this->config = new App();
@@ -48,7 +50,12 @@ protected function setUp(): void
4850
$this->routes = new RouteCollection(Services::locator(), new Modules());
4951
Services::injectMock('routes', $this->routes);
5052

51-
$this->request = new MockIncomingRequest($this->config, new URI('http://example.com'), null, new UserAgent());
53+
$this->request = new MockIncomingRequest(
54+
$this->config,
55+
new URI('http://example.com'),
56+
null,
57+
new UserAgent()
58+
);
5259
Services::injectMock('request', $this->request);
5360
}
5461

tests/system/HTTP/ResponseTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ protected function setUp(): void
3434
{
3535
$this->server = $_SERVER;
3636

37-
Services::reset();
38-
3937
parent::setUp();
38+
39+
$this->resetServices();
4040
}
4141

4242
protected function tearDown(): void
@@ -164,6 +164,8 @@ public function testSetLink()
164164
$config->baseURL = 'http://example.com/test/';
165165
Factories::injectMock('config', 'App', $config);
166166

167+
$this->resetServices();
168+
167169
$response = new Response($config);
168170
$pager = Services::pager();
169171

tests/system/Helpers/URLHelper/CurrentUrlTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,38 @@ protected function tearDown(): void
5858

5959
public function testCurrentURLReturnsBasicURL()
6060
{
61-
// Since we're on a CLI, we must provide our own URI
61+
$_SERVER['REQUEST_URI'] = '/public';
62+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
63+
6264
$this->config->baseURL = 'http://example.com/public';
6365

6466
$this->assertSame('http://example.com/public/index.php/', current_url());
6567
}
6668

69+
public function testCurrentURLReturnsAllowedHostname()
70+
{
71+
$_SERVER['HTTP_HOST'] = 'www.example.jp';
72+
$_SERVER['REQUEST_URI'] = '/public';
73+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
74+
75+
$this->config->baseURL = 'http://example.com/public';
76+
$this->config->allowedHostnames = ['www.example.jp'];
77+
78+
$this->assertSame('http://www.example.jp/public/index.php/', current_url());
79+
}
80+
81+
public function testCurrentURLReturnsBaseURLIfNotAllowedHostname()
82+
{
83+
$_SERVER['HTTP_HOST'] = 'invalid.example.org';
84+
$_SERVER['REQUEST_URI'] = '/public';
85+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
86+
87+
$this->config->baseURL = 'http://example.com/public';
88+
$this->config->allowedHostnames = ['www.example.jp'];
89+
90+
$this->assertSame('http://example.com/public/index.php/', current_url());
91+
}
92+
6793
public function testCurrentURLReturnsObject()
6894
{
6995
// Since we're on a CLI, we must provide our own URI

tests/system/Helpers/URLHelper/SiteUrlTest.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,49 @@ public function testBaseURLService()
281281
$_SERVER['HTTP_HOST'] = 'example.com';
282282
$_SERVER['REQUEST_URI'] = '/ci/v4/x/y';
283283

284-
$uri = new URI('http://example.com/ci/v4/x/y');
285-
Services::injectMock('uri', $uri);
286-
287284
$this->config->baseURL = 'http://example.com/ci/v4/';
288285
$request = Services::request($this->config);
289286
Services::injectMock('request', $request);
290287

291288
$this->assertSame('http://example.com/ci/v4/index.php/controller/method', site_url('controller/method', null, $this->config));
292289
$this->assertSame('http://example.com/ci/v4/controller/method', base_url('controller/method', null));
293290
}
291+
292+
public function testSiteURLWithAllowedHostname()
293+
{
294+
$_SERVER['HTTP_HOST'] = 'www.example.jp';
295+
$_SERVER['REQUEST_URI'] = '/public';
296+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
297+
298+
$this->config->baseURL = 'http://example.com/public/';
299+
$this->config->allowedHostnames = ['www.example.jp'];
300+
301+
// URI object are updated in IncomingRequest constructor.
302+
$request = Services::incomingrequest($this->config);
303+
Services::injectMock('request', $request);
304+
305+
$this->assertSame(
306+
'http://www.example.jp/public/index.php/controller/method',
307+
site_url('controller/method', null, $this->config)
308+
);
309+
}
310+
311+
public function testBaseURLWithAllowedHostname()
312+
{
313+
$_SERVER['HTTP_HOST'] = 'www.example.jp';
314+
$_SERVER['REQUEST_URI'] = '/public';
315+
$_SERVER['SCRIPT_NAME'] = '/public/index.php';
316+
317+
$this->config->baseURL = 'http://example.com/public/';
318+
$this->config->allowedHostnames = ['www.example.jp'];
319+
320+
// URI object are updated in IncomingRequest constructor.
321+
$request = Services::incomingrequest($this->config);
322+
Services::injectMock('request', $request);
323+
324+
$this->assertSame(
325+
'http://www.example.jp/public/controller/method',
326+
base_url('controller/method', null)
327+
);
328+
}
294329
}

0 commit comments

Comments
 (0)