Commit 38628e89a54 for php.net

commit 38628e89a545c06e1425aa639b1d624baacf938a
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Mar 26 18:05:28 2026 -0400

    Fix GH-17399: iconv memory leak on bailout

    Wrap bailable sections in php_iconv_string(), _php_iconv_substr(),
    _php_iconv_mime_encode(), and _php_iconv_mime_decode() with zend_try/zend_catch
    to ensure iconv handles allocated via system malloc are closed if a Zend OOM
    bailout fires during smart_str or zend_string operations.

    Fixes GH-17399
    Closes GH-21541

diff --git a/NEWS b/NEWS
index dc0e430775f..0e042bb864c 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,9 @@ PHP                                                                        NEWS
   . Fixed bug GH-19983 (GC assertion failure with fibers, generators and
     destructors). (iliaal)

+- Iconv:
+  . Fixed bug GH-17399 (iconv memory leak on bailout). (iliaal)
+
 - SPL:
   . Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent
     free). (Girgias)
diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c
index ba74eddd012..117a9e948f6 100644
--- a/ext/iconv/iconv.c
+++ b/ext/iconv/iconv.c
@@ -459,59 +459,65 @@ PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len,
 	out_left = in_len + 32; /* Avoid realloc() most cases */
 	out_size = 0;
 	bsz = out_left;
