diff --git a/ext/standard/html.c b/ext/standard/html.c index 075d629f10b6..56b2fa94e03a 100644 --- a/ext/standard/html.c +++ b/ext/standard/html.c @@ -168,7 +168,7 @@ static zend_always_inline unsigned int get_next_char_utf8( } /* {{{ get_next_char */ -static inline unsigned int get_next_char( +static zend_always_inline unsigned int get_next_char( enum entity_charset charset, const unsigned char *str, size_t str_len, @@ -1172,6 +1172,12 @@ static inline void find_entity_for_char_basic( } /* }}} */ +/* ASCII other than the characters htmlspecialchars() can escape */ +static zend_always_inline bool is_non_special_ascii(unsigned char c) +{ + return c < 0x80 && c != '&' && c != '"' && c != '\'' && c != '<' && c != '>'; +} + /* Character classes used by html_verbatim_prefix_len(). The quote classes are the flags * that make the quotes encoded. */ #define HTML_CC_SQUOTE (1<<0) /* ' */ @@ -1332,12 +1338,36 @@ static zend_string *escape_html_entities_from(const unsigned char *old, size_t o replaced = zend_string_alloc(maxlen, 0); memcpy(ZSTR_VAL(replaced), old, cursor); len = cursor; + /* htmlspecialchars() without ENT_DISALLOWED leaves such ASCII as is */ + const bool copy_ascii_runs = !all && !(flags & ENT_HTML_SUBSTITUTE_DISALLOWED_CHARS); + while (cursor < oldlen) { const unsigned char *mbsequence = NULL; size_t mbseqlen = 0, cursor_before = cursor; zend_result status = SUCCESS; - unsigned int this_char = get_next_char(charset, old, oldlen, &cursor, &status); + unsigned int this_char = old[cursor]; + + if (this_char >= 0x80) { + this_char = get_next_char(charset, old, oldlen, &cursor, &status); + } else if (copy_ascii_runs && is_non_special_ascii(this_char)) { + /* The run can be as long as the rest of the input */ + if (maxlen - len < oldlen - cursor + 40) { + replaced = zend_string_safe_realloc(replaced, maxlen, 1, oldlen - cursor + 128, 0); + maxlen += oldlen - cursor + 128; + } + const unsigned char *in = &old[cursor], *const end = &old[oldlen]; + char *out = &ZSTR_VAL(replaced)[len]; + do { + *out++ = *in++; + } while (in < end && is_non_special_ascii(*in)); + len = out - ZSTR_VAL(replaced); + cursor = in - old; + continue; + } else { + /* ASCII is a single byte in every charset */ + cursor++; + } /* guarantee we have at least 40 bytes to write. * In HTML5, entities may take up to 33 bytes */