Files
nginx-unit/test/test_php_targets.py
Andrew Clayton 420ddd4e49 PHP: Fix a potential problem parsing the path.
@dward on GitHub reported an issue with a URL like

  http://foo.bar/test.php?blah=test.php/foo

where we would end up trying to run the script

  test.php?blah=test.php

In the PHP module the format 'file.php/' is treated as a special case in
nxt_php_dynamic_request() where we check the _path_ part of the url for
the string '.php/'.

The problem is that the path actually also contains the query string,
thus we were finding 'test.php/' in the above URL and treating that
whole path as the script to run.

The fix is simple, replace the strstr(3) with a memmem(3), where we can
limit the amount of path we use for the check.

The trick here and what is not obvious from the code is that while
path.start points to the whole path including the query string,
path.length only contains the length of the _path_ part.

NOTE: memmem(3) is a GNU extension and is neither specified by POSIX or
ISO C, however it is available on a number of other systems, including:
FreeBSD, OpenBSD, NetBSD, illumos, and macOS.

If it comes to it we can implement a simple alternative for systems
which lack memmem(3).

This also adds a test case (provided by @dward) to cover this.

Closes: <https://github.com/nginx/unit/issues/781>
Cc: Andrei Zeliankou <zelenkov@nginx.com>
Reviewed-by: Alejandro Colomar <alx@nginx.com>
Reviewed-by: Andrei Zeliankou <zelenkov@nginx.com> [test]
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2022-11-07 00:06:43 +00:00

100 lines
3.7 KiB
Python

from unit.applications.lang.php import TestApplicationPHP
from unit.option import option
class TestPHPTargets(TestApplicationPHP):
prerequisites = {'modules': {'php': 'any'}}
def test_php_application_targets(self):
assert 'success' in self.conf(
{
"listeners": {"*:7080": {"pass": "routes"}},
"routes": [
{
"match": {"uri": "/1"},
"action": {"pass": "applications/targets/1"},
},
{
"match": {"uri": "/2"},
"action": {"pass": "applications/targets/2"},
},
{"action": {"pass": "applications/targets/default"}},
],
"applications": {
"targets": {
"type": self.get_application_type(),
"processes": {"spare": 0},
"targets": {
"1": {
"script": "1.php",
"root": option.test_dir + "/php/targets",
},
"2": {
"script": "2.php",
"root": option.test_dir + "/php/targets/2",
},
"default": {
"index": "index.php",
"root": option.test_dir + "/php/targets",
},
},
}
},
}
)
assert self.get(url='/1')['body'] == '1'
assert self.get(url='/2')['body'] == '2'
assert self.get(url='/blah')['status'] == 503 # TODO 404
assert self.get(url='/')['body'] == 'index'
assert self.get(url='/1.php?test=test.php/')['body'] == '1'
assert 'success' in self.conf(
"\"1.php\"", 'applications/targets/targets/default/index'
), 'change targets index'
assert self.get(url='/')['body'] == '1'
assert 'success' in self.conf_delete(
'applications/targets/targets/default/index'
), 'remove targets index'
assert self.get(url='/')['body'] == 'index'
def test_php_application_targets_error(self):
assert 'success' in self.conf(
{
"listeners": {
"*:7080": {"pass": "applications/targets/default"}
},
"applications": {
"targets": {
"type": self.get_application_type(),
"processes": {"spare": 0},
"targets": {
"default": {
"index": "index.php",
"root": option.test_dir + "/php/targets",
},
},
}
},
}
), 'initial configuration'
assert self.get()['status'] == 200
assert 'error' in self.conf(
{"pass": "applications/targets/blah"}, 'listeners/*:7080'
), 'invalid targets pass'
assert 'error' in self.conf(
'"' + option.test_dir + '/php/targets\"',
'applications/targets/root',
), 'invalid root'
assert 'error' in self.conf(
'"index.php"', 'applications/targets/index'
), 'invalid index'
assert 'error' in self.conf(
'"index.php"', 'applications/targets/script'
), 'invalid script'
assert 'error' in self.conf_delete(
'applications/targets/default/root'
), 'root remove'