diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 95fc5e25be4b..d8426c4c8300 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -370,8 +370,8 @@ static zend_result spl_filesystem_file_open(spl_filesystem_object *intern, bool Z_SET_REFCOUNT(intern->u.file.zresource, 1); */ - intern->u.file.delimiter = ','; - intern->u.file.enclosure = '"'; + intern->u.file.delimiter = '\0'; + intern->u.file.enclosure = '\0'; intern->u.file.escape = (unsigned char) '\\'; intern->u.file.is_escape_default = true; @@ -652,10 +652,19 @@ static inline HashTable *spl_filesystem_object_get_debug_info(zend_object *objec ZVAL_STR_COPY(&tmp, intern->u.file.open_mode); spl_set_private_debug_info_property(spl_ce_SplFileObject, "openMode", strlen("openMode"), debug_info, &tmp); - ZVAL_STR(&tmp, ZSTR_CHAR((zend_uchar)intern->u.file.delimiter)); + zend_uchar delimiter = (zend_uchar) intern->u.file.delimiter; + zend_uchar enclosure = (zend_uchar) intern->u.file.enclosure; + if(delimiter == '\0') { + delimiter = ','; + } + if(enclosure == '\0') { + enclosure = '"'; + } + + ZVAL_CHAR(&tmp, delimiter); spl_set_private_debug_info_property(spl_ce_SplFileObject, "delimiter", strlen("delimiter"), debug_info, &tmp); - ZVAL_STR(&tmp, ZSTR_CHAR((zend_uchar)intern->u.file.enclosure)); + ZVAL_CHAR(&tmp, enclosure); spl_set_private_debug_info_property(spl_ce_SplFileObject, "enclosure", strlen("enclosure"), debug_info, &tmp); } @@ -1889,10 +1898,18 @@ static zend_result spl_filesystem_file_read_csv(spl_filesystem_object *intern, c static zend_result spl_filesystem_file_read_line_ex(zval * this_ptr, spl_filesystem_object *intern, bool silent) /* {{{ */ { zval retval; + char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure; + + if(delimiter == '\0') { + delimiter = ','; + } + if(enclosure == '\0') { + enclosure = '"'; + } /* 1) use fgetcsv? 2) overloaded call the function, 3) do it directly */ if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV)) { - return spl_filesystem_file_read_csv(intern, intern->u.file.delimiter, intern->u.file.enclosure, intern->u.file.escape, NULL, silent); + return spl_filesystem_file_read_csv(intern, delimiter, enclosure, intern->u.file.escape, NULL, silent); } if (intern->u.file.func_getCurr->common.scope != spl_ce_SplFileObject) { spl_filesystem_file_free_line(intern); @@ -2268,14 +2285,24 @@ PHP_METHOD(SplFileObject, fgetcsv) zend_argument_value_error(1, "must be a single character"); RETURN_THROWS(); } - delimiter = delim[0]; + if(delimiter == '\0') { + delimiter = delim[0]; + } } if (enclo) { if (e_len != 1) { zend_argument_value_error(2, "must be a single character"); RETURN_THROWS(); } - enclosure = enclo[0]; + if(enclosure == '\0') { + enclosure = enclo[0]; + } + } + if(delimiter == '\0') { + delimiter = ','; + } + if(enclosure == '\0') { + enclosure = '"'; } int escape_char = spl_csv_enclosure_param_handling(escape_str, intern, 3); if (escape_char == PHP_CSV_ESCAPE_ERROR) { @@ -2309,14 +2336,24 @@ PHP_METHOD(SplFileObject, fputcsv) zend_argument_value_error(2, "must be a single character"); RETURN_THROWS(); } - delimiter = delim[0]; + if(delimiter == '\0') { + delimiter = delim[0]; + } } if (enclo) { if (e_len != 1) { zend_argument_value_error(3, "must be a single character"); RETURN_THROWS(); } - enclosure = enclo[0]; + if(enclosure == '\0') { + enclosure = enclo[0]; + } + } + if(delimiter == '\0') { + delimiter = ','; + } + if(enclosure == '\0') { + enclosure = '"'; } int escape_char = spl_csv_enclosure_param_handling(escape_str, intern, 4); if (escape_char == PHP_CSV_ESCAPE_ERROR) { @@ -2335,12 +2372,12 @@ PHP_METHOD(SplFileObject, fputcsv) PHP_METHOD(SplFileObject, setCsvControl) { spl_filesystem_object *intern = spl_filesystem_from_obj(Z_OBJ_P(ZEND_THIS)); - char delimiter = ',', enclosure = '"'; + char delimiter = '\0', enclosure = '\0'; char *delim = NULL, *enclo = NULL; size_t d_len = 0, e_len = 0; zend_string *escape_str = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ssS", &delim, &d_len, &enclo, &e_len, &escape_str) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s!S", &delim, &d_len, &enclo, &e_len, &escape_str) == FAILURE) { RETURN_THROWS(); } @@ -2381,11 +2418,17 @@ PHP_METHOD(SplFileObject, getCsvControl) ZEND_PARSE_PARAMETERS_NONE(); array_init(return_value); - delimiter[0] = intern->u.file.delimiter; delimiter[1] = '\0'; enclosure[0] = intern->u.file.enclosure; enclosure[1] = '\0'; + if(intern->u.file.enclosure == '\0') { + enclosure[0] = '"'; + } + if(intern->u.file.delimiter == '\0') { + delimiter[0] = ','; + } + if (intern->u.file.escape == PHP_CSV_NO_ESCAPE) { escape[0] = '\0'; } else { diff --git a/ext/spl/spl_directory.stub.php b/ext/spl/spl_directory.stub.php index 6194a8617b43..36653b7f3380 100644 --- a/ext/spl/spl_directory.stub.php +++ b/ext/spl/spl_directory.stub.php @@ -246,12 +246,12 @@ public function fread(int $length): string|false {} /** @tentative-return-type */ public function fgetcsv(string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false {} - + /** @tentative-return-type */ public function fputcsv(array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\", string $eol = "\n"): int|false {} /** @tentative-return-type */ - public function setCsvControl(string $separator = ",", string $enclosure = "\"", string $escape = "\\"): void {} + public function setCsvControl(?string $separator = null, ?string $enclosure = null, string $escape = "\\"): void {} /** @tentative-return-type */ public function getCsvControl(): array {} diff --git a/ext/spl/spl_directory_arginfo.h b/ext/spl/spl_directory_arginfo.h index 16860be558d7..cb9ceee174e2 100644 --- a/ext/spl/spl_directory_arginfo.h +++ b/ext/spl/spl_directory_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit spl_directory.stub.php instead. - * Stub hash: 802429d736404c2d66601f640942c827b6e6e94b */ + * Stub hash: 59db16e795714e62bf4562a783f586a990f7af58 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -192,8 +192,8 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_SplFileObject_fp ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_SplFileObject_setCsvControl, 0, 0, IS_VOID, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\",\"") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, enclosure, IS_STRING, 0, "\"\\\"\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, enclosure, IS_STRING, 1, "null") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, escape, IS_STRING, 0, "\"\\\\\"") ZEND_END_ARG_INFO() diff --git a/ext/spl/tests/gh22156.phpt b/ext/spl/tests/gh22156.phpt new file mode 100644 index 000000000000..a7631f154cb3 --- /dev/null +++ b/ext/spl/tests/gh22156.phpt @@ -0,0 +1,112 @@ +--TEST-- +Bug GH-22156 SplFileObject::fgetcsv() with named escape parameter +--FILE-- +fwrite("foo;bar;baz"); +$file->seek(0); + +$file->setCsvControl(';', "\n", "\\"); + +while (!$file->eof()) { + $line = $file->fgetcsv(escape: '\\'); + var_dump($line); +} + +echo "Test 2:\n"; +$f = new SplFileObject('php://memory', 'rw+'); +$f->setCsvControl(';', "\n", "\\"); +$f->fwrite("a,b;c\n"); +$f->seek(0); +var_dump($f->fgetcsv(',', '"', '\\')); + +echo "Test 3:\n"; +$f = new SplFileObject('php://memory', 'rw+'); +$f->setCsvControl(',', "'", "\\"); +$f->fwrite("a,'b,c'\n"); +$f->seek(0); +var_dump($f->fgetcsv(escape: '\\')); + +echo "Test 4:\n"; +$f = new SplFileObject('php://memory', 'rw+'); +$f->setCsvControl(',', "'"); +$f->fwrite("a,'b\\'c'\n"); +$f->seek(0); +var_dump($f->fgetcsv(escape: '\\')); + +echo "Test 5:\n"; + +$rm = new ReflectionMethod(SplFileObject::class, 'fgetcsv'); +foreach ($rm->getParameters() as $p) { + var_dump( + $p->getName(), + $p->allowsNull(), + $p->getDefaultValue() + ); +} + +echo "Test 6:\n"; +$f = new SplFileObject('php://memory', 'rw+'); +$f->setCsvControl(',', '"'); +$f->fwrite("a,b\n"); +$f->seek(0); +var_dump($f->fgetcsv()); + +?> +--EXPECTF-- +Test 1: +array(3) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" + [2]=> + string(3) "baz" +} +Test 2: +array(2) { + [0]=> + string(3) "a,b" + [1]=> + string(1) "c" +} +Test 3: +array(2) { + [0]=> + string(1) "a" + [1]=> + string(3) "b,c" +} +Test 4: + +Deprecated: SplFileObject::setCsvControl(): the $escape parameter must be provided as its default value will change in %s on line %d +array(2) { + [0]=> + string(1) "a" + [1]=> + string(4) "b\'c" +} +Test 5: +string(9) "separator" +bool(false) +string(1) "," +string(9) "enclosure" +bool(false) +string(1) """ +string(6) "escape" +bool(false) +string(1) "\" +Test 6: + +Deprecated: SplFileObject::setCsvControl(): the $escape parameter must be provided as its default value will change in %s on line %d + +Deprecated: SplFileObject::fgetcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in %s on line %d +array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" +}