-	out_buf = zend_string_alloc(bsz, 0);
-	out_p = ZSTR_VAL(out_buf);
-
-	while (in_left > 0) {
-		result = iconv(cd, (ICONV_CONST char **) &in_p, &in_left, (char **) &out_p, &out_left);
-		out_size = bsz - out_left;
-		if (result == (size_t)(-1)) {
-			if (ignore_ilseq && errno == EILSEQ) {
-				if (in_left <= 1) {
-					result = 0;
-				} else {
-					errno = 0;
-					in_p++;
-					in_left--;
-					continue;
+
+	zend_try {
+		out_buf = zend_string_alloc(bsz, 0);
+		out_p = ZSTR_VAL(out_buf);
+
+		while (in_left > 0) {
+			result = iconv(cd, (ICONV_CONST char **) &in_p, &in_left, (char **) &out_p, &out_left);
+			out_size = bsz - out_left;
+			if (result == (size_t)(-1)) {
+				if (ignore_ilseq && errno == EILSEQ) {
+					if (in_left <= 1) {
+						result = 0;
+					} else {
+						errno = 0;
+						in_p++;
+						in_left--;
+						continue;
+					}
 				}
-			}

-			if (errno == E2BIG && in_left > 0) {
-				/* converted string is longer than out buffer */
-				bsz += in_len;
+				if (errno == E2BIG && in_left > 0) {
+					/* converted string is longer than out buffer */
+					bsz += in_len;

-				out_buf = zend_string_extend(out_buf, bsz, 0);
-				out_p = ZSTR_VAL(out_buf);
-				out_p += out_size;
-				out_left = bsz - out_size;
-				continue;
+					out_buf = zend_string_extend(out_buf, bsz, 0);
+					out_p = ZSTR_VAL(out_buf);
+					out_p += out_size;
+					out_left = bsz - out_size;
+					continue;
+				}
 			}
+			break;
 		}
-		break;
-	}

-	if (result != (size_t)(-1)) {
-		/* flush the shift-out sequences */
-		for (;;) {
-		   	result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
-			out_size = bsz - out_left;
+		if (result != (size_t)(-1)) {
+			/* flush the shift-out sequences */
+			for (;;) {
+				result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
+				out_size = bsz - out_left;

-			if (result != (size_t)(-1)) {
-				break;
-			}
+				if (result != (size_t)(-1)) {
+					break;
+				}

-			if (errno == E2BIG) {
-				bsz += 16;
-				out_buf = zend_string_extend(out_buf, bsz, 0);
-				out_p = ZSTR_VAL(out_buf);
-				out_p += out_size;
-				out_left = bsz - out_size;
-			} else {
-				break;
+				if (errno == E2BIG) {
+					bsz += 16;
+					out_buf = zend_string_extend(out_buf, bsz, 0);
+					out_p = ZSTR_VAL(out_buf);
+					out_p += out_size;
+					out_left = bsz - out_size;
+				} else {
+					break;
+				}
 			}
 		}
-	}
+	} zend_catch {
+		iconv_close(cd);
+		zend_bailout();
+	} zend_end_try();

 	iconv_close(cd);

@@ -684,58 +690,63 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval,
 	errno = 0;
 	more = nbytes > 0 && len > 0;

-	for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) {
-		out_p = buf;
-		out_left = sizeof(buf);
+	bool bailout = false;
+	zend_try {
+		for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) {
+			out_p = buf;
+			out_left = sizeof(buf);

-		more = in_left > 0 && len > 0;
+			more = in_left > 0 && len > 0;

-		iconv(cd1, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left);
-		if (out_left == sizeof(buf)) {
-			break;
-		}
+			iconv(cd1, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left);
+			if (out_left == sizeof(buf)) {
+				break;
+			}

-		if ((zend_long)cnt >= offset) {
-			if (cd2 == (iconv_t)NULL) {
-				cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME);
+			if ((zend_long)cnt >= offset) {
+				if (cd2 == (iconv_t)NULL) {
+					cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME);

-				if (cd2 == (iconv_t)(-1)) {
-					cd2 = (iconv_t)NULL;
-					if (errno == EINVAL) {
-						err = PHP_ICONV_ERR_WRONG_CHARSET;
-					} else {
-						err = PHP_ICONV_ERR_CONVERTER;
+					if (cd2 == (iconv_t)(-1)) {
+						cd2 = (iconv_t)NULL;
+						if (errno == EINVAL) {
+							err = PHP_ICONV_ERR_WRONG_CHARSET;
+						} else {
+							err = PHP_ICONV_ERR_CONVERTER;
+						}
+						break;
 					}
+				}
+
+				if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) {
 					break;
 				}
+				--len;
 			}

-			if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) {
-				break;
-			}
-			--len;
 		}

-	}
-
-	switch (errno) {
-		case EINVAL:
-			err = PHP_ICONV_ERR_ILLEGAL_CHAR;
-			break;
+		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;
-	}
-	if (err == PHP_ICONV_ERR_SUCCESS) {
-		if (cd2 != (iconv_t)NULL) {
-			_php_iconv_appendl(pretval, NULL, 0, cd2);
+			case E2BIG:
+				break;
 		}
-		smart_str_0(pretval);
-	}
+		if (err == PHP_ICONV_ERR_SUCCESS) {
+			if (cd2 != (iconv_t)NULL) {
+				_php_iconv_appendl(pretval, NULL, 0, cd2);
+			}
+			smart_str_0(pretval);
+		}
+	} zend_catch {
+		bailout = true;
+	} zend_end_try();

 	if (cd1 != (iconv_t)NULL) {
 		iconv_close(cd1);
@@ -744,6 +755,9 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval,
 	if (cd2 != (iconv_t)NULL) {
 		iconv_close(cd2);
 	}
+	if (bailout) {
+		zend_bailout();
+	}
 	return err;
 }

@@ -904,6 +918,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn
 {
 	php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
 	iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1);
+	bool bailout = false;
 	size_t char_cnt = 0;
 	size_t out_charset_len;
 	size_t lfchars_len;
@@ -962,215 +977,219 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn
 		goto out;
 	}

-	buf = safe_emalloc(1, max_line_len, 5);
+	zend_try {
+		buf = safe_emalloc(1, max_line_len, 5);

-	char_cnt = max_line_len;
+		char_cnt = max_line_len;

-	_php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl);
-	char_cnt -= fname_nbytes;
-	smart_str_appendl(pretval, ": ", sizeof(": ") - 1);
-	char_cnt -= 2;
+		_php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl);
+		char_cnt -= fname_nbytes;
+		smart_str_appendl(pretval, ": ", sizeof(": ") - 1);
+		char_cnt -= 2;

-	in_p = fval;
-	in_left = fval_nbytes;
+		in_p = fval;
+		in_left = fval_nbytes;

-	do {
-		size_t prev_in_left;
-		size_t out_size;
-		size_t encoded_word_min_len = sizeof("=\?\?X\?\?=")-1 + out_charset_len + (enc_scheme == PHP_ICONV_ENC_SCHEME_BASE64 ? 4 : 3);
+		do {
+			size_t prev_in_left;
+			size_t out_size;
+			size_t encoded_word_min_len = sizeof("=\?\?X\?\?=")-1 + out_charset_len + (enc_scheme == PHP_ICONV_ENC_SCHEME_BASE64 ? 4 : 3);

-		if (char_cnt < encoded_word_min_len + lfchars_len + 1) {
-			/* lfchars must be encoded in ASCII here*/
-			smart_str_appendl(pretval, lfchars, lfchars_len);
-			smart_str_appendc(pretval, ' ');
-			char_cnt = max_line_len - 1;
-		}
+			if (char_cnt < encoded_word_min_len + lfchars_len + 1) {
+				/* lfchars must be encoded in ASCII here*/
+				smart_str_appendl(pretval, lfchars, lfchars_len);
+				smart_str_appendc(pretval, ' ');
+				char_cnt = max_line_len - 1;
+			}

-		smart_str_appendl(pretval, "=?", sizeof("=?") - 1);
-		char_cnt -= 2;
-		smart_str_appendl(pretval, out_charset, out_charset_len);
-		char_cnt -= out_charset_len;
-		smart_str_appendc(pretval, '?');
-		char_cnt --;
+			smart_str_appendl(pretval, "=?", sizeof("=?") - 1);
+			char_cnt -= 2;
+			smart_str_appendl(pretval, out_charset, out_charset_len);
+			char_cnt -= out_charset_len;
+			smart_str_appendc(pretval, '?');
+			char_cnt --;

-		switch (enc_scheme) {
-			case PHP_ICONV_ENC_SCHEME_BASE64: {
-				size_t ini_in_left;
-				const char *ini_in_p;
-				size_t out_reserved = 4;
+			switch (enc_scheme) {
+				case PHP_ICONV_ENC_SCHEME_BASE64: {
+					size_t ini_in_left;
+					const char *ini_in_p;
+					size_t out_reserved = 4;

-				smart_str_appendc(pretval, 'B');
-				char_cnt--;
-				smart_str_appendc(pretval, '?');
-				char_cnt--;
+					smart_str_appendc(pretval, 'B');
+					char_cnt--;
+					smart_str_appendc(pretval, '?');
+					char_cnt--;

-				prev_in_left = ini_in_left = in_left;
-				ini_in_p = in_p;
+					prev_in_left = ini_in_left = in_left;
+					ini_in_p = in_p;

-				out_size = (char_cnt - 2) / 4 * 3;
+					out_size = (char_cnt - 2) / 4 * 3;

-				for (;;) {
-					out_p = buf;
+					for (;;) {
+						out_p = buf;

-					if (out_size <= out_reserved) {
-						err = PHP_ICONV_ERR_TOO_BIG;
-						goto out;
-					}
+						if (out_size <= out_reserved) {
+							err = PHP_ICONV_ERR_TOO_BIG;
+							goto out;
+						}

-					out_left = out_size - out_reserved;
+						out_left = out_size - out_reserved;

-					if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
-						switch (errno) {
-							case EINVAL:
-								err = PHP_ICONV_ERR_ILLEGAL_CHAR;
-								goto out;
+						if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
+							switch (errno) {
+								case EINVAL:
+									err = PHP_ICONV_ERR_ILLEGAL_CHAR;
+									goto out;

-							case EILSEQ:
-								err = PHP_ICONV_ERR_ILLEGAL_SEQ;
-								goto out;
+								case EILSEQ:
+									err = PHP_ICONV_ERR_ILLEGAL_SEQ;
+									goto out;
+
+								case E2BIG:
+									if (prev_in_left == in_left) {
+										err = PHP_ICONV_ERR_TOO_BIG;
+										goto out;
+									}
+									break;

-							case E2BIG:
-								if (prev_in_left == in_left) {
-									err = PHP_ICONV_ERR_TOO_BIG;
+								default:
+									err = PHP_ICONV_ERR_UNKNOWN;
 									goto out;
-								}
-								break;
+							}
+						}

-							default:
+						out_left += out_reserved;
+
+						if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
+							if (errno != E2BIG) {
 								err = PHP_ICONV_ERR_UNKNOWN;
 								goto out;
+							}
+						} else {
+							break;
 						}
-					}
-
-					out_left += out_reserved;

-					if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
-						if (errno != E2BIG) {
+						if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
 							err = PHP_ICONV_ERR_UNKNOWN;
 							goto out;
 						}
-					} else {
-						break;
-					}

-					if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
-						err = PHP_ICONV_ERR_UNKNOWN;
-						goto out;
+						out_reserved += 4;
+						in_left = ini_in_left;
+						in_p = ini_in_p;
 					}

-					out_reserved += 4;
-					in_left = ini_in_left;
-					in_p = ini_in_p;
-				}
+					prev_in_left = in_left;

-				prev_in_left = in_left;
+					encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left));

-				encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left));
+					if (char_cnt < ZSTR_LEN(encoded)) {
+						/* something went wrong! */
+						err = PHP_ICONV_ERR_UNKNOWN;
+						goto out;
+					}

