/* retawq/parser.c - content parsing (HTML, ...)
This file is part of retawq (), a network
client created by Arne Thomassen; retawq is basically released under certain
versions of the GNU General Public License and WITHOUT ANY WARRANTY.
Read the file COPYING for license details, README for program information.
Copyright (C) 2001-2005 Arne Thomassen
*/
#include "stuff.h"
#include "parser.h"
declare_local_i18n_buffer
#if CONFIG_DEBUG
static const_after_init int fd_parsertest;
static char debugstrbuf[STRBUF_SIZE];
#define prsdbg(msg) my_write_str(fd_parsertest, msg)
#endif
static const char strCommentTag[] = "!--", strSingleQuote[] = "'",
strPipe[] = "|", strHref[] = "href", strType[] = "type",
strName[] = "name", strValue[] = "value", strDisabled[] = "disabled",
strTitle[] = "title", strStyle[] = "style", strReadonly[] = "readonly",
strAlt[] = "alt", strLabel[] = "label", strSize[] = "size",
strCenter[] = "center", strP[] = "p", strPi[] = "pi", strOtimes[] = "otimes";
#define strTimes (strOtimes + 1)
static const char strAcute[] = "acute", strCedil[] = "cedil",
strCirc[] = "circ", strTilde[] = "tilde", strUml[] = "uml", strSup[] = "sup";
my_enum1 enum
{ htfNone = 0, htfRequireEndtag = 0x01, htfAllowEndtag = 0x02,
htfForbidEndtag = 0x04, htfForbidPre = 0x08, htfBlock = 0x10,
htfPar = 0x20, htfSoakUpText = 0x40
} my_enum2(unsigned char) tHtmlTagFlags;
/* begin-autogenerated */
#define TAGOFFSET (1)
#define NUM_TAGDATA (66)
static const struct
{ const char* name; /* (sorted in alphabetical order) */
tHtmlTagFlags flags;
} tagdata[NUM_TAGDATA] =
{ { strA, htfRequireEndtag },
{ "address", htfRequireEndtag | htfBlock | htfPar },
{ "area", htfForbidEndtag },
{ "b", htfRequireEndtag },
{ "big", htfRequireEndtag },
{ "blockquote", htfRequireEndtag | htfBlock | htfPar },
{ "body", htfAllowEndtag },
{ "br", htfForbidEndtag },
{ strButton, htfRequireEndtag },
{ "caption", htfRequireEndtag | htfPar },
{ strCenter, htfRequireEndtag | htfBlock | htfPar }, /* deprecated */
{ "cite", htfRequireEndtag },
{ "dd", htfAllowEndtag | htfBlock },
{ "del", htfRequireEndtag },
{ "dfn", htfRequireEndtag },
{ "dir", htfRequireEndtag | htfBlock | htfPar }, /* deprecated */
{ "div", htfRequireEndtag | htfBlock | htfPar },
{ "dl", htfRequireEndtag | htfBlock | htfPar },
{ "dt", htfAllowEndtag | htfBlock },
{ "em", htfRequireEndtag },
{ "fieldset", htfRequireEndtag | htfBlock | htfPar },
{ "font", htfRequireEndtag }, /* deprecated */
{ "form", htfRequireEndtag | htfBlock | htfPar },
{ "frame", htfForbidEndtag | htfBlock },
{ "frameset", htfRequireEndtag | htfBlock | htfPar },
{ "h1", htfRequireEndtag | htfBlock | htfPar },
{ "h2", htfRequireEndtag | htfBlock | htfPar },
{ "h3", htfRequireEndtag | htfBlock | htfPar },
{ "h4", htfRequireEndtag | htfBlock | htfPar },
{ "h5", htfRequireEndtag | htfBlock | htfPar },
{ "h6", htfRequireEndtag | htfBlock | htfPar },
{ "head", htfAllowEndtag | htfForbidPre },
{ "hr", htfForbidEndtag | htfBlock },
{ strHtml, htfAllowEndtag },
{ "i", htfRequireEndtag },
{ "iframe", htfForbidEndtag | htfBlock },
{ "img", htfForbidEndtag },
{ "input", htfForbidEndtag },
{ "li", htfAllowEndtag | htfBlock },
{ "menu", htfRequireEndtag | htfBlock | htfPar }, /* deprecated */
{ "meta", htfForbidEndtag },
{ "noframes", htfRequireEndtag | htfBlock | htfPar },
{ "noscript", htfRequireEndtag | htfBlock | htfPar },
{ "object", htfRequireEndtag },
{ "ol", htfRequireEndtag | htfBlock | htfPar },
{ "optgroup", htfRequireEndtag },
{ "option", htfAllowEndtag | htfSoakUpText | htfForbidPre },
{ strP, htfAllowEndtag | htfBlock | htfPar },
{ "pre", htfRequireEndtag | htfBlock | htfPar },
{ "q", htfRequireEndtag }, /* stolen from J.B. & the next generation :-) */
{ "s", htfRequireEndtag }, /* deprecated */
{ "script", htfRequireEndtag | htfSoakUpText },
{ strSelect, htfRequireEndtag },
{ "strike", htfRequireEndtag }, /* deprecated */
{ "strong", htfRequireEndtag },
{ strStyle, htfRequireEndtag | htfSoakUpText },
{ "sub", htfRequireEndtag },
{ strSup, htfRequireEndtag },
{ "table", htfRequireEndtag | htfBlock | htfPar },
{ "td", htfAllowEndtag },
{ "textarea", htfRequireEndtag | htfSoakUpText },
{ "th", htfAllowEndtag },
{ strTitle, htfRequireEndtag | htfSoakUpText | htfForbidPre },
{ "tr", htfAllowEndtag | htfPar },
{ "u", htfRequireEndtag }, /* deprecated */
{ "ul", htfRequireEndtag | htfBlock | htfPar }
};
#if OPTION_CED > 0
#define NUM_CHENT (141)
#define CED01(ced0, ced1) ced1
#define DECLCED1(name, ced1) { name, ced1 },
#define CODE01(index0, index1) index1
#else
#define NUM_CHENT (89)
#define CED01(ced0, ced1) ced0
#define DECLCED1(name, ced1)
#define CODE01(index0, index1) index0
static const char chentcode_rerouter[] = "AaCcEeIiNnOoUuYy";
#endif
#define MAXLEN_CHENT (6)
static
#if OPTION_CED < 2
const
#else
const_after_init
#endif
struct
{ const char* name; /* (sorted in strcmp() order) */
const char* result;
} chent[NUM_CHENT] =
{ DECLCED1("AElig", "Æ") /*0*/
DECLCED1("Aacute", "Á")
DECLCED1("Acirc", "Â")
DECLCED1("Agrave", "À")
DECLCED1("Aring", "Å")
DECLCED1("Atilde", "Ã") /*5*/
{ "Auml", CED01("Ae", "Ä") },
DECLCED1("Ccedil", "Ç")
{ "Dagger", strHm },
{ "ETH", CED01("DH", "Ð") },
DECLCED1("Eacute", "É") /*10*/
DECLCED1("Ecirc", "Ê")
DECLCED1("Egrave", "È")
DECLCED1("Euml", "Ë")
DECLCED1("Iacute", "Í")
DECLCED1("Icirc", "Î") /*15*/
DECLCED1("Igrave", "Ì")
DECLCED1("Iuml", "Ï")
DECLCED1("Ntilde", "Ñ")
DECLCED1("Oacute", "Ó")
DECLCED1("Ocirc", "Ô") /*20*/
DECLCED1("Ograve", "Ò")
DECLCED1("Oslash", "Ø")
DECLCED1("Otilde", "Õ")
{ "Ouml", CED01("Oe", "Ö") },
{ "Prime", "''" }, /*25*/
{ "THORN", CED01("P", "Þ") },
DECLCED1("Uacute", "Ú")
DECLCED1("Ucirc", "Û")
DECLCED1("Ugrave", "Ù")
{ "Uuml", CED01("Ue", "Ü") }, /*30*/
DECLCED1("Yacute", "Ý")
DECLCED1("aacute", "á")
DECLCED1("acirc", "â")
{ strAcute, CED01(strSingleQuote, "´") },
DECLCED1("aelig", "æ") /*35*/
DECLCED1("agrave", "à")
{ "amp", "&" },
{ "apos", strSingleQuote },
DECLCED1("aring", "å")
DECLCED1("atilde", "ã") /*40*/
{ "auml", CED01("ae", "ä") },
{ "bdquo", strDoubleQuote },
DECLCED1("beta", "ß")
{ "boxv", strPipe },
{ "brkbar", strPipe }, /*45*/
{ "brvbar", strPipe },
{ "bull", "o" },
DECLCED1("ccedil", "ç")
{ strCedil, CED01(",", "¸") },
{ "cent", CED01("-c-", "¢") }, /*50*/
{ strCirc, "^" },
{ "copy", CED01("(C)", "©") },
{ "curren", CED01("CUR", "¤") },
{ "darr", "v" },
{ "deg", CED01("DEG", "°") }, /*55*/
{ "divide", CED01("/", "÷") },
DECLCED1("eacute", "é")
DECLCED1("ecirc", "ê")
DECLCED1("egrave", "è")
{ "eth", CED01("dh", "ð") }, /*60*/
DECLCED1("euml", "ë")
{ "euro", "EUR" }, /* inconsequent, but ISO */
{ "frac12", CED01(" 1/2", "½") },
{ "frac14", CED01(" 1/4", "¼") },
{ "frac34", CED01(" 3/4", "¾") }, /*65*/
{ "frasl", "/" },
{ "ge", ">=" },
{ "gt", strGt },
{ "hArr", "<=>" },
{ "harr", "<->" }, /*70*/
DECLCED1("iacute", "í")
DECLCED1("icirc", "î")
{ "iexcl", CED01("!", "¡") },
DECLCED1("igrave", "ì")
{ "iquest", CED01(strQm, "¿") }, /*75*/
DECLCED1("iuml", "ï")
{ "lArr", "<=" },
{ "lang", strLt },
{ "laquo", strDoubleQuote },
{ "larr", "<-" }, /*80*/
{ "ldquo", strDoubleQuote },
{ "le", "<=" },
{ "lsaquo", strSingleQuote },
{ "lsquo", strSingleQuote },
{ "lt", strLt }, /*85*/
{ "mdash", strMinus },
{ "middot", CED01(".", "·") },
{ "minus", strMinus },
{ "nbsp", strSpace }, /* FIXME! */
{ "ndash", strMinus }, /*90*/
{ "ne", "!=" }, /* CHECKME! */
{ "not", CED01("NOT", "¬") }, /* CHECKME! */
DECLCED1("ntilde", "ñ")
DECLCED1("oacute", "ó")
DECLCED1("ocirc", "ô") /*95*/
DECLCED1("ograve", "ò")
{ "oplus", "+" },
{ "ordf", CED01("-a", "ª") },
{ "ordm", CED01("-o", "º") },
DECLCED1("oslash", "ø") /*100*/
DECLCED1("otilde", "õ")
{ strOtimes, strAsterisk }, /* CHECKME! */
{ "ouml", CED01("oe", "ö") },
{ "para", CED01("par.", "¶") }, /* CHECKME! */
{ "permil", "o/oo" }, /*105*/
{ strPi, strPi },
{ "plusmn", CED01("+/-", "±") },
{ "pound", CED01("-L-", "£") },
{ "prime", strSingleQuote },
{ "quot", strDoubleQuote }, /*110*/
{ "rArr", "=>" },
{ "rang", strGt },
{ "raquo", strDoubleQuote },
{ "rarr", "->" },
{ "rdquo", strDoubleQuote }, /*115*/
{ "reg", CED01("(R)", "®") },
{ "rsaquo", strSingleQuote },
{ "rsquo", strSingleQuote },
{ "sbquo", strSingleQuote },
{ "sdot", CED01(strAsterisk, "·") }, /*120*/ /* CHECKME! */
{ "sect", CED01("sect.", "§") }, /* CHECKME! */
{ "shy", strEmpty },
{ "sim", "~" },
{ "sup1", CED01("^1", "¹") },
{ "sup2", CED01("^2", "²") }, /*125*/
{ "sup3", CED01("^3", "³") },
{ "szlig", CED01("ss", "ß") },
{ "thorn", CED01(strP, "þ") },
{ strTilde, "~" },
{ strTimes, CED01(strAsterisk, "×") }, /*130*/
{ "trade", "(tm)" },
DECLCED1("uacute", "ú")
{ "uarr", "^" },
DECLCED1("ucirc", "û")
DECLCED1("ugrave", "ù") /*135*/
{ strUml, CED01(strSpace, "¨") },
{ "uuml", CED01("ue", "ü") },
DECLCED1("yacute", "ý")
{ "yen", CED01("-Y-", "¥") },
DECLCED1("yuml", "ÿ")
};
typedef unsigned short tEntityCode;
#define NUM_CHENTCODE (139)
static const struct
{ tEntityCode code; /* (sorted in numerical order) */
signed short index; /* chent[] or chentcode_rerouter[] index */
} chentcode[NUM_CHENTCODE] =
{ { 34, CODE01(63, 110) },
{ 38, CODE01(8, 37) },
{ 39, CODE01(9, 38) },
{ 60, CODE01(44, 85) },
{ 62, CODE01(31, 68) },
{ 160, CODE01(48, 89) },
{ 161, CODE01(34, 73) },
{ 162, CODE01(17, 50) },
{ 163, CODE01(61, 108) },
{ 164, CODE01(20, 53) },
{ 165, CODE01(88, 139) },
{ 166, CODE01(14, 46) },
{ 167, CODE01(74, 121) },
{ 168, CODE01(86, 136) },
{ 169, CODE01(19, 52) },
{ 170, CODE01(53, 98) },
{ 171, CODE01(38, 79) },
{ 172, CODE01(51, 92) },
{ 173, CODE01(75, 122) },
{ 174, CODE01(69, 116) },
{ 176, CODE01(22, 55) },
{ 177, CODE01(60, 107) },
{ 178, CODE01(78, 125) },
{ 179, CODE01(79, 126) },
{ 180, CODE01(7, 34) },
{ 182, CODE01(57, 104) },
{ 183, CODE01(46, 87) },
{ 184, CODE01(16, 49) },
{ 185, CODE01(77, 124) },
{ 186, CODE01(54, 99) },
{ 187, CODE01(66, 113) },
{ 188, CODE01(27, 64) },
{ 189, CODE01(26, 63) },
{ 190, CODE01(28, 65) },
{ 191, CODE01(35, 75) },
{ 192, CODE01(-1, 3) },
{ 193, CODE01(-1, 1) },
{ 194, CODE01(-1, 2) },
{ 195, CODE01(-1, 5) },
{ 196, CODE01(0, 6) },
{ 197, CODE01(-1, 4) },
{ 198, CODE01(-1, 0) },
{ 199, CODE01(-3, 7) },
{ 200, CODE01(-5, 12) },
{ 201, CODE01(-5, 10) },
{ 202, CODE01(-5, 11) },
{ 203, CODE01(-5, 13) },
{ 204, CODE01(-7, 16) },
{ 205, CODE01(-7, 14) },
{ 206, CODE01(-7, 15) },
{ 207, CODE01(-7, 17) },
{ 208, CODE01(2, 9) },
{ 209, CODE01(-9, 18) },
{ 210, CODE01(-11, 21) },
{ 211, CODE01(-11, 19) },
{ 212, CODE01(-11, 20) },
{ 213, CODE01(-11, 23) },
{ 214, CODE01(3, 24) },
{ 215, CODE01(83, 130) },
{ 216, CODE01(-11, 22) },
{ 217, CODE01(-13, 29) },
{ 218, CODE01(-13, 27) },
{ 219, CODE01(-13, 28) },
{ 220, CODE01(6, 30) },
{ 221, CODE01(-15, 31) },
{ 222, CODE01(5, 26) },
{ 223, CODE01(80, 127) },
{ 224, CODE01(-2, 36) },
{ 225, CODE01(-2, 32) },
{ 226, CODE01(-2, 33) },
{ 227, CODE01(-2, 40) },
{ 228, CODE01(10, 41) },
{ 229, CODE01(-2, 39) },
{ 230, CODE01(-2, 35) },
{ 231, CODE01(-4, 48) },
{ 232, CODE01(-6, 59) },
{ 233, CODE01(-6, 57) },
{ 234, CODE01(-6, 58) },
{ 235, CODE01(-6, 61) },
{ 236, CODE01(-8, 74) },
{ 237, CODE01(-8, 71) },
{ 238, CODE01(-8, 72) },
{ 239, CODE01(-8, 76) },
{ 240, CODE01(24, 60) },
{ 241, CODE01(-10, 93) },
{ 242, CODE01(-12, 96) },
{ 243, CODE01(-12, 94) },
{ 244, CODE01(-12, 95) },
{ 245, CODE01(-12, 101) },
{ 246, CODE01(56, 103) },
{ 247, CODE01(23, 56) },
{ 248, CODE01(-12, 100) },
{ 249, CODE01(-14, 135) },
{ 250, CODE01(-14, 132) },
{ 251, CODE01(-14, 134) },
{ 252, CODE01(87, 137) },
{ 253, CODE01(-16, 138) },
{ 254, CODE01(81, 128) },
{ 255, CODE01(-16, 140) },
{ 710, CODE01(18, 51) },
{ 732, CODE01(82, 129) },
{ 960, CODE01(59, 106) },
{ 8211, CODE01(49, 90) },
{ 8212, CODE01(45, 86) },
{ 8216, CODE01(43, 84) },
{ 8217, CODE01(71, 118) },
{ 8218, CODE01(72, 119) },
{ 8220, CODE01(40, 81) },
{ 8221, CODE01(68, 115) },
{ 8222, CODE01(11, 42) },
{ 8225, CODE01(1, 8) },
{ 8226, CODE01(15, 47) },
{ 8240, CODE01(58, 105) },
{ 8242, CODE01(62, 109) },
{ 8243, CODE01(4, 25) },
{ 8249, CODE01(42, 83) },
{ 8250, CODE01(70, 117) },
{ 8260, CODE01(29, 66) },
{ 8364, CODE01(25, 62) },
{ 8482, CODE01(84, 131) },
{ 8592, CODE01(39, 80) },
{ 8593, CODE01(85, 133) },
{ 8594, CODE01(67, 114) },
{ 8595, CODE01(21, 54) },
{ 8596, CODE01(33, 70) },
{ 8656, CODE01(36, 77) },
{ 8658, CODE01(64, 111) },
{ 8660, CODE01(32, 69) },
{ 8722, CODE01(47, 88) },
{ 8764, CODE01(76, 123) },
{ 8800, CODE01(50, 91) },
{ 8804, CODE01(41, 82) },
{ 8805, CODE01(30, 67) },
{ 8853, CODE01(52, 97) },
{ 8855, CODE01(55, 102) },
{ 8901, CODE01(73, 120) },
{ 9001, CODE01(37, 78) },
{ 9002, CODE01(65, 112) },
{ 9474, CODE01(12, 44) }
};
/* character entity suffix handling */
enum { csiMax=13, csiDash=9, csiSpace=10 }; /* "csi": chentsuffix[] index */
static const char* const chentsuffix[csiMax + 1] =
{ strAcute, strCedil, strCirc, "grave", "lig", "ring", "slash", strTilde,
strUml, "dash", "sp", "caron", "comma", "cy"
};
static const unsigned char chentsuffixlen[csiMax + 1] =
{ 5, 5, 4, 5, 3, 4, 5, 5, 3, 4, 2, 5, 5, 2 };
static const struct
{ const char* str; /* (sorted in alphabetical order) */
tAttributeName an;
} attrdata[] =
{ { "action", anAction },
{ "align", anAlign },
{ strAlt, anAlt },
{ "checked", anChecked },
{ "class", anClass },
{ "color", anColor },
{ "content", anContent },
{ "declare", anDeclare },
{ strDisabled, anDisabled },
{ "enctype", anEnctype },
{ "face", anFace },
{ strHref, anHref },
{ "http-equiv", anHttpEquiv },
{ "id", anId },
{ strLabel, anLabel },
{ "language", anLanguage },
{ "maxlength", anMaxlength },
{ "media", anMedia },
{ "method", anMethod },
{ "multiple", anMultiple },
{ strName, anName },
{ strReadonly, anReadonly },
{ "selected", anSelected },
{ strSize, anSize },
{ "src", anSrc },
{ strStyle, anStyle },
{ strTitle, anTitle },
{ strType, anType },
{ strValue, anValue },
{ "width", anWidth }
};
/* which HTML attributes need value conversion (bitfield) */
static const unsigned char attrvalueconv[5] = { 106, 12, 145, 251, 1 };
/* end-autogenerated */
enum
{ hpsText = 0, hpsTag = 1, hpsAttrName = 2, hpsEquals = 3, hpsAttrValue = 4,
hpsDone = 5, hpsComment1 = 6, hpsComment2 = 7, hpsComment3 = 8
};
typedef unsigned char tHtmlParserState;
#define MAX_HPS (8)
#if CONFIG_DEBUG
static const char* const hps_name[MAX_HPS + 1] =
{ strText, "tag", "attrname", "=", "attrvalue", "done", "comment1", "comment2",
"comment3"
};
#endif
static tHtmlParserState state;
static tAttributeName current_attr_name;
static tHtmlTagKind current_tagkind;
static char* current_unknown_tagname;
static tAttribute* current_attributes;
static unsigned int bufsize, maxbufsize;
static tBoolean is_endtag, tagblock_ends;
static unsigned char attrvalue_quotes; /* 0=none, 1=single-, 2=double-quotes */
static tCantent* current_cantent;
static tBoolean is_current_node_valid, is_parsing_done, inside_select;
static tHtmlNode *current_node, *current_node_in_tree, *previous_node_in_tree,
*delayed_node, *select_node;
static const char* dataptr;
static char* buf;
static tContentblock *current_block, *lhpp_content;
static size_t current_block_sizeleft, lhpp_byte;
static tActiveElementBase curraebase, select_aebase, *aebase;
static tActiveElementNumber aenum, aemax;
#define IS_WHITESPACE(ch) \
( ((ch) == ' ') || ((ch) == '\t') || ((ch) == '\n') || ((ch) == '\r') )
static void buf_append(const char ch)
{ if (maxbufsize <= bufsize)
{ maxbufsize += 1024;
buf = memory_reallocate(buf, maxbufsize, mapString);
}
buf[bufsize++] = ch;
}
static tHtmlInputLength attr2htmlinputlength(const tAttribute* attr,
tHtmlInputLength _default)
{ tHtmlInputLength retval = _default;
if (attr != NULL)
{ const char* value = attr->value;
if (value != NULL)
{ int l;
my_atoi(value, &l, NULL, MAX_HTML_INPUT_LENGTH + 1);
if (l > MAX_HTML_INPUT_LENGTH) l = MAX_HTML_INPUT_LENGTH;
else if (l < 1) l = 1;
retval = (tHtmlInputLength) l;
}
}
return(retval);
}
static one_caller tMbsIndex do_lookup_tagkind(void)
{ my_binary_search(0, NUM_TAGDATA - 1, strcmp(buf, tagdata[idx].name),
return(idx))
}
static one_caller tHtmlTagKind lookup_tagkind(void)
/* transforms a tag name string (in ) to the corresponding tag kind
number */
{ tMbsIndex idx = do_lookup_tagkind();
if (idx >= 0) return(((tHtmlTagKind) idx) + TAGOFFSET);
else return(htkInvalid);
}
enum { ceisCopy = 0, ceisKind = 1, ceisNumkind = 2, ceisInside = 3 };
typedef unsigned char tCharacterEntityInterpreterState; /* (-: */
enum { ekString = 0, ekDecNumber = 1, ekHexNumber = 2 };
typedef unsigned char tEntityKind;
static one_caller tMbsIndex do_lookup_entity_string(const char* str,size_t len)
{ my_binary_search(0, NUM_CHENT - 1, strncmp(str, chent[idx].name, len),
return(idx))
}
static one_caller tMbsIndex lookup_entity_string(const char* str, size_t len)
{ /* Try to find a "candidate": */
tMbsIndex retval = do_lookup_entity_string(str, len);
/* Check whether the candidate is okay: */
if ( (retval >= 0) && (strlen(chent[retval].name) != len) )
retval = INVALID_INDEX;
return(retval);
}
#define cec(code1, code2) my_numcmp(code1, code2) /* compare entity codes */
static one_caller tMbsIndex lookup_entity_code(tEntityCode code)
{ my_binary_search(0, NUM_CHENTCODE - 1, cec(code, chentcode[idx].code),
return(idx))
}
static one_caller tBoolean guessed_entity(const char* str, size_t len,
char** _dest)
/* tries to "guess" the meaning of an unknown character entity by looking at
its prefix or suffix, tries to make the best out of that, and returns
whether all that worked */
{ if (len < 4) goto out; /* can't do anything here */
if ( (!strncmp(str, strSup, 3)) && /* entity has prefix "sup" */
(! ( (len == 4) && (str[3] == 'e') ) ) ) /* and isn't "⊇" */
{ char* dest;
str += 3; len -= 3;
copy:
dest = *_dest;
while (len-- > 0) *dest++ = *str++;
*_dest = dest;
return(truE);
}
else /* check for suffices */
{ const char* end = str + len;
unsigned short idx;
for (idx = 0; idx <= csiMax; idx++)
{ size_t sufflen = (size_t) chentsuffixlen[idx];
if (len > sufflen)
{ const char* suff = chentsuffix[idx];
if (!strncmp(end - sufflen, suff, sufflen))
{ if (idx == csiDash) { str = strMinus; len = 1; }
else if (idx == csiSpace) { str = strSpace; len = 1; }
else len -= sufflen;
goto copy;
}
}
}
}
out:
return(falsE); /* didn't find anything */
}
static one_caller tBoolean shall_interpret_chents_in_attrvalue(void)
{ tBoolean retval = cond2boolean( (current_attr_name < NUM_ATTRNAMES) &&
(my_bit_test(attrvalueconv, current_attr_name)) );
if (retval)
{ /* Have to handle some special cases. Let's thank the htmlspec writers for
this bogosity... */
if ( ( (current_tagkind == htkMeta) && (current_attr_name == anName) )
#if 0
/* These aren't yet implemented. */
|| ( (current_tagkind == htkLi) && (current_attr_name == anValue) )
|| ( (current_tagkind == htkSelect) && (current_attr_name == anSize) )
#endif
)
retval = falsE;
}
return(retval);
}
static one_caller void interpret_character_entities(char* origdest,
const char* src, tBoolean may_trim)
{ const char *start SHUT_UP_COMPILER(NULL), *start0 SHUT_UP_COMPILER(NULL);
char* dest = origdest;
tCharacterEntityInterpreterState ceis = ceisCopy;
tEntityKind kind SHUT_UP_COMPILER(ekString);
unsigned char lenleft SHUT_UP_COMPILER(0);
if (may_trim) { while (IS_WHITESPACE(*src)) src++; }
while (1)
{ char ch = *src;
unsigned int _code;
switch (ceis)
{case ceisCopy: /* the most likely case */
if (ch == '&') { start0 = src; ceis = ceisKind; }
else { *dest++ = ch; if (ch == '\0') goto out; }
break;
case ceisKind: /* find out the "kind" of the entity */
if (ch == '#') ceis = ceisNumkind; /* it's some numeric kind */
else /* it's a string */
{ kind = ekString; start = src; lenleft = MAXLEN_CHENT + 1;
ceis = ceisInside; goto inside;
}
break;
case ceisNumkind: /* find out whether it's decimal or hex */
lenleft = 4 + 1; ceis = ceisInside;
if ( (ch == 'x') || (ch == 'X') ) { kind = ekHexNumber; start = src + 1;}
else { kind = ekDecNumber; start = src; goto inside; }
break;
case ceisInside: /* "inside" the entity */
inside:
lenleft--;
if ( (ch == '\0') || (ch == ';') || (ch == ' ') || (ch == '&') ||
(!lenleft) )
{ /* found an end-point */
if (src <= start + 1) /* can't have found anything useful - CHECKME! */
{ postcopy:
while (start0 <= src) *dest++ = *start0++;
if (ch == '\0') goto out;
}
else if (kind == ekString)
{ size_t len = src - start;
tMbsIndex idx = lookup_entity_string(start, len);
if (idx >= 0) /* found in list */
{ const char* temp = chent[idx].result;
while (*temp) *dest++ = *temp++;
}
else if (!guessed_entity(start, len, &dest))
goto postcopy; /* no idea */
}
else if (kind == ekDecNumber)
{ const char* temp = start;
tMbsIndex idx;
_code = 0;
while (temp < src)
{ char c = *temp++;
if (my_isdigit(c)) _code = 10 * _code + (c - '0');
else goto postcopy; /* not a decimal number */
}
handle_code:
if (_code > chentcode[NUM_CHENTCODE - 1].code) goto postcopy;
idx = lookup_entity_code((tEntityCode) _code);
if (idx < 0)
{ if ( (_code >= 32) &&
#if OPTION_CED == 0
(_code < 127)
#else
(_code <= 255) && (_code != 127)
#endif
)
{ *dest++ = (char) _code; } /* interpreted as ASCII code */
else goto postcopy;
}
else
{ const signed short i = chentcode[idx].index;
#if OPTION_CED == 0
if (i < 0) *dest++ = chentcode_rerouter[-1 - i];
else
#endif
{ temp = chent[i].result;
while (*temp) *dest++ = *temp++;
}
}
}
else if (kind == ekHexNumber)
{ const char* temp = start;
_code = 0;
while (temp < src)
{ char c = *temp++;
unsigned int add;
if (my_isdigit(c))
{ add = c - '0';
calc:
_code = 16 * _code + add;
}
else if ( (c >= 'a') && (c <= 'f') )
{ add = c - 'a' + 10; goto calc; }
else if ( (c >= 'A') && (c <= 'F') )
{ add = c - 'A' + 10; goto calc; }
else goto postcopy; /* not a hexadecimal number */
}
goto handle_code;
}
ceis = ceisCopy;
}
break;
}
if (ch == '\0') { *dest = '\0'; goto out; }
else src++;
}
out:
if (may_trim) /* remove trailing whitespace */
{ dest = origdest + strlen(origdest) - 1; /* IMPROVEME? */
while (dest >= origdest)
{ const char c = *dest;
if (!IS_WHITESPACE(c)) break;
*dest-- = '\0';
}
}
}
static tAttribute* find_and_detach_attribute(tAttribute** list,
tAttributeName name)
/* searches and extracts an attribute of the given from the */
{ tAttribute *a = *list, *b;
if (a == NULL) return(NULL);
if (a->name == name) { *list = a->next; a->next = NULL; return(a); }
while ( (b = a->next) != NULL )
{ if (b->name == name) { a->next = b->next; b->next = NULL; return(b); }
a = b;
}
return(NULL);
}
static __my_inline tAttribute* _find_and_detach_attribute(void** list,
tAttributeName name)
{ return(find_and_detach_attribute((tAttribute**) list, name));
/* nasty casting rubbish */
}
#define __fada(list, name) find_and_detach_attribute(&list, name)
#define fada(name) __fada(current_attributes, name)
#if CONFIG_JAVASCRIPT
static one_caller tAttribute* fada_js(void** __list)
/* like find_and_detach_attribute(), but for Javascript-related attributes */
{ tAttribute **_list = (tAttribute**) __list; /* nasty casting rubbish */
tAttribute *retval, *list = *_list;
if (list == NULL) retval = NULL;
else if (is_an_for_javascript(list->name))
{ retval = list; *_list = retval->next; retval->next = NULL; }
else
{ tAttribute *a = list, *next;
while ( (next = a->next) != NULL )
{ if (is_an_for_javascript(next->name))
{ a->next = next->next; retval = next; retval->next = NULL; goto out; }
a = next;
}
retval = NULL;
}
out:
return(retval);
}
#endif
/* prepare curraebase */
#define set_caeb(_kind) \
do { my_memclr_var(curraebase); curraebase.kind = _kind; } while (0)
/* move attribute value */
#define __moav(dest, attr) \
do { curraebase.dest = attr->value; attr->value = NULL; } while (0)
#define moavd(attr) __moav(data, attr)
#define moavr(attr) __moav(render, attr)
/* deallocate an attribute and all associated data */
#define __deattr(a) do { __dealloc(a->value); memory_deallocate(a); } while (0)
#define deattr(a) do { if (a != NULL) __deattr(a); } while (0)
#define NUM_INPUT_TYPE (10)
static const struct
{ const char* name; /* (sorted in alphabetical order) */
tActiveElementKind kind;
} input_type[NUM_INPUT_TYPE] =
{ { strButton, aekFormButton },
{ strCheckbox, aekFormCheckbox },
{ strFile, aekFormFile },
{ "hidden", aekFormHidden },
{ strImage, aekFormImage },
{ "password", aekFormPassword },
{ "radio", aekFormRadio },
{ strReset, aekFormReset },
{ strSubmit, aekFormSubmit },
{ strText, aekFormText }
};
static one_caller tMbsIndex do_lookup_input_type(const char* str)
{ my_binary_search(0, NUM_INPUT_TYPE - 1, streqcase3(str,
input_type[idx].name), return(idx))
}
static one_caller tActiveElementKind lookup_input_type(const tAttribute* attr)
{ tActiveElementKind retval = aekFormText; /* htmlspec default */
if (attr != NULL)
{ const char* av = attr->value;
if ( (av != NULL) && (*av != '\0') ) /* non-empty attribute value */
{ tMbsIndex idx = do_lookup_input_type(av);
if (idx >= 0) retval = input_type[idx].kind;
else retval = aekUnknown;
}
}
return(retval);
}
#if CONFIG_JAVASCRIPT
static one_caller tMbsIndex lookup_javascript_event(void)
{ const char* str;
size_t len = bufsize - 1;
if ( (len < JAVASCRIPT_MIN_EVENT_NAME_LENGTH) ||
(my_tolower(buf[0]) != 'o') || (my_tolower(buf[1]) != 'n') )
return(INVALID_INDEX);
str = buf + 2;
my_binary_search(0, JAVASCRIPT_MAX_EVENT_CODE, strcmp(str, strJek[idx]),
return(idx))
}
#endif
static one_caller tBoolean use_curraebase(void)
/* checks whether an active-element base should be created for the current node
and prepares that if so */
{ tBoolean retval = falsE; /* the most likely result */
switch (current_tagkind)
{ case htkA: case htkArea:
{ tAttribute* h = fada(anHref);
if ( (h != NULL) && (h->value != NULL) && (h->value[0] != '\0') )
{ set_caeb(aekLink); moavd(h); retval = truE; }
deattr(h);
}
break;
case htkFrame: case htkIframe:
{ tAttribute *s = fada(anSrc), *t = fada(anTitle);
if ( (s != NULL) && (s->value != NULL) && (s->value[0] != '\0') )
{ char* tv;
set_caeb(aekLink); moavd(s); retval = truE;
if ( (t != NULL) && ( (tv = t->value) != NULL ) && (*tv != '\0') )
t->value = NULL; /* detach */
else tv = my_strdup(_("[a frame]"));
curraebase.render = tv;
}
deattr(s); deattr(t);
}
break;
case htkInput:
{ tAttribute *t = fada(anType), *n = fada(anName), *v = fada(anValue),
*s = fada(anSize), *m = fada(anMaxlength), *a = fada(anAlt),
*ch = fada(anChecked), *di = fada(anDisabled), *re = fada(anReadonly);
tActiveElementKind kind = lookup_input_type(t);
if (kind != aekUnknown)
{ tActiveElementFlags flags = aefNone;
const char* render;
set_caeb(kind);
if ( (n != NULL) && (n->value != NULL) && (n->value[0] != '\0') )
moavd(n);
switch (kind)
{ case aekFormSubmit:
render = _("Submit");
handle_render:
if ( (v != NULL) && (v->value != NULL) && (v->value[0] != '\0') )
{ render = v->value; v->value = NULL; } /* explicit value given */
else render = my_strdup(render);
curraebase.render = render; break;
case aekFormReset:
render = _("Reset"); goto handle_render; /*@notreached@*/ break;
case aekFormButton:
render = _("[a push button]"); goto handle_render; /*@notreached@*/
break;
case aekFormImage:
if ( (a != NULL) && (a->value != NULL) && (a->value[0] != '\0') )
{ curraebase.render = a->value; a->value = NULL; }
else curraebase.render = my_strdup(_("[a form image]"));
break;
case aekFormText: case aekFormPassword: case aekFormRadio:
case aekFormHidden: /* case aekFormFile: */
/* Privacy Note: for aekFormFile, we don't store the default value
because storing it might lead to an unwanted transmission of
local file contents (if the user submits the form without
recognizing that there is an aekFormFile element in it). See e.g.
RFC1867, 8. */
if ( (v != NULL) && (v->value != NULL) && (v->value[0] != '\0') )
moavr(v); /* default text */
break;
}
if (has_input_length(kind))
{ tHtmlInputLength max = attr2htmlinputlength(m,MAX_HTML_INPUT_LENGTH),
size = attr2htmlinputlength(s, 20);
if (size > max) size = max;
curraebase.size = size; curraebase.maxlength = max;
}
if (ch != NULL) flags |= aefCheckedSelected;
if (di != NULL) flags |= aefDisabled;
if (re != NULL) flags |= aefReadonly;
curraebase.flags = flags; retval = truE;
}
deattr(t); deattr(n); deattr(v); deattr(s); deattr(m); deattr(a);
deattr(ch); deattr(di); deattr(re);
}
break;
case htkTextarea:
{ tAttribute *n = fada(anName), *di = fada(anDisabled),
*re = fada(anReadonly);
tActiveElementFlags flags = aefNone;
set_caeb(aekFormText);
if ((n != NULL) && (n->value != NULL) && (n->value[0] != '\0')) moavd(n);
if (di != NULL) flags |= aefDisabled;
if (re != NULL) flags |= aefReadonly;
curraebase.flags = flags; curraebase.size = 20; retval = truE;
deattr(n); deattr(di); deattr(re);
}
break;
case htkButton:
{ tAttribute *t = fada(anType), *n = fada(anName), *v = fada(anValue),
*di = fada(anDisabled);
tActiveElementKind kind = aekFormSubmit; /* htmlspec default */
const char* temp;
if ((n != NULL) && (n->value != NULL) && (n->value[0] != '\0')) moavd(n);
if ( (t != NULL) && ( (temp = t->value) != NULL ) )
{ if (streqcase(temp, strButton)) kind = aekFormButton;
else if (streqcase(temp, strReset)) kind = aekFormReset;
else if (!streqcase(temp, strSubmit)) kind = aekUnknown;
}
if (kind != aekUnknown)
{ tActiveElementFlags flags = aefButtonTag;
if (di != NULL) flags |= aefDisabled;
set_caeb(kind);
if (v != NULL) moavr(v);
curraebase.flags = flags; retval = truE;
}
deattr(t); deattr(n); deattr(v); deattr(di);
}
break;
case htkSelect:
{ tAttribute *n = fada(anName), *mu = fada(anMultiple),
*di = fada(anDisabled);
tActiveElementFlags flags = aefNone;
set_caeb(aekFormSelect);
if ((n != NULL) && (n->value != NULL) && (n->value[0] != '\0')) moavd(n);
if (mu != NULL) flags |= aefMultiple;
if (di != NULL) flags |= aefDisabled;
curraebase.flags = flags;
select_aebase = curraebase; inside_select = truE;
/* retval = falsE; -- yes, not "truE" here; this one is special... */
deattr(n); deattr(mu); deattr(di);
}
break;
}
return(retval);
}
static my_inline __sallocator tAttribute* __callocator
create_attribute(const tAttributeName name)
{ tAttribute* retval = (tAttribute*) memory_allocate(sizeof(tAttribute),
mapOther);
retval->name = name;
return(retval);
}
static my_inline void deallocate_attributes(const tAttribute* a)
/* deallocates the given attribute list */
{ while (a != NULL)
{ const tAttribute* next = a->next;
__deattr(a); a = next;
}
}
void deallocate_html_node(const tHtmlNode* node)
{ if (node->kind == htkText) __dealloc((const char*) (node->data));
else deallocate_attributes((const tAttribute*) (node->data));
memory_deallocate(node);
}
void deallocate_one_aebase(const tActiveElementBase* aeb)
{ __dealloc(aeb->data);
if (aeb->kind != aekFormSelect) __dealloc(aeb->render); /* the simple case */
else
{ const tHtmlOption* o = (const tHtmlOption*) aeb->render;
while (o != NULL)
{ const tHtmlOption* next = o->next;
__dealloc(o->value); __dealloc(o->render); memory_deallocate(o);
o = next;
}
}
#if CONFIG_JAVASCRIPT
javascript_remove_ehs(aeb->eh);
#endif
}
static my_inline tBoolean htk_has_flag(const tHtmlTagKind kind,
const tHtmlTagFlags flag)
{ return(cond2boolean( (kind >= TAGOFFSET) && (kind < TAGOFFSET + NUM_TAGDATA)
&& (tagdata[kind - TAGOFFSET].flags & flag) ));
}
static __my_inline tBoolean htk_soaks_up_text(const tHtmlTagKind kind)
{ return(htk_has_flag(kind, htfSoakUpText));
}
__my_inline tBoolean htk_forbids_endtag(const tHtmlTagKind kind)
{ return(htk_has_flag(kind, htfForbidEndtag));
}
__my_inline tBoolean htk_forbids_pre(const tHtmlTagKind kind)
{ return(htk_has_flag(kind, htfForbidPre));
}
__my_inline tBoolean htk_is_block(const tHtmlTagKind kind)
{ return(htk_has_flag(kind, htfBlock));
}
__my_inline tBoolean htk_is_par(const tHtmlTagKind kind)
{ return(htk_has_flag(kind, htfPar));
}
static one_caller void create_html_form(const char* action,
tHtmlFormFlags flags)
{ tHtmlFormNumber num = current_cantent->hfnum, max = current_cantent->hfmax;
tHtmlForm* f;
if (num >= max)
{ max += ( (max >= 9) ? 10 : 3 ); current_cantent->hfmax = max;
current_cantent->form = memory_reallocate(current_cantent->form,
max * sizeof(tHtmlForm), mapOther);
}
f = &(current_cantent->form[num]); f->action_uri = action; f->flags = flags;
f->first_ae = f->last_ae = INVALID_AE; current_cantent->hfnum = num + 1;
#if CONFIG_DEBUG
sprint_safe(debugstrbuf,
"create_html_form(): num=%d, max=%d, action=*%s*, flags=%d\n",
num, max, action, flags);
debugmsg(debugstrbuf);
#endif
}
static void append_attribute_name(const tAttributeName name)
/* appends an attribute of the given to current_attributes (avoiding
duplicates) */
{ tAttribute* a = fada(name);
if (a != NULL) dealloc(a->value); /* "forget" old value */
else a = create_attribute(name);
a->next = current_attributes;
current_attributes = a;
}
static __my_inline void set_current_node(tHtmlNode* node)
{ current_node = node;
is_current_node_valid = truE;
}
static void store_html_node(tHtmlNode* node, tBoolean do_skip_char)
/* stores the in the tree, updates the lhpp_.... information and creates
an active-element base if appropriate */
{ const tHtmlTagKind htk = node->kind;
if (inside_select) /* don't store anything */
{ deallocate_html_node(node); return; }
if (previous_node_in_tree != NULL)
{ previous_node_in_tree->next = node; previous_node_in_tree = node; }
else
{ /* The is the first one for the tree of the current resource: */
current_cantent->tree = previous_node_in_tree = node;
}
node->flags |= hnfStoredInTree;
lhpp_content = current_block;
if (current_block == NULL)
{ /* This can e.g. happen if an HTML document ends with an opening
tag (incomplete document or just not yet completely received); in this
case we have the call chain "parser_html_next() -> change_state(hpsDone)
-> finish_delayed_node() -> store_html_node()". */
lhpp_byte = 0;
}
else
{ lhpp_byte = current_block->used - current_block_sizeleft +
boolean2bool(do_skip_char);
}
if (htk == htkTitle)
{ tAttribute* t = _find_and_detach_attribute(&(node->data), anInternalText);
const char* tv;
if ( (t != NULL) && ( (tv = t->value) != NULL ) && (*tv != '\0') )
{ __dealloc(current_cantent->major_html_title);
current_cantent->major_html_title = tv; t->value = NULL;
}
deattr(t);
}
if (node->flags & hnfHasAeBase)
{ if (aenum >= aemax) /* need to allocate more memory */
{ aemax += aenum_incvalue(aemax);
current_cantent->aebase = aebase = memory_reallocate(aebase, aemax *
sizeof(tActiveElementBase), mapOther);
}
#if CONFIG_JAVASCRIPT
{ /* extract event handlers */
const tJavascriptEventHandler* javascript_ehs = NULL;
const tAttribute* a;
while ( (a = fada_js(&(node->data))) != NULL )
{ const char* v = a->value;
const tJavascriptCode* code;
if ( (v != NULL) && (*v != '\0') &&
( (code = javascript_compile(v)) != NULL ) )
{ tJavascriptEventHandler* eh =
javascript_create_eh(a->name - anJavascriptBegin, code);
eh->next = javascript_ehs; javascript_ehs = eh;
}
}
curraebase.eh = javascript_ehs;
}
#endif
if (htk == htkTextarea)
{ tAttribute* t = _find_and_detach_attribute(&(node->data),anInternalText);
if ( (t != NULL) && (t->value != NULL) && (t->value[0] != '\0') )
moavr(t);
deattr(t);
}
aebase[aenum++] = curraebase;
}
set_current_node(node);
#if CONFIG_EXDEBUG
sprint_safe(debugstrbuf, "stored node %p, %d, %d\n", node, htk, node->flags);
prsdbg(debugstrbuf);
#endif
}
static __sallocator char* __callocator prepare_text(tBoolean may_trim)
{ char* text = (char*) __memory_allocate(bufsize, mapString);
interpret_character_entities(text, buf, may_trim);
return(text);
}
static one_caller void finish_delayed_node(tBoolean got_text,
tBoolean do_skip_char)
{ tHtmlTagKind htk = delayed_node->kind;
if ( (got_text) && (htk != htkScript) && (htk != htkStyle) )
{ /* add the text as an internal attribute */
tAttribute* a = create_attribute(anInternalText);
tBoolean may_trim = cond2boolean((htk == htkOption) || (htk == htkTitle));
a->value = prepare_text(may_trim); a->next = delayed_node->data;
delayed_node->data = a;
}
if ( (inside_select) && (htk == htkOption) )
{ /* add this option to the current