Skip to content

PHP 8.0: Make stream wrapper and windows drive checks locale-independent #7820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: PHP-8.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len)
/* Note that on Win32 CWD is per drive (heritage from CP/M).
* This means dirname("c:foo") maps to "c:." or "c:" - which means CWD on C: drive.
*/
if ((2 <= len) && isalpha((int)((unsigned char *)path)[0]) && (':' == path[1])) {
if ((2 <= len) && zend_isalpha_ascii(((unsigned char *)path)[0]) && (':' == path[1])) {
/* Skip over the drive spec (if any) so as not to change */
path += 2;
len_adjust += 2;
Expand Down
20 changes: 20 additions & 0 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ static const unsigned char tolower_map[256] = {

#define zend_tolower_ascii(c) (tolower_map[(unsigned char)(c)])

/* ctype's isalnum is isalpha + isdigit(0-9) */
ZEND_API const bool zend_isalnum_map[256] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ZEND_API const bool zend_isalnum_map[256] = {
ZEND_API const zend_bool zend_isalnum_map[256] = {

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};

/**
* Functions using locale lowercase:
zend_binary_strncasecmp_l
Expand Down
11 changes: 11 additions & 0 deletions Zend/zend_operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,17 @@ ZEND_API int ZEND_FASTCALL string_compare_function(zval *op1, zval *op2);
ZEND_API int ZEND_FASTCALL string_case_compare_function(zval *op1, zval *op2);
ZEND_API int ZEND_FASTCALL string_locale_compare_function(zval *op1, zval *op2);

/* NOTE: The locale-independent alternatives to ctype(isalpha/isalnum) were added to fix bugs in php 8.0 patch releases, and should not be used externally until php 8.2 */
ZEND_API extern const bool zend_isalnum_map[256];

static zend_always_inline bool zend_isalpha_ascii(unsigned char c) {
/* Returns true for a-z and A-Z in a locale-independent way.
* This is implemented in a way that can avoid branching. Note that ASCII 'a' == 'A' | 0x20. */
c = (c | 0x20) - 'a';
return c <= ('z' - 'a');
}
#define zend_isalnum_ascii(c) (zend_isalnum_map[(unsigned char)(c)])

ZEND_API void ZEND_FASTCALL zend_str_tolower(char *str, size_t length);
ZEND_API char* ZEND_FASTCALL zend_str_tolower_copy(char *dest, const char *source, size_t length);
ZEND_API char* ZEND_FASTCALL zend_str_tolower_dup(const char *source, size_t length);
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_virtual_cwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ typedef unsigned short mode_t;
#define IS_UNC_PATH(path, len) \
(len >= 2 && IS_SLASH(path[0]) && IS_SLASH(path[1]))
#define IS_ABSOLUTE_PATH(path, len) \
(len >= 2 && (/* is local */isalpha(path[0]) && path[1] == ':' || /* is UNC */IS_SLASH(path[0]) && IS_SLASH(path[1])))
(len >= 2 && (/* is local */zend_isalpha_ascii(path[0]) && path[1] == ':' || /* is UNC */IS_SLASH(path[0]) && IS_SLASH(path[1])))

#else
#ifdef HAVE_DIRENT_H
Expand Down
8 changes: 4 additions & 4 deletions ext/filter/logical_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -520,21 +520,21 @@ static int _php_filter_validate_domain(char * domain, int len, zend_long flags)
}

/* First char must be alphanumeric */
if(*s == '.' || (hostname && !isalnum((int)*(unsigned char *)s))) {
if(*s == '.' || (hostname && !zend_isalnum_ascii(*(unsigned char *)s))) {
return 0;
}

while (s < e) {
if (*s == '.') {
/* The first and the last character of a label must be alphanumeric */
if (*(s + 1) == '.' || (hostname && (!isalnum((int)*(unsigned char *)(s - 1)) || !isalnum((int)*(unsigned char *)(s + 1))))) {
if (*(s + 1) == '.' || (hostname && (!zend_isalnum_ascii(*(unsigned char *)(s - 1)) || !zend_isalnum_ascii(*(unsigned char *)(s + 1))))) {
return 0;
}

/* Reset label length counter */
i = 1;
} else {
if (i > 63 || (hostname && *s != '-' && !isalnum((int)*(unsigned char *)s))) {
if (i > 63 || (hostname && *s != '-' && !zend_isalnum_ascii(*(unsigned char *)s))) {
return 0;
}

Expand All @@ -561,7 +561,7 @@ static int is_userinfo_valid(zend_string *str)
const char *valid = "-._~!$&'()*+,;=:";
const char *p = ZSTR_VAL(str);
while (p - ZSTR_VAL(str) < ZSTR_LEN(str)) {
if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) {
if (zend_isalnum_ascii(*p) || strchr(valid, *p)) {
p++;
} else if (*p == '%' && p - ZSTR_VAL(str) <= ZSTR_LEN(str) - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) {
p += 3;
Expand Down
19 changes: 19 additions & 0 deletions ext/filter/tests/filter_validate_domain_locale.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
FILTER_VALIDATE_DOMAIN FILTER_FLAG_HOSTNAME should not be locale dependent
--EXTENSIONS--
filter
--SKIPIF--
<?php // try to activate a single-byte german locale
if (!setlocale(LC_ALL, 'de_DE', 'de_DE.ISO8859-1', 'de_DE.ISO_8859-1')) {
print "skip Can't find german locale\n";
}
?>
--FILE--
<?php
var_dump(filter_var('٪', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME));
setlocale(LC_ALL, 'de_DE', 'de_DE.ISO8859-1', 'de_DE.ISO_8859-1');
var_dump(filter_var('٪', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME));
?>
--EXPECT--
bool(false)
bool(false)
26 changes: 26 additions & 0 deletions ext/standard/tests/streams/locale.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Stream wrappers should not be locale dependent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes on Windows also without the patch for some reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the locale files might depend on OS too, for some reason, I remember seeing something about that while looking at other tests

--SKIPIF--
<?php // try to activate a single-byte german locale
if (!setlocale(LC_ALL, 'de_DE','de_DE.ISO8859-1','de_DE.ISO_8859-1')) {
print "skip Can't find german locale\n";
}
?>
--INI--
allow_url_fopen=1
display_errors=stderr
--FILE--
<?php
setlocale(LC_ALL, 'de_DE', 'de_DE.ISO8859-1', 'de_DE.ISO_8859-1');
class testwrapper {
}

var_dump(stream_wrapper_register("test٪", 'testwrapper', STREAM_IS_URL));

echo 'stream_open: ';
fopen("test٪://test", 'r');
?>
--EXPECTF--
Warning: stream_wrapper_register(): Invalid protocol scheme specified. Unable to register wrapper class testwrapper to test٪:// in %s on line 6
bool(false)
stream_open: Warning: fopen(test٪://test): Failed to open stream: No such file or directory in %s on line 9
6 changes: 3 additions & 3 deletions main/fopen_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt
}

/* Don't resolve paths which contain protocol (except of file://) */
for (p = filename; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
for (p = filename; zend_isalnum_ascii(*p) || *p == '+' || *p == '-' || *p == '.'; p++);
if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) {
wrapper = php_stream_locate_url_wrapper(filename, &actual_path, STREAM_OPEN_FOR_INCLUDE);
if (wrapper == &php_plain_files_wrapper) {
Expand Down Expand Up @@ -517,7 +517,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt
/* Check for stream wrapper */
int is_stream_wrapper = 0;

for (p = ptr; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
for (p = ptr; zend_isalnum_ascii(*p) || *p == '+' || *p == '-' || *p == '.'; p++);
if ((*p == ':') && (p - ptr > 1) && (p[1] == '/') && (p[2] == '/')) {
/* .:// or ..:// is not a stream wrapper */
if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) {
Expand Down Expand Up @@ -586,7 +586,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt
actual_path = trypath;

/* Check for stream wrapper */
for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
for (p = trypath; zend_isalnum_ascii(*p) || *p == '+' || *p == '-' || *p == '.'; p++);
if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') && (p[2] == '/')) {
wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE);
if (!wrapper) {
Expand Down
4 changes: 2 additions & 2 deletions main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -1724,7 +1724,7 @@ static inline int php_stream_wrapper_scheme_validate(const char *protocol, unsig
unsigned int i;

for(i = 0; i < protocol_len; i++) {
if (!isalnum((int)protocol[i]) &&
if (!zend_isalnum_ascii(protocol[i]) &&
protocol[i] != '+' &&
protocol[i] != '-' &&
protocol[i] != '.') {
Expand Down Expand Up @@ -1804,7 +1804,7 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const
return (php_stream_wrapper*)((options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper);
}

for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
for (p = path; zend_isalnum_ascii(*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
n++;
}

Expand Down
2 changes: 1 addition & 1 deletion main/streams/transports.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in
}
}

for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
for (p = name; zend_isalnum_ascii(*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
n++;
}

Expand Down