-				if (char_cnt < ZSTR_LEN(encoded)) {
-					/* something went wrong! */
-					err = PHP_ICONV_ERR_UNKNOWN;
-					goto out;
-				}
+					smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded));
+					char_cnt -= ZSTR_LEN(encoded);
+					smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
+					char_cnt -= 2;

-				smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded));
-				char_cnt -= ZSTR_LEN(encoded);
-				smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
-				char_cnt -= 2;
+					zend_string_release_ex(encoded, 0);
+					encoded = NULL;
+				} break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */

-				zend_string_release_ex(encoded, 0);
-				encoded = NULL;
-			} break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */
+				case PHP_ICONV_ENC_SCHEME_QPRINT: {
+					size_t ini_in_left;
+					const char *ini_in_p;
+					const unsigned char *p;
+					size_t nbytes_required;

-			case PHP_ICONV_ENC_SCHEME_QPRINT: {
-				size_t ini_in_left;
-				const char *ini_in_p;
-				const unsigned char *p;
-				size_t nbytes_required;
+					smart_str_appendc(pretval, 'Q');
+					char_cnt--;
+					smart_str_appendc(pretval, '?');
+					char_cnt--;

-				smart_str_appendc(pretval, 'Q');
-				char_cnt--;
-				smart_str_appendc(pretval, '?');
-				char_cnt--;
+					prev_in_left = ini_in_left = in_left;
+					ini_in_p = in_p;

-				prev_in_left = ini_in_left = in_left;
-				ini_in_p = in_p;
+					for (out_size = (char_cnt - 2); out_size > 0;) {

-				for (out_size = (char_cnt - 2); out_size > 0;) {
+						nbytes_required = 0;

-					nbytes_required = 0;
+						out_p = buf;
+						out_left = out_size;

-					out_p = buf;
-					out_left = out_size;
+						if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
+							switch (errno) {
+								case EINVAL:
+									err = PHP_ICONV_ERR_ILLEGAL_CHAR;
+									goto out;

-					if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) {
-						switch (errno) {
-							case EINVAL:
-								err = PHP_ICONV_ERR_ILLEGAL_CHAR;
-								goto out;
+								case EILSEQ:
+									err = PHP_ICONV_ERR_ILLEGAL_SEQ;
+									goto out;

-							case EILSEQ:
-								err = PHP_ICONV_ERR_ILLEGAL_SEQ;
-								goto out;
+								case E2BIG:
+									if (prev_in_left == in_left) {
+										err = PHP_ICONV_ERR_UNKNOWN;
+										goto out;
+									}
+									break;

-							case E2BIG:
-								if (prev_in_left == in_left) {
+								default:
 									err = PHP_ICONV_ERR_UNKNOWN;
 									goto out;
-								}
-								break;
-
-							default:
+							}
+						}
+						if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
+							if (errno != E2BIG) {
 								err = PHP_ICONV_ERR_UNKNOWN;
 								goto out;
+							}
 						}
-					}
-					if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) {
-						if (errno != E2BIG) {
-							err = PHP_ICONV_ERR_UNKNOWN;
-							goto out;
+
+						for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
+							nbytes_required += qp_table[*p];
 						}
-					}

-					for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
-						nbytes_required += qp_table[*p];
-					}
+						if (nbytes_required <= char_cnt - 2) {
+							break;
+						}

-					if (nbytes_required <= char_cnt - 2) {
-						break;
+						out_size -= ((nbytes_required - (char_cnt - 2)) + 2) / 3;
+						in_left = ini_in_left;
+						in_p = ini_in_p;
 					}

-					out_size -= ((nbytes_required - (char_cnt - 2)) + 2) / 3;
-					in_left = ini_in_left;
-					in_p = ini_in_p;
-				}
-
-				for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
-					if (qp_table[*p] == 1) {
-						smart_str_appendc(pretval, *(char *)p);
-						char_cnt--;
-					} else {
-						static const char qp_digits[] = "0123456789ABCDEF";
-						smart_str_appendc(pretval, '=');
-						smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]);
-						smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]);
-						char_cnt -= 3;
+					for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
+						if (qp_table[*p] == 1) {
+							smart_str_appendc(pretval, *(char *)p);
+							char_cnt--;
+						} else {
+							static const char qp_digits[] = "0123456789ABCDEF";
+							smart_str_appendc(pretval, '=');
+							smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]);
+							smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]);
+							char_cnt -= 3;
+						}
 					}
