From 49f1ef42dff5c0bd995b6f70aa7177742077bff3 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Thu, 13 Sep 2012 12:37:48 -0600 Subject: [PATCH 1/6] Expose fputcsv's escape_char to userland Allows users to assert that something other than the backslash should be considered an escape char; also follows the RFC 4180 recommendation that fields containing a " be enclosed. --- ext/standard/file.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index 5dc91b44b332..48b89110c1eb 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1796,22 +1796,23 @@ static const char *php_fgetcsv_lookup_trailing_spaces(const char *ptr, size_t le #define FPUTCSV_FLD_CHK(c) memchr(Z_STRVAL(field), c, Z_STRLEN(field)) -/* {{{ proto int fputcsv(resource fp, array fields [, string delimiter [, string enclosure]]) +/* {{{ proto int fputcsv(resource fp, array fields [, string delimiter [, string enclosure [, string escape_char]]]) Format line as CSV and write to file pointer */ PHP_FUNCTION(fputcsv) { char delimiter = ','; /* allow this to be set as parameter */ char enclosure = '"'; /* allow this to be set as parameter */ - const char escape_char = '\\'; + char escape_char = '\\'; php_stream *stream; zval *fp = NULL, *fields = NULL; int ret; - char *delimiter_str = NULL, *enclosure_str = NULL; - int delimiter_str_len = 0, enclosure_str_len = 0; + char *delimiter_str = NULL, *enclosure_str = NULL, *escape_str = NULL; + int delimiter_str_len = 0, enclosure_str_len = 0, escape_str_len = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra|ss", + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra|sss", &fp, &fields, &delimiter_str, &delimiter_str_len, - &enclosure_str, &enclosure_str_len) == FAILURE) { + &enclosure_str, &enclosure_str_len, + &escape_str, &escape_str_len) == FAILURE) { return; } @@ -1839,6 +1840,17 @@ PHP_FUNCTION(fputcsv) enclosure = *enclosure_str; } + if (escape_str != NULL) { + if (escape_str_len < 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } else if (escape_str_len > 1) { + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "escape must be a single character"); + } + /* use first character from string */ + escape_char = *escape_str; + } + PHP_STREAM_TO_ZVAL(stream, &fp); ret = php_fputcsv(stream, fields, delimiter, enclosure, escape_char TSRMLS_CC); @@ -1871,7 +1883,8 @@ PHPAPI int php_fputcsv(php_stream *stream, zval *fields, char delimiter, char en FPUTCSV_FLD_CHK('\n') || FPUTCSV_FLD_CHK('\r') || FPUTCSV_FLD_CHK('\t') || - FPUTCSV_FLD_CHK(' ') + FPUTCSV_FLD_CHK(' ') || + FPUTCSV_FLD_CHK('"') ) { char *ch = Z_STRVAL(field); char *end = ch + Z_STRLEN(field); From 1c3c343ccdd0d7db8b9164dd1789b55551a59cca Mon Sep 17 00:00:00 2001 From: Lee Leathers Date: Tue, 15 Jan 2013 00:44:21 -0800 Subject: [PATCH 2/6] add test for fputcsv improvements to allow RFC 4180 compliance --- ext/standard/file.c | 12 +- .../tests/file/fputcsv_variation15.phpt | 107 ++++++++++++++++++ 2 files changed, 113 insertions(+), 6 deletions(-) create mode 100755 ext/standard/tests/file/fputcsv_variation15.phpt diff --git a/ext/standard/file.c b/ext/standard/file.c index 48b89110c1eb..30b78a25f872 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -817,7 +817,7 @@ PHP_FUNCTION(tempnam) if (p_len > 64) { p[63] = '\0'; } - + RETVAL_FALSE; if ((fd = php_open_temporary_fd_ex(dir, p, &opened_path, 1 TSRMLS_CC)) >= 0) { @@ -1377,13 +1377,13 @@ PHP_FUNCTION(umask) { long arg1 = 0; int oldumask; - + oldumask = umask(077); if (BG(umask) == -1) { BG(umask) = oldumask; } - + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &arg1) == FAILURE) { RETURN_FALSE; } @@ -1800,9 +1800,9 @@ static const char *php_fgetcsv_lookup_trailing_spaces(const char *ptr, size_t le Format line as CSV and write to file pointer */ PHP_FUNCTION(fputcsv) { - char delimiter = ','; /* allow this to be set as parameter */ - char enclosure = '"'; /* allow this to be set as parameter */ - char escape_char = '\\'; + char delimiter = ','; /* allow this to be set as parameter */ + char enclosure = '"'; /* allow this to be set as parameter */ + char escape_char = '\\'; /* allow this to be set as parameter */ php_stream *stream; zval *fp = NULL, *fields = NULL; int ret; diff --git a/ext/standard/tests/file/fputcsv_variation15.phpt b/ext/standard/tests/file/fputcsv_variation15.phpt new file mode 100755 index 000000000000..dc4a9e2dbdfe --- /dev/null +++ b/ext/standard/tests/file/fputcsv_variation15.phpt @@ -0,0 +1,107 @@ +--TEST-- +various fputcsv() functionality tests +--CREDITS-- +Lee Leathers +--FILE-- + 'aaa,bbb', + 1 => 'aaa,"bbb"', + 2 => '"aaa","bbb"', + 3 => 'aaa,bbb', + 4 => '"aaa",bbb', + 5 => '"aaa", "bbb"', + 6 => ',', + 7 => 'aaa,', + 8 => ',"aaa"', + 9 => '"",""', + 10 => '"""""",', + 11 => '""""",aaa', + 12 => 'aaa,bbb ', + 13 => 'aaa,"bbb "', + 14 => 'aaa"aaa","bbb"bbb', + 15 => 'aaa"aaa""",bbb', + 16 => 'aaa,"/"bbb,ccc', + 17 => 'aaa"/"a","bbb"', + 18 => '"/"","aaa"', + 19 => '"/""",aaa', +); + +$file = dirname(__FILE__) . 'fgetcsv.csv'; +@unlink($file); + +$fp = fopen($file, "w"); +foreach ($list as $v) { + fputcsv($fp, explode(',', $v), ',', '"', '/'); +} +fclose($fp); + +$res = file($file); +foreach($res as &$val) +{ + $val = substr($val, 0, -1); +} +echo '$list = ';var_export($res);echo ";\n"; + +$fp = fopen($file, "r"); +$res = array(); +while($l=fgetcsv($fp, 0, ',', '"', '/')) +{ + $res[] = join(',',$l); +} +fclose($fp); + +echo '$list = ';var_export($res);echo ";\n"; + +@unlink($file); + +?> +===DONE=== + +--EXPECT-- +$list = array ( + 0 => 'aaa,bbb', + 1 => 'aaa,"""bbb"""', + 2 => '"""aaa""","""bbb"""', + 3 => 'aaa,bbb', + 4 => '"""aaa""",bbb', + 5 => '"""aaa"""," ""bbb"""', + 6 => ',', + 7 => 'aaa,', + 8 => ',"""aaa"""', + 9 => '"""""",""""""', + 10 => '"""""""""""""",', + 11 => '"""""""""""",aaa', + 12 => 'aaa,"bbb "', + 13 => 'aaa,"""bbb """', + 14 => '"aaa""aaa""","""bbb""bbb"', + 15 => '"aaa""aaa""""""",bbb', + 16 => 'aaa,"""/"bbb",ccc', + 17 => '"aaa""/"a""","""bbb"""', + 18 => '"""/"""","""aaa"""', + 19 => '"""/"""""",aaa', +); +$list = array ( + 0 => 'aaa,bbb', + 1 => 'aaa,"bbb"', + 2 => '"aaa","bbb"', + 3 => 'aaa,bbb', + 4 => '"aaa",bbb', + 5 => '"aaa", "bbb"', + 6 => ',', + 7 => 'aaa,', + 8 => ',"aaa"', + 9 => '"",""', + 10 => '"""""",', + 11 => '""""",aaa', + 12 => 'aaa,bbb ', + 13 => 'aaa,"bbb "', + 14 => 'aaa"aaa","bbb"bbb', + 15 => 'aaa"aaa""",bbb', + 16 => 'aaa,"/"bbb,ccc', + 17 => 'aaa"/"a","bbb"', + 18 => '"/"","aaa"', + 19 => '"/""",aaa', +); +===DONE=== From 86ca8a99cbe82e31c6c63bb3a6300d9989b3ed02 Mon Sep 17 00:00:00 2001 From: Lee Leathers Date: Tue, 15 Jan 2013 00:44:21 -0800 Subject: [PATCH 3/6] added test file from theoreticaLee --- .../tests/file/fputcsv_variation15.phpt | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 ext/standard/tests/file/fputcsv_variation15.phpt diff --git a/ext/standard/tests/file/fputcsv_variation15.phpt b/ext/standard/tests/file/fputcsv_variation15.phpt new file mode 100755 index 000000000000..dc4a9e2dbdfe --- /dev/null +++ b/ext/standard/tests/file/fputcsv_variation15.phpt @@ -0,0 +1,107 @@ +--TEST-- +various fputcsv() functionality tests +--CREDITS-- +Lee Leathers +--FILE-- + 'aaa,bbb', + 1 => 'aaa,"bbb"', + 2 => '"aaa","bbb"', + 3 => 'aaa,bbb', + 4 => '"aaa",bbb', + 5 => '"aaa", "bbb"', + 6 => ',', + 7 => 'aaa,', + 8 => ',"aaa"', + 9 => '"",""', + 10 => '"""""",', + 11 => '""""",aaa', + 12 => 'aaa,bbb ', + 13 => 'aaa,"bbb "', + 14 => 'aaa"aaa","bbb"bbb', + 15 => 'aaa"aaa""",bbb', + 16 => 'aaa,"/"bbb,ccc', + 17 => 'aaa"/"a","bbb"', + 18 => '"/"","aaa"', + 19 => '"/""",aaa', +); + +$file = dirname(__FILE__) . 'fgetcsv.csv'; +@unlink($file); + +$fp = fopen($file, "w"); +foreach ($list as $v) { + fputcsv($fp, explode(',', $v), ',', '"', '/'); +} +fclose($fp); + +$res = file($file); +foreach($res as &$val) +{ + $val = substr($val, 0, -1); +} +echo '$list = ';var_export($res);echo ";\n"; + +$fp = fopen($file, "r"); +$res = array(); +while($l=fgetcsv($fp, 0, ',', '"', '/')) +{ + $res[] = join(',',$l); +} +fclose($fp); + +echo '$list = ';var_export($res);echo ";\n"; + +@unlink($file); + +?> +===DONE=== + +--EXPECT-- +$list = array ( + 0 => 'aaa,bbb', + 1 => 'aaa,"""bbb"""', + 2 => '"""aaa""","""bbb"""', + 3 => 'aaa,bbb', + 4 => '"""aaa""",bbb', + 5 => '"""aaa"""," ""bbb"""', + 6 => ',', + 7 => 'aaa,', + 8 => ',"""aaa"""', + 9 => '"""""",""""""', + 10 => '"""""""""""""",', + 11 => '"""""""""""",aaa', + 12 => 'aaa,"bbb "', + 13 => 'aaa,"""bbb """', + 14 => '"aaa""aaa""","""bbb""bbb"', + 15 => '"aaa""aaa""""""",bbb', + 16 => 'aaa,"""/"bbb",ccc', + 17 => '"aaa""/"a""","""bbb"""', + 18 => '"""/"""","""aaa"""', + 19 => '"""/"""""",aaa', +); +$list = array ( + 0 => 'aaa,bbb', + 1 => 'aaa,"bbb"', + 2 => '"aaa","bbb"', + 3 => 'aaa,bbb', + 4 => '"aaa",bbb', + 5 => '"aaa", "bbb"', + 6 => ',', + 7 => 'aaa,', + 8 => ',"aaa"', + 9 => '"",""', + 10 => '"""""",', + 11 => '""""",aaa', + 12 => 'aaa,bbb ', + 13 => 'aaa,"bbb "', + 14 => 'aaa"aaa","bbb"bbb', + 15 => 'aaa"aaa""",bbb', + 16 => 'aaa,"/"bbb,ccc', + 17 => 'aaa"/"a","bbb"', + 18 => '"/"","aaa"', + 19 => '"/""",aaa', +); +===DONE=== From eaee0c7a6ffe0e6745f9ae9f293c9fc7e5c92b34 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Tue, 15 Jan 2013 08:12:40 -0700 Subject: [PATCH 4/6] cleaned up extra tabs and added a comment (credit goes to theoreticaLee) --- ext/standard/file.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index 48b89110c1eb..30b78a25f872 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -817,7 +817,7 @@ PHP_FUNCTION(tempnam) if (p_len > 64) { p[63] = '\0'; } - + RETVAL_FALSE; if ((fd = php_open_temporary_fd_ex(dir, p, &opened_path, 1 TSRMLS_CC)) >= 0) { @@ -1377,13 +1377,13 @@ PHP_FUNCTION(umask) { long arg1 = 0; int oldumask; - + oldumask = umask(077); if (BG(umask) == -1) { BG(umask) = oldumask; } - + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &arg1) == FAILURE) { RETURN_FALSE; } @@ -1800,9 +1800,9 @@ static const char *php_fgetcsv_lookup_trailing_spaces(const char *ptr, size_t le Format line as CSV and write to file pointer */ PHP_FUNCTION(fputcsv) { - char delimiter = ','; /* allow this to be set as parameter */ - char enclosure = '"'; /* allow this to be set as parameter */ - char escape_char = '\\'; + char delimiter = ','; /* allow this to be set as parameter */ + char enclosure = '"'; /* allow this to be set as parameter */ + char escape_char = '\\'; /* allow this to be set as parameter */ php_stream *stream; zval *fp = NULL, *fields = NULL; int ret; From 6fcbc1d4e9eaf8e8ce075549247b4df491475fc6 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Mon, 19 Aug 2013 02:21:40 -0600 Subject: [PATCH 5/6] Remove RFC 4180 compliance check that is causing tests to fail. --- ext/standard/file.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index dacd7005f49d..1ec6a74f3f0b 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1886,8 +1886,7 @@ PHPAPI int php_fputcsv(php_stream *stream, zval *fields, char delimiter, char en FPUTCSV_FLD_CHK('\n') || FPUTCSV_FLD_CHK('\r') || FPUTCSV_FLD_CHK('\t') || - FPUTCSV_FLD_CHK(' ') || - FPUTCSV_FLD_CHK('"') + FPUTCSV_FLD_CHK(' ') ) { char *ch = Z_STRVAL(field); char *end = ch + Z_STRLEN(field); From dff75cd273428fd45f5f65a5a0688447e358db3d Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Mon, 19 Aug 2013 02:53:25 -0600 Subject: [PATCH 6/6] Change expected error message in fputcsv_error.phpt now that a fifth parameter is allowed. --- ext/standard/tests/file/fputcsv_error.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/tests/file/fputcsv_error.phpt b/ext/standard/tests/file/fputcsv_error.phpt index 9403cf446a3d..ebffd454259c 100644 --- a/ext/standard/tests/file/fputcsv_error.phpt +++ b/ext/standard/tests/file/fputcsv_error.phpt @@ -48,7 +48,7 @@ Warning: fputcsv() expects at least 2 parameters, 0 given in %s on line %d NULL -- Testing fputcsv() with more than expected number of arguments -- -Warning: fputcsv() expects at most 4 parameters, 5 given in %s on line %d +Warning: fputcsv() expects parameter 5 to be string, resource given in %s on line %d NULL -- Testing fputcsv() with invalid arguments -- -- Iteration 1 --