Skip to content

Commit 32a2644

Browse files
committed
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.
1 parent 47c7455 commit 32a2644

File tree

3 files changed

+68
-38
lines changed

3 files changed

+68
-38
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ PHP NEWS
1313
. Fixed bug #79396 (DateTime hour incorrect during DST jump forward). (Nate
1414
Brunette)
1515

16+
- Iconv:
17+
. Fixed bug #79200 (Some iconv functions cut Windows-1258). (cmb)
18+
1619
- SimpleXML:
1720
. Fixed bug #61597 (SXE properties may lack attributes and content). (cmb)
1821

ext/iconv/iconv.c

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_
739739
size_t out_left;
740740

741741
size_t cnt;
742+
int more;
742743

743744
*pretval = (size_t)-1;
744745

@@ -758,25 +759,23 @@ static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_
758759

759760
errno = 0;
760761
out_left = 0;
762+
more = nbytes > 0;
761763

762-
for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0; cnt+=2) {
763-
size_t prev_in_left;
764+
for (in_p = str, in_left = nbytes, cnt = 0; more;) {
764765
out_p = buf;
765766
out_left = sizeof(buf);
766767

767-
prev_in_left = in_left;
768+
more = in_left > 0;
768769

769-
if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
770-
if (prev_in_left == in_left) {
771-
break;
772-
}
770+
iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left);
771+
if (out_left == sizeof(buf)) {
772+
break;
773+
} else {
774+
ZEND_ASSERT((sizeof(buf) - out_left) % GENERIC_SUPERSET_NBYTES == 0);
775+
cnt += (sizeof(buf) - out_left) / GENERIC_SUPERSET_NBYTES;
773776
}
774777
}
775778

776-
if (out_left > 0) {
777-
cnt -= out_left / GENERIC_SUPERSET_NBYTES;
778-
}
779-
780779
#if ICONV_SUPPORTS_ERRNO
781780
switch (errno) {
782781
case EINVAL:
@@ -825,6 +824,7 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval,
825824

826825
size_t cnt;
827826
size_t total_len;
827+
int more;
828828

829829
err = _php_iconv_strlen(&total_len, str, nbytes, enc);
830830
if (err != PHP_ICONV_ERR_SUCCESS) {
@@ -879,18 +879,17 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval,
879879

880880
cd2 = (iconv_t)NULL;
881881
errno = 0;
882+
more = nbytes > 0 && len > 0;
882883

883-
for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0 && len > 0; ++cnt) {
884-
size_t prev_in_left;
884+
for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) {
885885
out_p = buf;
886886
out_left = sizeof(buf);
887887

888-
prev_in_left = in_left;
888+
more = in_left > 0 && len > 0;
889889

890-
if (iconv(cd1, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
891-
if (prev_in_left == in_left) {
892-
break;
893-
}
890+
iconv(cd1, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left);
891+
if (out_left == sizeof(buf)) {
892+
break;
894893
}
895894

896895
if ((zend_long)cnt >= offset) {
@@ -978,6 +977,8 @@ static php_iconv_err_t _php_iconv_strpos(size_t *pretval,
978977
size_t ndl_buf_left;
979978

980979
size_t match_ofs;
980+
int more;
981+
size_t iconv_ret;
981982

982983
*pretval = (size_t)-1;
983984

@@ -1010,37 +1011,38 @@ static php_iconv_err_t _php_iconv_strpos(size_t *pretval,
10101011
ndl_buf_p = ZSTR_VAL(ndl_buf);
10111012
ndl_buf_left = ZSTR_LEN(ndl_buf);
10121013
match_ofs = (size_t)-1;
1014+
more = haystk_nbytes > 0;
10131015

1014-
for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; in_left > 0; ++cnt) {
1015-
size_t prev_in_left;
1016+
for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; more; ++cnt) {
10161017
out_p = buf;
10171018
out_left = sizeof(buf);
10181019

1019-
prev_in_left = in_left;
1020+
more = in_left > 0;
10201021

1021-
if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
1022-
if (prev_in_left == in_left) {
1022+
iconv_ret = iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left);
1023+
if (out_left == sizeof(buf)) {
1024+
break;
1025+
}
10231026
#if ICONV_SUPPORTS_ERRNO
1024-
switch (errno) {
1025-
case EINVAL:
1026-
err = PHP_ICONV_ERR_ILLEGAL_CHAR;
1027-
break;
1027+
if (iconv_ret == (size_t)-1) {
1028+
switch (errno) {
1029+
case EINVAL:
1030+
err = PHP_ICONV_ERR_ILLEGAL_CHAR;
1031+
break;
10281032

1029-
case EILSEQ:
1030-
err = PHP_ICONV_ERR_ILLEGAL_SEQ;
1031-
break;
1033+
case EILSEQ:
1034+
err = PHP_ICONV_ERR_ILLEGAL_SEQ;
1035+
break;
10321036

1033-
case E2BIG:
1034-
break;
1037+
case E2BIG:
1038+
break;
10351039

1036-
default:
1037-
err = PHP_ICONV_ERR_UNKNOWN;
1038-
break;
1039-
}
1040-
#endif
1041-
break;
1040+
default:
1041+
err = PHP_ICONV_ERR_UNKNOWN;
1042+
break;
10421043
}
10431044
}
1045+
#endif
10441046
if (offset >= 0) {
10451047
if (cnt >= (size_t)offset) {
10461048
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
20122014
*next_pos = p1;
20132015
}
20142016

2017+
if (cd != (iconv_t)(-1)) {
2018+
_php_iconv_appendl(pretval, NULL, 0, cd);
2019+
}
2020+
if (cd_pl != (iconv_t)(-1)) {
2021+
_php_iconv_appendl(pretval, NULL, 0, cd_pl);
2022+
}
2023+
20152024
smart_str_0(pretval);
20162025
out:
20172026
if (cd != (iconv_t)(-1)) {

ext/iconv/tests/bug79200.phpt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Bug #79200 (Some iconv functions cut Windows-1258)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('iconv')) die('skip iconv extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
var_dump(iconv_mime_decode('=?windows-1258?Q?test=20test?=', 0, 'UTF-8'));
10+
var_dump(iconv_strlen('test test', 'WINDOWS-1258'));
11+
var_dump(iconv_strpos('test test', 'test test', 0, 'WINDOWS-1258'));
12+
var_dump(iconv_substr('test test', 0 , 9, 'WINDOWS-1258'));
13+
?>
14+
--EXPECT--
15+
string(9) "test test"
16+
int(9)
17+
int(0)
18+
string(9) "test test"

0 commit comments

Comments
 (0)