-				}

-				smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
-				char_cnt -= 2;
+					smart_str_appendl(pretval, "?=", sizeof("?=") - 1);
+					char_cnt -= 2;

-				if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
-					err = PHP_ICONV_ERR_UNKNOWN;
-					goto out;
-				}
+					if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
+						err = PHP_ICONV_ERR_UNKNOWN;
+						goto out;
+					}

-			} break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */
-		}
-	} while (in_left > 0);
+				} break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */
+			}
+		} while (in_left > 0);

-	smart_str_0(pretval);
+		smart_str_0(pretval);
+	} zend_catch {
+		bailout = true;
+	} zend_end_try();

 out:
 	if (cd != (iconv_t)(-1)) {
@@ -1185,6 +1204,9 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn
 	if (buf != NULL) {
 		efree(buf);
 	}
+	if (bailout) {
+		zend_bailout();
+	}
 	return err;
 }
 /* }}} */
@@ -1193,6 +1215,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn
 static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode)
 {
 	php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
+	bool bailout = false;

 	iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1);

@@ -1224,203 +1247,226 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st
 	}

 	p1 = str;
-	for (str_left = str_nbytes; str_left > 0; str_left--, p1++) {
-		int eos = 0;
-
-		switch (scan_stat) {
-			case 0: /* expecting any character */
-				switch (*p1) {
-					case '\r': /* part of an EOL sequence? */
-						scan_stat = 7;
-						break;
+	zend_try {
+		for (str_left = str_nbytes; str_left > 0; str_left--, p1++) {
+			int eos = 0;
+
+			switch (scan_stat) {
+				case 0: /* expecting any character */
+					switch (*p1) {
+						case '\r': /* part of an EOL sequence? */
+							scan_stat = 7;
+							break;

-					case '\n':
-						scan_stat = 8;
-						break;
+						case '\n':
+							scan_stat = 8;
+							break;

-					case '=': /* first letter of an encoded chunk */
-						encoded_word = p1;
-						scan_stat = 1;
-						break;
+						case '=': /* first letter of an encoded chunk */
+							encoded_word = p1;
+							scan_stat = 1;
+							break;

-					case ' ': case '\t': /* a chunk of whitespaces */
-						spaces = p1;
-						scan_stat = 11;
-						break;
+						case ' ': case '\t': /* a chunk of whitespaces */
+							spaces = p1;
+							scan_stat = 11;
+							break;

-					default: /* first letter of a non-encoded word */
-						err = _php_iconv_appendc(pretval, *p1, cd_pl);
-						if (err != PHP_ICONV_ERR_SUCCESS) {
-							if (mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR) {
-								err = PHP_ICONV_ERR_SUCCESS;
-							} else {
-								goto out;
+						default: /* first letter of a non-encoded word */
+							err = _php_iconv_appendc(pretval, *p1, cd_pl);
+							if (err != PHP_ICONV_ERR_SUCCESS) {
+								if (mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR) {
+									err = PHP_ICONV_ERR_SUCCESS;
+								} else {
+									goto out;
+								}
 							}
-						}
-						encoded_word = NULL;
-						if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-							scan_stat = 12;
-						}
-						break;
-				}
-				break;
-
-			case 1: /* expecting a delimiter */
-				if (*p1 != '?') {
-					if (*p1 == '\r' || *p1 == '\n') {
-						--p1;
-					}
-					err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-					if (err != PHP_ICONV_ERR_SUCCESS) {
-						goto out;
-					}
-					encoded_word = NULL;
-					if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-						scan_stat = 12;
-					} else {
-						scan_stat = 0;
+							encoded_word = NULL;
+							if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+								scan_stat = 12;
+							}
+							break;
 					}
 					break;
-				}
-				csname = p1 + 1;
-				scan_stat = 2;
-				break;
-
-			case 2: /* expecting a charset name */
-				switch (*p1) {
-					case '?': /* normal delimiter: encoding scheme follows */
-						scan_stat = 3;
-						break;
-
-					case '*': /* new style delimiter: locale id follows */
-						scan_stat = 10;
-						break;

-					case '\r': case '\n': /* not an encoded-word */
-						--p1;
-						_php_iconv_appendc(pretval, '=', cd_pl);
-						_php_iconv_appendc(pretval, '?', cd_pl);
-						err = _php_iconv_appendl(pretval, csname, (size_t)((p1 + 1) - csname), cd_pl);
+				case 1: /* expecting a delimiter */
+					if (*p1 != '?') {
+						if (*p1 == '\r' || *p1 == '\n') {
+							--p1;
+						}
+						err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
 						if (err != PHP_ICONV_ERR_SUCCESS) {
 							goto out;
 						}
-						csname = NULL;
+						encoded_word = NULL;
 						if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
 							scan_stat = 12;
-						}
-						else {
+						} else {
 							scan_stat = 0;
 						}
-						continue;
-				}
-				if (scan_stat != 2) {
-					char tmpbuf[80];
-
-					if (csname == NULL) {
-						err = PHP_ICONV_ERR_MALFORMED;
-						goto out;
+						break;
 					}
+					csname = p1 + 1;
+					scan_stat = 2;
+					break;

-					csname_len = (size_t)(p1 - csname);
+				case 2: /* expecting a charset name */
+					switch (*p1) {
+						case '?': /* normal delimiter: encoding scheme follows */
+							scan_stat = 3;
+							break;

-					if (csname_len > sizeof(tmpbuf) - 1) {
-						if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-							err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+						case '*': /* new style delimiter: locale id follows */
+							scan_stat = 10;
+							break;
+
+						case '\r': case '\n': /* not an encoded-word */
+							--p1;
+							_php_iconv_appendc(pretval, '=', cd_pl);
+							_php_iconv_appendc(pretval, '?', cd_pl);
+							err = _php_iconv_appendl(pretval, csname, (size_t)((p1 + 1) - csname), cd_pl);
 							if (err != PHP_ICONV_ERR_SUCCESS) {
 								goto out;
 							}
-							encoded_word = NULL;
+							csname = NULL;
 							if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
 								scan_stat = 12;
-							} else {
+							}
+							else {
 								scan_stat = 0;
 							}
-							break;
-						} else {
+							continue;
+					}
+					if (scan_stat != 2) {
+						char tmpbuf[80];
+
+						if (csname == NULL) {
 							err = PHP_ICONV_ERR_MALFORMED;
 							goto out;
 						}
-					}
-
-					memcpy(tmpbuf, csname, csname_len);
-					tmpbuf[csname_len] = '\0';
-
-					if (cd != (iconv_t)(-1)) {
-						iconv_close(cd);
-					}

-					cd = iconv_open(enc, tmpbuf);
+						csname_len = (size_t)(p1 - csname);

-					if (cd == (iconv_t)(-1)) {
-						if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-							/* Bad character set, but the user wants us to
-							 * press on. In this case, we'll just insert the
-							 * undecoded encoded word, since there isn't really
-							 * a more sensible behaviour available; the only
-							 * other options are to swallow the encoded word
-							 * entirely or decode it with an arbitrarily chosen
-							 * single byte encoding, both of which seem to have
-							 * a higher WTF factor than leaving it undecoded.
-							 *
-							 * Given this approach, we need to skip ahead to
-							 * the end of the encoded word. */
-							int qmarks = 2;
-							while (qmarks > 0 && str_left > 1) {
-								if (*(++p1) == '?') {
-									--qmarks;
+						if (csname_len > sizeof(tmpbuf) - 1) {
+							if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+								err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+								if (err != PHP_ICONV_ERR_SUCCESS) {
+									goto out;
+								}
+								encoded_word = NULL;
+								if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+									scan_stat = 12;
+								} else {
+									scan_stat = 0;
 								}
-								--str_left;
+								break;
+							} else {
+								err = PHP_ICONV_ERR_MALFORMED;
+								goto out;
 							}
+						}
+
+						memcpy(tmpbuf, csname, csname_len);
+						tmpbuf[csname_len] = '\0';
+
+						if (cd != (iconv_t)(-1)) {
+							iconv_close(cd);
+						}
+
+						cd = iconv_open(enc, tmpbuf);

-							/* Look ahead to check for the terminating = that
-							 * should be there as well; if it's there, we'll
-							 * also include that. If it's not, there isn't much
-							 * we can do at this point. */
-							if (*(p1 + 1) == '=') {
-								++p1;
-								if (str_left > 1) {
+						if (cd == (iconv_t)(-1)) {
+							if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+								/* Bad character set, but the user wants us to
+								 * press on. In this case, we'll just insert the
+								 * undecoded encoded word, since there isn't really
+								 * a more sensible behaviour available; the only
+								 * other options are to swallow the encoded word
+								 * entirely or decode it with an arbitrarily chosen
+								 * single byte encoding, both of which seem to have
+								 * a higher WTF factor than leaving it undecoded.
+								 *
+								 * Given this approach, we need to skip ahead to
+								 * the end of the encoded word. */
+								int qmarks = 2;
+								while (qmarks > 0 && str_left > 1) {
+									if (*(++p1) == '?') {
+										--qmarks;
+									}
 									--str_left;
 								}
-							}

-							err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-							if (err != PHP_ICONV_ERR_SUCCESS) {
-								goto out;
-							}
+								/* Look ahead to check for the terminating = that
+								 * should be there as well; if it's there, we'll
+								 * also include that. If it's not, there isn't much
+								 * we can do at this point. */
+								if (*(p1 + 1) == '=') {
+									++p1;
+									if (str_left > 1) {
+										--str_left;
+									}
+								}

-							/* Let's go back and see if there are further
-							 * encoded words or bare content, and hope they
-							 * might actually have a valid character set. */
-							scan_stat = 12;
-							break;
-						} else {
-							if (errno == EINVAL) {
-								err = PHP_ICONV_ERR_WRONG_CHARSET;
+								err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+								if (err != PHP_ICONV_ERR_SUCCESS) {
+									goto out;
+								}
+
+								/* Let's go back and see if there are further
+								 * encoded words or bare content, and hope they
+								 * might actually have a valid character set. */
+								scan_stat = 12;
+								break;
 							} else {
-								err = PHP_ICONV_ERR_CONVERTER;
+								if (errno == EINVAL) {
+									err = PHP_ICONV_ERR_WRONG_CHARSET;
+								} else {
+									err = PHP_ICONV_ERR_CONVERTER;
+								}
+								goto out;
 							}
-							goto out;
 						}
 					}
-				}
-				break;
+					break;

-			case 3: /* expecting a encoding scheme specifier */
-				switch (*p1) {
-					case 'b':
-					case 'B':
-						enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64;
-						scan_stat = 4;
-						break;
+				case 3: /* expecting a encoding scheme specifier */
+					switch (*p1) {
+						case 'b':
+						case 'B':
+							enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64;
+							scan_stat = 4;
+							break;

-					case 'q':
-					case 'Q':
-						enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT;
-						scan_stat = 4;
-						break;
+						case 'q':
+						case 'Q':
+							enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT;
+							scan_stat = 4;
+							break;

-					default:
+						default:
+							if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+								err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+								if (err != PHP_ICONV_ERR_SUCCESS) {
+									goto out;
+								}
+								encoded_word = NULL;
+								if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+									scan_stat = 12;
+								} else {
+									scan_stat = 0;
+								}
+								break;
+							} else {
+								err = PHP_ICONV_ERR_MALFORMED;
+								goto out;
+							}
+					}
+					break;
+
+				case 4: /* expecting a delimiter */
+					if (*p1 != '?') {
 						if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+							/* pass the entire chunk through the converter */
 							err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
 							if (err != PHP_ICONV_ERR_SUCCESS) {
 								goto out;
@@ -1436,299 +1482,281 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st
 							err = PHP_ICONV_ERR_MALFORMED;
 							goto out;
 						}
-				}
-				break;
-
-			case 4: /* expecting a delimiter */
-				if (*p1 != '?') {
-					if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-						/* pass the entire chunk through the converter */
-						err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-						if (err != PHP_ICONV_ERR_SUCCESS) {
-							goto out;
-						}
-						encoded_word = NULL;
-						if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-							scan_stat = 12;
-						} else {
-							scan_stat = 0;
-						}
-						break;
-					} else {
-						err = PHP_ICONV_ERR_MALFORMED;
-						goto out;
 					}
-				}
-				encoded_text = p1 + 1;
-				scan_stat = 5;
-				break;
-
-			case 5: /* expecting an encoded portion */
-				if (*p1 == '?') {
-					encoded_text_len = (size_t)(p1 - encoded_text);
-					scan_stat = 6;
-				}
-				break;
+					encoded_text = p1 + 1;
+					scan_stat = 5;
+					break;

-			case 7: /* expecting a "\n" character */
-				if (*p1 == '\n') {
-					scan_stat = 8;
-				} else {
-					/* bare CR */
-					_php_iconv_appendc(pretval, '\r', cd_pl);
-					_php_iconv_appendc(pretval, *p1, cd_pl);
-					scan_stat = 0;
-				}
-				break;
+				case 5: /* expecting an encoded portion */
+					if (*p1 == '?') {
+						encoded_text_len = (size_t)(p1 - encoded_text);
+						scan_stat = 6;
+					}
+					break;

-			case 8: /* checking whether the following line is part of a
-					   folded header */
-				if (*p1 != ' ' && *p1 != '\t') {
-					--p1;
-					str_left = 1; /* quit_loop */
+				case 7: /* expecting a "\n" character */
+					if (*p1 == '\n') {
+						scan_stat = 8;
+					} else {
+						/* bare CR */
+						_php_iconv_appendc(pretval, '\r', cd_pl);
+						_php_iconv_appendc(pretval, *p1, cd_pl);
+						scan_stat = 0;
+					}
 					break;
-				}
-				if (encoded_word == NULL) {
-					_php_iconv_appendc(pretval, ' ', cd_pl);
-				}
-				spaces = NULL;
-				scan_stat = 11;
-				break;

-			case 6: /* expecting a End-Of-Chunk character "=" */
-				if (*p1 != '=') {
-					if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-						/* pass the entire chunk through the converter */
-						err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-						if (err != PHP_ICONV_ERR_SUCCESS) {
-							goto out;
-						}
-						encoded_word = NULL;
-						if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-							scan_stat = 12;
-						} else {
-							scan_stat = 0;
-						}
+				case 8: /* checking whether the following line is part of a
+						   folded header */
+					if (*p1 != ' ' && *p1 != '\t') {
+						--p1;
+						str_left = 1; /* quit_loop */
 						break;
-					} else {
-						err = PHP_ICONV_ERR_MALFORMED;
-						goto out;
 					}
-				}
-				scan_stat = 9;
-				if (str_left == 1) {
-					eos = 1;
-				} else {
+					if (encoded_word == NULL) {
+						_php_iconv_appendc(pretval, ' ', cd_pl);
+					}
+					spaces = NULL;
+					scan_stat = 11;
 					break;
-				}
-				/* TODO might want to rearrange logic so this is more obvious */
-				ZEND_FALLTHROUGH;

-			case 9: /* choice point, seeing what to do next.*/
-				switch (*p1) {
-					default:
-						/* Handle non-RFC-compliant formats
-						 *
-						 * RFC2047 requires the character that comes right
-						 * after an encoded word (chunk) to be a whitespace,
-						 * while there are lots of broken implementations that
-						 * generate such malformed headers that don't fulfill
-						 * that requirement.
-						 */
-						if (!eos) {
+				case 6: /* expecting a End-Of-Chunk character "=" */
+					if (*p1 != '=') {
+						if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+							/* pass the entire chunk through the converter */
+							err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+							if (err != PHP_ICONV_ERR_SUCCESS) {
+								goto out;
+							}
+							encoded_word = NULL;
 							if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-								/* pass the entire chunk through the converter */
-								err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-								if (err != PHP_ICONV_ERR_SUCCESS) {
-									goto out;
-								}
 								scan_stat = 12;
-								break;
+							} else {
+								scan_stat = 0;
 							}
+							break;
+						} else {
+							err = PHP_ICONV_ERR_MALFORMED;
+							goto out;
 						}
-						ZEND_FALLTHROUGH;
-
-					case '\r': case '\n': case ' ': case '\t': {
-						zend_string *decoded_text;
-
-						switch (enc_scheme) {
-							case PHP_ICONV_ENC_SCHEME_BASE64:
-								decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len);
-								break;
-
-							case PHP_ICONV_ENC_SCHEME_QPRINT:
-								decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1);
-								break;
-							default:
-								decoded_text = NULL;
-								break;
-						}
+					}
+					scan_stat = 9;
+					if (str_left == 1) {
+						eos = 1;
+					} else {
+						break;
+					}
+					/* TODO might want to rearrange logic so this is more obvious */
+					ZEND_FALLTHROUGH;

