"Fossies" - the Fresh Open Source Software Archive

Member "links-1.04/html.c" (12 Oct 2018, 70213 Bytes) of package /linux/www/links-1.04.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "html.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.03_vs_1.04.

    1 #include "links.h"
    2 
    3 struct list_head html_stack = {&html_stack, &html_stack};
    4 
    5 static inline int atchr(unsigned char c)
    6 {
    7     return /*isA(c) ||*/ (c > ' ' && c != '=' && c != '<' && c != '>');
    8 }
    9 
   10 int parse_element(unsigned char *e, unsigned char *eof, unsigned char **name, int *namelen, unsigned char **attr, unsigned char **end)
   11 {
   12     if (eof - e < 3 || *(e++) != '<') return -1;
   13     if (name) *name = e;
   14     if (*e == '/') e++;
   15     if (!isA(*e)) return -1;
   16     goto x1;
   17     while (isA(*e) || *e == '=') {
   18         x1:
   19         e++;
   20         if (e >= eof) return -1;
   21     }
   22     /*if ((!WHITECHAR(*e) && *e != '>' && *e != '<' && *e != '/' && *e != ':')) return -1;*/
   23     if (name && namelen) *namelen = e - *name;
   24     while ((WHITECHAR(*e) || *e == '/' || *e == ':')) {
   25         e++;
   26         if (e >= eof) return -1;
   27     }
   28     if ((!atchr(*e) && *e != '>' && *e != '<')) return -1;
   29     if (attr) *attr = e;
   30     nextattr:
   31     while (WHITECHAR(*e)) {
   32         e++;
   33         if (e >= eof) return -1;
   34     }
   35     if ((!atchr(*e) && *e != '>' && *e != '<')) return -1;
   36     if (*e == '>' || *e == '<') goto en;
   37     while (atchr(*e)) {
   38         e++;
   39         if (e >= eof) return -1;
   40     }
   41     while (WHITECHAR(*e)) {
   42         e++;
   43         if (e >= eof) return -1;
   44     }
   45     if (*e != '=') goto endattr;
   46     goto x2;
   47     while (WHITECHAR(*e)) {
   48         x2:
   49         e++;
   50         if (e >= eof) return -1;
   51     }
   52     if (U(*e)) {
   53         unsigned char uu = *e;
   54         /*u:*/
   55         goto x3;
   56         while (e < eof && *e != uu && *e /*(WHITECHAR(*e) || *e > ' ')*/) {
   57             x3:
   58             e++;
   59             if (e >= eof) return -1;
   60         }
   61         if (*e < ' ') return -1;
   62         e++;
   63         if (e >= eof /*|| (!WHITECHAR(*e) && *e != uu && *e != '>' && *e != '<')*/) return -1;
   64         /*if (*e == uu) goto u;*/
   65     } else {
   66         while (!WHITECHAR(*e) && *e != '>' && *e != '<') {
   67             e++;
   68             if (e >= eof) return -1;
   69         }
   70     }
   71     while (WHITECHAR(*e)) {
   72         e++;
   73         if (e >= eof) return -1;
   74     }
   75     endattr:
   76     if (*e != '>' && *e != '<') goto nextattr;
   77     en:
   78     if (end) *end = e + (*e == '>');
   79     return 0;
   80 }
   81 
   82 #define add_chr(s, l, c)                        \
   83 do {                                    \
   84     if (!((l) & (32 - 1))) {                    \
   85         if ((unsigned)(l) > MAXINT - 32) overalloc();       \
   86         (s) = mem_realloc((s), (l) + 32);           \
   87     }                               \
   88     (s)[(l)++] = (c);                       \
   89 } while (0)
   90 
   91 int get_attr_val_nl = 0;
   92 
   93 /* parses html element attributes */
   94 /* e is attr pointer previously get from parse_element, DON'T PASS HERE ANY OTHER VALUE!!! */
   95 /* name is searched attribute */
   96 /* returns allocated string containing the attribute, or NULL on unsuccess */
   97 unsigned char *get_attr_val(unsigned char *e, unsigned char *name)
   98 {
   99     unsigned char *n;
  100     unsigned char *a = DUMMY;
  101     int l = 0;
  102     int f;
  103     aa:
  104     while (WHITECHAR(*e)) e++;
  105     if (*e == '>' || *e == '<') return NULL;
  106     n = name;
  107     while (*n && upcase(*e) == upcase(*n)) e++, n++;
  108     f = *n;
  109     while (atchr(*e)) f = 1, e++;
  110     while (WHITECHAR(*e)) e++;
  111     if (*e != '=') goto ea;
  112     e++;
  113     while (WHITECHAR(*e)) e++;
  114     if (!U(*e)) {
  115         while (!WHITECHAR(*e) && *e != '>' && *e != '<') {
  116             if (!f) add_chr(a, l, *e);
  117             e++;
  118         }
  119     } else {
  120         char uu = *e;
  121         /*a:*/
  122         e++;
  123         while (*e != uu) {
  124             if (!*e) {
  125                 mem_free(a);
  126                 return NULL;
  127             }
  128             if (!f) {
  129                 if (get_attr_val_nl == 2) goto exact;
  130                 if (*e != 13) {
  131                     if (*e != 9 && *e != 10) exact:add_chr(a, l, *e);
  132                     else if (!get_attr_val_nl) add_chr(a, l, ' ');
  133                 }
  134             }
  135             e++;
  136         }
  137         e++;
  138         /*if (*e == uu) {
  139             if (!f) add_chr(a, l, *e);
  140             goto a;
  141         }*/
  142     }
  143     ea:
  144     if (!f) {
  145         unsigned char *b;
  146         add_chr(a, l, 0);
  147         if (strchr(a, '&')) {
  148             unsigned char *aa = a;
  149             int c = d_opt->cp;
  150             d_opt->cp = d_opt->real_cp;
  151             a = convert_string(NULL, aa, strlen(aa));
  152             d_opt->cp = c;
  153             mem_free(aa);
  154         }
  155         while ((b = strchr(a, 1))) *b = ' ';
  156         if (get_attr_val_nl != 2) {
  157             for (b = a; *b == ' '; b++);
  158             if (b != a) memmove(a, b, strlen(b) + 1);
  159             for (b = a + strlen(a) - 1; b >= a && *b == ' '; b--) *b = 0;
  160         }
  161         set_mem_comment(a, name, strlen(name));
  162         return a;
  163     }
  164     goto aa;
  165 }
  166 
  167 int has_attr(unsigned char *e, unsigned char *name)
  168 {
  169     char *a;
  170     if (!(a = get_attr_val(e, name))) return 0;
  171     mem_free(a);
  172     return 1;
  173 }
  174 
  175 /*
  176 unsigned char *get_url_val(unsigned char *e, unsigned char *name)
  177 {
  178     int n = 0;
  179     unsigned char *p, *q, *pp;
  180     if (!(pp = get_attr_val(e, name))) return NULL;
  181     p = pp; q = pp;
  182     while (1) {
  183         if (*p == '#') n = 1;
  184         if ((*p = *q) != ' ' || n) p++;
  185         if (!*q) break;
  186         q++;
  187     }
  188     return pp;
  189 }
  190 */
  191 
  192 unsigned char *get_url_val(unsigned char *e, unsigned char *name)
  193 {
  194     unsigned char *a;
  195     get_attr_val_nl = 1;
  196     a = get_attr_val(e, name);
  197     get_attr_val_nl = 0;
  198     return a;
  199 }
  200 
  201 unsigned char *get_exact_attr_val(unsigned char *e, unsigned char *name)
  202 {
  203     unsigned char *a;
  204     get_attr_val_nl = 2;
  205     a = get_attr_val(e, name);
  206     get_attr_val_nl = 0;
  207     if (a) {
  208         unsigned char *x1, *x2;
  209         for (x1 = x2 = a; *x1; x1++, x2++) {
  210             if (x1[0] == '\r') {
  211                 *x2 = '\n';
  212                 if (x1[1] == '\n') x1++;
  213             } else {
  214                 *x2 = *x1;
  215             }
  216         }
  217         *x2 = 0;
  218     }
  219     return a;
  220 }
  221 
  222 struct {
  223     unsigned short int n;
  224     char *s;
  225 } roman_tbl[] = {
  226     { 1000, "m" },
  227     { 999,  "im" },
  228 /*  { 995,  "vm" },*/
  229     { 990,  "xm" },
  230 /*  { 950,  "lm" },*/
  231     { 900,  "cm" },
  232     { 500,  "d" },
  233     { 499,  "id" },
  234 /*  { 495,  "vd" },*/
  235     { 490,  "xd" },
  236 /*  { 450,  "ld" },*/
  237     { 400,  "cd" },
  238     { 100,  "c" },
  239     { 99,   "ic" },
  240 /*  { 95,   "vc" },*/
  241     { 90,   "xc" },
  242     { 50,   "l" },
  243     { 49,   "il" },
  244 /*  { 45,   "vl" },*/
  245     { 40,   "xl" },
  246     { 10,   "x" },
  247     { 9,    "ix" },
  248     { 5,    "v" },
  249     { 4,    "iv" },
  250     { 1,    "i" },
  251     { 0,    NULL },
  252 };
  253 
  254 void roman(char *p, unsigned n)
  255 {
  256     int i = 0;
  257     if (n >= 4000) {
  258         strcpy(p, "---");
  259         return;
  260     }
  261     if (!n) {
  262         strcpy(p, "o");
  263         return;
  264     }
  265     p[0] = 0;
  266     while (n) {
  267         while (roman_tbl[i].n <= n) {
  268             n -= roman_tbl[i].n;
  269             strcat(p, roman_tbl[i].s);
  270         }
  271         i++;
  272         if (n && !roman_tbl[i].n) {
  273             internal("BUG in roman number convertor");
  274             return;
  275         }
  276     }
  277 }
  278 
  279 struct color_spec {
  280     char *name;
  281     int rgb;
  282 };
  283 
  284 struct color_spec color_specs[] = {
  285     {"aliceblue",       0xF0F8FF},
  286     {"antiquewhite",    0xFAEBD7},
  287     {"aqua",        0x00FFFF},
  288     {"aquamarine",      0x7FFFD4},
  289     {"azure",       0xF0FFFF},
  290     {"beige",       0xF5F5DC},
  291     {"bisque",      0xFFE4C4},
  292     {"black",       0x000000},
  293     {"blanchedalmond",  0xFFEBCD},
  294     {"blue",        0x0000FF},
  295     {"blueviolet",      0x8A2BE2},
  296     {"brown",       0xA52A2A},
  297     {"burlywood",       0xDEB887},
  298     {"cadetblue",       0x5F9EA0},
  299     {"chartreuse",      0x7FFF00},
  300     {"chocolate",       0xD2691E},
  301     {"coral",       0xFF7F50},
  302     {"cornflowerblue",  0x6495ED},
  303     {"cornsilk",        0xFFF8DC},
  304     {"crimson",     0xDC143C},
  305     {"cyan",        0x00FFFF},
  306     {"darkblue",        0x00008B},
  307     {"darkcyan",        0x008B8B},
  308     {"darkgoldenrod",   0xB8860B},
  309     {"darkgray",        0xA9A9A9},
  310     {"darkgreen",       0x006400},
  311     {"darkkhaki",       0xBDB76B},
  312     {"darkmagenta",     0x8B008B},
  313     {"darkolivegreen",  0x556B2F},
  314     {"darkorange",      0xFF8C00},
  315     {"darkorchid",      0x9932CC},
  316     {"darkred",     0x8B0000},
  317     {"darksalmon",      0xE9967A},
  318     {"darkseagreen",    0x8FBC8F},
  319     {"darkslateblue",   0x483D8B},
  320     {"darkslategray",   0x2F4F4F},
  321     {"darkturquoise",   0x00CED1},
  322     {"darkviolet",      0x9400D3},
  323     {"deeppink",        0xFF1493},
  324     {"deepskyblue",     0x00BFFF},
  325     {"dimgray",     0x696969},
  326     {"dodgerblue",      0x1E90FF},
  327     {"firebrick",       0xB22222},
  328     {"floralwhite",     0xFFFAF0},
  329     {"forestgreen",     0x228B22},
  330     {"fuchsia",     0xFF00FF},
  331     {"gainsboro",       0xDCDCDC},
  332     {"ghostwhite",      0xF8F8FF},
  333     {"gold",        0xFFD700},
  334     {"goldenrod",       0xDAA520},
  335     {"gray",        0x808080},
  336     {"green",       0x008000},
  337     {"greenyellow",     0xADFF2F},
  338     {"honeydew",        0xF0FFF0},
  339     {"hotpink",     0xFF69B4},
  340     {"indianred",       0xCD5C5C},
  341     {"indigo",      0x4B0082},
  342     {"ivory",       0xFFFFF0},
  343     {"khaki",       0xF0E68C},
  344     {"lavender",        0xE6E6FA},
  345     {"lavenderblush",   0xFFF0F5},
  346     {"lawngreen",       0x7CFC00},
  347     {"lemonchiffon",    0xFFFACD},
  348     {"lightblue",       0xADD8E6},
  349     {"lightcoral",      0xF08080},
  350     {"lightcyan",       0xE0FFFF},
  351     {"lightgoldenrodyellow",    0xFAFAD2},
  352     {"lightgreen",      0x90EE90},
  353     {"lightgrey",       0xD3D3D3},
  354     {"lightpink",       0xFFB6C1},
  355     {"lightsalmon",     0xFFA07A},
  356     {"lightseagreen",   0x20B2AA},
  357     {"lightskyblue",    0x87CEFA},
  358     {"lightslategray",  0x778899},
  359     {"lightsteelblue",  0xB0C4DE},
  360     {"lightyellow",     0xFFFFE0},
  361     {"lime",        0x00FF00},
  362     {"limegreen",       0x32CD32},
  363     {"linen",       0xFAF0E6},
  364     {"magenta",     0xFF00FF},
  365     {"maroon",      0x800000},
  366     {"mediumaquamarine",    0x66CDAA},
  367     {"mediumblue",      0x0000CD},
  368     {"mediumorchid",    0xBA55D3},
  369     {"mediumpurple",    0x9370DB},
  370     {"mediumseagreen",  0x3CB371},
  371     {"mediumslateblue", 0x7B68EE},
  372     {"mediumspringgreen",   0x00FA9A},
  373     {"mediumturquoise", 0x48D1CC},
  374     {"mediumvioletred", 0xC71585},
  375     {"midnightblue",    0x191970},
  376     {"mintcream",       0xF5FFFA},
  377     {"mistyrose",       0xFFE4E1},
  378     {"moccasin",        0xFFE4B5},
  379     {"navajowhite",     0xFFDEAD},
  380     {"navy",        0x000080},
  381     {"oldlace",     0xFDF5E6},
  382     {"olive",       0x808000},
  383     {"olivedrab",       0x6B8E23},
  384     {"orange",      0xFFA500},
  385     {"orangered",       0xFF4500},
  386     {"orchid",      0xDA70D6},
  387     {"palegoldenrod",   0xEEE8AA},
  388     {"palegreen",       0x98FB98},
  389     {"paleturquoise",   0xAFEEEE},
  390     {"palevioletred",   0xDB7093},
  391     {"papayawhip",      0xFFEFD5},
  392     {"peachpuff",       0xFFDAB9},
  393     {"peru",        0xCD853F},
  394     {"pink",        0xFFC0CB},
  395     {"plum",        0xDDA0DD},
  396     {"powderblue",      0xB0E0E6},
  397     {"purple",      0x800080},
  398     {"red",         0xFF0000},
  399     {"rosybrown",       0xBC8F8F},
  400     {"royalblue",       0x4169E1},
  401     {"saddlebrown",     0x8B4513},
  402     {"salmon",      0xFA8072},
  403     {"sandybrown",      0xF4A460},
  404     {"seagreen",        0x2E8B57},
  405     {"seashell",        0xFFF5EE},
  406     {"sienna",      0xA0522D},
  407     {"silver",      0xC0C0C0},
  408     {"skyblue",     0x87CEEB},
  409     {"slateblue",       0x6A5ACD},
  410     {"slategray",       0x708090},
  411     {"snow",        0xFFFAFA},
  412     {"springgreen",     0x00FF7F},
  413     {"steelblue",       0x4682B4},
  414     {"tan",         0xD2B48C},
  415     {"teal",        0x008080},
  416     {"thistle",     0xD8BFD8},
  417     {"tomato",      0xFF6347},
  418     {"turquoise",       0x40E0D0},
  419     {"violet",      0xEE82EE},
  420     {"wheat",       0xF5DEB3},
  421     {"white",       0xFFFFFF},
  422     {"whitesmoke",      0xF5F5F5},
  423     {"yellow",      0xFFFF00},
  424     {"yellowgreen",     0x9ACD32},
  425 };
  426 
  427 #define endof(T) ((T)+sizeof(T)/sizeof(*(T)))
  428 
  429 int decode_color(unsigned char *str, struct rgb *col)
  430 {
  431     unsigned long ch;
  432     if (*str != '#') {
  433         struct color_spec *cs;
  434         for (cs = color_specs; cs < endof(color_specs); cs++)
  435             if (!strcasecmp(cs->name, str)) {
  436                 ch = cs->rgb;
  437                 goto found;
  438             }
  439     } else {
  440         str++;
  441     }
  442     if (strlen(str) == 6) {
  443         char *end;
  444         ch = strtoul(str, &end, 16);
  445         if (!*end && ch < 0x1000000) {
  446 found:
  447             col->r = ch / 0x10000;
  448             col->g = ch / 0x100 % 0x100;
  449             col->b = ch % 0x100;
  450             return 0;
  451         }
  452     }
  453     return -1;
  454 }
  455 
  456 int get_color(unsigned char *a, unsigned char *c, struct rgb *rgb)
  457 {
  458     char *at;
  459     int r = -1;
  460     if (d_opt->col >= 1) if ((at = get_attr_val(a, c))) {
  461         r = decode_color(at, rgb);
  462         mem_free(at);
  463     }
  464     return r;
  465 }
  466 
  467 int get_bgcolor(unsigned char *a, struct rgb *rgb)
  468 {
  469     if (d_opt->col < 2) return -1;
  470     return get_color(a, "bgcolor", rgb);
  471 }
  472 
  473 unsigned char *get_target(unsigned char *a)
  474 {
  475     unsigned char *v = get_attr_val(a, "target");
  476     if (v) {
  477         if (!strcasecmp(v, "_self")) {
  478             mem_free(v);
  479             v = stracpy(d_opt->framename);
  480         }
  481     }
  482     return v;
  483 }
  484 
  485 void kill_html_stack_item(struct html_element *e)
  486 {
  487     if (!e || (void *)e == &html_stack) {
  488         internal("trying to free bad html element");
  489         return;
  490     }
  491     if (e->dontkill == 2) {
  492         internal("trying to kill unkillable element");
  493         return;
  494     }
  495     if (e->attr.link) mem_free(e->attr.link);
  496     if (e->attr.target) mem_free(e->attr.target);
  497     if (e->attr.image) mem_free(e->attr.image);
  498     if (e->attr.href_base) mem_free(e->attr.href_base);
  499     if (e->attr.target_base) mem_free(e->attr.target_base);
  500     if (e->attr.select) mem_free(e->attr.select);
  501     del_from_list(e);
  502     mem_free(e);
  503     /*if ((void *)(html_stack.next) == &html_stack || !html_stack.next) {
  504         debug("killing last element");
  505     }*/
  506 }
  507 
  508 #ifdef DEBUG
  509 void debug_stack()
  510 {
  511     struct html_element *e;
  512     printf("HTML stack debug: \n");
  513     foreachback(e, html_stack) {
  514         int i;
  515         printf("\"");
  516         for (i = 0; i < e->namelen; i++) printf("%c", e->name[i]);
  517         printf("\"\n");
  518     }
  519     printf("%c", 7);
  520     fflush(stdout);
  521     sleep(1);
  522 }
  523 #endif
  524 
  525 void html_stack_dup()
  526 {
  527     struct html_element *e;
  528     struct html_element *ep;
  529     if ((void *)(ep = html_stack.next) == &html_stack || !html_stack.next) {
  530         internal("html stack empty");
  531         return;
  532     }
  533     e = mem_alloc(sizeof(struct html_element));
  534     memcpy(e, ep, sizeof(struct html_element));
  535     e->attr.link = stracpy(ep->attr.link);
  536     e->attr.target = stracpy(ep->attr.target);
  537     e->attr.image = stracpy(ep->attr.image);
  538     e->attr.href_base = stracpy(ep->attr.href_base);
  539     e->attr.target_base = stracpy(ep->attr.target_base);
  540     e->attr.select = stracpy(ep->attr.select);
  541     /*if (e->name) {
  542         if (e->attr.link) set_mem_comment(e->attr.link, e->name, e->namelen);
  543         if (e->attr.target) set_mem_comment(e->attr.target, e->name, e->namelen);
  544         if (e->attr.image) set_mem_comment(e->attr.image, e->name, e->namelen);
  545         if (e->attr.href_base) set_mem_comment(e->attr.href_base, e->name, e->namelen);
  546         if (e->attr.target_base) set_mem_comment(e->attr.target_base, e->name, e->namelen);
  547         if (e->attr.select) set_mem_comment(e->attr.select, e->name, e->namelen);
  548     }*/
  549     e->name = e->options = NULL;
  550     e->namelen = 0;
  551     e->dontkill = 0;
  552     add_to_list(html_stack, e);
  553 }
  554 
  555 void *ff;
  556 int (*put_chars_f)(void *, unsigned char *, int);
  557 void (*line_break_f)(void *);
  558 void *(*special_f)(void *, int, ...);
  559 
  560 unsigned char *eoff;
  561 unsigned char *eofff;
  562 unsigned char *startf;
  563 
  564 int line_breax;
  565 int pos;
  566 int putsp;
  567 
  568 int was_br;
  569 int table_level;
  570 int empty_format;
  571 
  572 void ln_break(int n, void (*line_break)(void *), void *f)
  573 {
  574     if (!n || html_top.invisible) return;
  575     while (n > line_breax) line_breax++, line_break(f);
  576     pos = 0;
  577     putsp = -1;
  578 }
  579 
  580 void put_chrs(unsigned char *start, int len, int (*put_chars)(void *, unsigned char *, int), void *f)
  581 {
  582     if (par_format.align == AL_NO) putsp = 0;
  583     if (!len || html_top.invisible) return;
  584     if (putsp == 1) pos += put_chars(f, " ", 1), putsp = -1;
  585     if (putsp == -1) {
  586         if (start[0] == ' ') start++, len--;
  587         putsp = 0;
  588     }
  589     if (!len) {
  590         putsp = -1;
  591         if (par_format.align == AL_NO) putsp = 0;
  592         return;
  593     }
  594     if (start[len - 1] == ' ') putsp = -1;
  595     if (par_format.align == AL_NO) putsp = 0;
  596     was_br = 0;
  597     pos += put_chars(f, start, len);
  598     line_breax = 0;
  599 }
  600 
  601 void kill_until(int ls, ...)
  602 {
  603     int l;
  604     struct html_element *e = &html_top;
  605     if (ls) e = e->next;
  606     while ((void *)e != &html_stack) {
  607         int sk = 0;
  608         va_list arg;
  609         va_start(arg, ls);
  610         while (1) {
  611             char *s = va_arg(arg, char *);
  612             if (!s) break;
  613             if (!*s) sk++;
  614             else if ((size_t)e->namelen == strlen(s) && !casecmp(e->name, s, strlen(s))) {
  615                 if (!sk) {
  616                     if (e->dontkill) break;
  617                     va_end(arg);
  618                     goto killll;
  619                 } else if (sk == 1) {
  620                     va_end(arg);
  621                     goto killl;
  622                 } else break;
  623             }
  624         }
  625         va_end(arg);
  626         if (e->dontkill || (e->namelen == 5 && !casecmp(e->name, "TABLE", 5))) break;
  627         if (e->namelen == 2 && upcase(e->name[0]) == 'T' && (upcase(e->name[1]) == 'D' || upcase(e->name[1]) == 'H' || upcase(e->name[1]) == 'R')) break;
  628         e = e->next;
  629     }
  630     return;
  631     killl:
  632     e = e->prev;
  633     killll:
  634     l = 0;
  635     while ((void *)e != &html_stack) {
  636         if (ls && e == html_stack.next) break;
  637         if (e->linebreak > l) l = e->linebreak;
  638         e = e->prev;
  639         kill_html_stack_item(e->next);
  640     }
  641     ln_break(l, line_break_f, ff);
  642 }
  643 
  644 static inline unsigned char *top_href_base(void)
  645 {
  646     return ((struct html_element *)html_stack.prev)->attr.href_base;
  647 }
  648 
  649 int get_num(unsigned char *a, unsigned char *n)
  650 {
  651     char *al;
  652     if ((al = get_attr_val(a, n))) {
  653         char *end;
  654         unsigned long s = strtoul(al, &end, 10);
  655         if (!*al || *end || s > 10000) s = -1;
  656         mem_free(al);
  657         return s;
  658     }
  659     return -1;
  660 }
  661 
  662 int parse_width(unsigned char *w, int trunc)
  663 {
  664     unsigned char *end;
  665     int p = 0;
  666     long s;
  667     int l;
  668     while (WHITECHAR(*w)) w++;
  669     for (l = 0; w[l] && w[l] != ','; l++) ;
  670     while (l && WHITECHAR(w[l - 1])) l--;
  671     if (!l) return -1;
  672     if (w[l - 1] == '%') l--, p = 1;
  673     while (l && WHITECHAR(w[l - 1])) l--;
  674     if (!l) return -1;
  675     s = strtoul((char *)w, (char **)(void *)&end, 10);
  676     if (end - w < l || s < 0 || s > 10000) return -1;
  677     if (p) {
  678         if (trunc) s = s * (par_format.width - par_format.leftmargin - par_format.rightmargin) / 100;
  679         else return -1;
  680     } else s = (s + (HTML_CHAR_WIDTH - 1) / 2) / HTML_CHAR_WIDTH;
  681     if (trunc && s > par_format.width - par_format.leftmargin - par_format.rightmargin) s = par_format.width - par_format.leftmargin - par_format.rightmargin;
  682     if (s < 0) s = 0;
  683     return s;
  684 }
  685 
  686 int get_width(unsigned char *a, unsigned char *n, int trunc)
  687 {
  688     int r;
  689     unsigned char *w;
  690     if (!(w = get_attr_val(a, n))) return -1;
  691     r = parse_width(w, trunc);
  692     mem_free(w);
  693     return r;
  694 }
  695 
  696 struct form form = { NULL, NULL, 0, 0 };
  697 
  698 unsigned char *last_form_tag;
  699 unsigned char *last_form_attr;
  700 unsigned char *last_input_tag;
  701 
  702 void put_link_line(unsigned char *prefix, unsigned char *linkname, unsigned char *link, unsigned char *target)
  703 {
  704     html_stack_dup();
  705     ln_break(1, line_break_f, ff);
  706     if (format_.link) mem_free(format_.link), format_.link = NULL;
  707     if (format_.target) mem_free(format_.target), format_.target = NULL;
  708     format_.form = NULL;
  709     put_chrs(prefix, strlen(prefix), put_chars_f, ff);
  710     format_.link = join_urls(format_.href_base, link);
  711     format_.target = stracpy(target);
  712     memcpy(&format_.fg, &format_.clink, sizeof(struct rgb));
  713     put_chrs(linkname, strlen(linkname), put_chars_f, ff);
  714     ln_break(1, line_break_f, ff);
  715     kill_html_stack_item(&html_top);
  716 }
  717 
  718 void html_span(unsigned char *a) { }
  719 void html_bold(unsigned char *a) { format_.attr |= AT_BOLD; }
  720 void html_italic(unsigned char *a) { format_.attr |= AT_ITALIC; }
  721 void html_underline(unsigned char *a) { format_.attr |= AT_UNDERLINE; }
  722 void html_fixed(unsigned char *a) { format_.attr |= AT_FIXED; }
  723 
  724 void html_a(unsigned char *a)
  725 {
  726     char *al;
  727     if ((al = get_url_val(a, "href"))) {
  728         char *all = al;
  729         while (all[0] == ' ') all++;
  730         while (all[0] && all[strlen(all) - 1] == ' ') all[strlen(all) - 1] = 0;
  731         if (format_.link) mem_free(format_.link);
  732         format_.link = join_urls(format_.href_base, all);
  733         mem_free(al);
  734         if ((al = get_target(a))) {
  735             if (format_.target) mem_free(format_.target);
  736             format_.target = al;
  737         } else {
  738             if (format_.target) mem_free(format_.target);
  739             format_.target = stracpy(format_.target_base);
  740         }
  741         /*format_.attr ^= AT_BOLD;*/
  742         memcpy(&format_.fg, &format_.clink, sizeof(struct rgb));
  743     } else kill_html_stack_item(&html_top);
  744     if ((al = get_attr_val(a, "name"))) {
  745         special_f(ff, SP_TAG, al);
  746         mem_free(al);
  747     }
  748 }
  749 
  750 void html_font(unsigned char *a)
  751 {
  752     char *al;
  753     if ((al = get_attr_val(a, "size"))) {
  754         int p = 0;
  755         unsigned long s;
  756         char *nn = al;
  757         char *end;
  758         if (*al == '+') p = 1, nn++;
  759         if (*al == '-') p = -1, nn++;
  760         s = strtoul(nn, &end, 10);
  761         if (*nn && !*end) {
  762             if (s > 7) s = 7;
  763             if (!p) format_.fontsize = s;
  764             else format_.fontsize += p * s;
  765             if (format_.fontsize < 1) format_.fontsize = 1;
  766             if (format_.fontsize > 7) format_.fontsize = 7;
  767         }
  768         mem_free(al);
  769     }
  770     get_color(a, "color", &format_.fg);
  771 }
  772 
  773 void html_img(unsigned char *a)
  774 {
  775     unsigned char *al;
  776     int ismap, usemap = 0;
  777     /*put_chrs(" ", 1, put_chars_f, ff);*/
  778     if ((al = get_attr_val(a, "usemap"))) {
  779         unsigned char *u;
  780         usemap = 1;
  781         html_stack_dup();
  782         if (format_.link) mem_free(format_.link);
  783         if (format_.form) format_.form = NULL;
  784         u = join_urls(*al == '#' ? top_href_base() : format_.href_base, al);
  785         format_.link = mem_alloc(strlen(u) + 5);
  786         strcpy(format_.link, "MAP@");
  787         strcat(format_.link, u);
  788         format_.attr |= AT_BOLD;
  789         mem_free(u);
  790         mem_free(al);
  791     }
  792     ismap = format_.link && has_attr(a, "ismap") && !usemap;
  793     if ((!(al = get_attr_val(a, "alt")) && !(al = get_attr_val(a, "title"))) || !*al) {
  794         if (al) mem_free(al);
  795         if (!d_opt->images && !format_.link) return;
  796         if (usemap) al = stracpy("[USEMAP]");
  797         else if (ismap) al = stracpy("[ISMAP]");
  798         else al = stracpy("[IMG]");
  799     }
  800     if (format_.image) mem_free(format_.image), format_.image = NULL;
  801     if (al) {
  802         unsigned char *s;
  803         if ((s = get_url_val(a, "src")) || (s = get_attr_val(a, "dynsrc"))) {
  804             format_.image = join_urls(format_.href_base, s);
  805             mem_free(s);
  806         }
  807         if (ismap) {
  808             unsigned char *h;
  809             html_stack_dup();
  810             h = stracpy(format_.link);
  811             add_to_strn(&h, "?0,0");
  812             mem_free(format_.link);
  813             format_.link = h;
  814         }
  815         put_chrs(al, strlen(al), put_chars_f, ff);
  816         if (ismap) kill_html_stack_item(&html_top);
  817     }
  818     if (format_.image) mem_free(format_.image), format_.image = NULL;
  819     mem_free(al);
  820     if (usemap) kill_html_stack_item(&html_top);
  821     /*put_chrs(" ", 1, put_chars_f, ff);*/
  822 }
  823 
  824 void html_body(unsigned char *a)
  825 {
  826     get_color(a, "text", &format_.fg);
  827     get_color(a, "link", &format_.clink);
  828     get_color(a, "vlink", &format_.vlink);
  829     get_bgcolor(a, &format_.bg);
  830     get_bgcolor(a, &par_format.bgcolor);
  831 }
  832 
  833 void html_skip(unsigned char *a) { html_top.invisible = html_top.dontkill = 1; }
  834 
  835 void html_title(unsigned char *a) { html_top.invisible = html_top.dontkill = 1; }
  836 
  837 void html_center(unsigned char *a)
  838 {
  839     par_format.align = AL_CENTER;
  840     if (!table_level) par_format.leftmargin = par_format.rightmargin = 0;
  841 }
  842 
  843 void html_linebrk(unsigned char *a)
  844 {
  845     char *al;
  846     if ((al = get_attr_val(a, "align"))) {
  847         if (!strcasecmp(al, "left")) par_format.align = AL_LEFT;
  848         if (!strcasecmp(al, "right")) par_format.align = AL_RIGHT;
  849         if (!strcasecmp(al, "center")) {
  850             par_format.align = AL_CENTER;
  851             if (!table_level) par_format.leftmargin = par_format.rightmargin = 0;
  852         }
  853         if (!strcasecmp(al, "justify")) par_format.align = AL_BLOCK;
  854         mem_free(al);
  855     }
  856 }
  857 
  858 void html_br(unsigned char *a)
  859 {
  860     html_linebrk(a);
  861     if (was_br) ln_break(2, line_break_f, ff);
  862     was_br = 1;
  863 }
  864 
  865 void html_form(unsigned char *a)
  866 {
  867     was_br = 1;
  868 }
  869 
  870 void html_p(unsigned char *a)
  871 {
  872     if (par_format.leftmargin < margin) par_format.leftmargin = margin;
  873     if (par_format.rightmargin < margin) par_format.rightmargin = margin;
  874     /*par_format.align = AL_LEFT;*/
  875     html_linebrk(a);
  876 }
  877 
  878 void html_address(unsigned char *a)
  879 {
  880     par_format.leftmargin += 1;
  881     par_format.align = AL_LEFT;
  882 }
  883 
  884 void html_blockquote(unsigned char *a)
  885 {
  886     par_format.leftmargin += 2;
  887     par_format.align = AL_LEFT;
  888 }
  889 
  890 void html_h(int h, char *a)
  891 {
  892     do_not_optimize_here(&h);
  893     par_format.align = AL_LEFT;
  894     html_linebrk(a);
  895     switch (par_format.align) {
  896         case AL_LEFT:
  897             par_format.leftmargin = (h - 2) * 2;
  898             par_format.rightmargin = 0;
  899             break;
  900         case AL_RIGHT:
  901             par_format.leftmargin = 0;
  902             par_format.rightmargin = (h - 2) * 2;
  903             break;
  904         case AL_CENTER:
  905             par_format.leftmargin = par_format.rightmargin = 0;
  906             break;
  907         case AL_BLOCK:
  908             par_format.leftmargin = par_format.rightmargin = (h - 2) * 2;
  909             break;
  910     }
  911 }
  912 
  913 void html_h2(unsigned char *a) { html_h(2, a); }
  914 void html_h3(unsigned char *a) { html_h(3, a); }
  915 void html_h4(unsigned char *a) { html_h(4, a); }
  916 void html_h5(unsigned char *a) { html_h(5, a); }
  917 void html_h6(unsigned char *a) { html_h(6, a); }
  918 
  919 void html_pre(unsigned char *a)
  920 {
  921     par_format.align = AL_NO;
  922     par_format.leftmargin = par_format.leftmargin > 1;
  923     par_format.rightmargin = 0;
  924 }
  925 
  926 void html_hr(unsigned char *a)
  927 {
  928     int i/* = par_format.width - 10*/;
  929     unsigned char r = 205;
  930     int q = get_num(a, "size");
  931     if (q >= 0 && q < 2) r = 196;
  932     html_stack_dup();
  933     par_format.align = AL_CENTER;
  934     if (format_.link) mem_free(format_.link), format_.link = NULL;
  935     format_.form = NULL;
  936     html_linebrk(a);
  937     if (par_format.align == AL_BLOCK) par_format.align = AL_CENTER;
  938     par_format.leftmargin = margin;
  939     par_format.rightmargin = margin;
  940     if ((i = get_width(a, "width", 1)) == -1) i = par_format.width - 2 * margin - 4;
  941     format_.attr = AT_GRAPHICS;
  942     special_f(ff, SP_NOWRAP, 1);
  943     while (i-- > 0) put_chrs(&r, 1, put_chars_f, ff);
  944     special_f(ff, SP_NOWRAP, 0);
  945     ln_break(2, line_break_f, ff);
  946     kill_html_stack_item(&html_top);
  947 }
  948 
  949 void html_table(unsigned char *a)
  950 {
  951     par_format.leftmargin = margin;
  952     par_format.rightmargin = margin;
  953     par_format.align = AL_LEFT;
  954     html_linebrk(a);
  955     format_.attr = 0;
  956 }
  957 
  958 void html_tr(unsigned char *a)
  959 {
  960     html_linebrk(a);
  961 }
  962 
  963 void html_th(unsigned char *a)
  964 {
  965     /*html_linebrk(a);*/
  966     kill_until(1, "TD", "TH", "", "TR", "TABLE", NULL);
  967     format_.attr |= AT_BOLD;
  968     put_chrs(" ", 1, put_chars_f, ff);
  969 }
  970 
  971 void html_td(unsigned char *a)
  972 {
  973     /*html_linebrk(a);*/
  974     kill_until(1, "TD", "TH", "", "TR", "TABLE", NULL);
  975     format_.attr &= ~AT_BOLD;
  976     put_chrs(" ", 1, put_chars_f, ff);
  977 }
  978 
  979 void html_base(unsigned char *a)
  980 {
  981     char *al;
  982     if ((al = get_url_val(a, "href"))) {
  983         if (format_.href_base) mem_free(format_.href_base);
  984         format_.href_base = join_urls(top_href_base(), al);
  985         mem_free(al);
  986     }
  987     if ((al = get_target(a))) {
  988         if (format_.target_base) mem_free(format_.target_base);
  989         format_.target_base = al;
  990     }
  991 }
  992 
  993 void html_ul(unsigned char *a)
  994 {
  995     char *al;
  996     /*debug_stack();*/
  997     par_format.list_level++;
  998     par_format.list_number = 0;
  999     par_format.flags = P_STAR;
 1000     if ((al = get_attr_val(a, "type"))) {
 1001         if (!strcasecmp(al, "disc") || !strcasecmp(al, "circle")) par_format.flags = P_O;
 1002         if (!strcasecmp(al, "square")) par_format.flags = P_PLUS;
 1003         mem_free(al);
 1004     }
 1005     if ((par_format.leftmargin += 2 + (par_format.list_level > 1)) > par_format.width * 2 / 3 && !table_level)
 1006         par_format.leftmargin = par_format.width * 2 / 3;
 1007     par_format.align = AL_LEFT;
 1008     html_top.dontkill = 1;
 1009 }
 1010 
 1011 void html_ol(unsigned char *a)
 1012 {
 1013     char *al;
 1014     int st;
 1015     par_format.list_level++;
 1016     st = get_num(a, "start");
 1017     if (st == -1) st = 1;
 1018     par_format.list_number = st;
 1019     par_format.flags = P_NUMBER;
 1020     if ((al = get_attr_val(a, "type"))) {
 1021         if (!strcmp(al, "1")) par_format.flags = P_NUMBER;
 1022         if (!strcmp(al, "a")) par_format.flags = P_alpha;
 1023         if (!strcmp(al, "A")) par_format.flags = P_ALPHA;
 1024         if (!strcmp(al, "r")) par_format.flags = P_roman;
 1025         if (!strcmp(al, "R")) par_format.flags = P_ROMAN;
 1026         if (!strcmp(al, "i")) par_format.flags = P_roman;
 1027         if (!strcmp(al, "I")) par_format.flags = P_ROMAN;
 1028         mem_free(al);
 1029     }
 1030     if ((par_format.leftmargin += (par_format.list_level > 1)) > par_format.width * 2 / 3 && !table_level)
 1031         par_format.leftmargin = par_format.width * 2 / 3;
 1032     par_format.align = AL_LEFT;
 1033     html_top.dontkill = 1;
 1034 }
 1035 
 1036 void html_li(unsigned char *a)
 1037 {
 1038     /*kill_until(0, "", "UL", "OL", NULL);*/
 1039     if (!par_format.list_number) {
 1040         char x[7] = "*&nbsp;";
 1041         if ((par_format.flags & P_LISTMASK) == P_O) x[0] = 'o';
 1042         if ((par_format.flags & P_LISTMASK) == P_PLUS) x[0] = '+';
 1043         put_chrs(x, 7, put_chars_f, ff);
 1044         par_format.leftmargin += 2;
 1045         par_format.align = AL_LEFT;
 1046         putsp = -1;
 1047     } else {
 1048         char c = 0;
 1049         char n[32];
 1050         int t = par_format.flags & P_LISTMASK;
 1051         int s = get_num(a, "value");
 1052         if (s != -1) par_format.list_number = s;
 1053         if ((t != P_roman && t != P_ROMAN && par_format.list_number < 10) || t == P_alpha || t == P_ALPHA) put_chrs("&nbsp;", 6, put_chars_f, ff), c = 1;
 1054         if (t == P_ALPHA || t == P_alpha) {
 1055             n[0] = par_format.list_number ? (par_format.list_number - 1) % 26 + (t == P_ALPHA ? 'A' : 'a') : 0;
 1056             n[1] = 0;
 1057         } else if (t == P_ROMAN || t == P_roman) {
 1058             roman(n, par_format.list_number);
 1059             if (t == P_ROMAN) {
 1060                 char *x;
 1061                 for (x = n; *x; x++) *x = upcase(*x);
 1062             }
 1063         } else sprintf(n, "%d", par_format.list_number);
 1064         put_chrs(n, strlen(n), put_chars_f, ff);
 1065         put_chrs(".&nbsp;", 7, put_chars_f, ff);
 1066         par_format.leftmargin += strlen(n) + c + 2;
 1067         par_format.align = AL_LEFT;
 1068         html_top.next->parattr.list_number = par_format.list_number + 1;
 1069         par_format.list_number = 0;
 1070         putsp = -1;
 1071     }
 1072     line_breax = 2;
 1073 }
 1074 
 1075 void html_dl(unsigned char *a)
 1076 {
 1077     par_format.flags &= ~P_COMPACT;
 1078     if (has_attr(a, "compact")) par_format.flags |= P_COMPACT;
 1079     if (par_format.list_level) par_format.leftmargin += 5;
 1080     par_format.list_level++;
 1081     par_format.list_number = 0;
 1082     par_format.align = AL_LEFT;
 1083     par_format.dd_margin = par_format.leftmargin;
 1084     html_top.dontkill = 1;
 1085     if (!(par_format.flags & P_COMPACT)) {
 1086         ln_break(2, line_break_f, ff);
 1087         html_top.linebreak = 2;
 1088     }
 1089 }
 1090 
 1091 void html_dt(unsigned char *a)
 1092 {
 1093     kill_until(0, "", "DL", NULL);
 1094     par_format.align = AL_LEFT;
 1095     par_format.leftmargin = par_format.dd_margin;
 1096     if (!(par_format.flags & P_COMPACT) && !has_attr(a, "compact"))
 1097         ln_break(2, line_break_f, ff);
 1098 }
 1099 
 1100 void html_dd(unsigned char *a)
 1101 {
 1102     kill_until(0, "", "DL", NULL);
 1103     if ((par_format.leftmargin = par_format.dd_margin + (table_level ? 3 : 8)) > par_format.width * 2 / 3 && !table_level)
 1104         par_format.leftmargin = par_format.width * 2 / 3;
 1105     par_format.align = AL_LEFT;
 1106 }
 1107 
 1108 void get_html_form(unsigned char *a, struct form *form)
 1109 {
 1110     unsigned char *al;
 1111     unsigned char *ch;
 1112     form->method = FM_GET;
 1113     if ((al = get_attr_val(a, "method"))) {
 1114         if (!strcasecmp(al, "post")) {
 1115             char *ax;
 1116             form->method = FM_POST;
 1117             if ((ax = get_attr_val(a, "enctype"))) {
 1118                 if (!strcasecmp(ax, "multipart/form-data"))
 1119                     form->method = FM_POST_MP;
 1120                 mem_free(ax);
 1121             }
 1122         }
 1123         mem_free(al);
 1124     }
 1125     if ((al = get_url_val(a, "action"))) {
 1126         char *all = al;
 1127         while (all[0] == ' ') all++;
 1128         while (all[0] && all[strlen(all) - 1] == ' ') all[strlen(all) - 1] = 0;
 1129         form->action = join_urls(format_.href_base, all);
 1130         mem_free(al);
 1131     } else {
 1132         if ((ch = strchr(form->action = stracpy(format_.href_base), POST_CHAR))) *ch = 0;
 1133         if (form->method == FM_GET && (ch = strchr(form->action, '?'))) *ch = 0;
 1134     }
 1135     if ((al = get_target(a))) {
 1136         form->target = al;
 1137     } else {
 1138         form->target = stracpy(format_.target_base);
 1139     }
 1140     form->num = a - startf;
 1141 }
 1142 
 1143 void find_form_for_input(unsigned char *i, int when_unused)
 1144 {
 1145     unsigned char *s, *ss, *name, *attr, *lf, *la;
 1146     int namelen;
 1147     if (form.action) mem_free(form.action);
 1148     if (form.target) mem_free(form.target);
 1149     memset(&form, 0, sizeof(form));
 1150     if (!when_unused && !special_f(ff, SP_USED, NULL)) return;
 1151     if (last_form_tag && last_input_tag && i <= last_input_tag && i > last_form_tag) {
 1152         get_html_form(last_form_attr, &form);
 1153         return;
 1154     }
 1155     if (last_form_tag && last_input_tag && i > last_input_tag) {
 1156         if (parse_element(last_form_tag, i, &name, &namelen, &la, &s))
 1157             internal("couldn't parse already parsed tag");
 1158         lf = last_form_tag;
 1159         s = last_input_tag;
 1160     } else {
 1161         lf = NULL, la = NULL;
 1162         s = startf;
 1163     }
 1164     se:
 1165     while (s < i && *s != '<') sp:s++;
 1166     if (s >= i) goto end_parse;
 1167     if (eofff - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 1168         s = skip_comment(s, i);
 1169         goto se;
 1170     }
 1171     ss = s;
 1172     if (parse_element(s, i, &name, &namelen, &attr, &s)) goto sp;
 1173     if (namelen != 4 || casecmp(name, "FORM", 4)) goto se;
 1174     lf = ss;
 1175     la = attr;
 1176     goto se;
 1177 
 1178     end_parse:
 1179     if (lf) {
 1180         last_form_tag = lf;
 1181         last_form_attr = la;
 1182         last_input_tag = i;
 1183         get_html_form(la, &form);
 1184     } else {
 1185         memset(&form, 0, sizeof(struct form));
 1186     }
 1187 }
 1188 
 1189 void html_button(unsigned char *a)
 1190 {
 1191     char *al;
 1192     struct form_control *fc;
 1193     find_form_for_input(a, 0);
 1194     fc = mem_alloc(sizeof(struct form_control));
 1195     memset(fc, 0, sizeof(struct form_control));
 1196     if (!(al = get_attr_val(a, "type"))) {
 1197         fc->type = FC_SUBMIT;
 1198         goto xxx;
 1199     }
 1200     if (!strcasecmp(al, "submit")) fc->type = FC_SUBMIT;
 1201     else if (!strcasecmp(al, "reset")) fc->type = FC_RESET;
 1202     else if (!strcasecmp(al, "button")) {
 1203         mem_free(al);
 1204         put_chrs(" [&nbsp;", 8, put_chars_f, ff);
 1205         if ((al = get_attr_val(a, "value"))) {
 1206             put_chrs(al, strlen(al), put_chars_f, ff);
 1207             mem_free(al);
 1208         } else put_chrs("BUTTON", 6, put_chars_f, ff);
 1209         put_chrs("&nbsp;] ", 8, put_chars_f, ff);
 1210         mem_free(fc);
 1211         return;
 1212     } else {
 1213         mem_free(al);
 1214         mem_free(fc);
 1215         return;
 1216     }
 1217     mem_free(al);
 1218     xxx:
 1219     fc->form_num = last_form_tag - startf;
 1220     fc->ctrl_num = a - last_form_tag;
 1221     fc->position = a - startf;
 1222     fc->method = form.method;
 1223     fc->action = stracpy(form.action);
 1224     fc->name = get_attr_val(a, "name");
 1225     fc->default_value = get_exact_attr_val(a, "value");
 1226     fc->ro = has_attr(a, "disabled") ? 2 : has_attr(a, "readonly") ? 1 : 0;
 1227     if (fc->type == FC_IMAGE) fc->alt = get_attr_val(a, "alt");
 1228     if (fc->type == FC_SUBMIT && !fc->default_value) fc->default_value = stracpy("Submit");
 1229     if (fc->type == FC_RESET && !fc->default_value) fc->default_value = stracpy("Reset");
 1230     if (!fc->default_value) fc->default_value = stracpy("");
 1231     special_f(ff, SP_CONTROL, fc);
 1232     format_.form = fc;
 1233     format_.attr |= AT_BOLD;
 1234     /*put_chrs("[&nbsp;", 7, put_chars_f, ff);
 1235     if (fc->default_value) put_chrs(fc->default_value, strlen(fc->default_value), put_chars_f, ff);
 1236     put_chrs("&nbsp;]", 7, put_chars_f, ff);
 1237     put_chrs(" ", 1, put_chars_f, ff);*/
 1238 }
 1239 
 1240 void set_max_textarea_width(int *w)
 1241 {
 1242     int limit;
 1243     if (!table_level) {
 1244         limit = par_format.width - (par_format.leftmargin + par_format.rightmargin);
 1245     } else {
 1246         limit = d_opt->xw - 2;
 1247     }
 1248     if (*w > limit) {
 1249         *w = limit;
 1250         if (*w < HTML_MINIMAL_TEXTAREA_WIDTH) *w = HTML_MINIMAL_TEXTAREA_WIDTH;
 1251     }
 1252 }
 1253 
 1254 void html_input(unsigned char *a)
 1255 {
 1256     int i;
 1257     int size;
 1258     unsigned char *al;
 1259     struct form_control *fc;
 1260     find_form_for_input(a, 0);
 1261     fc = mem_alloc(sizeof(struct form_control));
 1262     memset(fc, 0, sizeof(struct form_control));
 1263     if (!(al = get_attr_val(a, "type"))) {
 1264         fc->type = FC_TEXT;
 1265         goto xxx;
 1266     }
 1267     if (!strcasecmp(al, "text")) fc->type = FC_TEXT;
 1268     else if (!strcasecmp(al, "password")) fc->type = FC_PASSWORD;
 1269     else if (!strcasecmp(al, "checkbox")) fc->type = FC_CHECKBOX;
 1270     else if (!strcasecmp(al, "radio")) fc->type = FC_RADIO;
 1271     else if (!strcasecmp(al, "submit")) fc->type = FC_SUBMIT;
 1272     else if (!strcasecmp(al, "reset")) fc->type = FC_RESET;
 1273     else if (!strcasecmp(al, "file")) fc->type = FC_FILE;
 1274     else if (!strcasecmp(al, "hidden")) fc->type = FC_HIDDEN;
 1275     else if (!strcasecmp(al, "image")) fc->type = FC_IMAGE;
 1276     else if (!strcasecmp(al, "button")) {
 1277         mem_free(al);
 1278         put_chrs(" [&nbsp;", 8, put_chars_f, ff);
 1279         if ((al = get_attr_val(a, "value"))) {
 1280             put_chrs(al, strlen(al), put_chars_f, ff);
 1281             mem_free(al);
 1282         } else put_chrs("BUTTON", 6, put_chars_f, ff);
 1283         put_chrs("&nbsp;] ", 8, put_chars_f, ff);
 1284         mem_free(fc);
 1285         return;
 1286     } else fc->type = FC_TEXT;
 1287     mem_free(al);
 1288     xxx:
 1289     fc->form_num = last_form_tag - startf;
 1290     fc->ctrl_num = a - last_form_tag;
 1291     fc->position = a - startf;
 1292     fc->method = form.method;
 1293     fc->action = stracpy(form.action);
 1294     fc->target = stracpy(form.target);
 1295     fc->name = get_attr_val(a, "name");
 1296     if (fc->type == FC_TEXT || fc->type == FC_PASSWORD) fc->default_value = get_attr_val(a, "value");
 1297     else if (fc->type != FC_FILE) fc->default_value = get_exact_attr_val(a, "value");
 1298     if (fc->type == FC_CHECKBOX && !fc->default_value) fc->default_value = stracpy("on");
 1299     if ((size = get_num(a, "size")) <= 0) size = HTML_DEFAULT_INPUT_SIZE;
 1300     size++;
 1301     if (size > HTML_MINIMAL_TEXTAREA_WIDTH) {
 1302         set_max_textarea_width(&size);
 1303     }
 1304     fc->size = size;
 1305     if ((fc->maxlength = get_num(a, "maxlength")) == -1) fc->maxlength = MAXINT / 4;
 1306     if (fc->type == FC_CHECKBOX || fc->type == FC_RADIO) fc->default_state = has_attr(a, "checked");
 1307     fc->ro = has_attr(a, "disabled") ? 2 : has_attr(a, "readonly") ? 1 : 0;
 1308     if (fc->type == FC_IMAGE) {
 1309         fc->alt = get_attr_val(a, "alt");
 1310         if (!fc->alt) fc->alt = get_attr_val(a, "title");
 1311         if (!fc->alt) fc->alt = get_attr_val(a, "name");
 1312     }
 1313     if (fc->type == FC_SUBMIT && !fc->default_value) fc->default_value = stracpy("Submit");
 1314     if (fc->type == FC_RESET && !fc->default_value) fc->default_value = stracpy("Reset");
 1315     if (!fc->default_value) fc->default_value = stracpy("");
 1316     if (fc->type == FC_HIDDEN) goto hid;
 1317     put_chrs(" ", 1, put_chars_f, ff);
 1318     html_stack_dup();
 1319     format_.form = fc;
 1320     switch (fc->type) {
 1321         case FC_TEXT:
 1322         case FC_PASSWORD:
 1323         case FC_FILE:
 1324             format_.attr |= AT_BOLD;
 1325             for (i = 0; i < fc->size; i++) put_chrs("_", 1, put_chars_f, ff);
 1326             break;
 1327         case FC_CHECKBOX:
 1328         case FC_RADIO:
 1329             format_.attr |= AT_BOLD;
 1330             put_chrs("[&nbsp;]", 8, put_chars_f, ff);
 1331             break;
 1332         case FC_IMAGE:
 1333             if (format_.image) mem_free(format_.image), format_.image = NULL;
 1334             if ((al = get_url_val(a, "src")) || (al = get_url_val(a, "dynsrc"))) {
 1335                 format_.image = join_urls(format_.href_base, al);
 1336                 mem_free(al);
 1337             }
 1338             format_.attr |= AT_BOLD;
 1339             put_chrs("[&nbsp;", 7, put_chars_f, ff);
 1340             if (fc->alt) put_chrs(fc->alt, strlen(fc->alt), put_chars_f, ff);
 1341             else put_chrs("Submit", 6, put_chars_f, ff);
 1342             put_chrs("&nbsp;]", 7, put_chars_f, ff);
 1343             break;
 1344         case FC_SUBMIT:
 1345         case FC_RESET:
 1346             format_.attr |= AT_BOLD;
 1347             put_chrs("[&nbsp;", 7, put_chars_f, ff);
 1348             if (fc->default_value) put_chrs(fc->default_value, strlen(fc->default_value), put_chars_f, ff);
 1349             put_chrs("&nbsp;]", 7, put_chars_f, ff);
 1350             break;
 1351         default:
 1352             internal("bad control type");
 1353     }
 1354     kill_html_stack_item(&html_top);
 1355     put_chrs(" ", 1, put_chars_f, ff);
 1356             
 1357     hid:
 1358     special_f(ff, SP_CONTROL, fc);
 1359 }
 1360 
 1361 void html_select(unsigned char *a)
 1362 {
 1363     char *al;
 1364     if (!(al = get_attr_val(a, "name"))) return;
 1365     html_top.dontkill = 1;
 1366     if (format_.select) mem_free(format_.select);
 1367     format_.select = al;
 1368     format_.select_disabled = 2 * has_attr(a, "disabled");
 1369 }
 1370 
 1371 void html_option(unsigned char *a)
 1372 {
 1373     struct form_control *fc;
 1374     unsigned char *val;
 1375     find_form_for_input(a, 0);
 1376     if (!format_.select) return;
 1377     fc = mem_alloc(sizeof(struct form_control));
 1378     memset(fc, 0, sizeof(struct form_control));
 1379     if (!(val = get_exact_attr_val(a, "value"))) {
 1380         unsigned char *p, *r;
 1381         unsigned char *name;
 1382         int namelen;
 1383         int l = 0;
 1384         for (p = a - 1; *p != '<'; p--) ;
 1385         val = init_str();
 1386         if (parse_element(p, eoff, NULL, NULL, NULL, &p)) {
 1387             internal("parse element failed");
 1388             goto x;
 1389         }
 1390         rrrr:
 1391         while (p < eoff && WHITECHAR(*p)) p++;
 1392         while (p < eoff && !WHITECHAR(*p) && *p != '<') {
 1393             pppp:
 1394             add_chr_to_str(&val, &l, *p), p++;
 1395         }
 1396         r = p;
 1397         while (r < eoff && WHITECHAR(*r)) r++;
 1398         if (r >= eoff) goto x;
 1399         if (eoff - r >= 2 && (r[1] == '!' || r[1] == '?')) {
 1400             p = skip_comment(r, eoff);
 1401             goto rrrr;
 1402         }
 1403         if (parse_element(r, eoff, &name, &namelen, NULL, &p)) goto pppp;
 1404         if (!((namelen == 6 && !casecmp(name, "OPTION", 6)) ||
 1405             (namelen == 7 && !casecmp(name, "/OPTION", 7)) ||
 1406             (namelen == 6 && !casecmp(name, "SELECT", 6)) ||
 1407             (namelen == 7 && !casecmp(name, "/SELECT", 7)) ||
 1408             (namelen == 8 && !casecmp(name, "OPTGROUP", 8)) ||
 1409             (namelen == 9 && !casecmp(name, "/OPTGROUP", 9)))) goto rrrr;
 1410     }
 1411     x:
 1412     fc->form_num = last_form_tag - startf;
 1413     fc->ctrl_num = a - last_form_tag;
 1414     fc->position = a - startf;
 1415     fc->method = form.method;
 1416     fc->action = stracpy(form.action);
 1417     fc->type = FC_CHECKBOX;
 1418     fc->name = stracpy(format_.select);
 1419     fc->default_value = val;
 1420     fc->default_state = has_attr(a, "selected");
 1421     fc->ro = format_.select_disabled;
 1422     if (has_attr(a, "disabled")) fc->ro = 2;
 1423     put_chrs(" ", 1, put_chars_f, ff);
 1424     html_stack_dup();
 1425     format_.form = fc;
 1426     format_.attr |= AT_BOLD;
 1427     put_chrs("[ ]", 3, put_chars_f, ff);
 1428     kill_html_stack_item(&html_top);
 1429     put_chrs(" ", 1, put_chars_f, ff);
 1430     special_f(ff, SP_CONTROL, fc);
 1431 }
 1432 
 1433 void clr_spaces(unsigned char *name)
 1434 {
 1435     unsigned char *n1, *n2;
 1436     for (n1 = name; *n1; n1++)
 1437         if (WHITECHAR(*n1) || *n1 == 1) *n1 = ' ';
 1438     /*for (nm = name; *nm; nm++)
 1439         while (nm[0] == ' ' && (nm == name || nm[1] == ' ' || !nm[1]))
 1440             memmove(nm, nm + 1, strlen(nm));*/
 1441     if (!strchr(name, ' ')) return;
 1442     for (n1 = name, n2 = name; *n1; n1++)
 1443         if (!(n1[0] == ' ' && (n2 == name || n1[1] == ' ' || !n1[1])))
 1444             *n2++ = *n1;
 1445     *n2 = 0;
 1446 }
 1447 
 1448 int menu_stack_size;
 1449 struct menu_item **menu_stack;
 1450 
 1451 void new_menu_item(unsigned char *name, int data, int fullname)
 1452     /* name == NULL - up;   data == -1 - down */
 1453 {
 1454     struct menu_item *top, *item, *nmenu = NULL; /* no uninitialized warnings */
 1455     if (name) {
 1456         clr_spaces(name);
 1457         if (!name[0]) mem_free(name), name = stracpy(" ");
 1458         if (name[0] == 1) name[0] = ' ';
 1459     }
 1460     if (name && data == -1) {
 1461         nmenu = mem_alloc(sizeof(struct menu_item));
 1462         memset(nmenu, 0, sizeof(struct menu_item));
 1463         /*nmenu->text = "";*/
 1464     }
 1465     if (menu_stack_size && name) {
 1466         top = item = menu_stack[menu_stack_size - 1];
 1467         while (item->text) item++;
 1468         if ((size_t)((char *)(item + 2) - (char *)top) > MAXINT) overalloc();
 1469         top = mem_realloc(top, (char *)(item + 2) - (char *)top);
 1470         item = item - menu_stack[menu_stack_size - 1] + top;
 1471         menu_stack[menu_stack_size - 1] = top;
 1472         if (menu_stack_size >= 2) {
 1473             struct menu_item *below = menu_stack[menu_stack_size - 2];
 1474             while (below->text) below++;
 1475             below[-1].data = top;
 1476         }
 1477         item->text = name;
 1478         item->rtext = data == -1 ? ">" : "";
 1479         item->hotkey = fullname ? "\000\001" : "\000\000"; /* dirty */
 1480         item->func = data == -1 ? MENU_FUNC do_select_submenu : MENU_FUNC selected_item;
 1481         item->data = data == -1 ? nmenu : (void *)(my_uintptr_t)data;
 1482         item->in_m = data == -1 ? 1 : 0;
 1483         item->free_i = 0;
 1484         item++;
 1485         memset(item, 0, sizeof(struct menu_item));
 1486         /*item->text = "";*/
 1487     } else if (name) mem_free(name);
 1488     if (name && data == -1) {
 1489         if ((unsigned)menu_stack_size > MAXINT / sizeof(struct menu_item *) - 1) overalloc();
 1490         menu_stack = mem_realloc(menu_stack, (menu_stack_size + 1) * sizeof(struct menu_item *));
 1491         menu_stack[menu_stack_size++] = nmenu;
 1492     }
 1493     if (!name) menu_stack_size--;
 1494 }
 1495 
 1496 void init_menu()
 1497 {
 1498     menu_stack_size = 0;
 1499     menu_stack = DUMMY;
 1500     new_menu_item(stracpy(""), -1, 0);
 1501 }
 1502 
 1503 void free_menu(struct menu_item *m) /* Grrr. Recursion */
 1504 {
 1505     struct menu_item *mm;
 1506     for (mm = m; mm->text; mm++) {
 1507         mem_free(mm->text);
 1508         if (mm->func == MENU_FUNC do_select_submenu) free_menu(mm->data);
 1509     }
 1510     mem_free(m);
 1511 }
 1512 
 1513 struct menu_item *detach_menu()
 1514 {
 1515     struct menu_item *i = NULL;
 1516     if (menu_stack_size) i = menu_stack[0];
 1517     if (menu_stack) mem_free(menu_stack);
 1518     return i;
 1519 }
 1520 
 1521 void destroy_menu()
 1522 {
 1523     if (menu_stack && menu_stack != DUMMY) free_menu(menu_stack[0]);
 1524     detach_menu();
 1525 }
 1526 
 1527 void menu_labels(struct menu_item *m, unsigned char *base, unsigned char **lbls)
 1528 {
 1529     unsigned char *bs;
 1530     for (; m->text; m++) {
 1531         if (m->func == MENU_FUNC do_select_submenu) {
 1532             if ((bs = stracpy(base))) {
 1533                 add_to_strn(&bs, m->text);
 1534                 add_to_strn(&bs, " ");
 1535                 menu_labels(m->data, bs, lbls);
 1536                 mem_free(bs);
 1537             }
 1538         } else {
 1539             if ((bs = stracpy(m->hotkey[1] ? (unsigned char *)"" : base))) add_to_strn(&bs, m->text);
 1540             lbls[(int)(my_uintptr_t)m->data] = bs;
 1541         }
 1542     }
 1543 }
 1544 
 1545 int menu_contains(struct menu_item *m, int f)
 1546 {
 1547     if (m->func != MENU_FUNC do_select_submenu) return (int)(my_uintptr_t)m->data == f;
 1548     for (m = m->data; m->text; m++) if (menu_contains(m, f)) return 1;
 1549     return 0;
 1550 }
 1551 
 1552 void do_select_submenu(struct terminal *term, struct menu_item *menu, struct session *ses)
 1553 {
 1554     struct menu_item *m;
 1555     int def = get_current_state(ses);
 1556     int sel = 0;
 1557     if (def < 0) def = 0;
 1558     for (m = menu; m->text; m++, sel++) if (menu_contains(m, def)) goto f;
 1559     sel = 0;
 1560     f:
 1561     do_menu_selected(term, menu, ses, sel, NULL, NULL);
 1562 }
 1563 
 1564 int do_html_select(unsigned char *attr, unsigned char *html, unsigned char *eof, unsigned char **end, void *f)
 1565 {
 1566     struct form_control *fc;
 1567     unsigned char *t_name, *t_attr, *en;
 1568     int t_namelen;
 1569     unsigned char *lbl;
 1570     int lbl_l;
 1571     unsigned char *vlbl;
 1572     int vlbl_l;
 1573     int nnmi = 0;
 1574     struct conv_table *ct = special_f(f, SP_TABLE, NULL);
 1575     unsigned char **val, **lbls;
 1576     int order, preselect, group;
 1577     int i, mw;
 1578     if (has_attr(attr, "multiple")) return 1;
 1579     find_form_for_input(attr, 0);
 1580     lbl = NULL;
 1581     lbl_l = 0;
 1582     vlbl = NULL;
 1583     vlbl_l = 0;
 1584     val = DUMMY;
 1585     order = 0, group = 0, preselect = -1;
 1586     init_menu();
 1587     se:
 1588     en = html;
 1589     see:
 1590     html = en;
 1591     while (html < eof && *html != '<') html++;
 1592     if (html >= eof) {
 1593         int i;
 1594         abort:
 1595         *end = html;
 1596         if (lbl) mem_free(lbl);
 1597         if (vlbl) mem_free(vlbl);
 1598         for (i = 0; i < order; i++) if (val[i]) mem_free(val[i]);
 1599         mem_free(val);
 1600         destroy_menu();
 1601         *end = en;
 1602         return 0;
 1603     }
 1604     if (lbl) {
 1605         unsigned char *q, *s = en;
 1606         int l = html - en;
 1607         while (l && WHITECHAR(s[0])) s++, l--;
 1608         while (l && WHITECHAR(s[l-1])) l--;
 1609         q = convert_string(ct, s, l);
 1610         if (q) add_to_str(&lbl, &lbl_l, q), mem_free(q);
 1611         add_bytes_to_str(&vlbl, &vlbl_l, s, l);
 1612     }
 1613     if (eof - html >= 2 && (html[1] == '!' || html[1] == '?')) {
 1614         html = skip_comment(html, eof);
 1615         goto se;
 1616     }
 1617     if (parse_element(html, eof, &t_name, &t_namelen, &t_attr, &en)) {
 1618         html++;
 1619         goto se;
 1620     }
 1621     if (t_namelen == 7 && !casecmp(t_name, "/SELECT", 7)) {
 1622         if (lbl) {
 1623             if (!val[order - 1]) val[order - 1] = stracpy(vlbl);
 1624             if (!nnmi) new_menu_item(lbl, order - 1, 1), lbl = NULL;
 1625             else mem_free(lbl), lbl = NULL;
 1626             mem_free(vlbl), vlbl = NULL;
 1627         }
 1628         goto end_parse;
 1629     }
 1630     if (t_namelen == 7 && !casecmp(t_name, "/OPTION", 7)) {
 1631         if (lbl) {
 1632             if (!val[order - 1]) val[order - 1] = stracpy(vlbl);
 1633             if (!nnmi) new_menu_item(lbl, order - 1, 1), lbl = NULL;
 1634             else mem_free(lbl), lbl = NULL;
 1635             mem_free(vlbl), vlbl = NULL;
 1636         }
 1637         goto see;
 1638     }
 1639     if (t_namelen == 6 && !casecmp(t_name, "OPTION", 6)) {
 1640         unsigned char *v, *vx;
 1641         if (lbl) {
 1642             if (!val[order - 1]) val[order - 1] = stracpy(vlbl);
 1643             if (!nnmi) new_menu_item(lbl, order - 1, 1), lbl = NULL;
 1644             else mem_free(lbl), lbl = NULL;
 1645             mem_free(vlbl), vlbl = NULL;
 1646         }
 1647         if (has_attr(t_attr, "disabled")) goto see;
 1648         if (preselect == -1 && has_attr(t_attr, "selected")) preselect = order;
 1649         v = get_exact_attr_val(t_attr, "value");
 1650         if (!(order & (ALLOC_GR - 1))) {
 1651             if ((unsigned)order > MAXINT / sizeof(unsigned char *) - ALLOC_GR) overalloc();
 1652             if ((unsigned)order > MAXINT / sizeof(char *) - ALLOC_GR) overalloc();
 1653             val = mem_realloc(val, (order + ALLOC_GR) * sizeof(unsigned char *));
 1654         }
 1655         val[order++] = v;
 1656         if ((vx = get_attr_val(t_attr, "label"))) {
 1657             new_menu_item(convert_string(ct, vx, strlen(vx)), order - 1, 0);
 1658             mem_free(vx);
 1659         }
 1660         if (!v || !vx) {
 1661             lbl = init_str(), lbl_l = 0;
 1662             vlbl = init_str(), vlbl_l = 0;
 1663             nnmi = !!vx;
 1664         }
 1665         goto see;
 1666     }
 1667     if ((t_namelen == 8 && !casecmp(t_name, "OPTGROUP", 8)) || (t_namelen == 9 && !casecmp(t_name, "/OPTGROUP", 9))) {
 1668         if (lbl) {
 1669             if (!val[order - 1]) val[order - 1] = stracpy(vlbl);
 1670             if (!nnmi) new_menu_item(lbl, order - 1, 1), lbl = NULL;
 1671             else mem_free(lbl), lbl = NULL;
 1672             mem_free(vlbl), vlbl = NULL;
 1673         }
 1674         if (group) new_menu_item(NULL, -1, 0), group = 0;
 1675     }
 1676     if (t_namelen == 8 && !casecmp(t_name, "OPTGROUP", 8)) {
 1677         char *la;
 1678         if (!(la = get_attr_val(t_attr, "label"))) la = stracpy("");
 1679         new_menu_item(convert_string(ct, la, strlen(la)), -1, 0);
 1680         mem_free(la);
 1681         group = 1;
 1682     }
 1683     goto see;
 1684 
 1685     end_parse:
 1686     *end = en;
 1687     if (!order) goto abort;
 1688     fc = mem_alloc(sizeof(struct form_control));
 1689     memset(fc, 0, sizeof(struct form_control));
 1690     lbls = mem_alloc(order * sizeof(char *));
 1691     memset(lbls, 0, order * sizeof(char *));
 1692     fc->form_num = last_form_tag - startf;
 1693     fc->ctrl_num = attr - last_form_tag;
 1694     fc->position = attr - startf;
 1695     fc->method = form.method;
 1696     fc->action = stracpy(form.action);
 1697     fc->name = get_attr_val(attr, "name");
 1698     fc->type = FC_SELECT;
 1699     fc->default_state = preselect < 0 ? 0 : preselect;
 1700     fc->default_value = order ? stracpy(val[fc->default_state]) : stracpy("");
 1701     fc->ro = has_attr(attr, "disabled") ? 2 : has_attr(attr, "readonly") ? 1 : 0;
 1702     fc->nvalues = order;
 1703     fc->values = val;
 1704     fc->menu = detach_menu();
 1705     fc->labels = lbls;
 1706     menu_labels(fc->menu, "", lbls);
 1707     put_chrs("[", 1, put_chars_f, f);
 1708     html_stack_dup();
 1709     format_.form = fc;
 1710     format_.attr |= AT_BOLD;
 1711     mw = 0;
 1712     for (i = 0; i < order; i++) if (lbls[i] && strlen(lbls[i]) > (size_t)mw) mw = strlen(lbls[i]);
 1713     for (i = 0; i < mw; i++) put_chrs("_", 1, put_chars_f, f);
 1714     kill_html_stack_item(&html_top);
 1715     put_chrs("]", 1, put_chars_f, f);
 1716     special_f(ff, SP_CONTROL, fc);
 1717     return 0;
 1718 }
 1719 
 1720 void html_textarea(unsigned char *a)
 1721 {
 1722     internal("This should be never called");
 1723 }
 1724 
 1725 void do_html_textarea(unsigned char *attr, unsigned char *html, unsigned char *eof, unsigned char **end, void *f)
 1726 {
 1727     struct form_control *fc;
 1728     unsigned char *p, *t_name, *w;
 1729     int t_namelen;
 1730     int cols, rows;
 1731     int i;
 1732     find_form_for_input(attr, 1);
 1733     while (html < eof && (*html == '\n' || *html == '\r')) html++;
 1734     p = html;
 1735     while (p < eof && *p != '<') {
 1736         pp:
 1737         p++;
 1738     }
 1739     if (p >= eof) {
 1740         *end = eof;
 1741         return;
 1742     }
 1743     if (parse_element(p, eof, &t_name, &t_namelen, NULL, end)) goto pp;
 1744     if (t_namelen != 9 || casecmp(t_name, "/TEXTAREA", 9)) goto pp;
 1745     if (!last_form_tag) return;
 1746     fc = mem_alloc(sizeof(struct form_control));
 1747     memset(fc, 0, sizeof(struct form_control));
 1748     fc->form_num = last_form_tag - startf;
 1749     fc->ctrl_num = attr - last_form_tag;
 1750     fc->position = attr - startf;
 1751     fc->method = form.method;
 1752     fc->action = stracpy(form.action);
 1753     fc->name = get_attr_val(attr, "name");
 1754     fc->type = FC_TEXTAREA;;
 1755     fc->ro = has_attr(attr, "disabled") ? 2 : has_attr(attr, "readonly") ? 1 : 0;
 1756     fc->default_value = memacpy(html, p - html);
 1757     if ((cols = get_num(attr, "cols")) < HTML_MINIMAL_TEXTAREA_WIDTH) cols = HTML_DEFAULT_TEXTAREA_WIDTH;
 1758     cols++;
 1759     set_max_textarea_width(&cols);
 1760     if ((rows = get_num(attr, "rows")) <= 0) rows = HTML_DEFAULT_TEXTAREA_HEIGHT;
 1761     if (rows > d_opt->yw) {
 1762         rows = d_opt->yw;
 1763         if (rows <= 0) rows = 1;
 1764     }
 1765     fc->cols = cols;
 1766     fc->rows = rows;
 1767     fc->wrap = 1;
 1768     if ((w = get_attr_val(attr, "wrap"))) {
 1769         if (!strcasecmp(w, "hard") || !strcasecmp(w, "physical")) fc->wrap = 2;
 1770         else if (!strcasecmp(w, "off")) fc->wrap = 0;
 1771         mem_free(w);
 1772     }
 1773     if ((fc->maxlength = get_num(attr, "maxlength")) == -1) fc->maxlength = MAXINT / 4;
 1774     if (rows > 1) ln_break(1, line_break_f, f);
 1775     else put_chrs(" ", 1, put_chars_f, f);
 1776     html_stack_dup();
 1777     format_.form = fc;
 1778     format_.attr |= AT_BOLD;
 1779     for (i = 0; i < rows; i++) {
 1780         int j;
 1781         for (j = 0; j < cols; j++) put_chrs("_", 1, put_chars_f, f);
 1782         if (i < rows - 1) ln_break(1, line_break_f, f);
 1783     }
 1784     kill_html_stack_item(&html_top);
 1785     if (rows > 1) ln_break(1, line_break_f, f);
 1786     else put_chrs(" ", 1, put_chars_f, f);
 1787     special_f(f, SP_CONTROL, fc);
 1788 }
 1789 
 1790 void html_iframe(unsigned char *a)
 1791 {
 1792     unsigned char *name, *url;
 1793     if (!(url = get_url_val(a, "src"))) return;
 1794     if (!(name = get_attr_val(a, "name"))) name = stracpy("");
 1795     if (*name) put_link_line("IFrame: ", name, url, d_opt->framename);
 1796     else put_link_line("", "IFrame", url, d_opt->framename);
 1797     mem_free(name);
 1798     mem_free(url);
 1799 }
 1800 
 1801 void html_noframes(unsigned char *a)
 1802 {
 1803     if (d_opt->frames) html_skip(a);
 1804 }
 1805 
 1806 void html_frame(unsigned char *a)
 1807 {
 1808     unsigned char *name, *u2, *url;
 1809     if (!(u2 = get_url_val(a, "src"))) {
 1810         url = stracpy("");
 1811     } else {
 1812         url = join_urls(format_.href_base, u2);
 1813         mem_free(u2);
 1814     }
 1815     if (!url) return;
 1816     name = get_attr_val (a, "name");
 1817     if (!name)
 1818         name = stracpy(url);
 1819     else if (!name[0]) { /* When name doesn't have a value */
 1820         mem_free(name);
 1821         name = stracpy(url);
 1822     }
 1823     if (!d_opt->frames || !html_top.frameset) put_link_line("Frame: ", name, url, "");
 1824     else {
 1825         struct frame_param fp;
 1826         fp.name = name;
 1827         fp.url = url;
 1828         fp.parent = html_top.frameset;
 1829         if (special_f(ff, SP_USED, NULL)) special_f(ff, SP_FRAME, &fp);
 1830     }
 1831     mem_free(name);
 1832     mem_free(url);
 1833 }
 1834 
 1835 void parse_frame_widths(unsigned char *a, int ww, int www, int **op, int *olp)
 1836 {
 1837     unsigned char *aa;
 1838     int q, qq, i, d, nn;
 1839     unsigned long n;
 1840     int *oo, *o;
 1841     int ol;
 1842     ol = 0;
 1843     o = DUMMY;
 1844     new_ch:
 1845     while (WHITECHAR(*a)) a++;
 1846     n = strtoul(a, (char **)(void *)&a, 10);
 1847     if (n > 10000) n = 10000;
 1848     q = n;
 1849     if (*a == '%') q = q * ww / 100;
 1850     else if (*a != '*') q = (q + (www - 1) / 2) / (www ? www : 1);
 1851     else if (!(q = -q)) q = -1;
 1852     if ((unsigned)ol > MAXINT / sizeof(int) - 1) overalloc();
 1853     o = mem_realloc(o, (ol + 1) * sizeof(int));
 1854     o[ol++] = q;
 1855     if ((aa = strchr(a, ','))) {
 1856         a = aa + 1;
 1857         goto new_ch;
 1858     }
 1859     *op = o;
 1860     *olp = ol;
 1861     q = 2 * ol - 1;
 1862     for (i = 0; i < ol; i++) if (o[i] > 0) q += o[i] - 1;
 1863     if (q >= ww) {
 1864         distribute:
 1865         for (i = 0; i < ol; i++) if (o[i] < 1) o[i] = 1;
 1866         q -= ww;
 1867         d = 0;
 1868         for (i = 0; i < ol; i++) d += o[i];
 1869         qq = q;
 1870         for (i = 0; i < ol; i++) {
 1871             q -= o[i] - o[i] * (d - qq) / (d ? d : 1);
 1872             do_not_optimize_here(&d);
 1873                 /* SIGH! gcc 2.7.2.* has an optimizer bug! */
 1874             o[i] = o[i] * (d - qq) / (d ? d : 1);
 1875         }
 1876         while (q) {
 1877             nn = 0;
 1878             for (i = 0; i < ol; i++) {
 1879                 if (q < 0) o[i]++, q++, nn = 1;
 1880                 if (q > 0 && o[i] > 1) o[i]--, q--, nn = 1;
 1881                 if (!q) break;
 1882             }
 1883             if (!nn) break;
 1884         }
 1885     } else {
 1886         int nn = 0;
 1887         for (i = 0; i < ol; i++) if (o[i] < 0) nn = 1;
 1888         if (!nn) goto distribute;
 1889         if ((unsigned)ol > MAXINT / sizeof(int)) overalloc();
 1890         oo = mem_alloc(ol * sizeof(int));
 1891         memcpy(oo, o, ol * sizeof(int));
 1892         for (i = 0; i < ol; i++) if (o[i] < 1) o[i] = 1;
 1893         q = ww - q;
 1894         d = 0;
 1895         for (i = 0; i < ol; i++) if (oo[i] < 0) d += -oo[i];
 1896         qq = q;
 1897         for (i = 0; i < ol; i++) if (oo[i] < 0) {
 1898             o[i] += (-oo[i] * qq / (d ? d : 1));
 1899             q -= (-oo[i] * qq / (d ? d : 1));
 1900         }
 1901         if (q < 0) {
 1902             q = 0;
 1903             /*internal("parse_frame_widths: q < 0"); may happen when page contains too big values */
 1904         }
 1905         for (i = 0; i < ol; i++) if (oo[i] < 0) {
 1906             if (q) o[i]++, q--;
 1907         }
 1908         if (q > 0) {
 1909             q = 0;
 1910             /*internal("parse_frame_widths: q > 0"); may happen when page contains too big values */
 1911         }
 1912         mem_free(oo);
 1913     }
 1914     for (i = 0; i < ol; i++) if (!o[i]) {
 1915         int j;
 1916         int m = 0;
 1917         int mj = 0;
 1918         for (j = 0; j < ol; j++) if (o[j] > m) m = o[j], mj = j;
 1919         if (m) o[i] = 1, o[mj]--;
 1920     }
 1921 }
 1922 
 1923 void html_frameset(unsigned char *a)
 1924 {
 1925     int x, y;
 1926     struct frameset_param fp;
 1927     unsigned char *c, *d;
 1928     if (!d_opt->frames || !special_f(ff, SP_USED, NULL)) return;
 1929     if (!(c = get_attr_val(a, "cols"))) c = stracpy("100%");
 1930     if (!(d = get_attr_val(a, "rows"))) d = stracpy("100%");
 1931     if (!html_top.frameset) {
 1932         x = d_opt->xw;
 1933         y = d_opt->yw;
 1934     } else {
 1935         struct frameset_desc *f = html_top.frameset;
 1936         if (f->yp >= f->y) goto free_cd;
 1937         x = f->f[f->xp + f->yp * f->x].xw;
 1938         y = f->f[f->xp + f->yp * f->x].yw;
 1939     }
 1940     parse_frame_widths(c, x, HTML_FRAME_CHAR_WIDTH, &fp.xw, &fp.x);
 1941     parse_frame_widths(d, y, HTML_FRAME_CHAR_HEIGHT, &fp.yw, &fp.y);
 1942     fp.parent = html_top.frameset;
 1943     if (fp.x && fp.y) html_top.frameset = special_f(ff, SP_FRAMESET, &fp);
 1944     mem_free(fp.xw);
 1945     mem_free(fp.yw);
 1946     free_cd:
 1947     mem_free(c);
 1948     mem_free(d);
 1949 }
 1950 
 1951 /*void html_frameset(unsigned char *a)
 1952 {
 1953     int w;
 1954     int horiz = 0;
 1955     struct frameset_param *fp;
 1956     unsigned char *c, *d;
 1957     if (!d_opt->frames || !special_f(ff, SP_USED, NULL)) return;
 1958     if (!(c = get_attr_val(a, "cols"))) {
 1959         horiz = 1;
 1960         if (!(c = get_attr_val(a, "rows"))) return;
 1961     }
 1962     fp = mem_alloc(sizeof(struct frameset_param));
 1963     fp->n = 0;
 1964     fp->horiz = horiz;
 1965     par_format.leftmargin = par_format.rightmargin = 0;
 1966     d = c;
 1967     while (1) {
 1968         while (WHITECHAR(*d)) d++;
 1969         if (!*d) break;
 1970         if (*d == ',') {
 1971             d++;
 1972             continue;
 1973         }
 1974         if ((w = parse_width(d, 1)) != -1) {
 1975             if ((unsigned)fp->n > (MAXINT - sizeof(struct frameset_param)) / sizeof(int) - 1) overalloc();
 1976             fp = mem_realloc(fp, sizeof(struct frameset_param) + (fp->n + 1) * sizeof(int));
 1977             fp->width[fp->n++] = w;
 1978         }
 1979         if (!(d = strchr(d, ','))) break;
 1980         d++;
 1981     }
 1982     fp->parent = html_top.frameset;
 1983     if (fp->n) html_top.frameset = special_f(ff, SP_FRAMESET, fp);
 1984     mem_free(fp);
 1985     f:
 1986     mem_free(c);
 1987 }*/
 1988 
 1989 void html_link(unsigned char *a)
 1990 {
 1991     unsigned char *name, *url, *title;
 1992     if ((name = get_attr_val(a, "type"))) {
 1993         if (strcasecmp(name, "text/html")) {
 1994             mem_free(name);
 1995             return;
 1996         }
 1997         mem_free(name);
 1998     }
 1999     if (!(url = get_url_val(a, "href"))) return;
 2000     if (!(name = get_attr_val(a, "rel")))
 2001         if (!(name = get_attr_val(a, "rev")))
 2002             name = get_attr_val(a, "ref");
 2003     if (name) {
 2004         unsigned char *lang;
 2005         if ((lang = get_attr_val(a, "hreflang"))) {
 2006             add_to_strn(&name, " ");
 2007             add_to_strn(&name, lang);
 2008             mem_free(lang);
 2009         }
 2010     }
 2011     if (!name)
 2012         name = stracpy(url);
 2013     if (
 2014         !casecmp(name, cast_uchar "apple-touch-icon", 16) ||
 2015         !casecmp(name, cast_uchar "schema", 6) ||
 2016         !strcasecmp(cast_const_char name, "Edit-Time-Data") ||
 2017         !strcasecmp(cast_const_char name, "File-List") ||
 2018         !strcasecmp(cast_const_char name, "alternate stylesheet") ||
 2019         !strcasecmp(cast_const_char name, "generator-home") ||
 2020         !strcasecmp(cast_const_char name, "https://github.com/WP-API/WP-API") ||
 2021         !strcasecmp(cast_const_char name, "icon") ||
 2022         !strcasecmp(cast_const_char name, "made") ||
 2023         !strcasecmp(cast_const_char name, "meta") ||
 2024         !strcasecmp(cast_const_char name, "pingback") ||
 2025         !strcasecmp(cast_const_char name, "preconnect") ||
 2026         !strcasecmp(cast_const_char name, "shortcut icon") ||
 2027         !strcasecmp(cast_const_char name, "stylesheet") ||
 2028         !strcasecmp(cast_const_char name, "https://api.w.org/") ||
 2029         !strcasecmp(cast_const_char name, "prefetch") ||
 2030         !strcasecmp(cast_const_char name, "dns-prefetch") ||
 2031         !strcasecmp(cast_const_char name, "prerender") ||
 2032         !strcasecmp(cast_const_char name, "preload") ||
 2033         0) goto skip;
 2034     if ((title = get_attr_val(a, "title"))) {
 2035         add_to_strn(&name, ": ");
 2036         add_to_strn(&name, title);
 2037         mem_free(title);
 2038     }
 2039     put_link_line("Link: ", name, url, format_.target_base);
 2040     skip:
 2041     mem_free(name);
 2042     mem_free(url);
 2043 }
 2044 
 2045 struct element_info {
 2046     char *name;
 2047     void (*func)(unsigned char *);
 2048     int linebreak;
 2049     int nopair;
 2050 };
 2051 
 2052 struct element_info elements[] = {
 2053     {"SPAN",    html_span,  0, 0},
 2054     {"B",       html_bold,  0, 0},
 2055     {"STRONG",  html_bold,  0, 0},
 2056     {"DFN",     html_bold,  0, 0},
 2057     {"I",       html_italic,    0, 0},
 2058     {"Q",       html_italic,    0, 0},
 2059     {"CITE",    html_italic,    0, 0},
 2060     {"EM",      html_italic,    0, 0},
 2061     {"ABBR",    html_italic,    0, 0},
 2062     {"U",       html_underline, 0, 0},
 2063     {"S",       html_underline, 0, 0},
 2064     {"STRIKE",  html_underline, 0, 0},
 2065     {"FIXED",   html_fixed, 0, 0},
 2066     {"CODE",    html_fixed, 0, 0},
 2067     {"FONT",    html_font,  0, 0},
 2068     {"A",       html_a,     0, 2},
 2069     {"IMG",     html_img,   0, 1},
 2070 
 2071     {"BASE",    html_base,  0, 1},
 2072     {"BASEFONT",    html_font,  0, 1},
 2073 
 2074     {"BODY",    html_body,  0, 0},
 2075 
 2076 /*  {"HEAD",    html_skip,  0, 0},*/
 2077     {"TITLE",   html_title, 0, 0},
 2078     {"SCRIPT",  html_skip,  0, 0},
 2079     {"STYLE",   html_skip,  0, 0},
 2080 
 2081     {"BR",      html_br,    1, 1},
 2082     {"DIV",     html_linebrk,   1, 0},
 2083     {"CENTER",  html_center,    1, 0},
 2084     {"CAPTION", html_center,    1, 0},
 2085     {"P",       html_p,     2, 2},
 2086     {"HR",      html_hr,    2, 1},
 2087     {"H1",      html_center,    2, 2},
 2088     {"H2",      html_h2,    2, 2},
 2089     {"H3",      html_h3,    2, 2},
 2090     {"H4",      html_h4,    2, 2},
 2091     {"H5",      html_h5,    2, 2},
 2092     {"H6",      html_h6,    2, 2},
 2093     {"BLOCKQUOTE",  html_blockquote,2, 0},
 2094     {"ADDRESS", html_address,   2, 0},
 2095     {"PRE",     html_pre,   2, 0},
 2096     {"LISTING", html_pre,   2, 0},
 2097 
 2098     {"UL",      html_ul,    1, 0},
 2099     {"DIR",     html_ul,    1, 0},
 2100     {"MENU",    html_ul,    1, 0},
 2101     {"OL",      html_ol,    1, 0},
 2102     {"LI",      html_li,    1, 3},
 2103     {"DL",      html_dl,    1, 0},
 2104     {"DT",      html_dt,    1, 1},
 2105     {"DD",      html_dd,    1, 1},
 2106 
 2107     {"TABLE",   html_table, 2, 0},
 2108     {"TR",      html_tr,    1, 0},
 2109     {"TD",      html_td,    0, 0},
 2110     {"TH",      html_th,    0, 0},
 2111 
 2112     {"FORM",    html_form,  1, 0},
 2113     {"INPUT",   html_input, 0, 1},
 2114     {"TEXTAREA",    html_textarea,  0, 1},
 2115     {"SELECT",  html_select,    0, 0},
 2116     {"OPTION",  html_option,    1, 1},
 2117     {"BUTTON",  html_button,    0, 0},
 2118 
 2119     {"LINK",    html_link,  1, 1},
 2120     {"IFRAME",  html_iframe,    1, 1},
 2121     {"FRAME",   html_frame, 1, 1},
 2122     {"FRAMESET",    html_frameset,  1, 0},
 2123     {"NOFRAMES",    html_noframes,  0, 0},
 2124 
 2125     {NULL,      NULL, 0, 0},
 2126 };
 2127 
 2128 unsigned char *skip_comment(unsigned char *html, unsigned char *eof)
 2129 {
 2130     int comm = eof - html >= 4 && html[2] == '-' && html[3] == '-';
 2131     html += comm ? 4 : 2;
 2132     while (html < eof) {
 2133         if (!comm && html[0] == '>') return html + 1;
 2134         if (comm && eof - html >= 2 && html[0] == '-' && html[1] == '-') {
 2135             html += 2;
 2136             while (html < eof && *html == '-') html++;
 2137             while (html < eof && WHITECHAR(*html)) html++;
 2138             if (html >= eof) return eof;
 2139             if (*html == '>') return html + 1;
 2140             continue;
 2141         }
 2142         html++;
 2143     }
 2144     return eof;
 2145 }
 2146 
 2147 void process_head(unsigned char *head)
 2148 {
 2149     unsigned char *r, *p;
 2150     if ((r = parse_http_header(head, "Refresh", NULL))) {
 2151         if ((p = parse_header_param(r, "URL")) || (p = parse_header_param(r, ""))) {
 2152             put_link_line("Refresh: ", p, p, d_opt->framename);
 2153             mem_free(p);
 2154         }
 2155         mem_free(r);
 2156     }
 2157 }
 2158 
 2159 int qd(unsigned char *html, unsigned char *eof, int *len)
 2160 {
 2161     int l;
 2162     *len = 1;
 2163     if (html >= eof) {
 2164         internal("qd: out of data, html == %p, eof == %p", html, eof);
 2165         return -1;
 2166     }
 2167     if (html[0] != '&' || d_opt->plain) return html[0];
 2168     if (html + 1 >= eof) return -1;
 2169     if (html[1] != '#') return -1;
 2170     for (l = 2; l < 10 && eof - html > l; l++) if (html[l] == ';') {
 2171         int n = get_entity_number(html + 2, l - 2);
 2172         if (n >= 0) {
 2173             *len = l + 1;
 2174             return n;
 2175         }
 2176         break;
 2177     }
 2178     return -1;
 2179 }
 2180 
 2181 void parse_html(unsigned char *html, unsigned char *eof, int (*put_chars)(void *, unsigned char *, int), void (*line_break)(void *), void *(*special)(void *, int, ...), void *f, unsigned char *head)
 2182 {
 2183     /*unsigned char *start = html;*/
 2184     unsigned char *lt;
 2185     putsp = -1;
 2186     line_breax = table_level ? 2 : 1;
 2187     pos = 0;
 2188     was_br = 0;
 2189     put_chars_f = put_chars;
 2190     line_break_f = line_break;
 2191     special_f = special;
 2192     ff = f;
 2193     eoff = eof;
 2194     if (head) process_head(head);
 2195     set_lt:
 2196     put_chars_f = put_chars;
 2197     line_break_f = line_break;
 2198     special_f = special;
 2199     ff = f;
 2200     eoff = eof;
 2201     lt = html;
 2202     while (html < eof) {
 2203         unsigned char *name, *attr, *end;
 2204         int namelen;
 2205         struct element_info *ei;
 2206         int inv;
 2207         if (WHITECHAR(*html) && par_format.align != AL_NO) {
 2208             unsigned char *h = html;
 2209             /*if (putsp == -1) {
 2210                 while (html < eof && WHITECHAR(*html)) html++;
 2211                 goto set_lt;
 2212             }
 2213             putsp = 0;*/
 2214             while (h < eof && WHITECHAR(*h)) h++;
 2215             if (eof - h > 1 && h[0] == '<' && h[1] == '/') {
 2216                 if (!parse_element(h, eof, &name, &namelen, &attr, &end)) {
 2217                     put_chrs(lt, html - lt, put_chars, f);
 2218                     lt = html = h;
 2219                     if (!html_top.invisible) putsp = 1;
 2220                     goto element;
 2221                 }
 2222             }
 2223             html++;
 2224             if (!(pos + (html-lt-1))) goto skip_w; /* ??? */
 2225             if (*(html - 1) == ' ') {
 2226                 if (html < eof && !WHITECHAR(*html)) continue;  /* BIG performance win; not sure if it doesn't cause any bug */
 2227                 put_chrs(lt, html - lt, put_chars, f);
 2228             } else {
 2229                 put_chrs(lt, html - 1 - lt, put_chars, f);
 2230                 put_chrs(" ", 1, put_chars, f);
 2231             }
 2232             skip_w:
 2233             while (html < eof && WHITECHAR(*html)) html++;
 2234             /*putsp = -1;*/
 2235             goto set_lt;
 2236             put_sp:
 2237             put_chrs(" ", 1, put_chars, f);
 2238             /*putsp = -1;*/
 2239         }
 2240         if (par_format.align == AL_NO && (*html < 32 || *html == '&')) {
 2241             int l;
 2242             int q = qd(html, eof, &l);
 2243             putsp = 0;
 2244             if (q == 9) {
 2245                 put_chrs(lt, html - lt, put_chars, f);
 2246                 put_chrs("        ", 8 - pos % 8, put_chars, f);
 2247                 html += l;
 2248                 goto set_lt;
 2249             } else if (q == 13 || q == 10) {
 2250                 put_chrs(lt, html - lt, put_chars, f);
 2251                 next_break:
 2252                 html += l;
 2253                 if (q == 13 && eof - html > 1 && qd(html, eof, &l) == 10) html += l;
 2254                 ln_break(1, line_break, f);
 2255                 if (html >= eof) goto set_lt;
 2256                 q = qd(html, eof, &l);
 2257                 if (q == 13 || q == 10) {
 2258                     line_breax = 0;
 2259                     goto next_break;
 2260                 }
 2261                 goto set_lt;
 2262             }
 2263         }
 2264         if (*html < ' ') {
 2265             /*if (putsp == 1) goto put_sp;
 2266             putsp = 0;*/
 2267             put_chrs(lt, html - lt, put_chars, f);
 2268             put_chrs(".", 1, put_chars, f);
 2269             html++;
 2270             goto set_lt;
 2271         }
 2272         if (eof - html >= 2 && html[0] == '<' && (html[1] == '!' || html[1] == '?') && !d_opt->plain) {
 2273             /*if (putsp == 1) goto put_sp;
 2274             putsp = 0;*/
 2275             put_chrs(lt, html - lt, put_chars, f);
 2276             html = skip_comment(html, eof);
 2277             goto set_lt;
 2278         }
 2279         if (*html != '<' || d_opt->plain || parse_element(html, eof, &name, &namelen, &attr, &end)) {
 2280             /*if (putsp == 1) goto put_sp;
 2281             putsp = 0;*/
 2282             html++;
 2283             continue;
 2284         }
 2285         element:
 2286         inv = *name == '/'; name += inv; namelen -= inv;
 2287         if (!inv && putsp == 1 && !html_top.invisible) goto put_sp;
 2288         put_chrs(lt, html - lt, put_chars, f);
 2289         if (par_format.align != AL_NO) if (!inv && !putsp) {
 2290             unsigned char *ee = end;
 2291             unsigned char *nm;
 2292             while (!parse_element(ee, eof, &nm, NULL, NULL, &ee))
 2293                 if (*nm == '/') goto ng;
 2294             if (ee < eof && WHITECHAR(*ee)) {
 2295                 /*putsp = -1;*/
 2296                 put_chrs(" ", 1, put_chars, f);
 2297             }
 2298             ng:;
 2299         }
 2300         html = end;
 2301         for (ei = elements; ei->name; ei++) {
 2302             if (ei->name &&
 2303                (strlen(ei->name) != (size_t)namelen || casecmp(ei->name, name, namelen)))
 2304                 continue;
 2305             if (!inv) {
 2306                 char *a;
 2307                 ln_break(ei->linebreak, line_break, f);
 2308                 if (ei->name && ((a = get_attr_val(attr, "id")))) {
 2309                     special(f, SP_TAG, a);
 2310                     mem_free(a);
 2311                 }
 2312                 if (!html_top.invisible) {
 2313                     int a = par_format.align == AL_NO;
 2314                     struct par_attrib pa = par_format;
 2315                     if (ei->func == html_table && d_opt->tables && table_level < HTML_MAX_TABLE_LEVEL) {
 2316                         format_table(attr, html, eof, &html, f);
 2317                         ln_break(2, line_break, f);
 2318                         goto set_lt;
 2319                     }
 2320                     if (ei->func == html_select) {
 2321                         if (!do_html_select(attr, html, eof, &html, f))
 2322                             goto set_lt;
 2323                     }
 2324                     if (ei->func == html_textarea) {
 2325                         do_html_textarea(attr, html, eof, &html, f);
 2326                         goto set_lt;
 2327                     }
 2328                     if (ei->nopair == 2 || ei->nopair == 3) {
 2329                         struct html_element *e;
 2330                         if (ei->nopair == 2) {
 2331                             foreach(e, html_stack) {
 2332                                 if (e->dontkill) break;
 2333                                 if (e->linebreak || !ei->linebreak) break;
 2334                             }
 2335                         } else foreach(e, html_stack) {
 2336                             if (e->linebreak && !ei->linebreak) break;
 2337                             if (e->dontkill) break;
 2338                             if (e->namelen == namelen && !casecmp(e->name, name, e->namelen)) break;
 2339                         }
 2340                         if (e->namelen == namelen && !casecmp(e->name, name, e->namelen)) {
 2341                             while (e->prev != (void *)&html_stack) kill_html_stack_item(e->prev);
 2342                             if (e->dontkill != 2) kill_html_stack_item(e);
 2343                         }
 2344                     }
 2345                     if (ei->nopair != 1) {
 2346                         html_stack_dup();
 2347                         html_top.name = name;
 2348                         html_top.namelen = namelen;
 2349                         html_top.options = attr;
 2350                         html_top.linebreak = ei->linebreak;
 2351                     }
 2352                     if (ei->func) ei->func(attr);
 2353                     if (ei->func != html_br) was_br = 0;
 2354                     if (a) par_format = pa;
 2355                 }
 2356             } else {
 2357                 struct html_element *e, *ff;
 2358                 int lnb = 0;
 2359                 int xxx = 0;
 2360                 was_br = 0;
 2361                 if (ei->nopair == 1 || ei->nopair == 3) break;
 2362                 /*debug_stack();*/
 2363                 foreach(e, html_stack) {
 2364                     if (e->linebreak && !ei->linebreak && ei->name) xxx = 1;
 2365                     if (e->namelen != namelen || casecmp(e->name, name, e->namelen)) {
 2366                         if (e->dontkill) break;
 2367                         else continue;
 2368                     }
 2369                     if (xxx) {
 2370                         kill_html_stack_item(e);
 2371                         break;
 2372                     }
 2373                     for (ff = e; ff != (void *)&html_stack; ff = ff->prev)
 2374                         if (ff->linebreak > lnb) lnb = ff->linebreak;
 2375                     ln_break(lnb, line_break, f);
 2376                     while (e->prev != (void *)&html_stack) kill_html_stack_item(e->prev);
 2377                     kill_html_stack_item(e);
 2378                     break;
 2379                 }
 2380                 /*debug_stack();*/
 2381             }
 2382             goto set_lt;
 2383         }
 2384         goto set_lt; /* unreachable */
 2385     }
 2386     put_chrs(lt, html - lt, put_chars, f);
 2387     ln_break(1, line_break, f);
 2388     putsp = -1;
 2389     pos = 0;
 2390     /*line_breax = 1;*/
 2391     was_br = 0;
 2392 }
 2393 
 2394 int get_image_map(unsigned char *head, unsigned char *s, unsigned char *eof, unsigned char *tag, struct menu_item **menu, struct memory_list **ml, unsigned char *href_base, unsigned char *target_base, int to, int def, int hdef)
 2395 {
 2396     unsigned char *name, *attr, *al, *label, *href, *target;
 2397     int namelen, lblen;
 2398     struct link_def *ld;
 2399     struct menu_item *nm;
 2400     int nmenu = 0;
 2401     int i;
 2402     unsigned char *hd = init_str();
 2403     int hdl = 0;
 2404     struct conv_table *ct;
 2405     if (head) add_to_str(&hd, &hdl, head);
 2406     scan_http_equiv(s, eof, &hd, &hdl, NULL);
 2407     ct = get_convert_table(hd, to, def, NULL, NULL, hdef);
 2408     mem_free(hd);
 2409     *menu = mem_alloc(sizeof(struct menu_item));
 2410     memset(*menu, 0, sizeof(struct menu_item));
 2411     /*(*menu)->text = NULL;*/
 2412     se:
 2413     while (s < eof && *s != '<') {
 2414         sp:
 2415         s++;
 2416     }
 2417     if (s >= eof) {
 2418         mem_free(*menu);
 2419         return -1;
 2420     }
 2421     if (eof - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 2422         s = skip_comment(s, eof);
 2423         goto se;
 2424     }
 2425     if (parse_element(s, eof, &name, &namelen, &attr, &s)) goto sp;
 2426     if (namelen != 3 || casecmp(name, "MAP", 3)) goto se;
 2427     if (tag && *tag) {
 2428         if (!(al = get_attr_val(attr, "name"))) goto se;
 2429         if (strcasecmp(al, tag)) {
 2430             mem_free(al);
 2431             goto se;
 2432         }
 2433         mem_free(al);
 2434     }
 2435     *ml = getml(NULL);
 2436     se2:
 2437     while (s < eof && *s != '<') {
 2438         sp2:
 2439         s++;
 2440     }
 2441     if (s >= eof) {
 2442         freeml(*ml);
 2443         mem_free(*menu);
 2444         return -1;
 2445     }
 2446     if (eof - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 2447         s = skip_comment(s, eof);
 2448         goto se2;
 2449     }
 2450     if (parse_element(s, eof, &name, &namelen, &attr, &s)) goto sp2;
 2451     if (namelen == 1 && !casecmp(name, "A", 1)) {
 2452         unsigned char *ss;
 2453         label = init_str();
 2454         lblen = 0;
 2455         se3:
 2456         ss = s;
 2457         se4:
 2458         while (ss < eof && *ss != '<') ss++;
 2459         if (ss >= eof) {
 2460             mem_free(label);
 2461             freeml(*ml);
 2462             mem_free(*menu);
 2463             return -1;
 2464         }
 2465         add_bytes_to_str(&label, &lblen, s, ss - s);
 2466         s = ss;
 2467         if (eof - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 2468             s = skip_comment(s, eof);
 2469             goto se3;
 2470         }
 2471         if (parse_element(s, eof, NULL, NULL, NULL, &ss)) {
 2472             ss = s + 1;
 2473             goto se4;
 2474         }
 2475         if (!((namelen == 1 && !casecmp(name, "A", 1)) ||
 2476               (namelen == 2 && !casecmp(name, "/A", 2)) ||
 2477               (namelen == 3 && !casecmp(name, "MAP", 3)) ||
 2478               (namelen == 4 && !casecmp(name, "/MAP", 4)) ||
 2479               (namelen == 4 && !casecmp(name, "AREA", 4)) ||
 2480               (namelen == 5 && !casecmp(name, "/AREA", 5)))) {
 2481                 s = ss;
 2482                 goto se3;
 2483         }
 2484     } else if (namelen == 4 && !casecmp(name, "AREA", 4)) {
 2485         unsigned char *l = get_attr_val(attr, "alt");
 2486         if (l) label = convert_string(ct, l, strlen(l)), mem_free(l);
 2487         else label = NULL;
 2488     } else if (namelen == 4 && !casecmp(name, "/MAP", 4)) goto done;
 2489     else goto se2;
 2490     if (!(href = get_url_val(attr, "href"))) {
 2491         if (label) mem_free(label);
 2492         goto se2;
 2493     }
 2494     if (!(target = get_target(attr)) && !(target = stracpy(target_base)))
 2495         target = stracpy("");
 2496     ld = mem_alloc(sizeof(struct link_def));
 2497     if (!(ld->link = join_urls(href_base, href))) {
 2498         mem_free(href);
 2499         mem_free(target);
 2500         f:
 2501         mem_free(ld);
 2502         if (label) mem_free(label);
 2503         goto se2;
 2504     }
 2505     mem_free(href);
 2506     ld->target = target;
 2507     for (i = 0; i < nmenu; i++) {
 2508         struct link_def *ll = (*menu)[i].data;
 2509         if (!strcmp(ll->link, ld->link) && !strcmp(ll->target, ld->target)) {
 2510             mem_free(ld->link);
 2511             mem_free(ld->target);
 2512             goto f;
 2513         }
 2514     }
 2515     if (label) clr_spaces(label);
 2516     if (label && !*label) mem_free(label), label = NULL;
 2517     if (!label) label = stracpy(ld->link);
 2518     if ((unsigned)nmenu > MAXINT / sizeof(struct menu_item) - 2) overalloc();
 2519     nm = mem_realloc(*menu, (nmenu + 2) * sizeof(struct menu_item));
 2520     *menu = nm;
 2521     memset(&nm[nmenu], 0, 2 * sizeof(struct menu_item));
 2522     nm[nmenu].text = label;
 2523     nm[nmenu].rtext = "";
 2524     nm[nmenu].hotkey = "";
 2525     nm[nmenu].func = MENU_FUNC map_selected;
 2526     nm[nmenu].data = ld;
 2527     nm[++nmenu].text = NULL;
 2528     add_to_ml(ml, ld, ld->link, ld->target, label, NULL);
 2529     goto se2;
 2530     done:
 2531     add_to_ml(ml, *menu, NULL);
 2532     return 0;
 2533 }
 2534 
 2535 void scan_http_equiv(unsigned char *s, unsigned char *eof, unsigned char **head, int *hdl, unsigned char **title)
 2536 {
 2537     unsigned char *name, *attr, *he, *c;
 2538     int namelen;
 2539     int tlen = 0;
 2540     if (title) *title = init_str();
 2541     add_chr_to_str(head, hdl, '\n');
 2542     se:
 2543     while (s < eof && *s != '<') sp:s++;
 2544     if (s >= eof) return;
 2545     if (eof - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 2546         s = skip_comment(s, eof);
 2547         goto se;
 2548     }
 2549     if (parse_element(s, eof, &name, &namelen, &attr, &s)) goto sp;
 2550     ps:
 2551     if (namelen == 5 && !casecmp(name, "/HEAD", 5)) return;
 2552     if (title && !tlen && namelen == 5 && !casecmp(name, "TITLE", 5)) {
 2553         unsigned char *s1;
 2554         xse:
 2555         s1 = s;
 2556         while (s < eof && *s != '<') xsp:s++;
 2557         add_bytes_to_str(title, &tlen, s1, s - s1);
 2558         if (s >= eof) goto se;
 2559         if (eof - s >= 2 && (s[1] == '!' || s[1] == '?')) {
 2560             s = skip_comment(s, eof);
 2561             goto xse;
 2562         }
 2563         if (parse_element(s, eof, &name, &namelen, &attr, &s)) {
 2564             s1 = s;
 2565             goto xsp;
 2566         }
 2567         clr_spaces(*title);
 2568         goto ps;
 2569     }
 2570     if (namelen != 4 || casecmp(name, "META", 4)) goto se;
 2571     if ((he = get_attr_val(attr, "charset"))) {
 2572         add_to_str(head, hdl, "Charset: ");
 2573         add_to_str(head, hdl, he);
 2574         add_to_str(head, hdl, "\r\n");
 2575         mem_free(he);
 2576     }
 2577     if (!(he = get_attr_val(attr, "http-equiv"))) goto se;
 2578     c = get_attr_val(attr, "content");
 2579     add_to_str(head, hdl, he);
 2580     if (c) add_to_str(head, hdl, ": "), add_to_str(head, hdl, c), mem_free(c);
 2581     mem_free(he);
 2582     add_to_str(head, hdl, "\r\n");
 2583     goto se;
 2584 }
 2585