- Standard:
. Fixed bug #74764 (Bindto IPv6 works with file_get_contents but fails with
stream_socket_client). (Ville Hukkamäki)
+ . Implemented FR #38301 (field enclosure behavior in fputcsv). (cmb)
+ . Implemented FR #51496 (fgetcsv should take empty string as an escape). (cmb)
- Reflection:
. Fixed bug #76737 (Unserialized reflection objects are broken, they
5. Changed Functions
========================================
+- SPL:
+ . SplFileObject::fputcsv(), ::fgetcsv() and ::setCsvControl() now accept an
+ emptry string as $escape argument, which disables the propriertary PHP
+ escaping mechanism. SplFileObject::getCsvControl() now may also return an
+ empty string for the third array element, accordingly.
+
+- Standard:
+ . fputcsv() and fgetcsv() now accept an emptry string as $escape argument,
+ which disables the propriertary PHP escaping mechanism. The behavior of
+ str_getcsv() has been adjusted accordingly (formerly, an empty string was
+ identical to using the default).
+
========================================
6. New Functions
========================================
f. get_properties_for() handler / Z_OBJDEBUG_P
g. Required object handlers
h. Immutable classes and op_arrays
+ i. php_fgetcsv() and php_fputcsv()
2. Build system changes
a. Abstract
zend_get_op_array_extension_handle() during MINIT and access its value
using ZEND_OP_ARRAY_EXTENSION(op_array, handle).
+ i. The type of the escape parameter of php_fgetcsv() and php_fputcsv() has
+ been changed from char to int. This allows to pass the new constant macro
+ PHP_CSV_NO_ESCAPE to this parameter, to disable PHP's proprietary escape
+ mechanism.
+
========================
2. Build system changes
========================
intern->u.file.delimiter = ',';
intern->u.file.enclosure = '"';
- intern->u.file.escape = '\\';
+ intern->u.file.escape = (unsigned char) '\\';
intern->u.file.func_getCurr = zend_hash_str_find_ptr(&intern->std.ce->function_table, "getcurrentline", sizeof("getcurrentline") - 1);
spl_filesystem_file_call(intern, func_ptr, pass_num_args, return_value, arg2); \
} /* }}} */
-static int spl_filesystem_file_read_csv(spl_filesystem_object *intern, char delimiter, char enclosure, char escape, zval *return_value) /* {{{ */
+static int spl_filesystem_file_read_csv(spl_filesystem_object *intern, char delimiter, char enclosure, int escape, zval *return_value) /* {{{ */
{
int ret = SUCCESS;
zval *value;
SPL_METHOD(SplFileObject, fgetcsv)
{
spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(ZEND_THIS);
- char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape;
+ char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure;
+ int escape = intern->u.file.escape;
char *delim = NULL, *enclo = NULL, *esc = NULL;
size_t d_len = 0, e_len = 0, esc_len = 0;
switch(ZEND_NUM_ARGS())
{
case 3:
- if (esc_len != 1) {
- php_error_docref(NULL, E_WARNING, "escape must be a character");
+ if (esc_len > 1) {
+ php_error_docref(NULL, E_WARNING, "escape must be empty or a single character");
RETURN_FALSE;
}
- escape = esc[0];
+ if (esc_len == 0) {
+ escape = PHP_CSV_NO_ESCAPE;
+ } else {
+ escape = (unsigned char) esc[0];
+ }
/* no break */
case 2:
if (e_len != 1) {
SPL_METHOD(SplFileObject, fputcsv)
{
spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(ZEND_THIS);
- char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape;
+ char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure;
+ int escape = intern->u.file.escape;
char *delim = NULL, *enclo = NULL, *esc = NULL;
size_t d_len = 0, e_len = 0, esc_len = 0;
zend_long ret;
switch(ZEND_NUM_ARGS())
{
case 4:
- if (esc_len != 1) {
- php_error_docref(NULL, E_WARNING, "escape must be a character");
- RETURN_FALSE;
+ switch (esc_len) {
+ case 0:
+ escape = PHP_CSV_NO_ESCAPE;
+ break;
+ case 1:
+ escape = (unsigned char) esc[0];
+ break;
+ default:
+ php_error_docref(NULL, E_WARNING, "escape must be empty or a single character");
+ RETURN_FALSE;
}
- escape = esc[0];
/* no break */
case 3:
if (e_len != 1) {
SPL_METHOD(SplFileObject, setCsvControl)
{
spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(ZEND_THIS);
- char delimiter = ',', enclosure = '"', escape='\\';
+ char delimiter = ',', enclosure = '"';
+ int escape = (unsigned char) '\\';
char *delim = NULL, *enclo = NULL, *esc = NULL;
size_t d_len = 0, e_len = 0, esc_len = 0;
switch(ZEND_NUM_ARGS())
{
case 3:
- if (esc_len != 1) {
- php_error_docref(NULL, E_WARNING, "escape must be a character");
- RETURN_FALSE;
+ switch (esc_len) {
+ case 0:
+ escape = PHP_CSV_NO_ESCAPE;
+ break;
+ case 1:
+ escape = (unsigned char) esc[0];
+ break;
+ default:
+ php_error_docref(NULL, E_WARNING, "escape must be empty or a single character");
+ RETURN_FALSE;
}
- escape = esc[0];
/* no break */
case 2:
if (e_len != 1) {
delimiter[1] = '\0';
enclosure[0] = intern->u.file.enclosure;
enclosure[1] = '\0';
- escape[0] = intern->u.file.escape;
- escape[1] = '\0';
+ if (intern->u.file.escape == PHP_CSV_NO_ESCAPE) {
+ escape[0] = '\0';
+ } else {
+ escape[0] = (unsigned char) intern->u.file.escape;
+ escape[1] = '\0';
+ }
add_next_index_string(return_value, delimiter);
add_next_index_string(return_value, enclosure);
zend_function *func_getCurr;
char delimiter;
char enclosure;
- char escape;
+ int escape;
} file;
} u;
zend_object std;
--- /dev/null
+--TEST--
+SplFileObject::fgetcsv() with empty $escape
+--FILE--
+<?php
+$contents = <<<EOS
+"cell1","cell2\\","cell3","cell4"
+"\\\\\\line1
+line2\\\\\\"
+EOS;
+$file = new SplTempFileObject;
+$file->fwrite($contents);
+$file->rewind();
+while (($data = $file->fgetcsv(',', '"', ''))) {
+ print_r($data);
+}
+?>
+===DONE===
+--EXPECT--
+Array
+(
+ [0] => cell1
+ [1] => cell2\
+ [2] => cell3
+ [3] => cell4
+)
+Array
+(
+ [0] => \\\line1
+line2\\\
+)
+===DONE===
unlink('SplFileObject__fgetcsv8.csv');
?>
--EXPECTF--
-Warning: SplFileObject::fgetcsv(): escape must be a character in %s on line %d
+Warning: SplFileObject::fgetcsv(): escape must be empty or a single character in %s on line %d
bool(false)
--- /dev/null
+--TEST--
+SplFileObject::fputcsv() with empty $escape
+--FILE--
+<?php
+$data = array(
+ ['\\'],
+ ['\\"']
+);
+$file = new SplTempFileObject;
+foreach ($data as $record) {
+ $file->fputcsv($record, ',', '"', '');
+}
+$file->rewind();
+foreach ($file as $line) {
+ echo $line;
+}
+?>
+===DONE===
+--EXPECT--
+\
+"\"""
+===DONE===
unlink('csv_control_data.csv');
?>
--EXPECTF--
-Warning: SplFileObject::setCsvControl(): escape must be a character in %s on line %d
+Warning: SplFileObject::setCsvControl(): escape must be empty or a single character in %s on line %d
--- /dev/null
+--TEST--
+SplFileObject::setCsvControl() and ::getCsvControl() with empty $escape
+--FILE--
+<?php
+$file = new SplTempFileObject;
+$file->setCsvControl(',', '"', '');
+var_dump($file->getCsvControl());
+?>
+===DONE===
+--EXPECT--
+array(3) {
+ [0]=>
+ string(1) ","
+ [1]=>
+ string(1) """
+ [2]=>
+ string(0) ""
+}
+===DONE===
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 = '\\'; /* allow this to be set as parameter */
+ char delimiter = ','; /* allow this to be set as parameter */
+ char enclosure = '"'; /* allow this to be set as parameter */
+ int escape_char = (unsigned char) '\\'; /* allow this to be set as parameter */
php_stream *stream;
zval *fp = NULL, *fields = NULL;
size_t ret;
}
if (escape_str != NULL) {
+ if (escape_str_len > 1) {
+ php_error_docref(NULL, E_NOTICE, "escape must be empty or a single character");
+ }
if (escape_str_len < 1) {
- php_error_docref(NULL, E_WARNING, "escape must be a character");
- RETURN_FALSE;
- } else if (escape_str_len > 1) {
- php_error_docref(NULL, E_NOTICE, "escape must be a single character");
+ escape_char = PHP_CSV_NO_ESCAPE;
+ } else {
+ /* use first character from string */
+ escape_char = (unsigned char) *escape_str;
}
- /* use first character from string */
- escape_char = *escape_str;
}
PHP_STREAM_TO_ZVAL(stream, fp);
}
/* }}} */
-/* {{{ PHPAPI size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, char escape_char) */
-PHPAPI size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, char escape_char)
+/* {{{ PHPAPI size_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)
{
int count, i = 0;
size_t ret;
zval *field_tmp;
smart_str csvline = {0};
+ ZEND_ASSERT((escape_char >= 0 && escape_char <= UCHAR_MAX) || escape_char == PHP_CSV_NO_ESCAPE);
count = zend_hash_num_elements(Z_ARRVAL_P(fields));
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(fields), field_tmp) {
zend_string *tmp_field_str;
/* enclose a field that contains a delimiter, an enclosure character, or a newline */
if (FPUTCSV_FLD_CHK(delimiter) ||
FPUTCSV_FLD_CHK(enclosure) ||
- FPUTCSV_FLD_CHK(escape_char) ||
+ (escape_char != PHP_CSV_NO_ESCAPE && FPUTCSV_FLD_CHK(escape_char)) ||
FPUTCSV_FLD_CHK('\n') ||
FPUTCSV_FLD_CHK('\r') ||
FPUTCSV_FLD_CHK('\t') ||
smart_str_appendc(&csvline, enclosure);
while (ch < end) {
- if (*ch == escape_char) {
+ if (escape_char != PHP_CSV_NO_ESCAPE && *ch == escape_char) {
escaped = 1;
} else if (!escaped && *ch == enclosure) {
smart_str_appendc(&csvline, enclosure);
{
char delimiter = ','; /* allow this to be set as parameter */
char enclosure = '"'; /* allow this to be set as parameter */
- char escape = '\\';
+ int escape = (unsigned char) '\\';
/* first section exactly as php_fgetss */
}
if (escape_str != NULL) {
- if (escape_str_len < 1) {
- php_error_docref(NULL, E_WARNING, "escape must be character");
- RETURN_FALSE;
- } else if (escape_str_len > 1) {
- php_error_docref(NULL, E_NOTICE, "escape must be a single character");
+ if (escape_str_len > 1) {
+ php_error_docref(NULL, E_NOTICE, "escape must be empty or a single character");
}
- escape = escape_str[0];
+ if (escape_str_len < 1) {
+ escape = PHP_CSV_NO_ESCAPE;
+ } else {
+ escape = (unsigned char) escape_str[0];
+ }
}
if (len_zv != NULL && Z_TYPE_P(len_zv) != IS_NULL) {
}
/* }}} */
-PHPAPI void php_fgetcsv(php_stream *stream, char delimiter, char enclosure, char escape_char, size_t buf_len, char *buf, zval *return_value) /* {{{ */
+PHPAPI void php_fgetcsv(php_stream *stream, char delimiter, char enclosure, int escape_char, size_t buf_len, char *buf, zval *return_value) /* {{{ */
{
char *temp, *tptr, *bptr, *line_end, *limit;
size_t temp_len, line_end_len;
int inc_len;
zend_bool first_field = 1;
+ ZEND_ASSERT((escape_char >= 0 && escape_char <= UCHAR_MAX) || escape_char == PHP_CSV_NO_ESCAPE);
+
/* initialize internal state */
php_mb_reset();
default:
if (*bptr == enclosure) {
state = 2;
- } else if (*bptr == escape_char) {
+ } else if (escape_char != PHP_CSV_NO_ESCAPE && *bptr == escape_char) {
state = 1;
}
bptr++;
PHPAPI int php_copy_file_ctx(const char *src, const char *dest, int src_chk, php_stream_context *ctx);
PHPAPI int php_mkdir_ex(const char *dir, zend_long mode, int options);
PHPAPI int php_mkdir(const char *dir, zend_long mode);
-PHPAPI void php_fgetcsv(php_stream *stream, char delimiter, char enclosure, char escape_char, size_t buf_len, char *buf, zval *return_value);
-PHPAPI size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, char escape_char);
+
+#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 size_t php_fputcsv(php_stream *stream, zval *fields, char delimiter, char enclosure, int escape_char);
#define META_DEF_BUFSIZE 8192
PHP_FUNCTION(str_getcsv)
{
zend_string *str;
- char delim = ',', enc = '"', esc = '\\';
+ char delim = ',', enc = '"';
+ int esc = (unsigned char) '\\';
char *delim_str = NULL, *enc_str = NULL, *esc_str = NULL;
size_t delim_len = 0, enc_len = 0, esc_len = 0;
delim = delim_len ? delim_str[0] : delim;
enc = enc_len ? enc_str[0] : enc;
- esc = esc_len ? esc_str[0] : esc;
+ if (esc_str != NULL) {
+ esc = esc_len ? (unsigned char) esc_str[0] : PHP_CSV_NO_ESCAPE;
+ }
php_fgetcsv(NULL, delim, enc, esc, ZSTR_LEN(str), ZSTR_VAL(str), return_value);
}
--- /dev/null
+--TEST--
+fgetcsv() with empty $escape
+--FILE--
+<?php
+$contents = <<<EOS
+"cell1","cell2\\","cell3","cell4"
+"\\\\\\line1
+line2\\\\\\"
+EOS;
+$stream = fopen('php://memory', 'w+');
+fwrite($stream, $contents);
+rewind($stream);
+while (($data = fgetcsv($stream, 0, ',', '"', '')) !== false) {
+ print_r($data);
+}
+fclose($stream);
+?>
+===DONE===
+--EXPECT--
+Array
+(
+ [0] => cell1
+ [1] => cell2\
+ [2] => cell3
+ [3] => cell4
+)
+Array
+(
+ [0] => \\\line1
+line2\\\
+)
+===DONE===
--- /dev/null
+--TEST--
+fputcsv() with empty $escape
+--FILE--
+<?php
+$data = array(
+ ['\\'],
+ ['\\"']
+);
+$stream = fopen('php://memory', 'w+');
+foreach ($data as $record) {
+ fputcsv($stream, $record, ',', '"', '');
+}
+rewind($stream);
+echo stream_get_contents($stream);
+fclose($stream);
+?>
+===DONE===
+--EXPECT--
+\
+"\"""
+===DONE===
--- /dev/null
+--TEST--
+str_getcsv() with empty $escape
+--FILE--
+<?php
+$contents = <<<EOS
+"cell1","cell2\\","cell3","cell4"
+EOS;
+print_r(str_getcsv($contents, ',', '"', ''));
+?>
+===DONE===
+--EXPECT--
+Array
+(
+ [0] => cell1
+ [1] => cell2\
+ [2] => cell3
+ [3] => cell4
+)
+===DONE===