-						if (decoded_text == NULL) {
-							if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-								/* pass the entire chunk through the converter */
-								err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
-								if (err != PHP_ICONV_ERR_SUCCESS) {
-									goto out;
-								}
-								encoded_word = NULL;
+				case 9: /* choice point, seeing what to do next.*/
+					switch (*p1) {
+						default:
+							/* Handle non-RFC-compliant formats
+							 *
+							 * RFC2047 requires the character that comes right
+							 * after an encoded word (chunk) to be a whitespace,
+							 * while there are lots of broken implementations that
+							 * generate such malformed headers that don't fulfill
+							 * that requirement.
+							 */
+							if (!eos) {
 								if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+									/* pass the entire chunk through the converter */
+									err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+									if (err != PHP_ICONV_ERR_SUCCESS) {
+										goto out;
+									}
 									scan_stat = 12;
-								} else {
-									scan_stat = 0;
+									break;
 								}
-								break;
-							} else {
-								err = PHP_ICONV_ERR_UNKNOWN;
-								goto out;
 							}
-						}
+							ZEND_FALLTHROUGH;

-						err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd);
-						if (err == PHP_ICONV_ERR_SUCCESS) {
-							err = _php_iconv_appendl(pretval, NULL, 0, cd);
-						}
-						zend_string_release_ex(decoded_text, 0);
+						case '\r': case '\n': case ' ': case '\t': {
+							zend_string *decoded_text;

-						if (err != PHP_ICONV_ERR_SUCCESS) {
-							if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-								/* pass the entire chunk through the converter */
-								err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl);
-								encoded_word = NULL;
-								if (err != PHP_ICONV_ERR_SUCCESS) {
+							switch (enc_scheme) {
+								case PHP_ICONV_ENC_SCHEME_BASE64:
+									decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len);
+									break;
+
+								case PHP_ICONV_ENC_SCHEME_QPRINT:
+									decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1);
+									break;
+								default:
+									decoded_text = NULL;
+									break;
+							}
+
+							if (decoded_text == NULL) {
+								if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+									/* pass the entire chunk through the converter */
+									err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl);
+									if (err != PHP_ICONV_ERR_SUCCESS) {
+										goto out;
+									}
+									encoded_word = NULL;
+									if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+										scan_stat = 12;
+									} else {
+										scan_stat = 0;
+									}
 									break;
