diff --git a/NEWS b/NEWS index e5dd4913e580..46275b5dbf8b 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,11 @@ PHP NEWS . Fixed bug GH-21314 (Different session garbage collector behavior between PHP 8.3 and PHP 8.5). (jorgsowa) +- SPL: + . spl_autoload() now builds the include file name from the case-preserved + class name instead of lowercasing it, fixing autoloading of classes with + non-lowercase names on case-sensitive file systems. (jorgsowa) + - Streams: . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL and a proxy set). (CVE-2026-12184) (ndossche) diff --git a/UPGRADING b/UPGRADING index b9c659ccc405..f020841b9257 100644 --- a/UPGRADING +++ b/UPGRADING @@ -164,6 +164,10 @@ PHP 8.6 UPGRADE NOTES . SplFileObject::next() past EOF no longer increments key() without bound. SplFileObject::seek() past EOF now produces the same key() value as SplTempFileObject; the two previously returned different values. + . The default spl_autoload() implementation now builds the include file + name from the case-preserved class name instead of its lowercased form. + On case-sensitive file systems the file name must match the class name + casing (e.g. MyClass.php instead of myclass.php). - Standard: . Form feed (\f) is now added in the default trimmed characters of trim(), diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index e80917a77067..052563d7caff 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -249,14 +249,14 @@ PHP_FUNCTION(spl_classes) } /* }}} */ -static bool spl_autoload(zend_string *lc_name, const char *ext, size_t ext_len) /* {{{ */ +static bool spl_autoload(const char *name, size_t name_len, const char *ext, size_t ext_len) /* {{{ */ { zend_string *class_file; zval dummy; zend_file_handle file_handle; zval result; - class_file = zend_string_concat2(ZSTR_VAL(lc_name), ZSTR_LEN(lc_name), ext, ext_len); + class_file = zend_string_concat2(name, name_len, ext, ext_len); #if DEFAULT_SLASH != '\\' { @@ -294,7 +294,7 @@ static bool spl_autoload(zend_string *lc_name, const char *ext, size_t ext_len) efree(new_op_array); zval_ptr_dtor(&result); - ret = zend_hash_exists(EG(class_table), lc_name); + ret = zend_hash_str_find_ptr_lc(EG(class_table), name, name_len) != NULL; } } zend_destroy_file_handle(&file_handle); @@ -307,7 +307,7 @@ PHP_FUNCTION(spl_autoload) { size_t pos_len, pos1_len; char *pos, *pos1; - zend_string *class_name, *lc_name, *file_exts = NULL; + zend_string *class_name, *file_exts = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|S!", &class_name, &file_exts) == FAILURE) { RETURN_THROWS(); @@ -325,12 +325,13 @@ PHP_FUNCTION(spl_autoload) pos_len = ZSTR_LEN(file_exts); } - if (ZSTR_VAL(class_name)[0] == '\\') { - lc_name = zend_string_alloc(ZSTR_LEN(class_name) - 1, 0); - zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(class_name) + 1, ZSTR_LEN(class_name) - 1); - } else { - lc_name = zend_string_tolower(class_name); + const char *name = ZSTR_VAL(class_name); + size_t name_len = ZSTR_LEN(class_name); + if (*name == '\\') { + name++; + name_len--; } + while (pos && *pos && !EG(exception)) { pos1 = strchr(pos, ','); if (pos1) { @@ -338,13 +339,12 @@ PHP_FUNCTION(spl_autoload) } else { pos1_len = pos_len; } - if (spl_autoload(lc_name, pos, pos1_len)) { + if (spl_autoload(name, name_len, pos, pos1_len)) { break; /* loaded */ } pos = pos1 ? pos1 + 1 : NULL; pos_len = pos1? pos_len - pos1_len - 1 : 0; } - zend_string_release(lc_name); } /* }}} */ /* {{{ Register and return default file extensions for spl_autoload */ diff --git a/ext/spl/tests/dualiterator.inc b/ext/spl/tests/DualIterator.inc similarity index 100% rename from ext/spl/tests/dualiterator.inc rename to ext/spl/tests/DualIterator.inc diff --git a/ext/spl/tests/recursivecomparedualiterator.inc b/ext/spl/tests/RecursiveCompareDualIterator.inc similarity index 100% rename from ext/spl/tests/recursivecomparedualiterator.inc rename to ext/spl/tests/RecursiveCompareDualIterator.inc diff --git a/ext/spl/tests/recursivedualiterator.inc b/ext/spl/tests/RecursiveDualIterator.inc similarity index 100% rename from ext/spl/tests/recursivedualiterator.inc rename to ext/spl/tests/RecursiveDualIterator.inc diff --git a/ext/spl/tests/autoloading/testclass b/ext/spl/tests/autoloading/TestClass similarity index 100% rename from ext/spl/tests/autoloading/testclass rename to ext/spl/tests/autoloading/TestClass diff --git a/ext/spl/tests/autoloading/testclass.class.inc b/ext/spl/tests/autoloading/TestClass.class.inc similarity index 100% rename from ext/spl/tests/autoloading/testclass.class.inc rename to ext/spl/tests/autoloading/TestClass.class.inc diff --git a/ext/spl/tests/autoloading/testclass.inc b/ext/spl/tests/autoloading/TestClass.inc similarity index 100% rename from ext/spl/tests/autoloading/testclass.inc rename to ext/spl/tests/autoloading/TestClass.inc diff --git a/ext/spl/tests/autoloading/testclass.php.inc b/ext/spl/tests/autoloading/TestClass.php.inc similarity index 100% rename from ext/spl/tests/autoloading/testclass.php.inc rename to ext/spl/tests/autoloading/TestClass.php.inc diff --git a/ext/spl/tests/autoloading/spl_autoload_001.phpt b/ext/spl/tests/autoloading/spl_autoload_001.phpt index befb96570950..021763c1395a 100644 --- a/ext/spl/tests/autoloading/spl_autoload_001.phpt +++ b/ext/spl/tests/autoloading/spl_autoload_001.phpt @@ -74,15 +74,15 @@ try { --EXPECTF-- ===EMPTY=== string(9) ".inc,.php" -%stestclass.inc +%sTestClass.inc Class TestClass could not be loaded ===()=== Class TestClass could not be loaded ===(1)=== Class TestClass could not be loaded ===(.inc,,.php.inc)=== -%stestclass -%stestclass.php.inc +%sTestClass +%sTestClass.php.inc Class TestClass could not be loaded ===()=== Class TestClass could not be loaded @@ -97,7 +97,7 @@ bool(false) ===LOAD=== TestFunc1(TestClass) TestFunc2(TestClass) -%stestclass.class.inc +%sTestClass.class.inc bool(true) ===NOFUNCTION=== spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, function "unavailable_autoload_function" not found or invalid function name diff --git a/ext/spl/tests/autoloading/spl_autoload_009.phpt b/ext/spl/tests/autoloading/spl_autoload_009.phpt index f12fd0af7e70..13dda73705a8 100644 --- a/ext/spl/tests/autoloading/spl_autoload_009.phpt +++ b/ext/spl/tests/autoloading/spl_autoload_009.phpt @@ -14,10 +14,10 @@ function my_autoload($name) spl_autoload_register("spl_autoload"); spl_autoload_register("my_autoload"); -$obj = new testclass; +$obj = new TestClass; ?> --EXPECTF-- -%stestclass.inc -%stestclass.class.inc +%sTestClass.inc +%sTestClass.class.inc bool(true) diff --git a/ext/spl/tests/autoloading/spl_autoload_call_basic.phpt b/ext/spl/tests/autoloading/spl_autoload_call_basic.phpt index 2bd65c22be4b..b69b1a1d536c 100644 --- a/ext/spl/tests/autoloading/spl_autoload_call_basic.phpt +++ b/ext/spl/tests/autoloading/spl_autoload_call_basic.phpt @@ -6,7 +6,7 @@ Jean-Marc Fontaine --FILE-- --EXPECTF-- -%stestclass.class.inc +%sTestClass.class.inc bool(true) diff --git a/ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt b/ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt index f16976e78ce2..a7708c5f9e13 100644 --- a/ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt +++ b/ext/spl/tests/autoloading/spl_autoload_warn_on_false_do_throw.phpt @@ -3,7 +3,7 @@ spl_autoload_register() function - warn when using false as second argument for --FILE-- --EXPECTF-- Notice: spl_autoload_register(): Argument #2 ($do_throw) has been ignored, spl_autoload_register() will always throw in %s on line %d -%stestclass.class.inc +%sTestClass.class.inc bool(true) diff --git a/ext/spl/tests/dualiterator_001.phpt b/ext/spl/tests/dualiterator_001.phpt index 2bbdc0c56fbd..874bec5da62c 100644 --- a/ext/spl/tests/dualiterator_001.phpt +++ b/ext/spl/tests/dualiterator_001.phpt @@ -5,7 +5,7 @@ SPL: DualIterator function spl_examples_autoload($classname) { - include(__DIR__ . '/' . strtolower($classname) . '.inc'); + include(__DIR__ . '/' . $classname . '.inc'); } spl_autoload_register('spl_examples_autoload');