diff options
author | Christoph M. Becker <cmbecker69@gmx.de> | 2020-03-16 13:09:16 +0100 |
---|---|---|
committer | Christoph M. Becker <cmbecker69@gmx.de> | 2020-03-23 14:12:37 +0100 |
commit | 32a26443052a734f7c24434497a0c9a2210607eb (patch) | |
tree | 1a7bcb49f48b28e051f75ff19391f2fb99d15ebc /ext/iconv | |
parent | 47c745555c9000706cd4de7fffc6e1c2f18d5521 (diff) | |
download | php-git-32a26443052a734f7c24434497a0c9a2210607eb.tar.gz |
Fix #79200: Some iconv functions cut Windows-1258
To cater to potentially state-dependent encodings, we have to reset the
conversion descriptor into its initial shift state to properly finish
the conversion. Furthermore, state-dependent encodings may not show
progress when comparing `in_left` before and after the conversion; we
rather have to see whether `out_left` has decreased. Also we have to
cater to the fact that the final potentially state resetting call does
not signal failure, but we still have to break respective loops
afterwards.
Diffstat (limited to 'ext/iconv')
-rw-r--r-- | ext/iconv/iconv.c | 85 | ||||
-rw-r--r-- | ext/iconv/tests/bug79200.phpt | 18 |
2 files changed, 65 insertions, 38 deletions
diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index a152187e6c..02580fff8a 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -739,6 +739,7 @@ static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_ size_t out_left; size_t cnt; + int more; *pretval = (size_t)-1; @@ -758,25 +759,23 @@ static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_ errno = 0; out_left = 0; + more = nbytes > 0; - for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0; cnt+=2) { - size_t prev_in_left; + for (in_p = str, in_left = nbytes, cnt = 0; more;) { out_p = buf; out_left = sizeof(buf); - prev_in_left = in_left; + more = in_left > 0; - if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { - if (prev_in_left == in_left) { - break; - } + iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + if (out_left == sizeof(buf)) { + break; + } else { + ZEND_ASSERT((sizeof(buf) - out_left) % GENERIC_SUPERSET_NBYTES == 0); + cnt += (sizeof(buf) - out_left) / GENERIC_SUPERSET_NBYTES; } } - if (out_left > 0) { - cnt -= out_left / GENERIC_SUPERSET_NBYTES; - } - #if ICONV_SUPPORTS_ERRNO switch (errno) { case EINVAL: @@ -825,6 +824,7 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval, size_t cnt; size_t total_len; + int more; err = _php_iconv_strlen(&total_len, str, nbytes, enc); if (err != PHP_ICONV_ERR_SUCCESS) { @@ -879,18 +879,17 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval, cd2 = (iconv_t)NULL; errno = 0; + more = nbytes > 0 && len > 0; - for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0 && len > 0; ++cnt) { - size_t prev_in_left; + for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) { out_p = buf; out_left = sizeof(buf); - prev_in_left = in_left; + more = in_left > 0 && len > 0; - if (iconv(cd1, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { - if (prev_in_left == in_left) { - break; - } + iconv(cd1, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + if (out_left == sizeof(buf)) { + break; } if ((zend_long)cnt >= offset) { @@ -978,6 +977,8 @@ static php_iconv_err_t _php_iconv_strpos(size_t *pretval, size_t ndl_buf_left; size_t match_ofs; + int more; + size_t iconv_ret; *pretval = (size_t)-1; @@ -1010,37 +1011,38 @@ static php_iconv_err_t _php_iconv_strpos(size_t *pretval, ndl_buf_p = ZSTR_VAL(ndl_buf); ndl_buf_left = ZSTR_LEN(ndl_buf); match_ofs = (size_t)-1; + more = haystk_nbytes > 0; - for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; in_left > 0; ++cnt) { - size_t prev_in_left; + for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; more; ++cnt) { out_p = buf; out_left = sizeof(buf); - prev_in_left = in_left; + more = in_left > 0; - if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { - if (prev_in_left == in_left) { + iconv_ret = iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + if (out_left == sizeof(buf)) { + break; + } #if ICONV_SUPPORTS_ERRNO - switch (errno) { - case EINVAL: - err = PHP_ICONV_ERR_ILLEGAL_CHAR; - break; + if (iconv_ret == (size_t)-1) { + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; - case EILSEQ: - err = PHP_ICONV_ERR_ILLEGAL_SEQ; - break; + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; - case E2BIG: - break; + case E2BIG: + break; - default: - err = PHP_ICONV_ERR_UNKNOWN; - break; - } -#endif - break; + default: + err = PHP_ICONV_ERR_UNKNOWN; + break; } } +#endif if (offset >= 0) { if (cnt >= (size_t)offset) { if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) { @@ -2012,6 +2014,13 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st *next_pos = p1; } + if (cd != (iconv_t)(-1)) { + _php_iconv_appendl(pretval, NULL, 0, cd); + } + if (cd_pl != (iconv_t)(-1)) { + _php_iconv_appendl(pretval, NULL, 0, cd_pl); + } + smart_str_0(pretval); out: if (cd != (iconv_t)(-1)) { diff --git a/ext/iconv/tests/bug79200.phpt b/ext/iconv/tests/bug79200.phpt new file mode 100644 index 0000000000..e8d86086a3 --- /dev/null +++ b/ext/iconv/tests/bug79200.phpt @@ -0,0 +1,18 @@ +--TEST-- +Bug #79200 (Some iconv functions cut Windows-1258) +--SKIPIF-- +<?php +if (!extension_loaded('iconv')) die('skip iconv extension not available'); +?> +--FILE-- +<?php +var_dump(iconv_mime_decode('=?windows-1258?Q?test=20test?=', 0, 'UTF-8')); +var_dump(iconv_strlen('test test', 'WINDOWS-1258')); +var_dump(iconv_strpos('test test', 'test test', 0, 'WINDOWS-1258')); +var_dump(iconv_substr('test test', 0 , 9, 'WINDOWS-1258')); +?> +--EXPECT-- +string(9) "test test" +int(9) +int(0) +string(9) "test test" |