+								} else {
+									err = PHP_ICONV_ERR_UNKNOWN;
+									goto out;
 								}
-							} else {
-								goto out;
 							}
-						}

-						if (eos) { /* reached end-of-string. done. */
-							scan_stat = 0;
-							break;
-						}
+							err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd);
+							if (err == PHP_ICONV_ERR_SUCCESS) {
+								err = _php_iconv_appendl(pretval, NULL, 0, cd);
+							}
+							zend_string_release_ex(decoded_text, 0);

-						switch (*p1) {
-							case '\r': /* part of an EOL sequence? */
-								scan_stat = 7;
-								break;
+							if (err != PHP_ICONV_ERR_SUCCESS) {
+								if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+									/* pass the entire chunk through the converter */
+									err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl);
+									encoded_word = NULL;
+									if (err != PHP_ICONV_ERR_SUCCESS) {
+										break;
+									}
+								} else {
+									goto out;
+								}
+							}

-							case '\n':
-								scan_stat = 8;
+							if (eos) { /* reached end-of-string. done. */
+								scan_stat = 0;
 								break;
+							}

-							case '=': /* first letter of an encoded chunk */
-								scan_stat = 1;
-								break;
+							switch (*p1) {
+								case '\r': /* part of an EOL sequence? */
+									scan_stat = 7;
+									break;

-							case ' ': case '\t': /* medial whitespaces */
-								spaces = p1;
-								scan_stat = 11;
-								break;
+								case '\n':
+									scan_stat = 8;
+									break;

-							default: /* first letter of a non-encoded word */
-								_php_iconv_appendc(pretval, *p1, cd_pl);
-								scan_stat = 12;
-								break;
-						}
-					} break;
-				}
-				break;
+								case '=': /* first letter of an encoded chunk */
+									scan_stat = 1;
+									break;

