diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c8fbf7452288..c20f625b1206 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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; diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index acda841979eb..f001e31dae7e 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -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] = { +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 diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index d543b7b03c2b..3082338647a7 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -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); diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index dfaa95932c3b..4c9b879961de 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -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 diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 1bf7c00d13c6..035ca5d6a4d9 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -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; } @@ -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; diff --git a/ext/filter/tests/filter_validate_domain_locale.phpt b/ext/filter/tests/filter_validate_domain_locale.phpt new file mode 100644 index 000000000000..2fff716298f2 --- /dev/null +++ b/ext/filter/tests/filter_validate_domain_locale.phpt @@ -0,0 +1,19 @@ +--TEST-- +FILTER_VALIDATE_DOMAIN FILTER_FLAG_HOSTNAME should not be locale dependent +--EXTENSIONS-- +filter +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(false) +bool(false) diff --git a/ext/standard/tests/streams/locale.phpt b/ext/standard/tests/streams/locale.phpt new file mode 100644 index 000000000000..602fef56141a --- /dev/null +++ b/ext/standard/tests/streams/locale.phpt @@ -0,0 +1,26 @@ +--TEST-- +Stream wrappers should not be locale dependent +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +display_errors=stderr +--FILE-- + +--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 diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index 39a9c82d5984..fe424cd8c780 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -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) { @@ -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) { @@ -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) { diff --git a/main/streams/streams.c b/main/streams/streams.c index 7e023e4593f1..d3a94fe3ffc5 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -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] != '.') { @@ -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++; } diff --git a/main/streams/transports.c b/main/streams/transports.c index fcc3f293300e..643675965e78 100644 --- a/main/streams/transports.c +++ b/main/streams/transports.c @@ -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++; }