ksgi
/
kcgihtml.c
896 строк · 24.2 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2012--2021 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include "config.h"
18
19#include <assert.h>
20#include <inttypes.h>
21#include <stdarg.h>
22#include <stdint.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "kcgi.h"
28#include "kcgihtml.h"
29
30/*
31* Maximum size of printing a signed 64-bit integer.
32*/
33#define INT_MAXSZ 22
34
35/*
36* A type of HTML5 element.
37* Note: we don't list transitional elements, though I do note them in
38* the tag array.
39*/
40enum htype {
41TAG_FLOW, /* flow (block) element */
42TAG_PHRASE,/* phrasing (inline) element */
43TAG_VOID, /* auto-closing (e.g., INPUT) */
44TAG_INSTRUCTION /* instruction */
45};
46
47/*
48* A tag describes an HTML element and its properties.
49*/
50struct tag {
51enum htype flags;
52const char *name;
53};
54
55static const uint16_t entities[KENTITY__MAX] = {
56198, /* KENTITY_AElig */
57193, /* KENTITY_Aacute */
58194, /* KENTITY_Acirc */
59192, /* KENTITY_Agrave */
60197, /* KENTITY_Aring */
61195, /* KENTITY_Atilde */
62196, /* KENTITY_Auml */
63199, /* KENTITY_Ccedil */
648225, /* KENTITY_Dagger */
65208, /* KENTITY_ETH */
66201, /* KENTITY_Eacute */
67202, /* KENTITY_Ecirc */
68200, /* KENTITY_Egrave */
69203, /* KENTITY_Euml */
70205, /* KENTITY_Iacute */
71206, /* KENTITY_Icirc */
72204, /* KENTITY_Igrave */
73207, /* KENTITY_Iuml */
74209, /* KENTITY_Ntilde */
75338, /* KENTITY_OElig */
76211, /* KENTITY_Oacute */
77212, /* KENTITY_Ocirc */
78210, /* KENTITY_Ograve */
79216, /* KENTITY_Oslash */
80213, /* KENTITY_Otilde */
81214, /* KENTITY_Ouml */
82352, /* KENTITY_Scaron */
83222, /* KENTITY_THORN */
84218, /* KENTITY_Uacute */
85219, /* KENTITY_Ucirc */
86217, /* KENTITY_Ugrave */
87220, /* KENTITY_Uuml */
88221, /* KENTITY_Yacute */
89376, /* KENTITY_Yuml */
90225, /* KENTITY_aacute */
91226, /* KENTITY_acirc */
92180, /* KENTITY_acute */
93230, /* KENTITY_aelig */
94224, /* KENTITY_agrave */
9538, /* KENTITY_amp */
9639, /* KENTITY_apos */
97229, /* KENTITY_aring */
98227, /* KENTITY_atilde */
99228, /* KENTITY_auml */
1008222, /* KENTITY_bdquo */
101166, /* KENTITY_brvbar */
102231, /* KENTITY_ccedil */
103184, /* KENTITY_cedil */
104162, /* KENTITY_cent */
105710, /* KENTITY_circ */
106169, /* KENTITY_copy */
107164, /* KENTITY_curren */
1088224, /* KENTITY_dagger */
109176, /* KENTITY_deg */
110247, /* KENTITY_divide */
111233, /* KENTITY_eacute */
112234, /* KENTITY_ecirc */
113232, /* KENTITY_egrave */
1148195, /* KENTITY_emsp */
1158194, /* KENTITY_ensp */
116240, /* KENTITY_eth */
117235, /* KENTITY_euml */
1188364, /* KENTITY_euro */
119189, /* KENTITY_frac12 */
120188, /* KENTITY_frac14 */
121190, /* KENTITY_frac34 */
12262, /* KENTITY_gt */
1238230, /* KENTITY_hellip */
124237, /* KENTITY_iacute */
125238, /* KENTITY_icirc */
126161, /* KENTITY_iexcl */
127236, /* KENTITY_igrave */
128191, /* KENTITY_iquest */
129239, /* KENTITY_iuml */
130171, /* KENTITY_laquo */
1318220, /* KENTITY_ldquo */
1328206, /* KENTITY_lrm */
1338249, /* KENTITY_lsaquo */
1348216, /* KENTITY_lsquo */
13560, /* KENTITY_lt */
136175, /* KENTITY_macr */
1378212, /* KENTITY_mdash */
138181, /* KENTITY_micro */
139183, /* KENTITY_middot */
140160, /* KENTITY_nbsp */
1418211, /* KENTITY_ndash */
142172, /* KENTITY_not */
143241, /* KENTITY_ntilde */
144243, /* KENTITY_oacute */
145244, /* KENTITY_ocirc */
146339, /* KENTITY_oelig */
147242, /* KENTITY_ograve */
148170, /* KENTITY_ordf */
149186, /* KENTITY_ordm */
150248, /* KENTITY_oslash */
151245, /* KENTITY_otilde */
152246, /* KENTITY_ouml */
153182, /* KENTITY_para */
1548240, /* KENTITY_permil */
155177, /* KENTITY_plusmn */
156163, /* KENTITY_pound */
15734, /* KENTITY_quot */
158187, /* KENTITY_raquo */
1598221, /* KENTITY_rdquo */
160174, /* KENTITY_reg */
1618207, /* KENTITY_rlm */
1628250, /* KENTITY_rsaquo */
1638217, /* KENTITY_rsquo */
1648218, /* KENTITY_sbquo */
165353, /* KENTITY_scaron */
166167, /* KENTITY_sect */
167173, /* KENTITY_shy */
168185, /* KENTITY_sup1 */
169178, /* KENTITY_sup2 */
170179, /* KENTITY_sup3 */
171223, /* KENTITY_szlig */
1728201, /* KENTITY_thinsp */
173254, /* KENTITY_thorn */
174732, /* KENTITY_tilde */
175215, /* KENTITY_times */
1768482, /* KENTITY_trade */
177250, /* KENTITY_uacute */
178251, /* KENTITY_ucirc */
179249, /* KENTITY_ugrave */
180168, /* KENTITY_uml */
181252, /* KENTITY_uuml */
182253, /* KENTITY_yacute */
183165, /* KENTITY_yen */
184255, /* KENTITY_yuml */
1858205, /* KENTITY_zwj */
1868204, /* KENTITY_zwnj */
187};
188
189static const struct tag tags[KELEM__MAX] = {
190{ TAG_PHRASE, "a" }, /* KELEM_A */ /* XXX: TRANS */
191{ TAG_PHRASE, "abbr" }, /* KELEM_ABBR */
192{ TAG_PHRASE, "address" }, /* KELEM_ADDRESS */
193{ TAG_VOID, "area" }, /* KELEM_AREA */
194{ TAG_FLOW, "article" }, /* KELEM_ARTICLE */
195{ TAG_FLOW, "aside" }, /* KELEM_ASIDE */
196{ TAG_FLOW, "audio" }, /* KELEM_AUDIO */ /* XXX: TRANS */
197{ TAG_PHRASE, "b" }, /* KELEM_B */
198{ TAG_VOID, "base" }, /* KELEM_BASE */
199{ TAG_PHRASE, "bdi" }, /* KELEM_BDI */
200{ TAG_PHRASE, "bdo" }, /* KELEM_BDO */
201{ TAG_FLOW, "blockquote" }, /* KELEM_BLOCKQUOTE */
202{ TAG_FLOW, "body" }, /* KELEM_BODY */
203{ TAG_VOID, "br" }, /* KELEM_BR */
204{ TAG_PHRASE, "button" }, /* KELEM_BUTTON */
205{ TAG_FLOW, "canvas" }, /* KELEM_CANVAS */ /* XXX: TRANS */
206{ TAG_FLOW, "caption" }, /* KELEM_CAPTION */
207{ TAG_PHRASE, "cite" }, /* KELEM_CITE */
208{ TAG_PHRASE, "code" }, /* KELEM_CODE */
209{ TAG_VOID, "col" }, /* KELEM_COL */
210{ TAG_PHRASE, "colgroup" }, /* KELEM_COLGROUP */
211{ TAG_PHRASE, "data" }, /* KELEM_DATA*/
212{ TAG_PHRASE, "datalist" }, /* KELEM_DATALIST */
213{ TAG_FLOW, "dd" }, /* KELEM_DD */
214{ TAG_PHRASE, "del" }, /* KELEM_DEL */ /* XXX: TRANS */
215{ TAG_FLOW, "details" }, /* KELEM_DETAILS */
216{ TAG_PHRASE, "dfn" }, /* KELEM_DFN */
217{ TAG_FLOW, "dialog" }, /* KELEM_DIALOG */
218{ TAG_FLOW, "div" }, /* KELEM_DIV */
219{ TAG_FLOW, "dl" }, /* KELEM_DL */
220{ TAG_INSTRUCTION, "!DOCTYPE html" }, /* KELEM_DOCTYPE */
221{ TAG_FLOW, "dt" }, /* KELEM_DT */
222{ TAG_PHRASE, "em" }, /* KELEM_EM */
223{ TAG_VOID, "embed" }, /* KELEM_EMBED */
224{ TAG_FLOW, "fieldset" }, /* KELEM_FIELDSET */
225{ TAG_FLOW, "figcaption" }, /* KELEM_FIGCAPTION */
226{ TAG_FLOW, "figure" }, /* KELEM_FIGURE */
227{ TAG_FLOW, "footer" }, /* KELEM_FOOTER */
228{ TAG_FLOW, "form" }, /* KELEM_FORM */
229{ TAG_PHRASE, "h1" }, /* KELEM_H1 */
230{ TAG_PHRASE, "h2" }, /* KELEM_H2 */
231{ TAG_PHRASE, "h3" }, /* KELEM_H3 */
232{ TAG_PHRASE, "h4" }, /* KELEM_H4 */
233{ TAG_PHRASE, "h5" }, /* KELEM_H5 */
234{ TAG_PHRASE, "h6" }, /* KELEM_H6 */
235{ TAG_FLOW, "head" }, /* KELEM_HEAD */
236{ TAG_FLOW, "header" }, /* KELEM_HEADER */
237{ TAG_FLOW, "hgroup" }, /* KELEM_HGROUP */
238{ TAG_VOID, "hr" }, /* KELEM_HR */
239{ TAG_FLOW, "html" }, /* KELEM_HTML */
240{ TAG_PHRASE, "i" }, /* KELEM_I */
241{ TAG_PHRASE, "iframe" }, /* KELEM_IFRAME */
242{ TAG_VOID, "img" }, /* KELEM_IMG */
243{ TAG_VOID, "input" }, /* KELEM_INPUT */
244{ TAG_PHRASE, "ins" }, /* KELEM_INS */ /* XXX: TRANS */
245{ TAG_PHRASE, "kbd" }, /* KELEM_KBD */
246{ TAG_VOID, "keygen" }, /* KELEM_KEYGEN */
247{ TAG_PHRASE, "label" }, /* KELEM_LABEL */
248{ TAG_PHRASE, "legend" }, /* KELEM_LEGEND */
249{ TAG_FLOW, "li" }, /* KELEM_LI */
250{ TAG_VOID, "link" }, /* KELEM_LINK */
251{ TAG_FLOW, "main" }, /* KELEM_MAIN */
252{ TAG_FLOW, "map" }, /* KELEM_MAP */ /* XXX: TRANS */
253{ TAG_PHRASE, "mark" }, /* KELEM_MARK */
254{ TAG_FLOW, "menu" }, /* KELEM_MENU */
255{ TAG_VOID, "meta" }, /* KELEM_META */
256{ TAG_PHRASE, "meter" }, /* KELEM_METER */
257{ TAG_FLOW, "nav" }, /* KELEM_NAV */
258{ TAG_FLOW, "noscript" }, /* KELEM_NOSCRIPT */ /* XXX: TRANS */
259{ TAG_FLOW, "object" }, /* KELEM_OBJECT */ /* XXX: TRANS */
260{ TAG_FLOW, "ol" }, /* KELEM_OL */
261{ TAG_FLOW, "optgroup" }, /* KELEM_OPTGROUP */
262{ TAG_PHRASE, "option" }, /* KELEM_OPTION */
263{ TAG_PHRASE, "output" }, /* KELEM_OUTPUT */
264{ TAG_PHRASE, "p" }, /* KELEM_P */
265{ TAG_VOID, "param" }, /* KELEM_PARAM */
266{ TAG_PHRASE, "picture" }, /* KELEM_PICTURE */ /* XXX: TRANS */
267{ TAG_PHRASE, "pre" }, /* KELEM_PRE */
268{ TAG_PHRASE, "progress" }, /* KELEM_PROGRESS */
269{ TAG_PHRASE, "q" }, /* KELEM_Q */
270{ TAG_PHRASE, "rb" }, /* KELEM_RB */
271{ TAG_PHRASE, "rp" }, /* KELEM_RP */
272{ TAG_PHRASE, "rt" }, /* KELEM_RT */
273{ TAG_PHRASE, "rtc" }, /* KELEM_RTC */
274{ TAG_PHRASE, "ruby" }, /* KELEM_RUBY */
275{ TAG_PHRASE, "s" }, /* KELEM_S */
276{ TAG_PHRASE, "samp" }, /* KELEM_SAMP */
277{ TAG_FLOW, "script" }, /* KELEM_SCRIPT */
278{ TAG_FLOW, "section" }, /* KELEM_SECTION */
279{ TAG_FLOW, "select" }, /* KELEM_SELECT */
280{ TAG_PHRASE, "small" }, /* KELEM_SMALL */
281{ TAG_VOID, "source" }, /* KELEM_SOURCE */
282{ TAG_PHRASE, "span" }, /* KELEM_SPAN */
283{ TAG_PHRASE, "strong" }, /* KELEM_STRONG */
284{ TAG_FLOW, "style" }, /* KELEM_STYLE */
285{ TAG_PHRASE, "sub" }, /* KELEM_SUB */
286{ TAG_PHRASE, "summary" }, /* KELEM_SUMMARY */
287{ TAG_PHRASE, "sup" }, /* KELEM_SUP */
288{ TAG_FLOW, "table" }, /* KELEM_TABLE */
289{ TAG_FLOW, "tbody" }, /* KELEM_TBODY */
290{ TAG_FLOW, "td" }, /* KELEM_TD */
291{ TAG_PHRASE, "textarea" }, /* KELEM_TEXTAREA */
292{ TAG_FLOW, "tfoot" }, /* KELEM_TFOOT */
293{ TAG_FLOW, "th" }, /* KELEM_TH */
294{ TAG_FLOW, "thead" }, /* KELEM_THEAD */
295{ TAG_PHRASE, "time" }, /* KELEM_TIME */
296{ TAG_PHRASE, "title" }, /* KELEM_TITLE */
297{ TAG_FLOW, "tr" }, /* KELEM_TR */
298{ TAG_VOID, "track" }, /* KELEM_TRACK */
299{ TAG_PHRASE, "u" }, /* KELEM_U */
300{ TAG_FLOW, "ul" }, /* KELEM_UL */
301{ TAG_PHRASE, "var" }, /* KELEM_VAR */
302{ TAG_FLOW, "video" }, /* KELEM_VIDEO */ /* XXX: TRANS */
303{ TAG_VOID, "wbr" }, /* KELEM_WBR */
304};
305
306static const char *const attrs[KATTR__MAX] = {
307"accept-charset", /* KATTR_ACCEPT_CHARSET */
308"accesskey", /* KATTR_ACCESSKEY */
309"action", /* KATTR_ACTION */
310"allowfullscreen", /* KATTR_ALLOWFULLSCREEN */
311"allowpaymentrequest", /* KATTR_ALLOWPAYMENTREQUEST */
312"alt", /* KATTR_ALT */
313"async", /* KATTR_ASYNC */
314"autocomplete", /* KATTR_AUTOCOMPLETE */
315"autofocus", /* KATTR_AUTOFOCUS */
316"autoplay", /* KATTR_AUTOPLAY */
317"border", /* KATTR_BORDER */
318"challenge", /* KATTR_CHALLENGE */
319"charset", /* KATTR_CHARSET */
320"checked", /* KATTR_CHECKED */
321"cite", /* KATTR_CITE */
322"class", /* KATTR_CLASS */
323"cols", /* KATTR_COLS */
324"colspan", /* KATTR_COLSPAN */
325"content", /* KATTR_CONTENT */
326"contenteditable", /* KATTR_CONTENTEDITABLE */
327"contextmenu", /* KATTR_CONTEXTMENU */
328"controls", /* KATTR_CONTROLS */
329"coords", /* KATTR_COORDS */
330"crossorigin", /* KATTR_CROSSORIGIN */
331"data", /* KATTR_DATA */
332"datetime", /* KATTR_DATETIME */
333"default", /* KATTR_DEFAULT */
334"defer", /* KATTR_DEFER */
335"dir", /* KATTR_DIR */
336"dirname", /* KATTR_DIRNAME */
337"disabled", /* KATTR_DISABLED */
338"download", /* KATTR_DOWNLOAD */
339"draggable", /* KATTR_DRAGGABLE */
340"dropzone", /* KATTR_DROPZONE */
341"enctype", /* KATTR_ENCTYPE */
342"for", /* KATTR_FOR */
343"form", /* KATTR_FORM */
344"formaction", /* KATTR_FORMACTION */
345"formenctype", /* KATTR_FORMENCTYPE */
346"formmethod", /* KATTR_FORMMETHOD */
347"formnovalidate", /* KATTR_FORMNOVALIDATE */
348"formtarget", /* KATTR_FORMTARGET */
349"header", /* KATTR_HEADER */
350"headers", /* KATTR_HEADERS */
351"height", /* KATTR_HEIGHT */
352"hidden", /* KATTR_HIDDEN */
353"high", /* KATTR_HIGH */
354"href", /* KATTR_HREF */
355"hreflang", /* KATTR_HREFLANG */
356"http-equiv", /* KATTR_HTTP_EQUIV */
357"icon", /* KATTR_ICON */
358"id", /* KATTR_ID */
359"ismap", /* KATTR_ISMAP */
360"keytype", /* KATTR_KEYTYPE */
361"kind", /* KATTR_KIND */
362"label", /* KATTR_LABEL */
363"lang", /* KATTR_LANG */
364"language", /* KATTR_LANGUAGE */
365"list", /* KATTR_LIST */
366"longdesc", /* KATTR_LONGDESC */
367"loop", /* KATTR_LOOP */
368"low", /* KATTR_LOW */
369"manifest", /* KATTR_MANIFEST */
370"max", /* KATTR_MAX */
371"maxlength", /* KATTR_MAXLENGTH */
372"media", /* KATTR_MEDIA */
373"mediagroup", /* KATTR_MEDIAGROUP */
374"method", /* KATTR_METHOD */
375"min", /* KATTR_MIN */
376"minlength", /* KATTR_MINLENGTH */
377"multiple", /* KATTR_MULTIPLE */
378"muted", /* KATTR_MUTED */
379"name", /* KATTR_NAME */
380"nonce", /* KATTR_NONCE */
381"novalidate", /* KATTR_NOVALIDATE */
382"onabort", /* KATTR_ONABORT */
383"onafterprint", /* KATTR_ONAFTERPRINT */
384"onauxclick", /* KATTR_ONAUXCLICK */
385"onbeforeprint", /* KATTR_ONBEFOREPRINT */
386"onbeforeunload", /* KATTR_ONBEFOREUNLOAD */
387"onblur", /* KATTR_ONBLUR */
388"oncancel", /* KATTR_ONCANCEL */
389"oncanplay", /* KATTR_ONCANPLAY */
390"oncanplaythrough", /* KATTR_ONCANPLAYTHROUGH */
391"onchange", /* KATTR_ONCHANGE */
392"onclick", /* KATTR_ONCLICK */
393"onclose", /* KATTR_ONCLOSE */
394"oncontextmenu", /* KATTR_ONCONTEXTMENU */
395"oncuechange", /* KATTR_ONCUECHANGE */
396"oncut", /* KATTR_ONCUT */
397"ondblclick", /* KATTR_ONDBLCLICK */
398"ondrag", /* KATTR_ONDRAG */
399"ondragend", /* KATTR_ONDRAGEND */
400"ondragenter", /* KATTR_ONDRAGENTER */
401"ondragexit", /* KATTR_ONDRAGEXIT */
402"ondragleave", /* KATTR_ONDRAGLEAVE */
403"ondragover", /* KATTR_ONDRAGOVER */
404"ondragstart", /* KATTR_ONDRAGSTART */
405"ondrop", /* KATTR_ONDROP */
406"ondurationchange", /* KATTR_ONDURATIONCHANGE */
407"onemptied", /* KATTR_ONEMPTIED */
408"onended", /* KATTR_ONENDED */
409"onerror", /* KATTR_ONERROR */
410"onfocus", /* KATTR_ONFOCUS */
411"onhashchange", /* KATTR_ONHASHCHANGE */
412"oninput", /* KATTR_ONINPUT */
413"oninvalid", /* KATTR_ONINVALID */
414"onkeydown", /* KATTR_ONKEYDOWN */
415"onkeypress", /* KATTR_ONKEYPRESS */
416"onkeyup", /* KATTR_ONKEYUP */
417"onload", /* KATTR_ONLOAD */
418"onloadeddata", /* KATTR_ONLOADEDDATA */
419"onloadedmetadata", /* KATTR_ONLOADEDMETADATA */
420"onloadstart", /* KATTR_ONLOADSTART */
421"onmessage", /* KATTR_ONMESSAGE */
422"onmousedown", /* KATTR_ONMOUSEDOWN */
423"onmouseenter", /* KATTR_ONMOUSEENTER */
424"onmouseleave", /* KATTR_ONMOUSELEAVE */
425"onmousemove", /* KATTR_ONMOUSEMOVE */
426"onmouseout", /* KATTR_ONMOUSEOUT */
427"onmouseover", /* KATTR_ONMOUSEOVER */
428"onmouseup", /* KATTR_ONMOUSEUP */
429"onmousewheel", /* KATTR_ONMOUSEWHEEL */
430"onoffline", /* KATTR_ONOFFLINE */
431"ononline", /* KATTR_ONONLINE */
432"onpagehide", /* KATTR_ONPAGEHIDE */
433"onpageshow", /* KATTR_ONPAGESHOW */
434"onpaste", /* KATTR_ONPASTE */
435"onpause", /* KATTR_ONPAUSE */
436"onplay", /* KATTR_ONPLAY */
437"onplaying", /* KATTR_ONPLAYING */
438"onpopstate", /* KATTR_ONPOPSTATE */
439"onprogress", /* KATTR_ONPROGRESS */
440"onratechange", /* KATTR_ONRATECHANGE */
441"onreadystatechange", /* KATTR_ONREADYSTATECHANGE */
442"onreset", /* KATTR_ONRESET */
443"onresize", /* KATTR_ONRESIZE */
444"onscroll", /* KATTR_ONSCROLL */
445"onseeked", /* KATTR_ONSEEKED */
446"onseeking", /* KATTR_ONSEEKING */
447"onselect", /* KATTR_ONSELECT */
448"onshow", /* KATTR_ONSHOW */
449"onstalled", /* KATTR_ONSTALLED */
450"onstorage", /* KATTR_ONSTORAGE */
451"onsubmit", /* KATTR_ONSUBMIT */
452"onsuspend", /* KATTR_ONSUSPEND */
453"ontimeupdate", /* KATTR_ONTIMEUPDATE */
454"ontoggle", /* KATTR_ONTOGGLE */
455"onunload", /* KATTR_ONUNLOAD */
456"onvolumechange", /* KATTR_ONVOLUMECHANGE */
457"onwaiting", /* KATTR_ONWAITING */
458"onwheel", /* KATTR_ONWHEEL */
459"open", /* KATTR_OPEN */
460"optimum", /* KATTR_OPTIMUM */
461"pattern", /* KATTR_PATTERN */
462"placeholder", /* KATTR_PLACEHOLDER */
463"poster", /* KATTR_POSTER */
464"preload", /* KATTR_PRELOAD */
465"radiogroup", /* KATTR_RADIOGROUP */
466"readonly", /* KATTR_READONLY */
467"referrerpolicy", /* KATTR_REFERRERPOLICY */
468"rel", /* KATTR_REL */
469"required", /* KATTR_REQUIRED */
470"rev", /* KATTR_REV */
471"reversed", /* KATTR_REVERSED */
472"role", /* KATTR_ROLE */
473"rows", /* KATTR_ROWS */
474"rowspan", /* KATTR_ROWSPAN */
475"sandbox", /* KATTR_SANDBOX */
476"scope", /* KATTR_SCOPE */
477"seamless", /* KATTR_SEAMLESS */
478"selected", /* KATTR_SELECTED */
479"shape", /* KATTR_SHAPE */
480"size", /* KATTR_SIZE */
481"sizes", /* KATTR_SIZES */
482"span", /* KATTR_SPAN */
483"spellcheck", /* KATTR_SPELLCHECK */
484"src", /* KATTR_SRC */
485"srcdoc", /* KATTR_SRCDOC */
486"srclang", /* KATTR_SRCLANG */
487"srcset", /* KATTR_SRCSET */
488"start", /* KATTR_START */
489"step", /* KATTR_STEP */
490"style", /* KATTR_STYLE */
491"tabindex", /* KATTR_TABINDEX */
492"target", /* KATTR_TARGET */
493"title", /* KATTR_TITLE */
494"translate", /* KATTR_TRANSLATE */
495"type", /* KATTR_TYPE */
496"typemustmatch", /* KATTR_TYPEMUSTMATCH */
497"usemap", /* KATTR_USEMAP */
498"value", /* KATTR_VALUE */
499"width", /* KATTR_WIDTH */
500"wrap", /* KATTR_WRAP */
501};
502
503size_t
504khtml_elemat(const struct khtmlreq *req)
505{
506
507return req->elemsz;
508}
509
510enum kcgi_err
511khtml_elem(struct khtmlreq *req, enum kelem elem)
512{
513
514return khtml_attr(req, elem, KATTR__MAX);
515}
516
517/*
518* Open a tag---only used when pretty-printing and a noop otherwise.
519* If we're a flow tag, emit a newline (unless already omitted).
520* Then if we're at a newline regardless of tag type, indent properly to
521* the point where we'll omit the tag name.
522*/
523static enum kcgi_err
524khtml_flow_open(struct khtmlreq *req, enum kelem elem)
525{
526size_t i;
527enum kcgi_err er;
528
529if ( ! (KHTML_PRETTY & req->opts))
530return(KCGI_OK);
531
532if (TAG_FLOW == tags[elem].flags && ! req->newln) {
533if (KCGI_OK != (er = kcgi_writer_putc(req->arg, '\n')))
534return(er);
535req->newln = 1;
536}
537
538if (req->newln)
539for (i = 0; i < req->elemsz; i++)
540if (KCGI_OK != (er =
541kcgi_writer_puts(req->arg, " ")))
542return(er);
543
544req->newln = 0;
545return(KCGI_OK);
546}
547
548/*
549* If we're pretty-printing and closing a flow or instruction tag, emit
550* a newline.
551* Otherwise do nothing.
552* Returns the usual write error code.
553*/
554static enum kcgi_err
555khtml_flow_close(struct khtmlreq *req, enum kelem elem)
556{
557enum kcgi_err er;
558
559if ( ! (KHTML_PRETTY & req->opts))
560return(KCGI_OK);
561
562if (TAG_FLOW == tags[elem].flags ||
563TAG_INSTRUCTION == tags[elem].flags) {
564if (KCGI_OK != (er = kcgi_writer_putc(req->arg, '\n')))
565return(er);
566req->newln = 1;
567} else
568req->newln = 0;
569
570return(KCGI_OK);
571}
572
573enum kcgi_err
574khtml_attrx(struct khtmlreq *req, enum kelem elem, ...)
575{
576va_list ap;
577enum kattr at;
578enum kcgi_err er = KCGI_OK;
579
580if (tags[elem].flags != TAG_VOID &&
581tags[elem].flags != TAG_INSTRUCTION &&
582req->elemsz >= KHTML_STACK_MAX) {
583kutil_warnx(NULL, NULL,
584"maximum html stack size exceeded");
585return KCGI_ENOMEM;
586}
587
588if ((er = khtml_flow_open(req, elem)) != KCGI_OK)
589return er;
590if ((er = kcgi_writer_putc(req->arg, '<')) != KCGI_OK)
591return er;
592if ((er = kcgi_writer_puts(req->arg, tags[elem].name)) != KCGI_OK)
593return er;
594
595va_start(ap, elem);
596while ((at = va_arg(ap, enum kattr)) != KATTR__MAX) {
597if ((er = kcgi_writer_putc(req->arg, ' ')) != KCGI_OK)
598goto out;
599if ((er = kcgi_writer_puts(req->arg, attrs[at])) != KCGI_OK)
600goto out;
601if ((er = kcgi_writer_puts(req->arg, "=\"")) != KCGI_OK)
602goto out;
603
604switch (va_arg(ap, enum kattrx)) {
605case KATTRX_STRING:
606er = khtml_puts(req, va_arg(ap, char *));
607break;
608case KATTRX_INT:
609er = khtml_int(req, va_arg(ap, int64_t));
610break;
611case KATTRX_DOUBLE:
612er = khtml_double(req, va_arg(ap, double));
613break;
614}
615if (er != KCGI_OK)
616goto out;
617if ((er = kcgi_writer_putc(req->arg, '"')) != KCGI_OK)
618goto out;
619}
620va_end(ap);
621
622if (tags[elem].flags == TAG_VOID &&
623(er = kcgi_writer_putc(req->arg, '/')) != KCGI_OK)
624return er;
625if ((er = kcgi_writer_putc(req->arg, '>')) != KCGI_OK)
626return er;
627if ((er = khtml_flow_close(req, elem)) != KCGI_OK)
628return er;
629
630if (tags[elem].flags != TAG_VOID &&
631tags[elem].flags != TAG_INSTRUCTION)
632req->elems[req->elemsz++] = elem;
633
634return KCGI_OK;
635out:
636va_end(ap);
637return er;
638}
639
640enum kcgi_err
641khtml_attr(struct khtmlreq *req, enum kelem elem, ...)
642{
643va_list ap;
644enum kattr at;
645const char *cp;
646enum kcgi_err er;
647
648if (tags[elem].flags != TAG_VOID &&
649tags[elem].flags != TAG_INSTRUCTION &&
650req->elemsz >= KHTML_STACK_MAX) {
651kutil_warnx(NULL, NULL,
652"maximum html stack size exceeded");
653return KCGI_ENOMEM;
654}
655
656if ((er = khtml_flow_open(req, elem)) != KCGI_OK)
657return er;
658if ((er = kcgi_writer_putc(req->arg, '<')) != KCGI_OK)
659return er;
660if ((er = kcgi_writer_puts(req->arg, tags[elem].name)) != KCGI_OK)
661return er;
662
663va_start(ap, elem);
664while ((at = va_arg(ap, enum kattr)) != KATTR__MAX) {
665cp = va_arg(ap, char *);
666
667/*
668* FIXME: shouldn't we allow situations where there's no
669* value, like <option selected>?
670* I don't know if this is valid XML.
671* Meanwhile, don't let it happen.
672*/
673
674assert(cp != NULL);
675if ((er = kcgi_writer_putc(req->arg, ' ')) != KCGI_OK)
676goto out;
677if ((er = kcgi_writer_puts(req->arg, attrs[at])) != KCGI_OK)
678goto out;
679if ((er = kcgi_writer_puts(req->arg, "=\"")) != KCGI_OK)
680goto out;
681if ((er = khtml_puts(req, cp)) != KCGI_OK)
682goto out;
683if ((er = kcgi_writer_putc(req->arg, '"')) != KCGI_OK)
684goto out;
685
686}
687va_end(ap);
688
689if (tags[elem].flags == TAG_VOID &&
690(er = kcgi_writer_putc(req->arg, '/')) != KCGI_OK)
691return er;
692if ((er = kcgi_writer_putc(req->arg, '>')) != KCGI_OK)
693return er;
694if ((er = khtml_flow_close(req, elem)) != KCGI_OK)
695return er;
696
697if (tags[elem].flags != TAG_VOID &&
698tags[elem].flags != TAG_INSTRUCTION)
699req->elems[req->elemsz++] = elem;
700
701return KCGI_OK;
702out:
703va_end(ap);
704return er;
705}
706
707enum kcgi_err
708khtml_closeelem(struct khtmlreq *req, size_t sz)
709{
710size_t i;
711enum kcgi_err er;
712
713if (sz == 0)
714sz = req->elemsz;
715if (sz > req->elemsz)
716sz = req->elemsz;
717
718for (i = 0; i < sz; i++) {
719assert(req->elemsz);
720req->elemsz--;
721er = khtml_flow_open(req, req->elems[req->elemsz]);
722if (er != KCGI_OK)
723return er;
724er = kcgi_writer_puts(req->arg, "</");
725if (er != KCGI_OK)
726return er;
727er = kcgi_writer_puts(req->arg,
728tags[req->elems[req->elemsz]].name);
729if (er != KCGI_OK)
730return er;
731er = kcgi_writer_putc(req->arg, '>');
732if (er != KCGI_OK)
733return er;
734er = khtml_flow_close(req, req->elems[req->elemsz]);
735if (er != KCGI_OK)
736return er;
737}
738
739return KCGI_OK;
740}
741
742enum kcgi_err
743khtml_closeto(struct khtmlreq *req, size_t pos)
744{
745
746if (pos >= req->elemsz)
747return KCGI_OK;
748return khtml_closeelem(req, req->elemsz - pos);
749}
750
751enum kcgi_err
752khtml_entity(struct khtmlreq *req, enum kentity entity)
753{
754
755assert(entity < KENTITY__MAX);
756return khtml_ncr(req, entities[entity]);
757}
758
759enum kcgi_err
760khtml_ncr(struct khtmlreq *req, uint32_t ncr)
761{
762char buf[INT_MAXSZ];
763enum kcgi_err er;
764
765/*
766* Don't check whether ncr is a valid unicode entity.
767* Right now these only go up to 150 000 or so.
768*/
769
770snprintf(buf, sizeof(buf), "%" PRIx32, ncr);
771if ((er = kcgi_writer_puts(req->arg, "&#x")) != KCGI_OK)
772return er;
773if ((er = kcgi_writer_puts(req->arg, buf)) != KCGI_OK)
774return er;
775return kcgi_writer_putc(req->arg, ';');
776}
777
778enum kcgi_err
779khtml_double(struct khtmlreq *req, double val)
780{
781char buf[256];
782
783(void)snprintf(buf, sizeof(buf), "%g", val);
784return(khtml_puts(req, buf));
785}
786
787enum kcgi_err
788khtml_int(struct khtmlreq *req, int64_t val)
789{
790char buf[INT_MAXSZ];
791
792(void)snprintf(buf, sizeof(buf), "%" PRId64, val);
793return(khtml_puts(req, buf));
794}
795
796enum kcgi_err
797khtml_putc(struct khtmlreq *r, char c)
798{
799enum kcgi_err er;
800
801switch (c) {
802case ('>'):
803er = khtml_entity(r, KENTITY_gt);
804break;
805case ('&'):
806er = khtml_entity(r, KENTITY_amp);
807break;
808case ('<'):
809er = khtml_entity(r, KENTITY_lt);
810break;
811case ('"'):
812er = khtml_entity(r, KENTITY_quot);
813break;
814case ('\''):
815er = khtml_ncr(r, 39);
816break;
817default:
818er = kcgi_writer_putc(r->arg, c);
819break;
820}
821
822return(er);
823}
824
825enum kcgi_err
826khtml_write(const char *cp, size_t sz, void *arg)
827{
828struct khtmlreq *r = arg;
829size_t i;
830enum kcgi_err er;
831
832if (cp == NULL || sz == 0)
833return KCGI_OK;
834
835for (i = 0; i < sz; i++)
836if ((er = khtml_putc(r, cp[i])) != KCGI_OK)
837return er;
838
839return KCGI_OK;
840}
841
842enum kcgi_err
843khtml_printf(struct khtmlreq *req, const char *fmt, ...)
844{
845char *buf;
846int len;
847va_list ap;
848enum kcgi_err er;
849
850if (fmt == NULL)
851return KCGI_OK;
852
853va_start(ap, fmt);
854len = vasprintf(&buf, fmt, ap);
855va_end(ap);
856
857if (len < 0)
858return KCGI_ENOMEM;
859
860er = khtml_write(buf, (size_t)len, req);
861free(buf);
862return er;
863}
864
865enum kcgi_err
866khtml_puts(struct khtmlreq *req, const char *cp)
867{
868
869if (cp == NULL)
870return KCGI_OK;
871return khtml_write(cp, strlen(cp), req);
872}
873
874
875enum kcgi_err
876khtml_open(struct khtmlreq *r, struct kreq *req, int opts)
877{
878
879memset(r, 0, sizeof(struct khtmlreq));
880if ((r->arg = kcgi_writer_get(req, 0)) == NULL)
881return KCGI_ENOMEM;
882
883r->opts = opts;
884return KCGI_OK;
885}
886
887enum kcgi_err
888khtml_close(struct khtmlreq *r)
889{
890enum kcgi_err er;
891
892er = khtml_closeelem(r, 0);
893kcgi_writer_free(r->arg);
894r->arg = NULL;
895return er;
896}
897