--- embedaddon/php/ext/standard/html.c 2012/02/21 23:48:02 1.1.1.1 +++ embedaddon/php/ext/standard/html.c 2012/05/29 12:34:43 1.1.1.2 @@ -13,22 +13,29 @@ | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Rasmus Lerdorf | - | Jaakko Hyvätti | - | Wez Furlong | + | Jaakko Hyvätti | + | Wez Furlong | + | Gustavo Lopes | +----------------------------------------------------------------------+ */ -/* $Id: html.c,v 1.1.1.1 2012/02/21 23:48:02 misho Exp $ */ +/* $Id: html.c,v 1.1.1.2 2012/05/29 12:34:43 misho Exp $ */ /* * HTML entity resources: * - * http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset2.asp - * http://msdn.microsoft.com/workshop/author/dhtml/reference/charsets/charset3.asp * http://www.unicode.org/Public/MAPPINGS/OBSOLETE/UNI2SGML.TXT * + * XHTML 1.0 DTD * http://www.w3.org/TR/2002/REC-xhtml1-20020801/dtds.html#h-A2 - * + * + * From HTML 4.01 strict DTD: + * http://www.w3.org/TR/html4/HTMLlat1.ent + * http://www.w3.org/TR/html4/HTMLsymbol.ent + * http://www.w3.org/TR/html4/HTMLspecial.ent + * + * HTML 5: + * http://dev.w3.org/html5/spec/Overview.html#named-character-references */ #include "php.h" @@ -37,7 +44,7 @@ #else #include #endif -#include "html.h" +#include "php_standard.h" #include "php_string.h" #include "SAPI.h" #if HAVE_LOCALE_H @@ -47,758 +54,343 @@ #include #endif -#if HAVE_MBSTRING -# include "ext/mbstring/mbstring.h" -ZEND_EXTERN_MODULE_GLOBALS(mbstring) -#endif +#include +#include "html_tables.h" -enum entity_charset { cs_terminator, cs_8859_1, cs_cp1252, - cs_8859_15, cs_utf_8, cs_big5, cs_gb2312, - cs_big5hkscs, cs_sjis, cs_eucjp, cs_koi8r, - cs_cp1251, cs_8859_5, cs_cp866, cs_macroman - }; -typedef const char *const entity_table_t; +/* Macro for disabling flag of translation of non-basic entities where this isn't supported. + * Not appropriate for html_entity_decode/htmlspecialchars_decode */ +#define LIMIT_ALL(all, doctype, charset) do { \ + (all) = (all) && !CHARSET_PARTIAL_SUPPORT((charset)) && ((doctype) != ENT_HTML_DOC_XML1); \ +} while (0) -/* codepage 1252 is a Windows extension to iso-8859-1. */ -static entity_table_t ent_cp_1252[] = { - "euro", NULL, "sbquo", "fnof", "bdquo", "hellip", "dagger", - "Dagger", "circ", "permil", "Scaron", "lsaquo", "OElig", - NULL, NULL, NULL, NULL, "lsquo", "rsquo", "ldquo", "rdquo", - "bull", "ndash", "mdash", "tilde", "trade", "scaron", "rsaquo", - "oelig", NULL, NULL, "Yuml" -}; +#define MB_FAILURE(pos, advance) do { \ + *cursor = pos + (advance); \ + *status = FAILURE; \ + return 0; \ +} while (0) -static entity_table_t ent_iso_8859_1[] = { - "nbsp", "iexcl", "cent", "pound", "curren", "yen", "brvbar", - "sect", "uml", "copy", "ordf", "laquo", "not", "shy", "reg", - "macr", "deg", "plusmn", "sup2", "sup3", "acute", "micro", - "para", "middot", "cedil", "sup1", "ordm", "raquo", "frac14", - "frac12", "frac34", "iquest", "Agrave", "Aacute", "Acirc", - "Atilde", "Auml", "Aring", "AElig", "Ccedil", "Egrave", - "Eacute", "Ecirc", "Euml", "Igrave", "Iacute", "Icirc", - "Iuml", "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde", - "Ouml", "times", "Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml", - "Yacute", "THORN", "szlig", "agrave", "aacute", "acirc", - "atilde", "auml", "aring", "aelig", "ccedil", "egrave", - "eacute", "ecirc", "euml", "igrave", "iacute", "icirc", - "iuml", "eth", "ntilde", "ograve", "oacute", "ocirc", "otilde", - "ouml", "divide", "oslash", "ugrave", "uacute", "ucirc", - "uuml", "yacute", "thorn", "yuml" -}; +#define CHECK_LEN(pos, chars_need) ((str_len - (pos)) >= (chars_need)) -static entity_table_t ent_iso_8859_15[] = { - "nbsp", "iexcl", "cent", "pound", "euro", "yen", "Scaron", - "sect", "scaron", "copy", "ordf", "laquo", "not", "shy", "reg", - "macr", "deg", "plusmn", "sup2", "sup3", NULL, /* Zcaron */ - "micro", "para", "middot", NULL, /* zcaron */ "sup1", "ordm", - "raquo", "OElig", "oelig", "Yuml", "iquest", "Agrave", "Aacute", - "Acirc", "Atilde", "Auml", "Aring", "AElig", "Ccedil", "Egrave", - "Eacute", "Ecirc", "Euml", "Igrave", "Iacute", "Icirc", - "Iuml", "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde", - "Ouml", "times", "Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml", - "Yacute", "THORN", "szlig", "agrave", "aacute", "acirc", - "atilde", "auml", "aring", "aelig", "ccedil", "egrave", - "eacute", "ecirc", "euml", "igrave", "iacute", "icirc", - "iuml", "eth", "ntilde", "ograve", "oacute", "ocirc", "otilde", - "ouml", "divide", "oslash", "ugrave", "uacute", "ucirc", - "uuml", "yacute", "thorn", "yuml" -}; +/* valid as single byte character or leading byte */ +#define utf8_lead(c) ((c) < 0x80 || ((c) >= 0xC2 && (c) <= 0xF4)) +/* whether it's actually valid depends on other stuff; + * this macro cannot check for non-shortest forms, surrogates or + * code points above 0x10FFFF */ +#define utf8_trail(c) ((c) >= 0x80 && (c) <= 0xBF) -static entity_table_t ent_uni_338_402[] = { - /* 338 (0x0152) */ - "OElig", "oelig", NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 352 (0x0160) */ - "Scaron", "scaron", NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 376 (0x0178) */ - "Yuml", NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 400 (0x0190) */ - NULL, NULL, "fnof" -}; +#define gb2312_lead(c) ((c) != 0x8E && (c) != 0x8F && (c) != 0xA0 && (c) != 0xFF) +#define gb2312_trail(c) ((c) >= 0xA1 && (c) <= 0xFE) -static entity_table_t ent_uni_spacing[] = { - /* 710 */ - "circ", - /* 711 - 730 */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 731 - 732 */ - NULL, "tilde" -}; +#define sjis_lead(c) ((c) != 0x80 && (c) != 0xA0 && (c) < 0xFD) +#define sjis_trail(c) ((c) >= 0x40 && (c) != 0x7F && (c) < 0xFD) -static entity_table_t ent_uni_greek[] = { - /* 913 */ - "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", - "Iota", "Kappa", "Lambda", "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", - NULL, "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi", "Omega", - /* 938 - 944 are not mapped */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", - "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", - "sigmaf", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", - /* 970 - 976 are not mapped */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - "thetasym", "upsih", - NULL, NULL, NULL, - "piv" -}; +/* {{{ get_next_char + */ +static inline unsigned int get_next_char( + enum entity_charset charset, + const unsigned char *str, + size_t str_len, + size_t *cursor, + int *status) +{ + size_t pos = *cursor; + unsigned int this_char = 0; -static entity_table_t ent_uni_punct[] = { - /* 8194 */ - "ensp", "emsp", NULL, NULL, NULL, NULL, NULL, - "thinsp", NULL, NULL, "zwnj", "zwj", "lrm", "rlm", - NULL, NULL, NULL, "ndash", "mdash", NULL, NULL, NULL, - /* 8216 */ - "lsquo", "rsquo", "sbquo", NULL, "ldquo", "rdquo", "bdquo", NULL, - "dagger", "Dagger", "bull", NULL, NULL, NULL, "hellip", - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "permil", NULL, - /* 8242 */ - "prime", "Prime", NULL, NULL, NULL, NULL, NULL, "lsaquo", "rsaquo", NULL, - NULL, NULL, "oline", NULL, NULL, NULL, NULL, NULL, - "frasl" -}; + *status = SUCCESS; + assert(pos <= str_len); -static entity_table_t ent_uni_euro[] = { - "euro" -}; + if (!CHECK_LEN(pos, 1)) + MB_FAILURE(pos, 1); -static entity_table_t ent_uni_8465_8501[] = { - /* 8465 */ - "image", NULL, NULL, NULL, NULL, NULL, NULL, - /* 8472 */ - "weierp", NULL, NULL, NULL, - /* 8476 */ - "real", NULL, NULL, NULL, NULL, NULL, - /* 8482 */ - "trade", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8501 */ - "alefsym", -}; + switch (charset) { + case cs_utf_8: + { + /* We'll follow strategy 2. from section 3.6.1 of UTR #36: + * "In a reported illegal byte sequence, do not include any + * non-initial byte that encodes a valid character or is a leading + * byte for a valid sequence." */ + unsigned char c; + c = str[pos]; + if (c < 0x80) { + this_char = c; + pos++; + } else if (c < 0xc2) { + MB_FAILURE(pos, 1); + } else if (c < 0xe0) { + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); -static entity_table_t ent_uni_8592_9002[] = { - /* 8592 (0x2190) */ - "larr", "uarr", "rarr", "darr", "harr", NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8608 (0x21a0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8624 (0x21b0) */ - NULL, NULL, NULL, NULL, NULL, "crarr", NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8640 (0x21c0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8656 (0x21d0) */ - "lArr", "uArr", "rArr", "dArr", "hArr", NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8672 (0x21e0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8704 (0x2200) */ - "forall", NULL, "part", "exist", NULL, "empty", NULL, "nabla", - "isin", "notin", NULL, "ni", NULL, NULL, NULL, "prod", - /* 8720 (0x2210) */ - NULL, "sum", "minus", NULL, NULL, NULL, NULL, "lowast", - NULL, NULL, "radic", NULL, NULL, "prop", "infin", NULL, - /* 8736 (0x2220) */ - "ang", NULL, NULL, NULL, NULL, NULL, NULL, "and", - "or", "cap", "cup", "int", NULL, NULL, NULL, NULL, - /* 8752 (0x2230) */ - NULL, NULL, NULL, NULL, "there4", NULL, NULL, NULL, - NULL, NULL, NULL, NULL, "sim", NULL, NULL, NULL, - /* 8768 (0x2240) */ - NULL, NULL, NULL, NULL, NULL, "cong", NULL, NULL, - "asymp", NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8784 (0x2250) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8800 (0x2260) */ - "ne", "equiv", NULL, NULL, "le", "ge", NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8816 (0x2270) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8832 (0x2280) */ - NULL, NULL, "sub", "sup", "nsub", NULL, "sube", "supe", - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8848 (0x2290) */ - NULL, NULL, NULL, NULL, NULL, "oplus", NULL, "otimes", - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8864 (0x22a0) */ - NULL, NULL, NULL, NULL, NULL, "perp", NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8880 (0x22b0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8896 (0x22c0) */ - NULL, NULL, NULL, NULL, NULL, "sdot", NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8912 (0x22d0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8928 (0x22e0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8944 (0x22f0) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8960 (0x2300) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - "lceil", "rceil", "lfloor", "rfloor", NULL, NULL, NULL, NULL, - /* 8976 (0x2310) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - /* 8992 (0x2320) */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, "lang", "rang" -}; + if (!utf8_trail(str[pos + 1])) { + MB_FAILURE(pos, utf8_lead(str[pos + 1]) ? 1 : 2); + } + this_char = ((c & 0x1f) << 6) | (str[pos + 1] & 0x3f); + if (this_char < 0x80) { /* non-shortest form */ + MB_FAILURE(pos, 2); + } + pos += 2; + } else if (c < 0xf0) { + size_t avail = str_len - pos; -static entity_table_t ent_uni_9674[] = { - /* 9674 */ - "loz" -}; + if (avail < 3 || + !utf8_trail(str[pos + 1]) || !utf8_trail(str[pos + 2])) { + if (avail < 2 || utf8_lead(str[pos + 1])) + MB_FAILURE(pos, 1); + else if (avail < 3 || utf8_lead(str[pos + 2])) + MB_FAILURE(pos, 2); + else + MB_FAILURE(pos, 3); + } -static entity_table_t ent_uni_9824_9830[] = { - /* 9824 */ - "spades", NULL, NULL, "clubs", NULL, "hearts", "diams" -}; + this_char = ((c & 0x0f) << 12) | ((str[pos + 1] & 0x3f) << 6) | (str[pos + 2] & 0x3f); + if (this_char < 0x800) { /* non-shortest form */ + MB_FAILURE(pos, 3); + } else if (this_char >= 0xd800 && this_char <= 0xdfff) { /* surrogate */ + MB_FAILURE(pos, 3); + } + pos += 3; + } else if (c < 0xf5) { + size_t avail = str_len - pos; -static entity_table_t ent_koi8r[] = { - "#1105", /* "jo "*/ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, "#1025", /* "JO" */ - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - "#1102", "#1072", "#1073", "#1094", "#1076", "#1077", "#1092", - "#1075", "#1093", "#1080", "#1081", "#1082", "#1083", "#1084", - "#1085", "#1086", "#1087", "#1103", "#1088", "#1089", "#1090", - "#1091", "#1078", "#1074", "#1100", "#1099", "#1079", "#1096", - "#1101", "#1097", "#1095", "#1098", "#1070", "#1040", "#1041", - "#1062", "#1044", "#1045", "#1060", "#1043", "#1061", "#1048", - "#1049", "#1050", "#1051", "#1052", "#1053", "#1054", "#1055", - "#1071", "#1056", "#1057", "#1058", "#1059", "#1046", "#1042", - "#1068", "#1067", "#1047", "#1064", "#1069", "#1065", "#1063", - "#1066" -}; + if (avail < 4 || + !utf8_trail(str[pos + 1]) || !utf8_trail(str[pos + 2]) || + !utf8_trail(str[pos + 3])) { + if (avail < 2 || utf8_lead(str[pos + 1])) + MB_FAILURE(pos, 1); + else if (avail < 3 || utf8_lead(str[pos + 2])) + MB_FAILURE(pos, 2); + else if (avail < 4 || utf8_lead(str[pos + 3])) + MB_FAILURE(pos, 3); + else + MB_FAILURE(pos, 4); + } + + this_char = ((c & 0x07) << 18) | ((str[pos + 1] & 0x3f) << 12) | ((str[pos + 2] & 0x3f) << 6) | (str[pos + 3] & 0x3f); + if (this_char < 0x10000 || this_char > 0x10FFFF) { /* non-shortest form or outside range */ + MB_FAILURE(pos, 4); + } + pos += 4; + } else { + MB_FAILURE(pos, 1); + } + } + break; -static entity_table_t ent_cp_1251[] = { - "#1026", "#1027", "#8218", "#1107", "#8222", "hellip", "dagger", - "Dagger", "euro", "permil", "#1033", "#8249", "#1034", "#1036", - "#1035", "#1039", "#1106", "#8216", "#8217", "#8219", "#8220", - "bull", "ndash", "mdash", NULL, "trade", "#1113", "#8250", - "#1114", "#1116", "#1115", "#1119", "nbsp", "#1038", "#1118", - "#1032", "curren", "#1168", "brvbar", "sect", "#1025", "copy", - "#1028", "laquo", "not", "shy", "reg", "#1031", "deg", "plusmn", - "#1030", "#1110", "#1169", "micro", "para", "middot", "#1105", - "#8470", "#1108", "raquo", "#1112", "#1029", "#1109", "#1111", - "#1040", "#1041", "#1042", "#1043", "#1044", "#1045", "#1046", - "#1047", "#1048", "#1049", "#1050", "#1051", "#1052", "#1053", - "#1054", "#1055", "#1056", "#1057", "#1058", "#1059", "#1060", - "#1061", "#1062", "#1063", "#1064", "#1065", "#1066", "#1067", - "#1068", "#1069", "#1070", "#1071", "#1072", "#1073", "#1074", - "#1075", "#1076", "#1077", "#1078", "#1079", "#1080", "#1081", - "#1082", "#1083", "#1084", "#1085", "#1086", "#1087", "#1088", - "#1089", "#1090", "#1091", "#1092", "#1093", "#1094", "#1095", - "#1096", "#1097", "#1098", "#1099", "#1100", "#1101", "#1102", - "#1103" -}; + case cs_big5: + /* reference http://demo.icu-project.org/icu-bin/convexp?conv=big5 */ + { + unsigned char c = str[pos]; + if (c >= 0x81 && c <= 0xFE) { + unsigned char next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); -static entity_table_t ent_iso_8859_5[] = { - "#1056", "#1057", "#1058", "#1059", "#1060", "#1061", "#1062", - "#1063", "#1064", "#1065", "#1066", "#1067", "#1068", "#1069", - "#1070", "#1071", "#1072", "#1073", "#1074", "#1075", "#1076", - "#1077", "#1078", "#1079", "#1080", "#1081", "#1082", "#1083", - "#1084", "#1085", "#1086", "#1087", "#1088", "#1089", "#1090", - "#1091", "#1092", "#1093", "#1094", "#1095", "#1096", "#1097", - "#1098", "#1099", "#1100", "#1101", "#1102", "#1103", "#1104", - "#1105", "#1106", "#1107", "#1108", "#1109", "#1110", "#1111", - "#1112", "#1113", "#1114", "#1115", "#1116", "#1117", "#1118", - "#1119" -}; + next = str[pos + 1]; -static entity_table_t ent_cp_866[] = { + if ((next >= 0x40 && next <= 0x7E) || + (next >= 0xA1 && next <= 0xFE)) { + this_char = (c << 8) | next; + } else { + MB_FAILURE(pos, 1); + } + pos += 2; + } else { + this_char = c; + pos += 1; + } + } + break; - "#9492", "#9524", "#9516", "#9500", "#9472", "#9532", "#9566", - "#9567", "#9562", "#9556", "#9577", "#9574", "#9568", "#9552", - "#9580", "#9575", "#9576", "#9572", "#9573", "#9561", "#9560", - "#9554", "#9555", "#9579", "#9578", "#9496", "#9484", "#9608", - "#9604", "#9612", "#9616", "#9600", "#1088", "#1089", "#1090", - "#1091", "#1092", "#1093", "#1094", "#1095", "#1096", "#1097", - "#1098", "#1099", "#1100", "#1101", "#1102", "#1103", "#1025", - "#1105", "#1028", "#1108", "#1031", "#1111", "#1038", "#1118", - "#176", "#8729", "#183", "#8730", "#8470", "#164", "#9632", - "#160" -}; + case cs_big5hkscs: + { + unsigned char c = str[pos]; + if (c >= 0x81 && c <= 0xFE) { + unsigned char next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); -/* MacRoman has a couple of low-ascii chars that need mapping too */ -/* Vertical tab (ASCII 11) is often used to store line breaks inside */ -/* DB exports, this mapping changes it to a space */ -static entity_table_t ent_macroman[] = { - "sp", NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, "quot", NULL, - NULL, NULL, "amp", NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, "lt", NULL, "gt", NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, "Auml", "Aring", "Ccedil", "Eacute", "Ntilde", "Ouml", - "Uuml", "aacute", "agrave", "acirc", "auml", "atilde", "aring", - "ccedil", "eacute", "egrave", "ecirc", "euml", "iacute", "igrave", - "icirc", "iuml", "ntilde", "oacute", "ograve", "ocirc", "ouml", - "otilde", "uacute", "ugrave", "ucirc", "uuml", "dagger", "deg", - "cent", "pound", "sect", "bull", "para", "szlig", "reg", - "copy", "trade", "acute", "uml", "ne", "AElig", "Oslash", - "infin", "plusmn", "le", "ge", "yen", "micro", "part", - "sum", "prod", "pi", "int", "ordf", "ordm", "Omega", - "aelig", "oslash", "iquest", "iexcl", "not", "radic", "fnof", - "asymp", "#8710", "laquo", "raquo", "hellip", "nbsp", "Agrave", - "Atilde", "Otilde", "OElig", "oelig", "ndash", "mdash", "ldquo", - "rdquo", "lsquo", "rsquo", "divide", "loz", "yuml", "Yuml", - "frasl", "euro", "lsaquo", "rsaquo", "#xFB01", "#xFB02", "Dagger", - "middot", "sbquo", "bdquo", "permil", "Acirc", "Ecirc", "Aacute", - "Euml", "Egrave", "Iacute", "Icirc", "Iuml", "Igrave", "Oacute", - "Ocirc", "#xF8FF", "Ograve", "Uacute", "Ucirc", "Ugrave", "#305", - "circ", "tilde", "macr", "#728", "#729", "#730", "cedil", - "#733", "#731", "#711" -}; + next = str[pos + 1]; -struct html_entity_map { - enum entity_charset charset; /* charset identifier */ - unsigned int basechar; /* char code at start of table */ - unsigned int endchar; /* last char code in the table */ - entity_table_t *table; /* the table of mappings */ -}; + if ((next >= 0x40 && next <= 0x7E) || + (next >= 0xA1 && next <= 0xFE)) { + this_char = (c << 8) | next; + } else if (next != 0x80 && next != 0xFF) { + MB_FAILURE(pos, 1); + } else { + MB_FAILURE(pos, 2); + } + pos += 2; + } else { + this_char = c; + pos += 1; + } + } + break; -static const struct html_entity_map entity_map[] = { - { cs_cp1252, 0x80, 0x9f, ent_cp_1252 }, - { cs_cp1252, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_8859_1, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_8859_15, 0xa0, 0xff, ent_iso_8859_15 }, - { cs_utf_8, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_utf_8, 338, 402, ent_uni_338_402 }, - { cs_utf_8, 710, 732, ent_uni_spacing }, - { cs_utf_8, 913, 982, ent_uni_greek }, - { cs_utf_8, 8194, 8260, ent_uni_punct }, - { cs_utf_8, 8364, 8364, ent_uni_euro }, - { cs_utf_8, 8465, 8501, ent_uni_8465_8501 }, - { cs_utf_8, 8592, 9002, ent_uni_8592_9002 }, - { cs_utf_8, 9674, 9674, ent_uni_9674 }, - { cs_utf_8, 9824, 9830, ent_uni_9824_9830 }, - { cs_big5, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_gb2312, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_big5hkscs, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_sjis, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_eucjp, 0xa0, 0xff, ent_iso_8859_1 }, - { cs_koi8r, 0xa3, 0xff, ent_koi8r }, - { cs_cp1251, 0x80, 0xff, ent_cp_1251 }, - { cs_8859_5, 0xc0, 0xff, ent_iso_8859_5 }, - { cs_cp866, 0xc0, 0xff, ent_cp_866 }, - { cs_macroman, 0x0b, 0xff, ent_macroman }, - { cs_terminator } -}; + case cs_gb2312: /* EUC-CN */ + { + unsigned char c = str[pos]; + if (c >= 0xA1 && c <= 0xFE) { + unsigned char next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); -static const struct { - const char *codeset; - enum entity_charset charset; -} charset_map[] = { - { "ISO-8859-1", cs_8859_1 }, - { "ISO8859-1", cs_8859_1 }, - { "ISO-8859-15", cs_8859_15 }, - { "ISO8859-15", cs_8859_15 }, - { "utf-8", cs_utf_8 }, - { "cp1252", cs_cp1252 }, - { "Windows-1252", cs_cp1252 }, - { "1252", cs_cp1252 }, - { "BIG5", cs_big5 }, - { "950", cs_big5 }, - { "GB2312", cs_gb2312 }, - { "936", cs_gb2312 }, - { "BIG5-HKSCS", cs_big5hkscs }, - { "Shift_JIS", cs_sjis }, - { "SJIS", cs_sjis }, - { "932", cs_sjis }, - { "EUCJP", cs_eucjp }, - { "EUC-JP", cs_eucjp }, - { "KOI8-R", cs_koi8r }, - { "koi8-ru", cs_koi8r }, - { "koi8r", cs_koi8r }, - { "cp1251", cs_cp1251 }, - { "Windows-1251", cs_cp1251 }, - { "win-1251", cs_cp1251 }, - { "iso8859-5", cs_8859_5 }, - { "iso-8859-5", cs_8859_5 }, - { "cp866", cs_cp866 }, - { "866", cs_cp866 }, - { "ibm866", cs_cp866 }, - { "MacRoman", cs_macroman }, - { NULL } -}; + next = str[pos + 1]; -static const struct { - unsigned short charcode; - char *entity; - int entitylen; - int flags; -} basic_entities[] = { - { '"', """, 6, ENT_HTML_QUOTE_DOUBLE }, - { '\'', "'", 6, ENT_HTML_QUOTE_SINGLE }, - { '\'', "'", 5, ENT_HTML_QUOTE_SINGLE }, - { '<', "<", 4, 0 }, - { '>', ">", 4, 0 }, - { 0, NULL, 0, 0 } -}; - -struct basic_entities_dec { - unsigned short charcode; - char entity[8]; - int entitylen; -}; - -#define MB_RETURN { \ - *newpos = pos; \ - mbseq[mbpos] = '\0'; \ - *mbseqlen = mbpos; \ - return this_char; } - -#define MB_WRITE(mbchar) { \ - mbspace--; \ - if (mbspace == 0) { \ - MB_RETURN; \ - } \ - mbseq[mbpos++] = (mbchar); } + if (gb2312_trail(next)) { + this_char = (c << 8) | next; + } else if (gb2312_lead(next)) { + MB_FAILURE(pos, 1); + } else { + MB_FAILURE(pos, 2); + } + pos += 2; + } else if (gb2312_lead(c)) { + this_char = c; + pos += 1; + } else { + MB_FAILURE(pos, 1); + } + } + break; -/* skip one byte and return */ -#define MB_FAILURE(pos) do { \ - *newpos = pos + 1; \ - *status = FAILURE; \ - return 0; \ -} while (0) + case cs_sjis: + { + unsigned char c = str[pos]; + if ((c >= 0x81 && c <= 0x9F) || (c >= 0xE0 && c <= 0xFC)) { + unsigned char next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); -#define CHECK_LEN(pos, chars_need) \ - if (chars_need < 1) { \ - if((str_len - (pos)) < chars_need) { \ - *newpos = pos; \ - *status = FAILURE; \ - return 0; \ - } \ - } else { \ - if((str_len - (pos)) < chars_need) { \ - *newpos = pos + 1; \ - *status = FAILURE; \ - return 0; \ - } \ - } + next = str[pos + 1]; -/* {{{ get_next_char - */ -inline static unsigned int get_next_char(enum entity_charset charset, - unsigned char * str, - int str_len, - int * newpos, - unsigned char * mbseq, - int * mbseqlen, - int *status) -{ - int pos = *newpos; - int mbpos = 0; - int mbspace = *mbseqlen; - unsigned int this_char = 0; - unsigned char next_char; + if (sjis_trail(next)) { + this_char = (c << 8) | next; + } else if (sjis_lead(next)) { + MB_FAILURE(pos, 1); + } else { + MB_FAILURE(pos, 2); + } + pos += 2; + } else if (c < 0x80 || (c >= 0xA1 && c <= 0xDF)) { + this_char = c; + pos += 1; + } else { + MB_FAILURE(pos, 1); + } + } + break; - *status = SUCCESS; + case cs_eucjp: + { + unsigned char c = str[pos]; - if (mbspace <= 0) { - *mbseqlen = 0; - CHECK_LEN(pos, 1); - *newpos = pos + 1; - return str[pos]; - } + if (c >= 0xA1 && c <= 0xFE) { + unsigned next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); + next = str[pos + 1]; - switch (charset) { - case cs_utf_8: - { - unsigned char c; - CHECK_LEN(pos, 1); - c = str[pos]; - if (c < 0x80) { - MB_WRITE(c); - this_char = c; - pos++; - } else if (c < 0xc2) { - MB_FAILURE(pos); - } else if (c < 0xe0) { - CHECK_LEN(pos, 2); - if (str[pos + 1] < 0x80 || str[pos + 1] > 0xbf) { - MB_FAILURE(pos); - } - this_char = ((c & 0x1f) << 6) | (str[pos + 1] & 0x3f); - if (this_char < 0x80) { - MB_FAILURE(pos); - } - MB_WRITE((unsigned char)c); - MB_WRITE((unsigned char)str[pos + 1]); - pos += 2; - } else if (c < 0xf0) { - CHECK_LEN(pos, 3); - if (str[pos + 1] < 0x80 || str[pos + 1] > 0xbf) { - MB_FAILURE(pos); - } - if (str[pos + 2] < 0x80 || str[pos + 2] > 0xbf) { - MB_FAILURE(pos); - } - this_char = ((c & 0x0f) << 12) | ((str[pos + 1] & 0x3f) << 6) | (str[pos + 2] & 0x3f); - if (this_char < 0x800) { - MB_FAILURE(pos); - } else if (this_char >= 0xd800 && this_char <= 0xdfff) { - MB_FAILURE(pos); - } - MB_WRITE((unsigned char)c); - MB_WRITE((unsigned char)str[pos + 1]); - MB_WRITE((unsigned char)str[pos + 2]); - pos += 3; - } else if (c < 0xf5) { - CHECK_LEN(pos, 4); - if (str[pos + 1] < 0x80 || str[pos + 1] > 0xbf) { - MB_FAILURE(pos); - } - if (str[pos + 2] < 0x80 || str[pos + 2] > 0xbf) { - MB_FAILURE(pos); - } - if (str[pos + 3] < 0x80 || str[pos + 3] > 0xbf) { - MB_FAILURE(pos); - } - this_char = ((c & 0x07) << 18) | ((str[pos + 1] & 0x3f) << 12) | ((str[pos + 2] & 0x3f) << 6) | (str[pos + 3] & 0x3f); - if (this_char < 0x10000 || this_char > 0x10FFFF) { - MB_FAILURE(pos); - } - MB_WRITE((unsigned char)c); - MB_WRITE((unsigned char)str[pos + 1]); - MB_WRITE((unsigned char)str[pos + 2]); - MB_WRITE((unsigned char)str[pos + 3]); - pos += 4; + if (next >= 0xA1 && next <= 0xFE) { + /* this a jis kanji char */ + this_char = (c << 8) | next; } else { - MB_FAILURE(pos); + MB_FAILURE(pos, (next != 0xA0 && next != 0xFF) ? 1 : 2); } - } - break; - case cs_big5: - case cs_gb2312: - case cs_big5hkscs: - { - CHECK_LEN(pos, 1); - this_char = str[pos++]; - /* check if this is the first of a 2-byte sequence */ - if (this_char >= 0x81 && this_char <= 0xfe) { - /* peek at the next char */ - CHECK_LEN(pos, 1); - next_char = str[pos++]; - if ((next_char >= 0x40 && next_char <= 0x7e) || - (next_char >= 0xa1 && next_char <= 0xfe)) { - /* yes, this a wide char */ - MB_WRITE(this_char); - MB_WRITE(next_char); - this_char = (this_char << 8) | next_char; - } else { - MB_FAILURE(pos); - } + pos += 2; + } else if (c == 0x8E) { + unsigned next; + if (!CHECK_LEN(pos, 2)) + MB_FAILURE(pos, 1); + + next = str[pos + 1]; + if (next >= 0xA1 && next <= 0xDF) { + /* JIS X 0201 kana */ + this_char = (c << 8) | next; } else { - MB_WRITE(this_char); + MB_FAILURE(pos, (next != 0xA0 && next != 0xFF) ? 1 : 2); } - } - break; - case cs_sjis: - { - CHECK_LEN(pos, 1); - this_char = str[pos++]; - /* check if this is the first of a 2-byte sequence */ - if ((this_char >= 0x81 && this_char <= 0x9f) || - (this_char >= 0xe0 && this_char <= 0xfc)) { - /* peek at the next char */ - CHECK_LEN(pos, 1); - next_char = str[pos++]; - if ((next_char >= 0x40 && next_char <= 0x7e) || - (next_char >= 0x80 && next_char <= 0xfc)) - { - /* yes, this a wide char */ - MB_WRITE(this_char); - MB_WRITE(next_char); - this_char = (this_char << 8) | next_char; - } else { - MB_FAILURE(pos); - } + pos += 2; + } else if (c == 0x8F) { + size_t avail = str_len - pos; + + if (avail < 3 || !(str[pos + 1] >= 0xA1 && str[pos + 1] <= 0xFE) || + !(str[pos + 2] >= 0xA1 && str[pos + 2] <= 0xFE)) { + if (avail < 2 || (str[pos + 1] != 0xA0 && str[pos + 1] != 0xFF)) + MB_FAILURE(pos, 1); + else if (avail < 3 || (str[pos + 2] != 0xA0 && str[pos + 2] != 0xFF)) + MB_FAILURE(pos, 2); + else + MB_FAILURE(pos, 3); } else { - MB_WRITE(this_char); + /* JIS X 0212 hojo-kanji */ + this_char = (c << 16) | (str[pos + 1] << 8) | str[pos + 2]; } - break; + pos += 3; + } else if (c != 0xA0 && c != 0xFF) { + /* character encoded in 1 code unit */ + this_char = c; + pos += 1; + } else { + MB_FAILURE(pos, 1); } - case cs_eucjp: - { - CHECK_LEN(pos, 1); - this_char = str[pos++]; - /* check if this is the first of a multi-byte sequence */ - if (this_char >= 0xa1 && this_char <= 0xfe) { - /* peek at the next char */ - CHECK_LEN(pos, 1); - next_char = str[pos++]; - if (next_char >= 0xa1 && next_char <= 0xfe) { - /* yes, this a jis kanji char */ - MB_WRITE(this_char); - MB_WRITE(next_char); - this_char = (this_char << 8) | next_char; - } else { - MB_FAILURE(pos); - } - } else if (this_char == 0x8e) { - /* peek at the next char */ - CHECK_LEN(pos, 1); - next_char = str[pos++]; - if (next_char >= 0xa1 && next_char <= 0xdf) { - /* JIS X 0201 kana */ - MB_WRITE(this_char); - MB_WRITE(next_char); - this_char = (this_char << 8) | next_char; - } else { - MB_FAILURE(pos); - } - } else if (this_char == 0x8f) { - /* peek at the next two char */ - unsigned char next2_char; - CHECK_LEN(pos, 2); - next_char = str[pos]; - next2_char = str[pos + 1]; - pos += 2; - if ((next_char >= 0xa1 && next_char <= 0xfe) && - (next2_char >= 0xa1 && next2_char <= 0xfe)) { - /* JIS X 0212 hojo-kanji */ - MB_WRITE(this_char); - MB_WRITE(next_char); - MB_WRITE(next2_char); - this_char = (this_char << 16) | (next_char << 8) | next2_char; - } else { - MB_FAILURE(pos); - } - } else { - MB_WRITE(this_char); - } - break; - } - default: - /* single-byte charsets */ - CHECK_LEN(pos, 1); - this_char = str[pos++]; - MB_WRITE(this_char); - break; + } + break; + default: + /* single-byte charsets */ + this_char = str[pos++]; + break; } - MB_RETURN; + + *cursor = pos; + return this_char; } /* }}} */ +/* {{{ php_next_utf8_char + * Public interface for get_next_char used with UTF-8 */ + PHPAPI unsigned int php_next_utf8_char( + const unsigned char *str, + size_t str_len, + size_t *cursor, + int *status) +{ + return get_next_char(cs_utf_8, str, str_len, cursor, status); +} +/* }}} */ + /* {{{ entity_charset determine_charset * returns the charset identifier based on current locale or a hint. - * defaults to iso-8859-1 */ + * defaults to UTF-8 */ static enum entity_charset determine_charset(char *charset_hint TSRMLS_DC) { int i; - enum entity_charset charset = cs_8859_1; + enum entity_charset charset = cs_utf_8; int len = 0; - zval *uf_result = NULL; + const zend_encoding *zenc; - /* Guarantee default behaviour for backwards compatibility */ + /* Default is now UTF-8 */ if (charset_hint == NULL) - return cs_8859_1; + return cs_utf_8; if ((len = strlen(charset_hint)) != 0) { goto det_charset; } -#if HAVE_MBSTRING -#if !defined(COMPILE_DL_MBSTRING) - /* XXX: Ugly things. Why don't we look for a more sophisticated way? */ - switch (MBSTRG(current_internal_encoding)) { - case mbfl_no_encoding_8859_1: - return cs_8859_1; - case mbfl_no_encoding_utf8: - return cs_utf_8; - - case mbfl_no_encoding_euc_jp: - case mbfl_no_encoding_eucjp_win: - return cs_eucjp; - - case mbfl_no_encoding_sjis: - case mbfl_no_encoding_sjis_open: - case mbfl_no_encoding_cp932: - return cs_sjis; - - case mbfl_no_encoding_cp1252: - return cs_cp1252; - - case mbfl_no_encoding_8859_15: - return cs_8859_15; - - case mbfl_no_encoding_big5: - return cs_big5; - - case mbfl_no_encoding_euc_cn: - case mbfl_no_encoding_hz: - case mbfl_no_encoding_cp936: - return cs_gb2312; - - case mbfl_no_encoding_koi8r: - return cs_koi8r; - - case mbfl_no_encoding_cp866: - return cs_cp866; - - case mbfl_no_encoding_cp1251: - return cs_cp1251; - - case mbfl_no_encoding_8859_5: - return cs_8859_5; - - default: - ; - } -#else - { - zval nm_mb_internal_encoding; - - ZVAL_STRING(&nm_mb_internal_encoding, "mb_internal_encoding", 0); - - if (call_user_function_ex(CG(function_table), NULL, &nm_mb_internal_encoding, &uf_result, 0, NULL, 1, NULL TSRMLS_CC) != FAILURE) { - - charset_hint = Z_STRVAL_P(uf_result); - len = Z_STRLEN_P(uf_result); - - if (len == 4) { /* sizeof(none|auto|pass)-1 */ - if (!memcmp("pass", charset_hint, sizeof("pass") - 1) || - !memcmp("auto", charset_hint, sizeof("auto") - 1) || - !memcmp("none", charset_hint, sizeof("none") - 1)) { - - charset_hint = NULL; - len = 0; - } + zenc = zend_multibyte_get_internal_encoding(TSRMLS_C); + if (zenc != NULL) { + charset_hint = (char *)zend_multibyte_get_encoding_name(zenc); + if (charset_hint != NULL && (len=strlen(charset_hint)) != 0) { + if ((len == 4) /* sizeof (none|auto|pass) */ && + (!memcmp("pass", charset_hint, 4) || + !memcmp("auto", charset_hint, 4) || + !memcmp("auto", charset_hint, 4))) { + charset_hint = NULL; + len = 0; + } else { + goto det_charset; } - goto det_charset; } } -#endif -#endif charset_hint = SG(default_charset); if (charset_hint != NULL && (len=strlen(charset_hint)) != 0) { @@ -855,22 +447,21 @@ det_charset: } } if (!found) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "charset `%s' not supported, assuming iso-8859-1", + php_error_docref(NULL TSRMLS_CC, E_WARNING, "charset `%s' not supported, assuming utf-8", charset_hint); } } - if (uf_result != NULL) { - zval_ptr_dtor(&uf_result); - } return charset; } /* }}} */ /* {{{ php_utf32_utf8 */ -size_t php_utf32_utf8(unsigned char *buf, unsigned k) +static inline size_t php_utf32_utf8(unsigned char *buf, unsigned k) { size_t retval = 0; + /* assert(0x0 <= k <= 0x10FFFF); */ + if (k < 0x80) { buf[0] = k; retval = 1; @@ -883,387 +474,957 @@ size_t php_utf32_utf8(unsigned char *buf, unsigned k) buf[1] = 0x80 | ((k >> 6) & 0x3f); buf[2] = 0x80 | (k & 0x3f); retval = 3; - } else if (k < 0x200000) { + } else { buf[0] = 0xf0 | (k >> 18); buf[1] = 0x80 | ((k >> 12) & 0x3f); buf[2] = 0x80 | ((k >> 6) & 0x3f); buf[3] = 0x80 | (k & 0x3f); retval = 4; - } else if (k < 0x4000000) { - buf[0] = 0xf8 | (k >> 24); - buf[1] = 0x80 | ((k >> 18) & 0x3f); - buf[2] = 0x80 | ((k >> 12) & 0x3f); - buf[3] = 0x80 | ((k >> 6) & 0x3f); - buf[4] = 0x80 | (k & 0x3f); - retval = 5; - } else { - buf[0] = 0xfc | (k >> 30); - buf[1] = 0x80 | ((k >> 24) & 0x3f); - buf[2] = 0x80 | ((k >> 18) & 0x3f); - buf[3] = 0x80 | ((k >> 12) & 0x3f); - buf[4] = 0x80 | ((k >> 6) & 0x3f); - buf[5] = 0x80 | (k & 0x3f); - retval = 6; } - buf[retval] = '\0'; + /* UTF-8 has been restricted to max 4 bytes since RFC 3629 */ return retval; } /* }}} */ -/* {{{ php_unescape_html_entities - */ -PHPAPI char *php_unescape_html_entities(unsigned char *old, int oldlen, int *newlen, int all, int quote_style, char *hint_charset TSRMLS_DC) +/* {{{ php_mb2_int_to_char + * Convert back big endian int representation of sequence of one or two 8-bit code units. */ +static inline size_t php_mb2_int_to_char(unsigned char *buf, unsigned k) { - int retlen; - int j, k; - char *replaced, *ret, *p, *q, *lim, *next; - enum entity_charset charset = determine_charset(hint_charset TSRMLS_CC); - unsigned char replacement[15]; - int replacement_len; + assert(k <= 0xFFFFU); + /* one or two bytes */ + if (k <= 0xFFU) { /* 1 */ + buf[0] = k; + return 1U; + } else { /* 2 */ + buf[0] = k >> 8; + buf[1] = k & 0xFFU; + return 2U; + } +} +/* }}} */ - ret = estrndup(old, oldlen); - retlen = oldlen; - if (!retlen) { - goto empty_source; +/* {{{ php_mb3_int_to_char + * Convert back big endian int representation of sequence of one to three 8-bit code units. + * For EUC-JP. */ +static inline size_t php_mb3_int_to_char(unsigned char *buf, unsigned k) +{ + assert(k <= 0xFFFFFFU); + /* one to three bytes */ + if (k <= 0xFFU) { /* 1 */ + buf[0] = k; + return 1U; + } else if (k <= 0xFFFFU) { /* 2 */ + buf[0] = k >> 8; + buf[1] = k & 0xFFU; + return 2U; + } else { + buf[0] = k >> 16; + buf[1] = (k >> 8) & 0xFFU; + buf[2] = k & 0xFFU; + return 3U; } +} +/* }}} */ + + +/* {{{ unimap_bsearc_cmp + * Binary search of unicode code points in unicode <--> charset mapping. + * Returns the code point in the target charset (whose mapping table was given) or 0 if + * the unicode code point is not in the table. + */ +static inline unsigned char unimap_bsearch(const uni_to_enc *table, unsigned code_key_a, size_t num) +{ + const uni_to_enc *l = table, + *h = &table[num-1], + *m; + unsigned short code_key; + + /* we have no mappings outside the BMP */ + if (code_key_a > 0xFFFFU) + return 0; + + code_key = (unsigned short) code_key_a; - if (all) { - /* look for a match in the maps for this charset */ - for (j = 0; entity_map[j].charset != cs_terminator; j++) { - if (entity_map[j].charset != charset) - continue; + while (l <= h) { + m = l + (h - l) / 2; + if (code_key < m->un_code_point) + h = m - 1; + else if (code_key > m->un_code_point) + l = m + 1; + else + return m->cs_code; + } + return 0; +} +/* }}} */ - for (k = entity_map[j].basechar; k <= entity_map[j].endchar; k++) { - unsigned char entity[32]; - int entity_length = 0; +/* {{{ map_from_unicode */ +static inline int map_from_unicode(unsigned code, enum entity_charset charset, unsigned *res) +{ + unsigned char found; + const uni_to_enc *table; + size_t table_size; - if (entity_map[j].table[k - entity_map[j].basechar] == NULL) - continue; + switch (charset) { + case cs_8859_1: + /* identity mapping of code points to unicode */ + if (code > 0xFF) { + return FAILURE; + } + *res = code; + break; - entity_length = slprintf(entity, sizeof(entity), "&%s;", entity_map[j].table[k - entity_map[j].basechar]); - if (entity_length >= sizeof(entity)) { - continue; - } + case cs_8859_5: + if (code <= 0xA0 || code == 0xAD /* soft hyphen */) { + *res = code; + } else if (code == 0x2116) { + *res = 0xF0; /* numero sign */ + } else if (code == 0xA7) { + *res = 0xFD; /* section sign */ + } else if (code >= 0x0401 && code <= 0x044F) { + if (code == 0x040D || code == 0x0450 || code == 0x045D) + return FAILURE; + *res = code - 0x360; + } else { + return FAILURE; + } + break; + + case cs_8859_15: + if (code < 0xA4 || (code > 0xBE && code <= 0xFF)) { + *res = code; + } else { /* between A4 and 0xBE */ + found = unimap_bsearch(unimap_iso885915, + code, sizeof(unimap_iso885915) / sizeof(*unimap_iso885915)); + if (found) + *res = found; + else + return FAILURE; + } + break; - /* When we have MBCS entities in the tables above, this will need to handle it */ - replacement_len = 0; - switch (charset) { - case cs_8859_1: - case cs_cp1252: - case cs_8859_15: - case cs_cp1251: - case cs_8859_5: - case cs_cp866: - case cs_koi8r: - replacement[0] = k; - replacement[1] = '\0'; - replacement_len = 1; - break; + case cs_cp1252: + if (code <= 0x7F || (code >= 0xA0 && code <= 0xFF)) { + *res = code; + } else { + found = unimap_bsearch(unimap_win1252, + code, sizeof(unimap_win1252) / sizeof(*unimap_win1252)); + if (found) + *res = found; + else + return FAILURE; + } + break; - case cs_big5: - case cs_gb2312: - case cs_big5hkscs: - case cs_sjis: - case cs_eucjp: - /* we cannot properly handle those multibyte encodings - * with php_str_to_str. skip it. */ - continue; + case cs_macroman: + if (code == 0x7F) + return FAILURE; + table = unimap_macroman; + table_size = sizeof(unimap_macroman) / sizeof(*unimap_macroman); + goto table_over_7F; + case cs_cp1251: + table = unimap_win1251; + table_size = sizeof(unimap_win1251) / sizeof(*unimap_win1251); + goto table_over_7F; + case cs_koi8r: + table = unimap_koi8r; + table_size = sizeof(unimap_koi8r) / sizeof(*unimap_koi8r); + goto table_over_7F; + case cs_cp866: + table = unimap_cp866; + table_size = sizeof(unimap_cp866) / sizeof(*unimap_cp866); + +table_over_7F: + if (code <= 0x7F) { + *res = code; + } else { + found = unimap_bsearch(table, code, table_size); + if (found) + *res = found; + else + return FAILURE; + } + break; - case cs_utf_8: - replacement_len = php_utf32_utf8(replacement, k); - break; + /* from here on, only map the possible characters in the ASCII range. + * to improve support here, it's a matter of building the unicode mappings. + * See */ + case cs_sjis: + case cs_eucjp: + /* we interpret 0x5C as the Yen symbol. This is not universal. + * See */ + if (code >= 0x20 && code <= 0x7D) { + if (code == 0x5C) + return FAILURE; + *res = code; + } else { + return FAILURE; + } + break; - default: - php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot yet handle MBCS!"); - efree(ret); - return NULL; - } + case cs_big5: + case cs_big5hkscs: + case cs_gb2312: + if (code >= 0x20 && code <= 0x7D) { + *res = code; + } else { + return FAILURE; + } + break; - if (php_memnstr(ret, entity, entity_length, ret+retlen)) { - replaced = php_str_to_str(ret, retlen, entity, entity_length, replacement, replacement_len, &retlen); - efree(ret); - ret = replaced; - } + default: + return FAILURE; + } + + return SUCCESS; +} +/* }}} */ + +/* {{{ */ +static inline void map_to_unicode(unsigned code, const enc_to_uni *table, unsigned *res) +{ + /* only single byte encodings are currently supported; assumed code <= 0xFF */ + *res = table->inner[ENT_ENC_TO_UNI_STAGE1(code)]->uni_cp[ENT_ENC_TO_UNI_STAGE2(code)]; +} +/* }}} */ + +/* {{{ unicode_cp_is_allowed */ +static inline int unicode_cp_is_allowed(unsigned uni_cp, int document_type) +{ + /* XML 1.0 HTML 4.01 HTML 5 + * 0x09..0x0A 0x09..0x0A 0x09..0x0A + * 0x0D 0x0D 0x0C..0x0D + * 0x0020..0xD7FF 0x20..0x7E 0x20..0x7E + * 0x00A0..0xD7FF 0x00A0..0xD7FF + * 0xE000..0xFFFD 0xE000..0x10FFFF 0xE000..0xFDCF + * 0x010000..0x10FFFF 0xFDF0..0x10FFFF (*) + * + * (*) exclude code points where ((code & 0xFFFF) >= 0xFFFE) + * + * References: + * XML 1.0: + * HTML 4.01: + * HTML 5: + * + * Not sure this is the relevant part for HTML 5, though. I opted to + * disallow the characters that would result in a parse error when + * preprocessing of the input stream. See also section 8.1.3. + * + * It's unclear if XHTML 1.0 allows C1 characters. I'll opt to apply to + * XHTML 1.0 the same rules as for XML 1.0. + * See . + */ + + switch (document_type) { + case ENT_HTML_DOC_HTML401: + return (uni_cp >= 0x20 && uni_cp <= 0x7E) || + (uni_cp == 0x0A || uni_cp == 0x09 || uni_cp == 0x0D) || + (uni_cp >= 0xA0 && uni_cp <= 0xD7FF) || + (uni_cp >= 0xE000 && uni_cp <= 0x10FFFF); + case ENT_HTML_DOC_HTML5: + return (uni_cp >= 0x20 && uni_cp <= 0x7E) || + (uni_cp >= 0x09 && uni_cp <= 0x0D && uni_cp != 0x0B) || /* form feed U+0C allowed */ + (uni_cp >= 0xA0 && uni_cp <= 0xD7FF) || + (uni_cp >= 0xE000 && uni_cp <= 0x10FFFF && + ((uni_cp & 0xFFFF) < 0xFFFE) && /* last two of each plane (nonchars) disallowed */ + (uni_cp < 0xFDD0 || uni_cp > 0xFDEF)); /* U+FDD0-U+FDEF (nonchars) disallowed */ + case ENT_HTML_DOC_XHTML: + case ENT_HTML_DOC_XML1: + return (uni_cp >= 0x20 && uni_cp <= 0xD7FF) || + (uni_cp == 0x0A || uni_cp == 0x09 || uni_cp == 0x0D) || + (uni_cp >= 0xE000 && uni_cp <= 0x10FFFF && uni_cp != 0xFFFE && uni_cp != 0xFFFF); + default: + return 1; + } +} +/* }}} */ + +/* {{{ unicode_cp_is_allowed */ +static inline int numeric_entity_is_allowed(unsigned uni_cp, int document_type) +{ + /* less restrictive than unicode_cp_is_allowed */ + switch (document_type) { + case ENT_HTML_DOC_HTML401: + /* all non-SGML characters (those marked with UNUSED in DESCSET) should be + * representable with numeric entities */ + return uni_cp <= 0x10FFFF; + case ENT_HTML_DOC_HTML5: + /* 8.1.4. The numeric character reference forms described above are allowed to + * reference any Unicode code point other than U+0000, U+000D, permanently + * undefined Unicode characters (noncharacters), and control characters other + * than space characters (U+0009, U+000A, U+000C and U+000D) */ + /* seems to allow surrogate characters, then */ + return (uni_cp >= 0x20 && uni_cp <= 0x7E) || + (uni_cp >= 0x09 && uni_cp <= 0x0C && uni_cp != 0x0B) || /* form feed U+0C allowed, but not U+0D */ + (uni_cp >= 0xA0 && uni_cp <= 0x10FFFF && + ((uni_cp & 0xFFFF) < 0xFFFE) && /* last two of each plane (nonchars) disallowed */ + (uni_cp < 0xFDD0 || uni_cp > 0xFDEF)); /* U+FDD0-U+FDEF (nonchars) disallowed */ + case ENT_HTML_DOC_XHTML: + case ENT_HTML_DOC_XML1: + /* OTOH, XML 1.0 requires "character references to match the production for Char + * See */ + return unicode_cp_is_allowed(uni_cp, document_type); + default: + return 1; + } +} +/* }}} */ + +/* {{{ process_numeric_entity + * Auxiliary function to traverse_for_entities. + * On input, *buf should point to the first character after # and on output, it's the last + * byte read, no matter if there was success or insuccess. + */ +static inline int process_numeric_entity(const char **buf, unsigned *code_point) +{ + long code_l; + int hexadecimal = (**buf == 'x' || **buf == 'X'); /* TODO: XML apparently disallows "X" */ + char *endptr; + + if (hexadecimal && (**buf != '\0')) + (*buf)++; + + /* strtol allows whitespace and other stuff in the beginning + * we're not interested */ + if ((hexadecimal && !isxdigit(**buf)) || + (!hexadecimal && !isdigit(**buf))) { + return FAILURE; + } + + code_l = strtol(*buf, &endptr, hexadecimal ? 16 : 10); + /* we're guaranteed there were valid digits, so *endptr > buf */ + *buf = endptr; + + if (**buf != ';') + return FAILURE; + + /* many more are invalid, but that depends on whether it's HTML + * (and which version) or XML. */ + if (code_l > 0x10FFFFL) + return FAILURE; + + if (code_point != NULL) + *code_point = (unsigned)code_l; + + return SUCCESS; +} +/* }}} */ + +/* {{{ process_named_entity */ +static inline int process_named_entity_html(const char **buf, const char **start, size_t *length) +{ + *start = *buf; + + /* "&" is represented by a 0x26 in all supported encodings. That means + * the byte after represents a character or is the leading byte of an + * sequence of 8-bit code units. If in the ranges below, it represents + * necessarily a alpha character because none of the supported encodings + * has an overlap with ASCII in the leading byte (only on the second one) */ + while ((**buf >= 'a' && **buf <= 'z') || + (**buf >= 'A' && **buf <= 'Z') || + (**buf >= '0' && **buf <= '9')) { + (*buf)++; + } + + if (**buf != ';') + return FAILURE; + + /* cast to size_t OK as the quantity is always non-negative */ + *length = *buf - *start; + + if (*length == 0) + return FAILURE; + + return SUCCESS; +} +/* }}} */ + +/* {{{ resolve_named_entity_html */ +static inline int resolve_named_entity_html(const char *start, size_t length, const entity_ht *ht, unsigned *uni_cp1, unsigned *uni_cp2) +{ + const entity_cp_map *s; + ulong hash = zend_inline_hash_func(start, length); + + s = ht->buckets[hash % ht->num_elems]; + while (s->entity) { + if (s->entity_len == length) { + if (memcmp(start, s->entity, length) == 0) { + *uni_cp1 = s->codepoint1; + *uni_cp2 = s->codepoint2; + return SUCCESS; } } + s++; } + return FAILURE; +} +/* }}} */ - for (j = 0; basic_entities[j].charcode != 0; j++) { +static inline size_t write_octet_sequence(unsigned char *buf, enum entity_charset charset, unsigned code) { + /* code is not necessarily a unicode code point */ + switch (charset) { + case cs_utf_8: + return php_utf32_utf8(buf, code); - if (basic_entities[j].flags && (quote_style & basic_entities[j].flags) == 0) - continue; - - replacement[0] = (unsigned char)basic_entities[j].charcode; - replacement[1] = '\0'; + case cs_8859_1: + case cs_cp1252: + case cs_8859_15: + case cs_koi8r: + case cs_cp1251: + case cs_8859_5: + case cs_cp866: + case cs_macroman: + /* single byte stuff */ + *buf = code; + return 1; - if (php_memnstr(ret, basic_entities[j].entity, basic_entities[j].entitylen, ret+retlen)) { - replaced = php_str_to_str(ret, retlen, basic_entities[j].entity, basic_entities[j].entitylen, replacement, 1, &retlen); - efree(ret); - ret = replaced; - } + case cs_big5: + case cs_big5hkscs: + case cs_sjis: + case cs_gb2312: + /* we don't have complete unicode mappings for these yet in entity_decode, + * and we opt to pass through the octet sequences for these in htmlentities + * instead of converting to an int and then converting back. */ +#if 0 + return php_mb2_int_to_char(buf, code); +#else +#ifdef ZEND_DEBUG + assert(code <= 0xFFU); +#endif + *buf = code; + return 1; +#endif + + case cs_eucjp: +#if 0 /* idem */ + return php_mb2_int_to_char(buf, code); +#else +#ifdef ZEND_DEBUG + assert(code <= 0xFFU); +#endif + *buf = code; + return 1; +#endif + + default: + assert(0); + return 0; } +} - /* replace numeric entities & "&" */ - lim = ret + retlen; - for (p = ret, q = ret; p < lim;) { - int code; +/* {{{ traverse_for_entities + * Auxiliary function to php_unescape_html_entities(). + * - The argument "all" determines if all numeric entities are decode or only those + * that correspond to quotes (depending on quote_style). + */ +/* maximum expansion (factor 1.2) for HTML 5 with ≫⃒ and ≪⃒ */ +/* +2 is 1 because of rest (probably unnecessary), 1 because of terminating 0 */ +#define TRAVERSE_FOR_ENTITIES_EXPAND_SIZE(oldlen) ((oldlen) + (oldlen) / 5 + 2) +static void traverse_for_entities( + const char *old, + size_t oldlen, + char *ret, /* should have allocated TRAVERSE_FOR_ENTITIES_EXPAND_SIZE(olden) */ + size_t *retlen, + int all, + int flags, + const entity_ht *inv_map, + enum entity_charset charset) +{ + const char *p, + *lim; + char *q; + int doctype = flags & ENT_HTML_DOC_TYPE_MASK; - if (p[0] == '&') { - if (p + 2 < lim) { - if (p[1] == '#') { - int invalid_code = 0; + lim = old + oldlen; /* terminator address */ + assert(*lim == '\0'); - if (p[2] == 'x' || p[2] == 'X') { - code = strtol(p + 3, &next, 16); - } else { - code = strtol(p + 2, &next, 10); - } + for (p = old, q = ret; p < lim;) { + unsigned code, code2 = 0; + const char *next = NULL; /* when set, next > p, otherwise possible inf loop */ - if ((code == '\'' && !(quote_style & ENT_HTML_QUOTE_SINGLE)) || - (code == '"' && !(quote_style & ENT_HTML_QUOTE_DOUBLE))) { - invalid_code = 1; - } + /* Shift JIS, Big5 and HKSCS use multi-byte encodings where an + * ASCII range byte can be part of a multi-byte sequence. + * However, they start at 0x40, therefore if we find a 0x26 byte, + * we're sure it represents the '&' character. */ - if (next != NULL && *next == ';' && !invalid_code) { - switch (charset) { - case cs_utf_8: - q += php_utf32_utf8(q, code); - break; + /* assumes there are no single-char entities */ + if (p[0] != '&' || (p + 3 >= lim)) { + *(q++) = *(p++); + continue; + } - case cs_8859_1: - case cs_8859_5: - case cs_8859_15: - if ((code >= 0x80 && code < 0xa0) || code > 0xff) { - invalid_code = 1; - } else { - *(q++) = code; - } - break; + /* now p[3] is surely valid and is no terminator */ - case cs_cp1252: - if (code > 0xff) { - invalid_code = 1; - } else { - *(q++) = code; - } - break; + /* numerical entity */ + if (p[1] == '#') { + next = &p[2]; + if (process_numeric_entity(&next, &code) == FAILURE) + goto invalid_code; - case cs_cp1251: - case cs_cp866: - case cs_big5: - case cs_big5hkscs: - case cs_sjis: - case cs_eucjp: - if (code >= 0x80) { - invalid_code = 1; - } else { - *(q++) = code; - } - break; + /* If we're in htmlspecialchars_decode, we're only decoding entities + * that represent &, <, >, " and '. Is this one of them? */ + if (!all && (code > 63U || + stage3_table_be_apos_00000[code].data.ent.entity == NULL)) + goto invalid_code; - case cs_gb2312: - if (code >= 0x81) { - invalid_code = 1; - } else { - *(q++) = code; - } - break; + /* are we allowed to decode this entity in this document type? + * HTML 5 is the only that has a character that cannot be used in + * a numeric entity but is allowed literally (U+000D). The + * unoptimized version would be ... || !numeric_entity_is_allowed(code) */ + if (!unicode_cp_is_allowed(code, doctype) || + (doctype == ENT_HTML_DOC_HTML5 && code == 0x0D)) + goto invalid_code; + } else { + const char *start; + size_t ent_len; - default: - /* for backwards compatilibity */ - invalid_code = 1; - break; - } - if (invalid_code) { - for (; p <= next; p++) { - *(q++) = *p; - } - } - p = next + 1; - } else { - *(q++) = *(p++); - *(q++) = *(p++); - } - } else if (p + 4 < lim && - p[1] == 'a' && p[2] == 'm' &&p[3] == 'p' && - p[4] == ';') { - *(q++) = '&'; - p += 5; + next = &p[1]; + start = next; + + if (process_named_entity_html(&next, &start, &ent_len) == FAILURE) + goto invalid_code; + + if (resolve_named_entity_html(start, ent_len, inv_map, &code, &code2) == FAILURE) { + if (doctype == ENT_HTML_DOC_XHTML && ent_len == 4 && start[0] == 'a' + && start[1] == 'p' && start[2] == 'o' && start[3] == 's') { + /* uses html4 inv_map, which doesn't include apos;. This is a + * hack to support it */ + code = (unsigned) '\''; } else { - *(q++) = *(p++); - *(q++) = *(p++); + goto invalid_code; } - } else { - *(q++) = *(p++); } - } else { - *(q++) = *(p++); } + + assert(*next == ';'); + + if (((code == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) || + (code == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE))) + /* && code2 == '\0' always true for current maps */) + goto invalid_code; + + /* UTF-8 doesn't need mapping (ISO-8859-1 doesn't either, but + * the call is needed to ensure the codepoint <= U+00FF) */ + if (charset != cs_utf_8) { + /* replace unicode code point */ + if (map_from_unicode(code, charset, &code) == FAILURE || code2 != 0) + goto invalid_code; /* not representable in target charset */ + } + + q += write_octet_sequence(q, charset, code); + if (code2) { + q += write_octet_sequence(q, charset, code2); + } + + /* jump over the valid entity; may go beyond size of buffer; np */ + p = next + 1; + continue; + +invalid_code: + for (; p < next; p++) { + *(q++) = *p; + } } + *q = '\0'; - retlen = (size_t)(q - ret); + *retlen = (size_t)(q - ret); +} +/* }}} */ + +/* {{{ unescape_inverse_map */ +static const entity_ht *unescape_inverse_map(int all, int flags) +{ + int document_type = flags & ENT_HTML_DOC_TYPE_MASK; + + if (all) { + switch (document_type) { + case ENT_HTML_DOC_HTML401: + case ENT_HTML_DOC_XHTML: /* but watch out for '...*/ + return &ent_ht_html4; + case ENT_HTML_DOC_HTML5: + return &ent_ht_html5; + default: + return &ent_ht_be_apos; + } + } else { + switch (document_type) { + case ENT_HTML_DOC_HTML401: + return &ent_ht_be_noapos; + default: + return &ent_ht_be_apos; + } + } +} +/* }}} */ + +/* {{{ determine_entity_table + * Entity table to use. Note that entity tables are defined in terms of + * unicode code points */ +static entity_table_opt determine_entity_table(int all, int doctype) +{ + entity_table_opt retval = {NULL}; + + assert(!(doctype == ENT_HTML_DOC_XML1 && all)); + + if (all) { + retval.ms_table = (doctype == ENT_HTML_DOC_HTML5) ? + entity_ms_table_html5 : entity_ms_table_html4; + } else { + retval.table = (doctype == ENT_HTML_DOC_HTML401) ? + stage3_table_be_noapos_00000 : stage3_table_be_apos_00000; + } + return retval; +} +/* }}} */ + +/* {{{ php_unescape_html_entities + * The parameter "all" should be true to decode all possible entities, false to decode + * only the basic ones, i.e., those in basic_entities_ex + the numeric entities + * that correspond to quotes. + */ +PHPAPI char *php_unescape_html_entities(unsigned char *old, size_t oldlen, size_t *newlen, int all, int flags, char *hint_charset TSRMLS_DC) +{ + size_t retlen; + char *ret; + enum entity_charset charset; + const entity_ht *inverse_map = NULL; + size_t new_size = TRAVERSE_FOR_ENTITIES_EXPAND_SIZE(oldlen); + + if (all) { + charset = determine_charset(hint_charset TSRMLS_CC); + } else { + charset = cs_8859_1; /* charset shouldn't matter, use ISO-8859-1 for performance */ + } + + /* don't use LIMIT_ALL! */ + + if (oldlen > new_size) { + /* overflow, refuse to do anything */ + ret = estrndup((char*)old, oldlen); + retlen = oldlen; + goto empty_source; + } + ret = emalloc(new_size); + *ret = '\0'; + retlen = oldlen; + if (retlen == 0) { + goto empty_source; + } + + inverse_map = unescape_inverse_map(all, flags); + + /* replace numeric entities */ + traverse_for_entities(old, oldlen, ret, &retlen, all, flags, inverse_map, charset); + empty_source: *newlen = retlen; return ret; } /* }}} */ -PHPAPI char *php_escape_html_entities(unsigned char *old, int oldlen, int *newlen, int all, int quote_style, char *hint_charset TSRMLS_DC) +PHPAPI char *php_escape_html_entities(unsigned char *old, size_t oldlen, size_t *newlen, int all, int flags, char *hint_charset TSRMLS_DC) { - return php_escape_html_entities_ex(old, oldlen, newlen, all, quote_style, hint_charset, 1 TSRMLS_CC); + return php_escape_html_entities_ex(old, oldlen, newlen, all, flags, hint_charset, 1 TSRMLS_CC); } +/* {{{ find_entity_for_char */ +static inline void find_entity_for_char( + unsigned int k, + enum entity_charset charset, + const entity_stage1_row *table, + const unsigned char **entity, + size_t *entity_len, + unsigned char *old, + size_t oldlen, + size_t *cursor) +{ + unsigned stage1_idx = ENT_STAGE1_INDEX(k); + const entity_stage3_row *c; + + if (stage1_idx > 0x1D) { + *entity = NULL; + *entity_len = 0; + return; + } + c = &table[stage1_idx][ENT_STAGE2_INDEX(k)][ENT_STAGE3_INDEX(k)]; + + if (!c->ambiguous) { + *entity = (const unsigned char *)c->data.ent.entity; + *entity_len = c->data.ent.entity_len; + } else { + /* peek at next char */ + size_t cursor_before = *cursor; + int status = SUCCESS; + unsigned next_char; + + if (!(*cursor < oldlen)) + goto no_suitable_2nd; + + next_char = get_next_char(charset, old, oldlen, cursor, &status); + + if (status == FAILURE) + goto no_suitable_2nd; + + { + const entity_multicodepoint_row *s, *e; + + s = &c->data.multicodepoint_table[1]; + e = s - 1 + c->data.multicodepoint_table[0].leading_entry.size; + /* we could do a binary search but it's not worth it since we have + * at most two entries... */ + for ( ; s <= e; s++) { + if (s->normal_entry.second_cp == next_char) { + *entity = s->normal_entry.entity; + *entity_len = s->normal_entry.entity_len; + return; + } + } + } +no_suitable_2nd: + *cursor = cursor_before; + *entity = (const unsigned char *) + c->data.multicodepoint_table[0].leading_entry.default_entity; + *entity_len = c->data.multicodepoint_table[0].leading_entry.default_entity_len; + } +} +/* }}} */ + +/* {{{ find_entity_for_char_basic */ +static inline void find_entity_for_char_basic( + unsigned int k, + const entity_stage3_row *table, + const unsigned char **entity, + size_t *entity_len) +{ + if (k >= 64U) { + *entity = NULL; + *entity_len = 0; + return; + } + + *entity = table[k].data.ent.entity; + *entity_len = table[k].data.ent.entity_len; +} +/* }}} */ + /* {{{ php_escape_html_entities */ -PHPAPI char *php_escape_html_entities_ex(unsigned char *old, int oldlen, int *newlen, int all, int quote_style, char *hint_charset, zend_bool double_encode TSRMLS_DC) +PHPAPI char *php_escape_html_entities_ex(unsigned char *old, size_t oldlen, size_t *newlen, int all, int flags, char *hint_charset, zend_bool double_encode TSRMLS_DC) { - int i, j, maxlen, len; + size_t cursor, maxlen, len; char *replaced; enum entity_charset charset = determine_charset(hint_charset TSRMLS_CC); - int matches_map; + int doctype = flags & ENT_HTML_DOC_TYPE_MASK; + entity_table_opt entity_table; + const enc_to_uni *to_uni_table = NULL; + const entity_ht *inv_map = NULL; /* used for !double_encode */ + /* only used if flags includes ENT_HTML_IGNORE_ERRORS or ENT_HTML_SUBSTITUTE_DISALLOWED_CHARS */ + const unsigned char *replacement; + size_t replacement_len; - maxlen = 2 * oldlen; - if (maxlen < 128) - maxlen = 128; - replaced = emalloc (maxlen); + if (all) { /* replace with all named entities */ + if (CHARSET_PARTIAL_SUPPORT(charset)) { + php_error_docref0(NULL TSRMLS_CC, E_STRICT, "Only basic entities " + "substitution is supported for multi-byte encodings other than UTF-8; " + "functionality is equivalent to htmlspecialchars"); + } + LIMIT_ALL(all, doctype, charset); + } + entity_table = determine_entity_table(all, doctype); + if (all && !CHARSET_UNICODE_COMPAT(charset)) { + to_uni_table = enc_to_uni_index[charset]; + } + + if (!double_encode) { + /* first arg is 1 because we want to identify valid named entities + * even if we are only encoding the basic ones */ + inv_map = unescape_inverse_map(1, flags); + } + + if (flags & (ENT_HTML_SUBSTITUTE_ERRORS | ENT_HTML_SUBSTITUTE_DISALLOWED_CHARS)) { + if (charset == cs_utf_8) { + replacement = (const unsigned char*)"\xEF\xBF\xBD"; + replacement_len = sizeof("\xEF\xBF\xBD") - 1; + } else { + replacement = (const unsigned char*)"�"; + replacement_len = sizeof("�") - 1; + } + } + + /* initial estimate */ + if (oldlen < 64) { + maxlen = 128; + } else { + maxlen = 2 * oldlen; + if (maxlen < oldlen) { + zend_error_noreturn(E_ERROR, "Input string is too long"); + return NULL; + } + } + + replaced = emalloc(maxlen + 1); /* adding 1 is safe: maxlen is even */ len = 0; - i = 0; - while (i < oldlen) { - unsigned char mbsequence[16]; /* allow up to 15 characters in a multibyte sequence */ - int mbseqlen = sizeof(mbsequence); - int status = SUCCESS; - unsigned int this_char = get_next_char(charset, old, oldlen, &i, mbsequence, &mbseqlen, &status); + cursor = 0; + while (cursor < oldlen) { + const unsigned char *mbsequence = NULL; + size_t mbseqlen = 0, + cursor_before = cursor; + int status = SUCCESS; + unsigned int this_char = get_next_char(charset, old, oldlen, &cursor, &status); - if(status == FAILURE) { + /* guarantee we have at least 40 bytes to write. + * In HTML5, entities may take up to 33 bytes */ + if (len > maxlen - 40) { /* maxlen can never be smaller than 128 */ + replaced = safe_erealloc(replaced, maxlen , 1, 128 + 1); + maxlen += 128; + } + + if (status == FAILURE) { /* invalid MB sequence */ - if (quote_style & ENT_HTML_IGNORE_ERRORS) { + if (flags & ENT_HTML_IGNORE_ERRORS) { continue; + } else if (flags & ENT_HTML_SUBSTITUTE_ERRORS) { + memcpy(&replaced[len], replacement, replacement_len); + len += replacement_len; + continue; + } else { + efree(replaced); + *newlen = 0; + return STR_EMPTY_ALLOC(); } - efree(replaced); - if(!PG(display_errors)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid multibyte sequence in argument"); - } - *newlen = 0; - return STR_EMPTY_ALLOC(); + } else { /* SUCCESS */ + mbsequence = &old[cursor_before]; + mbseqlen = cursor - cursor_before; } - matches_map = 0; - if (len + 16 > maxlen) - replaced = erealloc (replaced, maxlen += 128); + if (this_char != '&') { /* no entity on this position */ + const unsigned char *rep = NULL; + size_t rep_len = 0; - if (all) { - /* look for a match in the maps for this charset */ - unsigned char *rep = NULL; + if (((this_char == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) || + (this_char == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE)))) + goto pass_char_through; - - for (j = 0; entity_map[j].charset != cs_terminator; j++) { - if (entity_map[j].charset == charset - && this_char >= entity_map[j].basechar - && this_char <= entity_map[j].endchar) { - rep = (unsigned char*)entity_map[j].table[this_char - entity_map[j].basechar]; - if (rep == NULL) { - /* there is no entity for this position; fall through and - * just output the character itself */ - break; - } - - matches_map = 1; - break; + if (all) { /* false that CHARSET_PARTIAL_SUPPORT(charset) */ + if (to_uni_table != NULL) { + /* !CHARSET_UNICODE_COMPAT therefore not UTF-8; since UTF-8 + * is the only multibyte encoding with !CHARSET_PARTIAL_SUPPORT, + * we're using a single byte encoding */ + map_to_unicode(this_char, to_uni_table, &this_char); + if (this_char == 0xFFFF) /* no mapping; pass through */ + goto pass_char_through; } + /* the cursor may advance */ + find_entity_for_char(this_char, charset, entity_table.ms_table, &rep, + &rep_len, old, oldlen, &cursor); + } else { + find_entity_for_char_basic(this_char, entity_table.table, &rep, &rep_len); } - if (matches_map) { - int l = strlen(rep); - /* increase the buffer size */ - if (len + 2 + l >= maxlen) { - replaced = erealloc(replaced, maxlen += 128); - } - + if (rep != NULL) { replaced[len++] = '&'; - strlcpy(replaced + len, rep, maxlen); - len += l; + memcpy(&replaced[len], rep, rep_len); + len += rep_len; replaced[len++] = ';'; - } - } - if (!matches_map) { - int is_basic = 0; - - if (this_char == '&') { - if (double_encode) { -encode_amp: - memcpy(replaced + len, "&", sizeof("&") - 1); - len += sizeof("&") - 1; - } else { - char *e = memchr(old + i, ';', oldlen - i); - char *s = old + i; - - if (!e || (e - s) > 10) { /* minor optimization to avoid "entities" over 10 chars in length */ - goto encode_amp; + } else { + /* we did not find an entity for this char. + * check for its validity, if its valid pass it unchanged */ + if (flags & ENT_HTML_SUBSTITUTE_DISALLOWED_CHARS) { + if (CHARSET_UNICODE_COMPAT(charset)) { + if (!unicode_cp_is_allowed(this_char, doctype)) { + mbsequence = replacement; + mbseqlen = replacement_len; + } + } else if (to_uni_table) { + if (!all) /* otherwise we already did this */ + map_to_unicode(this_char, to_uni_table, &this_char); + if (!unicode_cp_is_allowed(this_char, doctype)) { + mbsequence = replacement; + mbseqlen = replacement_len; + } } else { - if (*s == '#') { /* numeric entities */ - s++; - /* Hex (Z) */ - if (*s == 'x' || *s == 'X') { - s++; - while (s < e) { - if (!isxdigit((int)*(unsigned char *)s++)) { - goto encode_amp; - } - } - /* Dec (Z)*/ - } else { - while (s < e) { - if (!isdigit((int)*(unsigned char *)s++)) { - goto encode_amp; - } - } - } - } else { /* text entities */ - while (s < e) { - if (!isalnum((int)*(unsigned char *)s++)) { - goto encode_amp; - } - } + /* not a unicode code point, unless, coincidentally, it's in + * the 0x20..0x7D range (except 0x5C in sjis). We know nothing + * about other code points, because we have no tables. Since + * Unicode code points in that range are not disallowed in any + * document type, we could do nothing. However, conversion + * tables frequently map 0x00-0x1F to the respective C0 code + * points. Let's play it safe and admit that's the case */ + if (this_char <= 0x7D && + !unicode_cp_is_allowed(this_char, doctype)) { + mbsequence = replacement; + mbseqlen = replacement_len; } - replaced[len++] = '&'; } } - is_basic = 1; - } else { - for (j = 0; basic_entities[j].charcode != 0; j++) { - if ((basic_entities[j].charcode != this_char) || - (basic_entities[j].flags && - (quote_style & basic_entities[j].flags) == 0)) { - continue; - } - - memcpy(replaced + len, basic_entities[j].entity, basic_entities[j].entitylen); - len += basic_entities[j].entitylen; - - is_basic = 1; - break; - } - } - - if (!is_basic) { - /* a wide char without a named entity; pass through the original sequence */ +pass_char_through: if (mbseqlen > 1) { memcpy(replaced + len, mbsequence, mbseqlen); len += mbseqlen; } else { - replaced[len++] = (unsigned char)this_char; + replaced[len++] = mbsequence[0]; } } + } else { /* this_char == '&' */ + if (double_encode) { +encode_amp: + memcpy(&replaced[len], "&", sizeof("&") - 1); + len += sizeof("&") - 1; + } else { /* no double encode */ + /* check if entity is valid */ + size_t ent_len; /* not counting & or ; */ + /* peek at next char */ + if (old[cursor] == '#') { /* numeric entity */ + unsigned code_point; + int valid; + char *pos = (char*)&old[cursor+1]; + valid = process_numeric_entity((const char **)&pos, &code_point); + if (valid == FAILURE) + goto encode_amp; + if (flags & ENT_HTML_SUBSTITUTE_DISALLOWED_CHARS) { + if (!numeric_entity_is_allowed(code_point, doctype)) + goto encode_amp; + } + ent_len = pos - (char*)&old[cursor]; + } else { /* named entity */ + /* check for vality of named entity */ + const char *start = &old[cursor], + *next = start; + unsigned dummy1, dummy2; + + if (process_named_entity_html(&next, &start, &ent_len) == FAILURE) + goto encode_amp; + if (resolve_named_entity_html(start, ent_len, inv_map, &dummy1, &dummy2) == FAILURE) { + if (!(doctype == ENT_HTML_DOC_XHTML && ent_len == 4 && start[0] == 'a' + && start[1] == 'p' && start[2] == 'o' && start[3] == 's')) { + /* uses html4 inv_map, which doesn't include apos;. This is a + * hack to support it */ + goto encode_amp; + } + } + } + /* checks passed; copy entity to result */ + /* entity size is unbounded, we may need more memory */ + /* at this point maxlen - len >= 40 */ + if (maxlen - len < ent_len + 2 /* & and ; */) { + /* ent_len < oldlen, which is certainly <= SIZE_MAX/2 */ + replaced = safe_erealloc(replaced, maxlen, 1, ent_len + 128 + 1); + maxlen += ent_len + 128; + } + replaced[len++] = '&'; + memcpy(&replaced[len], &old[cursor], ent_len); + len += ent_len; + replaced[len++] = ';'; + cursor += ent_len + 1; + } } } replaced[len] = '\0'; *newlen = len; return replaced; - - } /* }}} */ @@ -1273,17 +1434,17 @@ static void php_html_entities(INTERNAL_FUNCTION_PARAME { char *str, *hint_charset = NULL; int str_len, hint_charset_len = 0; - int len; - long quote_style = ENT_COMPAT; + size_t new_len; + long flags = ENT_COMPAT; char *replaced; zend_bool double_encode = 1; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ls!b", &str, &str_len, "e_style, &hint_charset, &hint_charset_len, &double_encode) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ls!b", &str, &str_len, &flags, &hint_charset, &hint_charset_len, &double_encode) == FAILURE) { return; } - replaced = php_escape_html_entities_ex(str, str_len, &len, all, quote_style, hint_charset, double_encode TSRMLS_CC); - RETVAL_STRINGL(replaced, len, 0); + replaced = php_escape_html_entities_ex(str, str_len, &new_len, all, (int) flags, hint_charset, double_encode TSRMLS_CC); + RETVAL_STRINGL(replaced, (int)new_len, 0); } /* }}} */ @@ -1300,6 +1461,12 @@ void register_html_constants(INIT_FUNC_ARGS) REGISTER_LONG_CONSTANT("ENT_QUOTES", ENT_QUOTES, CONST_PERSISTENT|CONST_CS); REGISTER_LONG_CONSTANT("ENT_NOQUOTES", ENT_NOQUOTES, CONST_PERSISTENT|CONST_CS); REGISTER_LONG_CONSTANT("ENT_IGNORE", ENT_IGNORE, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_SUBSTITUTE", ENT_SUBSTITUTE, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_DISALLOWED", ENT_DISALLOWED, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_HTML401", ENT_HTML401, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_XML1", ENT_XML1, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_XHTML", ENT_XHTML, CONST_PERSISTENT|CONST_CS); + REGISTER_LONG_CONSTANT("ENT_HTML5", ENT_HTML5, CONST_PERSISTENT|CONST_CS); } /* }}} */ @@ -1315,65 +1482,21 @@ PHP_FUNCTION(htmlspecialchars) Convert special HTML entities back to characters */ PHP_FUNCTION(htmlspecialchars_decode) { - char *str, *new_str, *e, *p; - int len, j, i, new_len; + char *str; + int str_len; + size_t new_len = 0; long quote_style = ENT_COMPAT; - struct basic_entities_dec basic_entities_dec[8]; + char *replaced; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &len, "e_style) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, "e_style) == FAILURE) { return; } - new_str = estrndup(str, len); - new_len = len; - e = new_str + new_len; - - if (!(p = memchr(new_str, '&', new_len))) { - RETURN_STRINGL(new_str, new_len, 0); + replaced = php_unescape_html_entities(str, str_len, &new_len, 0 /*!all*/, quote_style, NULL TSRMLS_CC); + if (replaced) { + RETURN_STRINGL(replaced, (int)new_len, 0); } - - for (j = 0, i = 0; basic_entities[i].charcode != 0; i++) { - if (basic_entities[i].flags && !(quote_style & basic_entities[i].flags)) { - continue; - } - basic_entities_dec[j].charcode = basic_entities[i].charcode; - memcpy(basic_entities_dec[j].entity, basic_entities[i].entity, basic_entities[i].entitylen + 1); - basic_entities_dec[j].entitylen = basic_entities[i].entitylen; - j++; - } - basic_entities_dec[j].charcode = '&'; - basic_entities_dec[j].entitylen = sizeof("&") - 1; - memcpy(basic_entities_dec[j].entity, "&", sizeof("&")); - i = j + 1; - - do { - int l = e - p; - - for (j = 0; j < i; j++) { - if (basic_entities_dec[j].entitylen > l) { - continue; - } - if (!memcmp(p, basic_entities_dec[j].entity, basic_entities_dec[j].entitylen)) { - int e_len = basic_entities_dec[j].entitylen - 1; - - *p++ = basic_entities_dec[j].charcode; - memmove(p, p + e_len, (e - p - e_len)); - e -= e_len; - goto done; - } - } - p++; - -done: - if (p >= e) { - break; - } - } while ((p = memchr(p, '&', (e - p)))); - - new_len = e - new_str; - - new_str[new_len] = '\0'; - RETURN_STRINGL(new_str, new_len, 0); + RETURN_FALSE; } /* }}} */ @@ -1382,7 +1505,8 @@ done: PHP_FUNCTION(html_entity_decode) { char *str, *hint_charset = NULL; - int str_len, hint_charset_len = 0, len; + int str_len, hint_charset_len = 0; + size_t new_len = 0; long quote_style = ENT_COMPAT; char *replaced; @@ -1391,9 +1515,9 @@ PHP_FUNCTION(html_entity_decode) return; } - replaced = php_unescape_html_entities(str, str_len, &len, 1, quote_style, hint_charset TSRMLS_CC); + replaced = php_unescape_html_entities(str, str_len, &new_len, 1 /*all*/, quote_style, hint_charset TSRMLS_CC); if (replaced) { - RETURN_STRINGL(replaced, len, 0); + RETURN_STRINGL(replaced, (int)new_len, 0); } RETURN_FALSE; } @@ -1408,86 +1532,168 @@ PHP_FUNCTION(htmlentities) } /* }}} */ -/* {{{ proto array get_html_translation_table([int table [, int quote_style [, string charset_hint]]]) +/* {{{ write_s3row_data */ +static inline void write_s3row_data( + const entity_stage3_row *r, + unsigned orig_cp, + enum entity_charset charset, + zval *arr) +{ + char key[9] = ""; /* two unicode code points in UTF-8 */ + char entity[LONGEST_ENTITY_LENGTH + 2] = {'&'}; + size_t written_k1; + + written_k1 = write_octet_sequence(key, charset, orig_cp); + + if (!r->ambiguous) { + size_t l = r->data.ent.entity_len; + memcpy(&entity[1], r->data.ent.entity, l); + entity[l + 1] = ';'; + add_assoc_stringl_ex(arr, key, written_k1 + 1, entity, l + 2, 1); + } else { + unsigned i, + num_entries; + const entity_multicodepoint_row *mcpr = r->data.multicodepoint_table; + + if (mcpr[0].leading_entry.default_entity != NULL) { + size_t l = mcpr[0].leading_entry.default_entity_len; + memcpy(&entity[1], mcpr[0].leading_entry.default_entity, l); + entity[l + 1] = ';'; + add_assoc_stringl_ex(arr, key, written_k1 + 1, entity, l + 2, 1); + } + num_entries = mcpr[0].leading_entry.size; + for (i = 1; i <= num_entries; i++) { + size_t l, + written_k2; + unsigned uni_cp, + spe_cp; + + uni_cp = mcpr[i].normal_entry.second_cp; + l = mcpr[i].normal_entry.entity_len; + + if (!CHARSET_UNICODE_COMPAT(charset)) { + if (map_from_unicode(uni_cp, charset, &spe_cp) == FAILURE) + continue; /* non representable in this charset */ + } else { + spe_cp = uni_cp; + } + + written_k2 = write_octet_sequence(&key[written_k1], charset, spe_cp); + memcpy(&entity[1], mcpr[i].normal_entry.entity, l); + entity[l + 1] = ';'; + entity[l + 1] = '\0'; + add_assoc_stringl_ex(arr, key, written_k1 + written_k2 + 1, entity, l + 1, 1); + } + } +} +/* }}} */ + +/* {{{ proto array get_html_translation_table([int table [, int flags [, string charset_hint]]]) Returns the internal translation table used by htmlspecialchars and htmlentities */ PHP_FUNCTION(get_html_translation_table) { - long which = HTML_SPECIALCHARS, quote_style = ENT_COMPAT; - unsigned int i; - int j; - unsigned char ind[5]; /* max # of 8-bit code units (4; for UTF-8) + 1 for \0 */ - void *dummy; + long all = HTML_SPECIALCHARS, + flags = ENT_COMPAT; + int doctype; + entity_table_opt entity_table; + const enc_to_uni *to_uni_table; char *charset_hint = NULL; int charset_hint_len; enum entity_charset charset; + /* in this function we have to jump through some loops because we're + * getting the translated table from data structures that are optimized for + * random access, not traversal */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|lls", - &which, "e_style, &charset_hint, &charset_hint_len) == FAILURE) { + &all, &flags, &charset_hint, &charset_hint_len) == FAILURE) { return; } charset = determine_charset(charset_hint TSRMLS_CC); + doctype = flags & ENT_HTML_DOC_TYPE_MASK; + LIMIT_ALL(all, doctype, charset); array_init(return_value); + + entity_table = determine_entity_table(all, doctype); + if (all && !CHARSET_UNICODE_COMPAT(charset)) { + to_uni_table = enc_to_uni_index[charset]; + } - switch (which) { - case HTML_ENTITIES: - for (j = 0; entity_map[j].charset != cs_terminator; j++) { - if (entity_map[j].charset != charset) - continue; - for (i = 0; i <= entity_map[j].endchar - entity_map[j].basechar; i++) { - char buffer[16]; - unsigned k; - size_t written; + if (all) { /* HTML_ENTITIES (actually, any non-zero value for 1st param) */ + const entity_stage1_row *ms_table = entity_table.ms_table; - if (entity_map[j].table[i] == NULL) + if (CHARSET_UNICODE_COMPAT(charset)) { + unsigned i, j, k, + max_i, max_j, max_k; + /* no mapping to unicode required */ + if (CHARSET_SINGLE_BYTE(charset)) { + max_i = 1; max_j = 1; max_k = 64; + } else { + max_i = 0x1E; max_j = 64; max_k = 64; + } + + for (i = 0; i < max_i; i++) { + if (ms_table[i] == empty_stage2_table) continue; - - k = i + entity_map[j].basechar; + for (j = 0; j < max_j; j++) { + if (ms_table[i][j] == empty_stage3_table) + continue; + for (k = 0; k < max_k; k++) { + const entity_stage3_row *r = &ms_table[i][j][k]; + unsigned code; - switch (charset) { - case cs_utf_8: - written = php_utf32_utf8(ind, k); - ind[written] = '\0'; - break; - case cs_big5: - case cs_gb2312: - case cs_big5hkscs: - case cs_sjis: - /* we have no mappings for these, but if we had... */ - /* break through */ - default: /* one byte */ - written = 1; - ind[0] = (unsigned char)k; - ind[1] = '\0'; - break; - } + if (r->data.ent.entity == NULL) + continue; - snprintf(buffer, sizeof(buffer), "&%s;", entity_map[j].table[i]); - if (zend_hash_find(Z_ARRVAL_P(return_value), (const char*)ind, written+1, &dummy) == FAILURE) { - /* in case of the single quote, which is repeated, the first one wins, - * so don't replace the existint mapping */ - add_assoc_string(return_value, (const char*)ind, buffer, 1); + code = ENT_CODE_POINT_FROM_STAGES(i, j, k); + if (((code == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) || + (code == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE)))) + continue; + write_s3row_data(r, code, charset, return_value); + } } } - } - /* break thru */ + } else { + /* we have to iterate through the set of code points for this + * encoding and map them to unicode code points */ + unsigned i; + for (i = 0; i <= 0xFF; i++) { + const entity_stage3_row *r; + unsigned uni_cp; - case HTML_SPECIALCHARS: - add_assoc_stringl(return_value, "&", "&", sizeof("&") - 1, 1); - for (j = 0; basic_entities[j].charcode != 0; j++) { - if (basic_entities[j].flags && (quote_style & basic_entities[j].flags) == 0) - continue; - - ind[0] = (unsigned char)basic_entities[j].charcode; - ind[1] = '\0'; - if (zend_hash_find(Z_ARRVAL_P(return_value), (const char*)ind, 2, &dummy) == FAILURE) { - add_assoc_stringl(return_value, ind, basic_entities[j].entity, - basic_entities[j].entitylen, 1); + /* can be done before mapping, they're invariant */ + if (((i == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) || + (i == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE)))) + continue; + + map_to_unicode(i, to_uni_table, &uni_cp); + r = &ms_table[ENT_STAGE1_INDEX(uni_cp)][ENT_STAGE2_INDEX(uni_cp)][ENT_STAGE3_INDEX(uni_cp)]; + if (r->data.ent.entity == NULL) + continue; + + write_s3row_data(r, i, charset, return_value); } } + } else { + /* we could use sizeof(stage3_table_be_apos_00000) as well */ + unsigned j, + numelems = sizeof(stage3_table_be_noapos_00000) / + sizeof(*stage3_table_be_noapos_00000); - break; + for (j = 0; j < numelems; j++) { + const entity_stage3_row *r = &entity_table.table[j]; + if (r->data.ent.entity == NULL) + continue; + + if (((j == '\'' && !(flags & ENT_HTML_QUOTE_SINGLE)) || + (j == '"' && !(flags & ENT_HTML_QUOTE_DOUBLE)))) + continue; + + /* charset is indifferent, used cs_8859_1 for efficiency */ + write_s3row_data(r, j, cs_8859_1, return_value); + } } } /* }}} */