summaryrefslogtreecommitdiff
path: root/ext/iconv
diff options
context:
space:
mode:
authorChristoph M. Becker <cmbecker69@gmx.de>2020-03-16 13:09:16 +0100
committerChristoph M. Becker <cmbecker69@gmx.de>2020-03-23 14:12:37 +0100
commit32a26443052a734f7c24434497a0c9a2210607eb (patch)
tree1a7bcb49f48b28e051f75ff19391f2fb99d15ebc /ext/iconv
parent47c745555c9000706cd4de7fffc6e1c2f18d5521 (diff)
downloadphp-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.c85
-rw-r--r--ext/iconv/tests/bug79200.phpt18
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"