Skip to content

Commit

Permalink
Fix #42357: fputcsv() has an optional parameter for line endings
Browse files Browse the repository at this point in the history
fputcsv does not terminate lines correctly as per RFC 41801[1]. After adding a new parameter fputcsv may now use a user defined line ending,. In order to maintain backwards compatibility fputcsv() still terminates lines with "\n" by default.

Also fixes: #46367[2], #62770[3]
Ref: #42357[4]

[1] <https://tools.ietf.org/html/rfc4180>
[2] <https://bugs.php.net/bug.php?id=46367>
[3] <https://bugs.php.net/bug.php?id=62770>
[4] <https://bugs.php.net/bug.php?id=42357>
  • Loading branch information
CameronHall authored and Girgias committed Mar 29, 2021
1 parent 8f1ec5b commit 5b29eba
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 14 deletions.
7 changes: 5 additions & 2 deletions ext/spl/spl_directory.c
Expand Up @@ -2360,11 +2360,13 @@ PHP_METHOD(SplFileObject, fputcsv)
size_t d_len = 0, e_len = 0, esc_len = 0;
zend_long ret;
zval *fields = NULL;
zend_string *eol = NULL;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|sss", &fields, &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|sssS", &fields, &delim, &d_len, &enclo, &e_len, &esc, &esc_len, &eol) == SUCCESS) {

switch(ZEND_NUM_ARGS())
{
case 5:
case 4:
switch (esc_len) {
case 0:
Expand Down Expand Up @@ -2396,7 +2398,8 @@ PHP_METHOD(SplFileObject, fputcsv)
case 0:
break;
}
ret = php_fputcsv(intern->u.file.stream, fields, delimiter, enclosure, escape);

ret = php_fputcsv(intern->u.file.stream, fields, delimiter, enclosure, escape, eol);
if (ret < 0) {
RETURN_FALSE;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/spl/spl_directory.stub.php
Expand Up @@ -209,7 +209,7 @@ public function fread(int $length) {}
public function fgetcsv(string $separator = ",", string $enclosure = "\"", string $escape = "\\") {}

/** @return int|false */
public function fputcsv(array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\") {}
public function fputcsv(array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\", string $eol = "\n") {}

/** @return bool|null */
public function setCsvControl(string $separator = ",", string $enclosure = "\"", string $escape = "\\") {}
Expand Down
3 changes: 2 additions & 1 deletion ext/spl/spl_directory_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: de510a0512057bfaecbac8228107600ed14e2ba5 */
* Stub hash: 00139cce188b3950e5a7606c70c5848c6280851d */

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
Expand Down Expand Up @@ -181,6 +181,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileObject_fputcsv, 0, 0, 1)
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, escape, IS_STRING, 0, "\"\\\\\"")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, eol, IS_STRING, 0, "\"\\n\"")
ZEND_END_ARG_INFO()

#define arginfo_class_SplFileObject_setCsvControl arginfo_class_SplFileObject_fgetcsv
Expand Down
32 changes: 32 additions & 0 deletions ext/spl/tests/SplFileObject_fputcsv_variation16.phpt
@@ -0,0 +1,32 @@
--TEST--
SplFileObject::fputcsv() with user provided eol
--FILE--
<?php
$data = [
['aaa', 'bbb', 'ccc', 'dddd'],
['123', '456', '789'],
['"aaa"', '"bbb"'],
];

$eol_chars = ['||', '|', '\n', "\n"];
foreach ($eol_chars as $eol_char) {
$file = new SplTempFileObject;
foreach ($data as $record) {
$file->fputcsv($record, ',', '"', '', $eol_char);
}

$file->rewind();
foreach ($file as $line) {
echo $line;
}

echo "\n";
}
?>
--EXPECT--
aaa,bbb,ccc,dddd||123,456,789||"""aaa""","""bbb"""||
aaa,bbb,ccc,dddd|123,456,789|"""aaa""","""bbb"""|
aaa,bbb,ccc,dddd\n123,456,789\n"""aaa""","""bbb"""\n
aaa,bbb,ccc,dddd
123,456,789
"""aaa""","""bbb"""
7 changes: 6 additions & 1 deletion ext/spl/tests/bug68479.phpt
Expand Up @@ -9,7 +9,7 @@ var_dump($params);

?>
--EXPECT--
array(4) {
array(5) {
[0]=>
object(ReflectionParameter)#2 (1) {
["name"]=>
Expand All @@ -30,4 +30,9 @@ array(4) {
["name"]=>
string(6) "escape"
}
[4]=>
object(ReflectionParameter)#6 (1) {
["name"]=>
string(3) "eol"
}
}
2 changes: 1 addition & 1 deletion ext/standard/basic_functions.stub.php
Expand Up @@ -867,7 +867,7 @@ function unlink(string $filename, $context = null): bool {}
function file_put_contents(string $filename, mixed $data, int $flags = 0, $context = null): int|false {}

/** @param resource $stream */
function fputcsv($stream, array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): int|false {}
function fputcsv($stream, array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\", string $eol = "\n"): int|false {}

/** @param resource $stream */
function fgetcsv($stream, ?int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false {}
Expand Down
3 changes: 2 additions & 1 deletion ext/standard/basic_functions_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 97edf8c87780c892984099e52ad1c6c745b919f8 */
* Stub hash: 23c263defa042155631bec5fcb5282e4cd1e88a7 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
Expand Down Expand Up @@ -1335,6 +1335,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_fputcsv, 0, 2, MAY_BE_LONG|MAY_B
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, escape, IS_STRING, 0, "\"\\\\\"")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, eol, IS_STRING, 0, "\"\\n\"")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_fgetcsv, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE)
Expand Down
18 changes: 12 additions & 6 deletions ext/standard/file.c
Expand Up @@ -1795,14 +1795,16 @@ PHP_FUNCTION(fputcsv)
ssize_t ret;
char *delimiter_str = NULL, *enclosure_str = NULL, *escape_str = NULL;
size_t delimiter_str_len = 0, enclosure_str_len = 0, escape_str_len = 0;
zend_string *eol_str = NULL;

ZEND_PARSE_PARAMETERS_START(2, 5)
ZEND_PARSE_PARAMETERS_START(2, 6)
Z_PARAM_RESOURCE(fp)
Z_PARAM_ARRAY(fields)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(delimiter_str, delimiter_str_len)
Z_PARAM_STRING(enclosure_str, enclosure_str_len)
Z_PARAM_STRING(escape_str, escape_str_len)
Z_PARAM_STR_OR_NULL(eol_str)
ZEND_PARSE_PARAMETERS_END();

if (delimiter_str != NULL) {
Expand Down Expand Up @@ -1840,16 +1842,16 @@ PHP_FUNCTION(fputcsv)

PHP_STREAM_TO_ZVAL(stream, fp);

ret = php_fputcsv(stream, fields, delimiter, enclosure, escape_char);
ret = php_fputcsv(stream, fields, delimiter, enclosure, escape_char, eol_str);
if (ret < 0) {
RETURN_FALSE;
}
RETURN_LONG(ret);
}
/* }}} */

/* {{{ PHPAPI size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char) */
PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char)
/* {{{ PHPAPI size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char, zend_string *eol_str) */
PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char, zend_string *eol_str)
{
int count, i = 0;
size_t ret;
Expand Down Expand Up @@ -1897,8 +1899,12 @@ PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, cha
}
zend_tmp_string_release(tmp_field_str);
} ZEND_HASH_FOREACH_END();

smart_str_appendc(&csvline, '\n');

if (eol_str) {
smart_str_append(&csvline, eol_str);
} else {
smart_str_appendc(&csvline, '\n');
}
smart_str_0(&csvline);

ret = php_stream_write(stream, ZSTR_VAL(csvline.s), ZSTR_LEN(csvline.s));
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/file.h
Expand Up @@ -49,7 +49,7 @@ PHPAPI void php_flock_common(php_stream *stream, zend_long operation, uint32_t o

#define PHP_CSV_NO_ESCAPE EOF
PHPAPI void php_fgetcsv(php_stream *stream, char delimiter, char enclosure, int escape_char, size_t buf_len, char *buf, zval *return_value);
PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char);
PHPAPI ssize_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char, zend_string *eol_str);

#define META_DEF_BUFSIZE 8192

Expand Down
Binary file added ext/standard/tests/file/fputcsv_variation17.phpt
Binary file not shown.

0 comments on commit 5b29eba

Please sign in to comment.