-			case 10: /* expects a language specifier. dismiss it for now */
-				if (*p1 == '?') {
-					scan_stat = 3;
-				}
-				break;
+								case ' ': case '\t': /* medial whitespaces */
+									spaces = p1;
+									scan_stat = 11;
+									break;

-			case 11: /* expecting a chunk of whitespaces */
-				switch (*p1) {
-					case '\r': /* part of an EOL sequence? */
-						scan_stat = 7;
-						break;
+								default: /* first letter of a non-encoded word */
+									_php_iconv_appendc(pretval, *p1, cd_pl);
+									scan_stat = 12;
+									break;
+							}
+						} break;
+					}
+					break;

-					case '\n':
-						scan_stat = 8;
-						break;
+				case 10: /* expects a language specifier. dismiss it for now */
+					if (*p1 == '?') {
+						scan_stat = 3;
+					}
+					break;

-					case '=': /* first letter of an encoded chunk */
-						if (spaces != NULL && encoded_word == NULL) {
-							_php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
-							spaces = NULL;
-						}
-						encoded_word = p1;
-						scan_stat = 1;
-						break;
+				case 11: /* expecting a chunk of whitespaces */
+					switch (*p1) {
+						case '\r': /* part of an EOL sequence? */
+							scan_stat = 7;
+							break;

-					case ' ': case '\t':
-						break;
+						case '\n':
+							scan_stat = 8;
+							break;

-					default: /* first letter of a non-encoded word */
-						if (spaces != NULL) {
-							_php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
-							spaces = NULL;
-						}
-						_php_iconv_appendc(pretval, *p1, cd_pl);
-						encoded_word = NULL;
-						if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-							scan_stat = 12;
-						} else {
-							scan_stat = 0;
-						}
-						break;
-				}
-				break;
+						case '=': /* first letter of an encoded chunk */
+							if (spaces != NULL && encoded_word == NULL) {
+								_php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
+								spaces = NULL;
+							}
+							encoded_word = p1;
+							scan_stat = 1;
+							break;

