diff --git a/php.ini-development b/php.ini-development index 37619fd071c55..162fb3f25c19c 100644 --- a/php.ini-development +++ b/php.ini-development @@ -817,6 +817,12 @@ enable_dl = Off ; https://php.net/fastcgi.impersonate ;fastcgi.impersonate = 1 +; Prevent decoding of SCRIPT_FILENAME when using Apache ProxyPass or +; ProxyPassMatch. This should only be used if script file paths are already +; stored in an encoded format on the file system. +; Default is 0. +;fastcgi.script_path_encoded = 1 + ; Disable logging through FastCGI connection. PHP's default behavior is to enable ; this feature. ;fastcgi.logging = 0 diff --git a/php.ini-production b/php.ini-production index 60069f69b8b90..042d246943d81 100644 --- a/php.ini-production +++ b/php.ini-production @@ -819,6 +819,12 @@ enable_dl = Off ; https://php.net/fastcgi.impersonate ;fastcgi.impersonate = 1 +; Prevent decoding of SCRIPT_FILENAME when using Apache ProxyPass or +; ProxyPassMatch. This should only be used if script file paths are already +; stored in an encoded format on the file system. +; Default is 0. +;fastcgi.script_path_encoded = 1 + ; Disable logging through FastCGI connection. PHP's default behavior is to enable ; this feature. ;fastcgi.logging = 0 diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index 12edcedd951df..8af1e51d512fb 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -144,6 +144,7 @@ typedef struct _php_cgi_globals_struct { bool nph; bool fix_pathinfo; bool discard_path; + bool fcgi_script_path_encoded; bool fcgi_logging; bool fcgi_logging_request_started; HashTable user_config_cache; @@ -1066,6 +1067,10 @@ static void init_request_info(void) } } + if (apache_was_here && !CGIG(fcgi_script_path_encoded)) { + php_raw_url_decode(env_script_filename, strlen(env_script_filename)); + } + if (CGIG(fix_pathinfo)) { struct stat st; char *real_path = NULL; @@ -1174,7 +1179,7 @@ static void init_request_info(void) * it is probably also in SCRIPT_NAME and need to be removed */ size_t decoded_path_info_len = 0; - if (strchr(path_info, '%')) { + if (CGIG(fcgi_script_path_encoded) && strchr(path_info, '%')) { decoded_path_info = estrdup(path_info); decoded_path_info_len = php_raw_url_decode(decoded_path_info, strlen(path_info)); } @@ -1421,13 +1426,14 @@ static void fastcgi_ini_parser(zval *arg1, zval *arg2, zval *arg3, int callback_ /* }}} */ PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_BOOLEAN("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_BOOLEAN("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_BOOLEAN("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_BOOLEAN("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_ENTRY("fastcgi.error_header", NULL, PHP_INI_SYSTEM, OnUpdateString, error_header, php_cgi_globals_struct, php_cgi_globals) - STD_PHP_INI_ENTRY("fpm.config", NULL, PHP_INI_SYSTEM, OnUpdateString, fpm_config, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("fastcgi.script_path_encoded", "0", PHP_INI_SYSTEM, OnUpdateBool, fcgi_script_path_encoded, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_BOOLEAN("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fastcgi.error_header", NULL, PHP_INI_SYSTEM, OnUpdateString, error_header, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fpm.config", NULL, PHP_INI_SYSTEM, OnUpdateString, fpm_config, php_cgi_globals_struct, php_cgi_globals) PHP_INI_END() /* {{{ php_cgi_globals_ctor */ @@ -1437,6 +1443,7 @@ static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals) php_cgi_globals->nph = 0; php_cgi_globals->fix_pathinfo = 1; php_cgi_globals->discard_path = 0; + php_cgi_globals->fcgi_script_path_encoded = 0; php_cgi_globals->fcgi_logging = 1; php_cgi_globals->fcgi_logging_request_started = false; zend_hash_init(&php_cgi_globals->user_config_cache, 0, NULL, user_config_cache_entry_dtor, 1); diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-decoding.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-decoding.phpt new file mode 100644 index 0000000000000..d6189107fb84b --- /dev/null +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-decoding.phpt @@ -0,0 +1,54 @@ +--TEST-- +FPM: FastCGI change for Apache ProxyPass SCRIPT_FILENAME decoding (GH-17645) +--SKIPIF-- + +--FILE-- +createSourceFileAndScriptName('+'); +$tester->start(); +$tester->expectLogStartNotices(); +$tester + ->request( + uri: $scriptName . '/1%202', + scriptFilename: "proxy:fcgi://" . $tester->getAddr() . str_replace('+', '%2B', $sourceFilePath) . '/1%202', + scriptName: $scriptName . '/1 2' + ) + ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']); +$tester->terminate(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-encoded.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-encoded.phpt new file mode 100644 index 0000000000000..3b5ccc86b6926 --- /dev/null +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-encoded.phpt @@ -0,0 +1,55 @@ +--TEST-- +FPM: FastCGI change for Apache ProxyPass SCRIPT_FILENAME decoding - fallback (GH-17645) +--SKIPIF-- + +--FILE-- +createSourceFileAndScriptName('%2B'); +$tester->start(); +$tester->expectLogStartNotices(); +$tester + ->request( + uri: $scriptName . '/1%202', + scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%202', + scriptName: $scriptName . '/1 2' + ) + ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']); +$tester->terminate(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 8e67d17430a64..f486309085e6d 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -47,7 +47,7 @@ class Tester self::FILE_EXT_LOG_ERR, self::FILE_EXT_LOG_SLOW, self::FILE_EXT_PID, - 'src.php', + '*src.php', 'ini', 'skip.ini', '*.sock', @@ -107,6 +107,11 @@ class Tester */ private string $fileName; + /** + * @var string + */ + private ?string $sourceFile = null; + /** * @var resource */ @@ -1483,21 +1488,27 @@ class Tester /** * Create a source code file. * + * @param string $nameSuffix * @return string */ - public function makeSourceFile(): string + public function makeSourceFile(string $nameSuffix = ''): string { - return $this->makeFile('src.php', $this->code, overwrite: false); + if (is_null($this->sourceFile)) { + $this->sourceFile = $this->makeFile($nameSuffix . 'src.php', $this->code, overwrite: false); + } + + return $this->sourceFile; } /** * Create a source file and script name. * + * @param string $nameSuffix * @return string[] */ - public function createSourceFileAndScriptName(): array + public function createSourceFileAndScriptName(string $nameSuffix = ''): array { - $sourceFile = $this->makeFile('src.php', $this->code, overwrite: false); + $sourceFile = $this->makeSourceFile($nameSuffix); return [$sourceFile, '/' . basename($sourceFile)]; }