@@ -1687,59 +1687,41 @@ static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{
16871687}
16881688/* }}} */
16891689
1690- zend_result cgi_limit_extensions (char * path ) {
1690+ bool cgi_extension_is_allowed (char * path ) {
16911691 const char * allowed_extensions = CGIG (security_limit_extensions );
16921692 if (!path || !allowed_extensions || strlen (allowed_extensions ) == 0 ) {
16931693 // No path, or no filter configured
1694- return SUCCESS ;
1694+ return true ;
16951695 }
16961696
16971697 const char * dot = strrchr (path , '.' );
16981698 if (!dot ) {
16991699 // No file extension
1700- return FAILURE ;
1700+ return false ;
17011701 }
17021702
1703- #ifndef PHP_WIN32
1704- const char dir_separator = '\\' ;
1705- #else
1706- const char dir_separator = '/' ;
1707- #endif
1708-
1709- if (strchr (dot , dir_separator ) != NULL ) {
1710- /* Dot is followed by a directory separator at some point, so this is
1711- * a dot in a directory name, rather than the actual file extension;
1712- * strrchr() above means that this was the last dot, so the actual file
1713- * has no extension. */
1714- return FAILURE ;
1715- }
1716-
1717- /* `dot` is now the pointer to the start of a null-terminated string for
1718- * the file extension, *including* the dot, e.g. `.php`. We need that string
1719- * to be present in the allowed extensions, and
1720- * - preceded by either a space or tab, or be at the start of the string,
1721- * so that `.template.php` can be allowed without allowing all `.php`
1722- * files
1723- * - followed by either a space or tab, or be at the end of the string, for
1724- * the same reason, `.php.trusted` should not allow all of `.php`
1725- */
1726- char * next = strstr (allowed_extensions , dot );
1727- const size_t extension_len = strlen (dot );
1703+ /* We want to be able to allow things like `.test.php` (but not all of
1704+ * `.php` or `.test`. For each extension in the list, check if the path
1705+ * given ends with exactly that. Allowing `.php` allows `.test.php`,
1706+ * `.foo.php`, and everything else because dots are valid file name
1707+ * characters. */
1708+ const char * next = allowed_extensions ;
1709+ const size_t path_len = strlen (path );
17281710 while (next && * next ) {
1729- /* We have a pointer to within the allowed extensions, which is followed
1730- * by the extension; adding extension_len will bring us to either the
1731- * character after the extension, or the null terminating byte. */
1732- char * after = next + extension_len ;
1733- if (
1734- (next == allowed_extensions || (* (next - 1 ) == ' ' ) || (* (next - 1 ) == '\t' ))
1735- && (* after == '\0' || * after == ' ' || * after == '\t' )
1736- ) {
1737- return SUCCESS ;
1711+ size_t extension_len = strcspn (next , " \t" );
1712+
1713+ if (extension_len <= path_len ) {
1714+ if (memcmp (path + path_len - extension_len , next , extension_len ) == 0 ) {
1715+ return true;
1716+ }
1717+ }
1718+ next += extension_len ;
1719+ if (* next ) {
1720+ next += strspn (next , " \t" );
17381721 }
1739- next = strstr (next + 1 , dot );
17401722 }
17411723
1742- return FAILURE ;
1724+ return false ;
17431725}
17441726
17451727PHP_FUNCTION (apache_response_headers ) /* {{{ */
@@ -2533,7 +2515,7 @@ consult the installation file that came with this distribution, or visit \n\
25332515 2. we are running as cgi or fastcgi
25342516 */
25352517 if (cgi || fastcgi || SG (request_info ).path_translated ) {
2536- bool limited_extension = ( cgi_limit_extensions ( SG (request_info ).path_translated ) == FAILURE );
2518+ bool limited_extension = ! cgi_extension_is_allowed ( SG (request_info ).path_translated );
25372519 if (limited_extension || php_fopen_primary_script (& file_handle ) == FAILURE ) {
25382520 zend_try {
25392521 if (limited_extension || errno == EACCES ) {
0 commit comments