-			case 12: /* expecting a non-encoded word */
-				switch (*p1) {
-					case '\r': /* part of an EOL sequence? */
-						scan_stat = 7;
-						break;
+						case ' ': case '\t':
+							break;

-					case '\n':
-						scan_stat = 8;
-						break;
+						default: /* first letter of a non-encoded word */
+							if (spaces != NULL) {
+								_php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl);
+								spaces = NULL;
+							}
+							_php_iconv_appendc(pretval, *p1, cd_pl);
+							encoded_word = NULL;
+							if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+								scan_stat = 12;
+							} else {
+								scan_stat = 0;
+							}
+							break;
+					}
+					break;

-					case ' ': case '\t':
-						spaces = p1;
-						scan_stat = 11;
-						break;
+				case 12: /* expecting a non-encoded word */
+					switch (*p1) {
+						case '\r': /* part of an EOL sequence? */
+							scan_stat = 7;
+							break;

-					case '=': /* first letter of an encoded chunk */
-						if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) {
-							encoded_word = p1;
-							scan_stat = 1;
+						case '\n':
+							scan_stat = 8;
 							break;
-						}
-						ZEND_FALLTHROUGH;

-					default:
-						_php_iconv_appendc(pretval, *p1, cd_pl);
-						break;
-				}
-				break;
+						case ' ': case '\t':
+							spaces = p1;
+							scan_stat = 11;
+							break;
+
+						case '=': /* first letter of an encoded chunk */
+							if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) {
+								encoded_word = p1;
+								scan_stat = 1;
+								break;
+							}
+							ZEND_FALLTHROUGH;
+
+						default:
+							_php_iconv_appendc(pretval, *p1, cd_pl);
+							break;
+					}
+					break;
+			}
 		}
-	}
-	switch (scan_stat) {
-		case 0: case 8: case 11: case 12:
-			break;
-		default:
-			if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
-				if (scan_stat == 1) {
-					_php_iconv_appendc(pretval, '=', cd_pl);
+		switch (scan_stat) {
+			case 0: case 8: case 11: case 12:
+				break;
+			default:
+				if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) {
+					if (scan_stat == 1) {
+						_php_iconv_appendc(pretval, '=', cd_pl);
+					}
+					err = PHP_ICONV_ERR_SUCCESS;
+				} else {
+					err = PHP_ICONV_ERR_MALFORMED;
+					goto out;
 				}
-				err = PHP_ICONV_ERR_SUCCESS;
-			} else {
-				err = PHP_ICONV_ERR_MALFORMED;
-				goto out;
-			}
-	}
+		}

-	if (next_pos != NULL) {
-		*next_pos = p1;
-	}
+		if (next_pos != NULL) {
+			*next_pos = p1;
+		}
+
+		smart_str_0(pretval);
+	} zend_catch {
+		bailout = true;
+	} zend_end_try();

-	smart_str_0(pretval);
 out:
 	if (cd != (iconv_t)(-1)) {
 		iconv_close(cd);
@@ -1736,6 +1764,9 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st
 	if (cd_pl != (iconv_t)(-1)) {
 		iconv_close(cd_pl);
 	}
+	if (bailout) {
+		zend_bailout();
+	}
 	return err;
 }
 /* }}} */
diff --git a/ext/iconv/tests/gh17399.phpt b/ext/iconv/tests/gh17399.phpt
new file mode 100644
index 00000000000..b93fc638e84
--- /dev/null
+++ b/ext/iconv/tests/gh17399.phpt
@@ -0,0 +1,13 @@
+--TEST--
+GH-17399 (iconv memory leak with large line-length in iconv_mime_encode)
+--EXTENSIONS--
+iconv
+--FILE--
+<?php
+$options = [
+    'line-length' => PHP_INT_MAX,
+];
+iconv_mime_encode('Subject', 'test', $options);
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d
diff --git a/ext/iconv/tests/gh17399_iconv.phpt b/ext/iconv/tests/gh17399_iconv.phpt
new file mode 100644
index 00000000000..2cdd0e176b2
--- /dev/null
+++ b/ext/iconv/tests/gh17399_iconv.phpt
@@ -0,0 +1,13 @@
+--TEST--
+GH-17399 (iconv() leak on bailout)
+--EXTENSIONS--
+iconv
+--INI--
+memory_limit=2M
+--FILE--
+<?php
+$str = str_repeat('a', 1024 * 1024);
+iconv('UTF-8', 'ISO-8859-1', $str);
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d
diff --git a/ext/iconv/tests/gh17399_mime_decode.phpt b/ext/iconv/tests/gh17399_mime_decode.phpt
new file mode 100644
index 00000000000..95a37d364f3
--- /dev/null
+++ b/ext/iconv/tests/gh17399_mime_decode.phpt
@@ -0,0 +1,13 @@
+--TEST--
+GH-17399 (iconv_mime_decode() leak on bailout)
+--EXTENSIONS--
+iconv
+--INI--
+memory_limit=2M
+--FILE--
+<?php
+$str = str_repeat('a', 1024 * 1024);
+iconv_mime_decode($str, 0, 'UTF-8');
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d
diff --git a/ext/iconv/tests/gh17399_substr.phpt b/ext/iconv/tests/gh17399_substr.phpt
new file mode 100644
index 00000000000..35d791db59f
--- /dev/null
+++ b/ext/iconv/tests/gh17399_substr.phpt
@@ -0,0 +1,13 @@
+--TEST--
+GH-17399 (iconv_substr() leak on bailout)
+--EXTENSIONS--
+iconv
+--INI--
+memory_limit=2M
+--FILE--
+<?php
+$str = str_repeat('a', 1024 * 1024);
+iconv_substr($str, 0, 1024 * 1024, 'UTF-8');
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d