3
* Copyright (c) Karl Dahlke, 2006
4
* This file is part of the edbrowse project, released under GPL.
9
/* Close an open anchor when you see this tag. */
11
/* You won't see the text between <foo> and </fooo> */
12
#define TAG_INVISIBLE 2
13
/* sometimes </foo> means nothing. */
17
INP_RESET, INP_BUTTON, INP_IMAGE, INP_SUBMIT,
19
INP_TEXT, INP_PW, INP_NUMBER, INP_FILE,
20
INP_SELECT, INP_TA, INP_RADIO, INP_CHECKBOX,
24
TAGACT_ZERO, TAGACT_A, TAGACT_INPUT, TAGACT_TITLE, TAGACT_TA,
25
TAGACT_BUTTON, TAGACT_SELECT, TAGACT_OPTION,
26
TAGACT_NOP, TAGACT_JS, TAGACT_H, TAGACT_SUB, TAGACT_SUP,
27
TAGACT_DW, TAGACT_BODY, TAGACT_HEAD,
28
TAGACT_MUSIC, TAGACT_IMAGE, TAGACT_BR, TAGACT_IBR, TAGACT_P,
29
TAGACT_BASE, TAGACT_META, TAGACT_PRE,
30
TAGACT_DT, TAGACT_LI, TAGACT_HR, TAGACT_TABLE, TAGACT_TR, TAGACT_TD,
31
TAGACT_DIV, TAGACT_SPAN,
32
TAGACT_FORM, TAGACT_FRAME,
33
TAGACT_MAP, TAGACT_AREA, TAGACT_SCRIPT, TAGACT_EMBED, TAGACT_OBJ,
36
static const char *const inp_types[] = {
37
"reset", "button", "image", "submit",
39
"text", "password", "number", "file",
40
"select", "textarea", "radio", "checkbox",
44
static const char dfvl[] = "defaultValue";
45
static const char dfck[] = "defaultChecked";
47
static struct listHead htmlStack;
48
static struct htmlTag **tagArray, *topTag;
49
static int ntags; /* number of tags in this page */
50
static char *topAttrib;
51
static char *basehref;
52
static struct htmlTag *currentForm; /* the open form */
53
bool parsePage; /* parsing html */
54
int browseLine; /* for error reporting */
55
static char *radioChecked;
56
static int radioChecked_l;
57
static char *preamble;
58
static int preamble_l;
60
static void htmlName(void);
62
/* Switch from the linked list of tags to an array. */
72
tagArray = allocMem(sizeof (struct htmlTag *) * ((ntags + 32) & ~31));
80
htmlAttrPresent(const char *e, const char *name)
83
if(!(a = htmlAttrVal(e, name)))
87
} /* htmlAttrPresent */
90
hrefVal(const char *e, const char *name)
93
htmlAttrVal_nl = true;
94
a = htmlAttrVal(e, name);
95
htmlAttrVal_nl = false; /* put it back */
100
targetVal(const char *e)
102
return htmlAttrVal(e, "target");
106
toPreamble(int tagno, const char *msg, const char *j, const char *h)
109
sprintf(buf, "\r\200%d{%s", tagno, msg);
117
if(memEqualCI(j, "javascript:", 11))
120
if(isalphaByte(*j) || *j == '_') {
123
for(s = fn + 2; isalnumByte(*j) || *j == '_'; ++j) {
124
if(s < fn + sizeof (fn) - 3)
133
strcat(buf, "\2000}\r");
135
preamble = initString(&preamble_l);
136
stringAndString(&preamble, &preamble_l, buf);
143
uchar nest; /* must nest, like parentheses */
144
uchar para; /* paragraph and line breaks */
145
ushort bits; /* a bunch of boolean attributes */
148
static const struct tagInfo elements[] = {
149
{"BASE", "base reference for relative URLs", TAGACT_BASE, 0, 0, 5},
150
{"A", "an anchor", TAGACT_A, 1, 0, 1},
151
{"INPUT", "an input item", TAGACT_INPUT, 0, 0, 5},
152
{"TITLE", "the title", TAGACT_TITLE, 1, 0, 1},
153
{"TEXTAREA", "an input text area", TAGACT_TA, 1, 0, 1},
154
{"SELECT", "an option list", TAGACT_SELECT, 1, 0, 1},
155
{"OPTION", "a select option", TAGACT_OPTION, 0, 0, 5},
156
{"SUB", "a subscript", TAGACT_SUB, 3, 0, 0},
157
{"SUP", "a superscript", TAGACT_SUP, 3, 0, 0},
158
{"FONT", "a font", TAGACT_NOP, 3, 0, 0},
159
{"CENTER", "centered text", TAGACT_NOP, 3, 0, 0},
160
{"DOCWRITE", "document.write() text", TAGACT_DW, 0, 0, 0},
161
{"CAPTION", "a caption", TAGACT_NOP, 1, 5, 0},
162
{"HEAD", "the html header information", TAGACT_HEAD, 1, 0, 5},
163
{"BODY", "the html body", TAGACT_BODY, 1, 0, 5},
164
{"BGSOUND", "background music", TAGACT_MUSIC, 0, 0, 5},
165
{"AUDIO", "audio passage", TAGACT_MUSIC, 0, 0, 5},
166
{"META", "a meta tag", TAGACT_META, 0, 0, 4},
167
{"IMG", "an image", TAGACT_IMAGE, 0, 0, 4},
168
{"IMAGE", "an image", TAGACT_IMAGE, 0, 0, 4},
169
{"BR", "a line break", TAGACT_BR, 0, 1, 4},
170
{"P", "a paragraph", TAGACT_NOP, 0, 2, 5},
171
{"DIV", "a divided section", TAGACT_DIV, 3, 5, 0},
172
{"HTML", "html", TAGACT_NOP, 0, 0, 0},
173
{"BLOCKQUOTE", "a quoted paragraph", TAGACT_NOP, 1, 10, 1},
174
{"H1", "a level 1 header", TAGACT_NOP, 1, 10, 1},
175
{"H2", "a level 2 header", TAGACT_NOP, 1, 10, 1},
176
{"H3", "a level 3 header", TAGACT_NOP, 1, 10, 1},
177
{"H4", "a level 4 header", TAGACT_NOP, 1, 10, 1},
178
{"H5", "a level 5 header", TAGACT_NOP, 1, 10, 1},
179
{"H6", "a level 6 header", TAGACT_NOP, 1, 10, 1},
180
{"DT", "a term", TAGACT_DT, 0, 2, 5},
181
{"DD", "a definition", TAGACT_DT, 0, 1, 5},
182
{"LI", "a list item", TAGACT_LI, 0, 1, 5},
183
{"UL", "a bullet list", TAGACT_NOP, 3, 5, 1},
184
{"DIR", "a directory list", TAGACT_NOP, 3, 5, 1},
185
{"MENU", "a menu", TAGACT_NOP, 3, 5, 1},
186
{"OL", "a numbered list", TAGACT_NOP, 3, 5, 1},
187
{"DL", "a definition list", TAGACT_NOP, 3, 5, 1},
188
{"HR", "a horizontal line", TAGACT_HR, 0, 5, 5},
189
{"FORM", "a form", TAGACT_FORM, 1, 0, 1},
190
{"BUTTON", "a button", TAGACT_BUTTON, 0, 0, 5},
191
{"FRAME", "a frame", TAGACT_FRAME, 0, 2, 5},
192
{"IFRAME", "a frame", TAGACT_FRAME, 0, 2, 5},
193
{"MAP", "an image map", TAGACT_MAP, 0, 2, 5},
194
{"AREA", "an image map area", TAGACT_AREA, 0, 0, 1},
195
{"TABLE", "a table", TAGACT_TABLE, 3, 10, 1},
196
{"TR", "a table row", TAGACT_TR, 3, 5, 1},
197
{"TD", "a table entry", TAGACT_TD, 3, 0, 1},
198
{"TH", "a table heading", TAGACT_TD, 1, 0, 1},
199
{"PRE", "a preformatted section", TAGACT_PRE, 1, 1, 0},
200
{"LISTING", "a listing", TAGACT_PRE, 1, 1, 0},
201
{"XMP", "an example", TAGACT_PRE, 1, 1, 0},
202
{"FIXED", "a fixed presentation", TAGACT_NOP, 1, 1, 0},
203
{"CODE", "a block of code", TAGACT_NOP, 1, 0, 0},
204
{"SAMP", "a block of sample text", TAGACT_NOP, 1, 0, 0},
205
{"ADDRESS", "an address block", TAGACT_NOP, 1, 1, 0},
206
{"STYLE", "a style block", TAGACT_NOP, 1, 0, 2},
207
{"SCRIPT", "a script", TAGACT_SCRIPT, 0, 0, 1},
208
{"NOSCRIPT", "no script section", TAGACT_NOP, 1, 0, 3},
209
{"NOFRAMES", "no frames section", TAGACT_NOP, 1, 0, 3},
210
{"EMBED", "embedded html", TAGACT_MUSIC, 1, 0, 3},
211
{"NOEMBED", "no embed section", TAGACT_NOP, 1, 0, 3},
212
{"OBJECT", "an html object", TAGACT_OBJ, 0, 0, 3},
213
{"EM", "emphasized text", TAGACT_JS, 1, 0, 0},
214
{"LABEL", "a label", TAGACT_JS, 1, 0, 0},
215
{"STRIKE", "emphasized text", TAGACT_JS, 1, 0, 0},
216
{"S", "emphasized text", TAGACT_JS, 1, 0, 0},
217
{"STRONG", "emphasized text", TAGACT_JS, 1, 0, 0},
218
{"B", "bold text", TAGACT_JS, 1, 0, 0},
219
{"I", "italicized text", TAGACT_JS, 1, 0, 0},
220
{"U", "underlined text", TAGACT_JS, 1, 0, 0},
221
{"DFN", "definition text", TAGACT_JS, 1, 0, 0},
222
{"Q", "quoted text", TAGACT_JS, 1, 0, 0},
223
{"ABBR", "an abbreviation", TAGACT_JS, 1, 0, 0},
224
{"SPAN", "an html span", TAGACT_SPAN, 1, 0, 0},
225
{"FRAMESET", "a frame set", TAGACT_JS, 3, 0, 1},
230
struct htmlTag *next, *prev;
231
void *jv; /* corresponding java variable */
233
int ln; /* line number */
234
int lic; /* list item count, highly overloaded */
236
const struct tagInfo *info;
237
/* the form that owns this input tag, etc */
238
struct htmlTag *controller;
239
bool slash:1; /* as in </A> */
240
bool balanced:1; /* <foo> and </foo> */
244
bool clickable:1; /* but not an input field */
247
bool rchecked:1; /* for reset */
248
bool post:1; /* post, rather than get */
249
bool javapost:1; /* post by calling javascript */
250
bool mime:1; /* encode as mime, rather than url encode */
251
bool bymail:1; /* send by mail, rather than http */
254
uchar itype; /* input type = */
255
short ninp; /* number of nonhidden inputs */
257
char *name, *id, *value, *href;
258
const char *inner; /* for inner html */
261
static const char *const handlers[] = {
262
"onmousemove", "onmouseover", "onmouseout", "onmouseup", "onmousedown",
263
"onclick", "ondblclick", "onblur", "onfocus",
264
"onchange", "onsubmit", "onreset",
265
"onload", "onunload",
266
"onkeypress", "onkeyup", "onkeydown",
271
freeTag(struct htmlTag *e)
286
struct htmlTag *t, **e;
289
return; /* no tags */
291
/* drop empty textarea buffers created by this session */
292
for(e = a; t = *e; ++e) {
293
if(t->action != TAGACT_INPUT)
295
if(t->itype != INP_TA)
299
if(!(w = sessionList[n].lw))
305
if(w != sessionList[n].fw)
307
/* We could have added a line, then deleted it */
308
w->changeMode = false;
310
} /* loop over tags */
312
for(e = a; t = *e; ++e)
318
get_js_event(const char *name)
320
void *ev = topTag->jv;
323
int action = topTag->action;
324
int itype = topTag->itype;
325
if(!(s = htmlAttrVal(topAttrib, name)))
326
return; /* not there */
328
handlerSet(ev, name, s);
331
/* This code is only here to print warnings if js is disabled
332
* or otherwise broken. Record the fact that handlers exist,
333
* outside the context of js. */
334
if(stringEqual(name, "onclick")) {
335
if(action == TAGACT_A || action == TAGACT_AREA || action == TAGACT_FRAME
336
|| action == TAGACT_INPUT && (itype >= INP_RADIO ||
337
itype <= INP_SUBMIT) || action == TAGACT_OPTION) {
338
topTag->handler = true;
339
if(currentForm && action == TAGACT_INPUT && itype == INP_BUTTON)
340
currentForm->submitted = true;
343
if(stringEqual(name, "onsubmit") || stringEqual(name, "onreset")) {
344
if(action == TAGACT_FORM)
345
topTag->handler = true;
347
if(stringEqual(name, "onchange")) {
348
if(action == TAGACT_INPUT || action == TAGACT_SELECT) {
350
runningError(MSG_OnchangeText);
351
else if(itype > INP_HIDDEN && itype <= INP_SELECT) {
352
topTag->handler = true;
354
currentForm->submitted = true;
360
static bool strayClick;
365
void *ev = topTag->jv;
368
int action = topTag->action;
369
int itype = topTag->itype;
371
for(j = 0; t = handlers[j]; ++j)
377
/* Some warnings about some handlers that we just don't "handle" */
378
if(handlerPresent(ev, "onkeypress") ||
379
handlerPresent(ev, "onkeyup") || handlerPresent(ev, "onkeydown"))
380
browseError(MSG_JSKeystroke);
381
if(handlerPresent(ev, "onfocus") || handlerPresent(ev, "onblur"))
382
browseError(MSG_JSFocus);
383
if(handlerPresent(ev, "ondblclick"))
384
runningError(MSG_Doubleclick);
385
if(handlerPresent(ev, "onclick")) {
386
if((action == TAGACT_A || action == TAGACT_AREA || action == TAGACT_FRAME) && topTag->href || action == TAGACT_INPUT && (itype <= INP_SUBMIT || itype >= INP_RADIO)) ; /* ok */
390
if(handlerPresent(ev, "onchange")) {
391
if(action != TAGACT_INPUT && action != TAGACT_SELECT || itype == INP_TA)
392
browseError(MSG_StrayOnchange);
394
/* Other warnings might be appropriate, but I'm going to assume this
395
* is valid javascript, and you won't put an onsubmit function on <P> etc */
396
} /* get_js_events */
399
tagHandler(int seqno, const char *name)
401
const struct htmlTag **list = cw->tags;
402
const struct htmlTag *t = list[seqno];
403
return t->handler | handlerPresent(t->jv, name);
409
const struct htmlTag *t, **list;
414
for(n = 0; list[n]; ++n) ;
419
while((t = *list)->action != TAGACT_BASE);
426
char *name, *content, *heq;
432
domLink("Meta", topTag->name, topTag->id, 0, 0, "metas", jdoc, false);
434
content = htmlAttrVal(topAttrib, "content");
435
if(content == EMPTYSTRING)
438
establish_property_string(e, "content", content, true);
439
heq = htmlAttrVal(topAttrib, "http-equiv");
440
if(heq == EMPTYSTRING)
446
/* It's not clear if we should process the http refresh command
447
* immediately, the moment we spot it, or if we finish parsing
448
* all the html first.
449
* Does it matter? It might.
450
* A subsequent meta tag could use http-equiv to set a cooky,
451
* and we won't see that cooky if we jump to the new page right now.
452
* And there's no telling what subsequent javascript might do.
453
* So - I'm going to postpone the refresh, until everything is parsed.
454
* Bear in mind, we really don't want to refresh if we're working
455
* on a local file. */
456
if(stringEqualCI(heq, "Set-Cookie")) {
457
rc = receiveCookie(cw->fileName, content);
458
debugPrint(3, rc ? "jar" : "rejected");
461
if(allowRedirection && !browseLocal && stringEqualCI(heq, "Refresh")) {
462
if(parseRefresh(content, &delay)) {
464
unpercentURL(content);
465
newcontent = resolveURL(basehref, content);
466
gotoLocation(newcontent, delay, true);
473
if(stringEqualCI(name, "description"))
475
if(stringEqualCI(name, "keywords"))
477
if(ptr && !*ptr && content) {
491
char *name = htmlAttrVal(topAttrib, "name");
492
char *id = htmlAttrVal(topAttrib, "id");
493
if(name == EMPTYSTRING)
496
if(id == EMPTYSTRING)
502
htmlHref(const char *desc)
504
char *h = hrefVal(topAttrib, desc);
505
if(h == EMPTYSTRING) {
507
if(topTag->action == TAGACT_A)
508
h = cloneString("#");
512
topTag->href = resolveURL(basehref, h);
518
formControl(bool namecheck)
520
void *fo = 0; /* form object */
521
void *e; /* the new element */
522
const char *typedesc;
523
int itype = topTag->itype;
524
int isradio = itype == INP_RADIO;
525
int isselect = (itype == INP_SELECT) * 2;
528
topTag->controller = currentForm;
529
fo = currentForm->jv;
530
} else if(itype != INP_BUTTON)
531
browseError(MSG_NotInForm2, topTag->info->desc);
534
if(namecheck && !topTag->name)
535
browseError(MSG_FieldNoName, topTag->info->desc);
539
domLink("Element", topTag->name, topTag->id, 0, 0,
540
"elements", fo, isradio | isselect);
543
domLink("Element", topTag->name, topTag->id, 0, 0, 0,
544
jdoc, isradio | isselect);
550
if(itype <= INP_RADIO) {
551
establish_property_string(e, "value", topTag->value, false);
552
if(itype != INP_FILE) {
553
/* No default value on file, for security reasons */
554
establish_property_string(e, dfvl, topTag->value, true);
559
typedesc = topTag->multiple ? "select-multiple" : "select-one";
561
typedesc = inp_types[itype];
562
establish_property_string(e, "type", typedesc, true);
564
if(itype >= INP_RADIO) {
565
establish_property_bool(e, "checked", topTag->checked, false);
566
establish_property_bool(e, dfck, topTag->checked, true);
577
domLink("Image", topTag->name, topTag->id, "src", topTag->href,
578
"images", jdoc, false);
580
/* don't know if javascript ever looks at alt. Probably not. */
583
a = htmlAttrVal(topAttrib, "alt");
585
establish_property_string(topTag->jv, "alt", a, true);
592
void *fv; /* form variable in javascript */
596
currentForm = topTag;
600
a = htmlAttrVal(topAttrib, "method");
602
if(stringEqualCI(a, "post"))
604
else if(!stringEqualCI(a, "get"))
605
browseError(MSG_GetPost);
609
a = htmlAttrVal(topAttrib, "enctype");
611
if(stringEqualCI(a, "multipart/form-data"))
613
else if(!stringEqualCI(a, "application/x-www-form-urlencoded"))
614
browseError(MSG_Enctype);
618
if(a = topTag->href) {
619
const char *prot = getProtURL(a);
621
if(stringEqualCI(prot, "mailto"))
622
topTag->bymail = true;
623
else if(stringEqualCI(prot, "javascript"))
624
topTag->javapost = true;
625
else if(stringEqualCI(prot, "https"))
626
topTag->secure = true;
627
else if(!stringEqualCI(prot, "http"))
628
browseError(MSG_FormProtBad, prot);
632
nzFree(radioChecked);
636
domLink("Form", topTag->name, topTag->id, "action", topTag->href,
637
"forms", jdoc, false);
641
establish_property_array(fv, "elements");
650
memcpy(cw->dw + 3, "<html>\n", 7);
651
side = sideBuffer(0, cw->dw + 10, -1, cw->fileName, true);
653
i_printf(MSG_SideBufferX, side);
655
i_puts(MSG_NoSideBuffer);
656
printf("%s\n", cw->dw + 10);
668
char *s = htmlAttrVal(topAttrib, "type");
671
n = stringInList(inp_types, s);
673
browseError(MSG_InputType, s);
678
if(htmlAttrPresent(topAttrib, "readonly"))
679
topTag->rdonly = true;
680
s = htmlAttrVal(topAttrib, "maxlength");
683
len = stringIsNum(s);
687
/* store the original text in value. */
688
/* This makes it easy to push the reset button. */
689
s = htmlAttrVal(topAttrib, "value");
693
if(n >= INP_RADIO && htmlAttrPresent(topAttrib, "checked")) {
695
if(n == INP_RADIO && topTag->name &&
696
strlen(topTag->name) < sizeof (namebuf) - 3) {
698
radioChecked = initString(&radioChecked_l);
699
stringAndChar(&radioChecked, &radioChecked_l, '|');
701
sprintf(namebuf, "|%s|", topTag->name);
702
if(strstr(radioChecked, namebuf)) {
703
browseError(MSG_RadioMany);
706
stringAndString(&radioChecked, &radioChecked_l, namebuf + 1);
708
topTag->rchecked = true;
709
topTag->checked = true;
712
/* Even the submit fields can have a name, but they don't have to */
713
formControl(n > INP_SUBMIT);
719
struct htmlTag *t = allocZeroMem(sizeof (struct htmlTag));
720
addToListBack(&htmlStack, t);
722
t->info = elements + 2;
723
t->action = TAGACT_INPUT;
724
t->controller = currentForm;
725
t->itype = INP_SUBMIT;
728
/* display the checked options */
730
displayOptions(const struct htmlTag *sel)
732
const struct htmlTag *t;
735
new = initString(&l);
736
foreach(t, htmlStack) {
737
if(t->controller != sel)
742
stringAndChar(&new, &l, ',');
743
stringAndString(&new, &l, t->name);
746
} /* displayOptions */
748
static struct htmlTag *
749
locateOptionByName(const struct htmlTag *sel, const char *name, int *pmc,
752
struct htmlTag **list = cw->tags, *t, *em = 0, *pm = 0;
753
int pmcount = 0; /* partial match count */
756
if(t->controller != sel)
760
if(stringEqualCI(s, name)) {
766
if(strstrCI(s, name)) {
777
} /* locateOptionByName */
779
static struct htmlTag *
780
locateOptionByNum(const struct htmlTag *sel, int n)
782
struct htmlTag **list = cw->tags, *t;
785
if(t->controller != sel)
794
} /* locateOptionByNum */
797
locateOptions(const struct htmlTag *sel, const char *input,
798
char **disp_p, char **val_p, bool setcheck)
800
struct htmlTag *t, **list;
801
char *display = 0, *value = 0;
803
int len = strlen(input);
806
const char *s, *e; /* start and end of an option */
807
char *iopt; /* individual option */
810
display = initString(&disp_l);
812
value = initString(&val_l);
813
iopt = allocMem(len + 1);
816
/* Uncheck all existing options, then check the ones selected. */
818
set_property_number(ev, "selectedIndex", -1);
821
if(t->controller == sel && t->name) {
824
set_property_bool(t->jv, "selected", false);
836
strncpy(iopt, s, len);
842
t = locateOptionByName(sel, iopt, &pmc, true);
844
n = stringIsNum(iopt);
846
t = locateOptionByNum(sel, n);
849
t = locateOptionByName(sel, iopt, &pmc, false);
852
setError(MSG_XOutOfRange, n);
854
setError(pmc + MSG_OptMatchNone, iopt);
855
/* This should never happen when we're doing a set check */
857
runningError(MSG_OptionSync, iopt);
865
stringAndChar(&value, &val_l, '\1');
866
stringAndString(&value, &val_l, t->value);
870
stringAndChar(&display, &disp_l, ',');
871
stringAndString(&display, &disp_l, t->name);
876
set_property_bool(t->jv, "selected", true);
878
set_property_number(ev, "selectedIndex", t->lic);
881
} /* loop over multiple options */
895
} /* locateOptions */
898
/*********************************************************************
899
Sync up the javascript variables with the input fields.
900
This is required before running any javascript, e.g. an onclick function.
901
After all, the input fields may have changed.
902
You may have changed the last name from Flintstone to Rubble.
903
This has to propagate down to the javascript strings in the DOM.
904
This is quick and stupid; I just update everything.
905
Most of the time I'm setting the strings to what they were before;
906
that's the way it goes.
907
*********************************************************************/
912
const struct htmlTag *t, **list;
913
void *eo; /* element object */
918
return; /* not necessary */
921
debugPrint(5, "jSyncup starts");
928
if(t->action != TAGACT_INPUT)
933
if(itype <= INP_HIDDEN)
936
if(itype >= INP_RADIO) {
937
int checked = fieldIsChecked(t->seqno);
939
checked = t->rchecked;
940
set_property_bool(eo, "checked", checked);
944
value = getFieldFromBuffer(t->seqno);
945
/* If that line has been deleted from the user's buffer,
946
* indicated by value = 0,
947
* revert back to the original (reset) value. */
949
if(itype == INP_SELECT) {
950
locateOptions(t, (value ? value : t->value), 0, 0, true);
952
value = cloneString( get_property_option(eo));
955
if(itype == INP_TA) {
957
set_property_string(eo, "value", 0);
960
/* Now value is just <buffer 3>, which is meaningless. */
965
/* The unfold command should never fail */
966
if(!unfoldBuffer(cx, false, &cxbuf, &j))
968
set_property_string(eo, "value", cxbuf);
974
set_property_string(eo, "value", value);
977
set_property_string(eo, "value", t->value);
978
} /* loop over tags */
980
debugPrint(5, "jSyncup ends");
984
/* Find the <foo> tag to match </foo> */
985
static struct htmlTag *
986
findOpenTag(const char *name)
989
bool closing = topTag->slash;
991
const char *desc = topTag->info->desc;
992
foreachback(t, htmlStack) {
994
continue; /* last one doesn't count */
1000
continue; /* unbalanced slash, should never happen */
1001
/* Now we have an unbalanced open tag */
1002
match = stringEqualCI(t->info->name, name);
1003
/* I expect tags to nest perfectly, like labeled parentheses */
1007
browseError(MSG_TagNest, desc, t->info->desc);
1012
if(!(t->info->nest & 2))
1013
browseError(MSG_TagInTag, desc, desc);
1018
browseError(MSG_TagClose, desc);
1022
static struct htmlTag *
1023
newTag(const char *name)
1026
const struct tagInfo *ti;
1028
for(ti = elements; ti->name; ++ti)
1029
if(stringEqualCI(ti->name, name))
1033
action = ti->action;
1034
t = allocZeroMem(sizeof (struct htmlTag));
1039
if(stringEqual(name, "a"))
1040
t->clickable = true;
1041
addToListBack(&htmlStack, t);
1046
onloadGo(void *obj, const char *jsrc, const char *tagname)
1051
/* The first one is easy - one line of code. */
1052
handlerGo(obj, "onload");
1054
if(!handlerPresent(obj, "onunload"))
1056
if(handlerPresent(obj, "onclick")) {
1057
runningError(MSG_UnloadClick);
1063
t->href = cloneString("#");
1064
link_onunload_onclick(obj);
1065
sprintf(buf, "on close %s", tagname);
1066
caseShift(buf, 'm');
1067
toPreamble(t->seqno, buf, jsrc, 0);
1070
/* Always returns a string, even if errors were found.
1071
* Internet web pages often contain errors!
1072
* This routine mucks with the passed-in string, and frees it
1073
* when finished. Thus the argument is not const.
1074
* The produced string contains only these tags.
1075
* Input field open and close.
1076
* Hyperlink open and close.
1078
* Preformat open and close.
1079
* Try to keep this list up to date.
1080
* We want a handle on what tags are hanging around during formatting. */
1082
encodeTags(char *html)
1084
const struct tagInfo *ti;
1085
struct htmlTag *t, *open, *v;
1088
char *a; /* for a specific attribute */
1089
char *new; /* the new string */
1091
int js_nl; /* number of newlines in javascript */
1092
const char *name, *attrib, *end;
1095
int j, l, namelen, lns;
1096
int dw_line, dw_nest = 0;
1097
char hnum[40]; /* hidden number */
1099
bool retainTag, onload_done = false;
1100
bool a_text; /* visible text within the hyperlink */
1101
bool slash, a_href, rc;
1102
bool premode = false, invisible = false;
1103
/* Tags that cannot nest, one open at a time. */
1104
struct htmlTag *currentA; /* the open anchor */
1105
struct htmlTag *currentSel; /* the open select */
1106
struct htmlTag *currentOpt; /* the current option */
1107
struct htmlTag *currentTitle;
1108
struct htmlTag *currentTA; /* text area */
1109
int offset; /* where the current x starts */
1110
int ln = 1; /* line number */
1111
int action; /* action of the tag */
1113
int nopt; /* number of options */
1114
int intable = 0, inrow = 0;
1116
void *to; /* table object */
1117
void *ev; /* generic event variable */
1119
currentA = currentForm = currentSel = currentOpt = currentTitle =
1121
initList(&htmlStack);
1123
new = initString(&l);
1127
/* first tag is a base tag, from the filename */
1129
t->href = cloneString(cw->fileName);
1136
++ln; /* keep track of line numbers */
1139
if(strchr("\r\n\f", c) && !currentTA) {
1140
if(!premode || c == '\r' && h[1] == '\n')
1145
if(lastact == TAGACT_TD && c == ' ')
1147
stringAndChar(&new, &l, c);
1148
if(!isspaceByte(c)) {
1158
if(h[1] == '!' || h[1] == '?') {
1159
h = (char *)skipHtmlComment(h, &lns);
1164
if(!parseTag(h, &name, &namelen, &attrib, &end, &lns))
1167
/* html tag found */
1169
h = (char *)end; /* skip past tag */
1175
slash = true, ++name, --namelen;
1176
if(namelen > sizeof (tagname) - 1)
1177
namelen = sizeof (tagname) - 1;
1178
strncpy(tagname, name, namelen);
1179
tagname[namelen] = 0;
1181
for(ti = elements; ti->name; ++ti)
1182
if(stringEqualCI(ti->name, tagname))
1184
action = ti->action;
1185
debugPrint(7, "tag %s %d %d %d", tagname, ntags, ln, action);
1188
/* Sometimes a textarea is used to present a chunk of html code.
1189
* "Cut and paste this into your web page."
1190
* So it may contain tags. Ignore them!
1191
* All except the textarea tag. */
1192
if(action != TAGACT_TA) {
1193
ln = browseLine, h = save_h; /* back up */
1197
currentTA->action = TAGACT_INPUT;
1198
currentTA->itype = INP_TA;
1199
currentTA->balanced = true;
1200
s = currentTA->value = andTranslate(new + offset, true);
1201
/* Text starts at the next line boundary */
1202
while(*s == '\t' || *s == ' ')
1208
if(s > currentTA->value)
1209
strcpy(currentTA->value, s);
1210
a = currentTA->value;
1212
while(a > currentTA->value && (a[-1] == ' ' || a[-1] == '\t'))
1216
establish_innerHTML(currentTA->jv, currentTA->inner, save_h,
1218
establish_property_string(currentTA->jv, "value",
1219
currentTA->value, false);
1220
establish_property_string(currentTA->jv, dfvl, currentTA->value,
1223
l -= strlen(new + offset);
1225
j = sideBuffer(0, currentTA->value, -1, 0, false);
1228
sprintf(hnum, "%c%d<buffer %d%c0>",
1229
InternalCodeChar, currentTA->seqno, j, InternalCodeChar);
1230
stringAndString(&new, &l, hnum);
1232
stringAndString(&new, &l, "<buffer ?>");
1236
browseError(MSG_TextareaNest);
1240
continue; /* tag not recognized */
1242
topTag = t = allocZeroMem(sizeof (struct htmlTag));
1243
addToListBack(&htmlStack, t);
1245
sprintf(hnum, "%c%d", InternalCodeChar, ntags);
1252
t->action = action; /* we might change this later */
1254
topAttrib = t->attrib = j ? pullString(attrib, j) : EMPTYSTRING;
1258
open = findOpenTag(ti->name);
1261
continue; /* unbalanced </ul> means nothing */
1262
open->balanced = t->balanced = true;
1264
establish_innerHTML(open->jv, open->inner, save_h, false);
1269
if(slash && ti->bits & TAG_NOSLASH)
1270
continue; /* negated version means nothing */
1272
/* Does this tag force the closure of an anchor? */
1273
if(currentA && (action != TAGACT_A || !slash)) {
1274
if(open && open->clickable)
1275
goto forceCloseAnchor;
1276
rc = htmlAttrPresent(topAttrib, "onclick");
1278
ti->bits & TAG_CLOSEA && (a_text || action <= TAGACT_OPTION)) {
1279
browseError(MSG_InAnchor, ti->desc);
1281
stringAndString(&new, &l, "\2000}");
1282
currentA->balanced = true;
1284
/* if/when the </a> comes along, it will be unbalanced, and we'll ignore it. */
1289
if(ti->bits & TAG_INVISIBLE)
1293
if(ti->bits & TAG_INVISIBLE)
1298
/* Are we gathering text to build title or option? */
1299
if(currentTitle || currentOpt) {
1301
v = (currentTitle ? currentTitle : currentOpt);
1302
/* Should we print an error message? */
1303
if(v->action != action &&
1304
!(v->action == TAGACT_OPTION && action == TAGACT_SELECT) ||
1305
action != TAGACT_OPTION && !slash)
1306
browseError(MSG_HasTags, v->info->desc);
1307
if(!(ti->bits & TAG_CLOSEA))
1309
/* close off the title or option */
1312
if(currentTitle && !cw->ft)
1318
a = andTranslate(new + offset, true);
1320
if(currentOpt && strchr(a, ',') && currentSel->multiple) {
1325
browseError(MSG_OptionComma);
1327
spaceCrunch(a, true, true);
1332
establish_property_string(jdoc, "title", a, true);
1337
browseError(MSG_OptionEmpty);
1340
v->value = cloneString(a);
1343
if(ev = currentSel->jv) { /* element variable */
1344
void *ov = establish_js_option(ev, v->lic);
1346
establish_property_string(ov, "text", v->name, true);
1347
establish_property_string(ov, "value", v->value, true);
1348
establish_property_bool(ov, "selected", v->checked,
1350
establish_property_bool(ov, "defaultSelected",
1352
} /* select has corresponding java variables */
1355
/* ptr is nonzero */
1356
l -= strlen(new + offset);
1358
currentTitle = currentOpt = 0;
1364
if(t->itype == INP_HIDDEN)
1370
++currentForm->ninp;
1371
if(t->itype == INP_SUBMIT || t->itype == INP_IMAGE)
1372
currentForm->submitted = true;
1375
stringAndString(&new, &l, hnum);
1376
if(t->itype < INP_RADIO) {
1378
stringAndString(&new, &l, t->value);
1379
else if(t->itype == INP_SUBMIT || t->itype == INP_IMAGE)
1380
stringAndString(&new, &l, "Go");
1381
else if(t->itype == INP_RESET)
1382
stringAndString(&new, &l, "Reset");
1384
stringAndChar(&new, &l, t->checked ? '+' : '-');
1385
if(currentForm && (t->itype == INP_SUBMIT || t->itype == INP_IMAGE)) {
1386
if(currentForm->secure)
1387
stringAndString(&new, &l, " secure");
1388
if(currentForm->bymail)
1389
stringAndString(&new, &l, " bymail");
1391
stringAndString(&new, &l, "\2000>");
1398
browseError(MSG_ManyTitles);
1413
domLink("Anchor", topTag->name, topTag->id, "href",
1414
topTag->href, "links", jdoc, false);
1418
topTag->clickable = true;
1424
strcpy(hnum, "\2000}");
1431
retainTag = false; /* no need to keep this anchor */
1449
domLink("Head", topTag->name, topTag->id, 0, 0,
1450
"heads", jdoc, false);
1451
goto plainWithElements;
1456
domLink("Body", topTag->name, topTag->id, 0, 0,
1457
"bodies", jdoc, false);
1460
establish_property_array(t->jv, "elements");
1465
/* check for javascript events, that's it */
1468
/* no need to keep these tags in the output */
1476
/* Look for the open UL or OL */
1478
foreachback(v, htmlStack) {
1479
if(v->balanced || !v->info->nest)
1482
continue; /* should never happen */
1484
if(stringEqual(s, "OL") ||
1485
stringEqual(s, "UL") ||
1486
stringEqual(s, "MENU") || stringEqual(s, "DIR")) {
1493
browseError(MSG_NotInList, ti->desc);
1501
sprintf(hnum + 1, "%d. ", j);
1502
stringAndString(&new, &l, hnum);
1506
foreachback(v, htmlStack) {
1507
if(v->balanced || !v->info->nest)
1510
continue; /* should never happen */
1512
if(stringEqual(s, "DL"))
1515
if(v == (struct htmlTag *)&htmlStack)
1516
browseError(MSG_NotInList, ti->desc);
1523
domLink("Table", topTag->name, topTag->id, 0, 0,
1524
"tables", jdoc, false);
1526
/* create the array of rows under the table */
1528
establish_property_array(to, "rows");
1538
browseError(MSG_NotInTable, ti->desc);
1546
if((!slash) && (open = findOpenTag("table")) && open->jv) {
1549
domLink("Trow", topTag->name, topTag->id, 0, 0,
1550
"rows", open->jv, false);
1552
establish_property_array(to, "cells");
1558
browseError(MSG_NotInRow, ti->desc);
1565
else if(retainTag) {
1566
while(l && new[l - 1] == ' ')
1569
stringAndChar(&new, &l, '|');
1571
if((open = findOpenTag("tr")) && open->jv) {
1574
domLink("Cell", topTag->name, topTag->id, 0, 0,
1575
"cells", open->jv, false);
1584
domLink("Div", topTag->name, topTag->id, 0, 0,
1585
"divs", jdoc, false);
1594
domLink("Span", topTag->name, topTag->id, 0, 0,
1595
"spans", jdoc, false);
1601
if(lastact == TAGACT_TD)
1618
if(action == TAGACT_BR)
1623
stringAndChar(&new, &l, c);
1630
currentSel->action = TAGACT_INPUT;
1631
if(currentSel->controller)
1632
++currentSel->controller->ninp;
1633
currentSel->value = a = displayOptions(currentSel);
1635
currentSel->retain = true;
1636
/* Crank out the input tag */
1637
sprintf(hnum, "%c%d<", InternalCodeChar, currentSel->seqno);
1638
stringAndString(&new, &l, hnum);
1639
stringAndString(&new, &l, a);
1640
stringAndString(&new, &l, "\2000>");
1644
if(action == TAGACT_FORM && slash && currentForm) {
1645
if(retainTag && currentForm->href && !currentForm->submitted) {
1647
sprintf(hnum, " %c%d<Go", InternalCodeChar, ntags - 1);
1648
stringAndString(&new, &l, hnum);
1649
if(currentForm->secure)
1650
stringAndString(&new, &l, " secure");
1651
if(currentForm->bymail)
1652
stringAndString(&new, &l, " bymail");
1653
stringAndString(&new, &l, " implicit\2000>");
1667
t->itype = INP_SELECT;
1668
if(htmlAttrPresent(topAttrib, "readonly"))
1670
if(htmlAttrPresent(topAttrib, "multiple"))
1679
browseError(MSG_NotInSelect);
1684
t->controller = currentSel;
1686
t->value = htmlAttrVal(topAttrib, "value");
1687
if(htmlAttrPresent(topAttrib, "selected")) {
1688
if(currentSel->lic && !currentSel->multiple)
1689
browseError(MSG_ManyOptSelected);
1691
t->checked = t->rchecked = true, ++currentSel->lic;
1698
stringAndString(&new, &l,
1699
"\r--------------------------------------------------------------------------------\r");
1707
j = (action == TAGACT_SUP ? 2 : 1);
1710
stringAndString(&new, &l, (j == 2 ? "^(" : "["));
1713
/* backup, and see if we can get rid of the parentheses or brackets */
1714
a = new + open->lic + j;
1715
if(j == 2 && isalphaByte(*a) && !a[1])
1717
if(j == 2 && isalnumByte(a[-3]) && (stringEqual(a, "th") ||
1718
stringEqual(a, "rd") ||
1719
stringEqual(a, "nd") || stringEqual(a, "st"))) {
1724
while(isdigitByte(*a))
1728
stringAndChar(&new, &l, (j == 2 ? ')' : ']'));
1730
/* ok, we can trash the original ( or [ */
1732
a = new + open->lic + j - 1;
1736
stringAndChar(&new, &l, ' ');
1742
if(action == TAGACT_FRAME) {
1745
domLink("Frame", topTag->name, 0, "src",
1746
topTag->href, "frames", jwin, false);
1750
domLink("Area", topTag->name, topTag->id, "href",
1751
topTag->href, "areas", jdoc, false);
1753
topTag->clickable = true;
1757
stringAndString(&new, &l,
1758
action == TAGACT_FRAME ? "\rFrame " : "\r");
1761
name = altText(t->href);
1763
name = (action == TAGACT_FRAME ? "???" : "area");
1766
stringAndString(&new, &l, hnum);
1767
t->action = TAGACT_A;
1770
if(t->href || action == TAGACT_FRAME)
1771
stringAndString(&new, &l, name);
1773
stringAndString(&new, &l, "\2000}");
1774
stringAndChar(&new, &l, '\r');
1783
toPreamble(t->seqno,
1784
(ti->name[0] == 'A' ? "Audio passage" : "Background Music"),
1786
t->action = TAGACT_A;
1794
debugPrint(3, "base href %s", basehref);
1800
/* I'm going to assume that if the web designer took the time
1801
* to put in an alt tag, then it's worth reading.
1802
* You can turn this feature off, but I don't think you'd want to. */
1803
if(a = htmlAttrVal(topAttrib, "alt"))
1804
stringAndString(&new, &l, a);
1812
a = htmlAttrVal(topAttrib, "alt");
1818
s = altText(t->name);
1820
s = altText(currentA->href);
1822
s = altText(t->href);
1825
stringAndString(&new, &l, s);
1832
rc = findEndScript(h, ti->name,
1833
(ti->action == TAGACT_SCRIPT), &h, &javatext, &js_nl);
1835
h = strchr(h, '>') + 1;
1837
/* I'm not going to process an open ended script. */
1840
runningError(MSG_ScriptNotClosed);
1845
a = htmlAttrVal(topAttrib, "language");
1846
/* If no language is specified, javascript is default. */
1848
(!a || !*a || !intFlag &&
1849
a && memEqualCI(a, "javascript", 10) && !isalphaByte(a[10]))) {
1850
/* It's javascript, run with the source, or the inline text. */
1851
int js_line = browseLine;
1852
char *js_file = cw->fileName;
1853
if(t->href) { /* fetch the javascript page */
1856
if(javaOK(t->href)) {
1857
debugPrint(2, "java source %s", t->href);
1858
if(browseLocal && !isURL(t->href)) {
1859
if(!fileIntoMemory(t->href, &serverData,
1861
runningError(MSG_GetLocalJS, errorMsg);
1863
javatext = serverData;
1864
prepareForBrowse(javatext, serverDataLen);
1866
} else if(httpConnect(basehref, t->href)) {
1868
javatext = serverData;
1869
prepareForBrowse(javatext, serverDataLen);
1872
runningError(MSG_GetJS, t->href, hcode);
1875
runningError(MSG_GetJS2, errorMsg);
1880
} /* fetch from the net */
1884
w = strrchr(js_file, '/');
1886
/* Trailing slash doesn't count */
1887
if(w[1] == 0 && w > js_file)
1888
for(--w; w >= js_file && *w != '/'; --w) ;
1891
debugPrint(3, "execute %s at %d", js_file, js_line);
1892
javaParseExecute(jwin, javatext, js_file, js_line);
1893
debugPrint(3, "execution complete");
1895
/* See if the script has produced html, via document.write() */
1897
int afterlen; /* after we fold in this string */
1900
debugPrint(3, "docwrite %d bytes", cw->dw_l);
1901
debugPrint(4, "<<\n%s\n>>", cw->dw + 10);
1902
stringAndString(&cw->dw, &cw->dw_l, "</docwrite>");
1903
afterlen = strlen(h) + strlen(cw->dw);
1904
after = allocMem(afterlen + 1);
1905
strcpy(after, cw->dw);
1912
/* After the realloc, the inner pointers are no longer valid. */
1913
foreach(z, htmlStack) z->inner = 0;
1914
} /* document.write */
1922
/* no clue what to do here */
1928
browseLine = ln = dw_line;
1932
browseLine = ln = 0;
1937
browseError(MSG_BadTag, action);
1944
if(!strpbrk(hnum, "{}")) {
1946
/* Leave the meaningless tags out. */
1947
if(action == TAGACT_PRE || action == TAGACT_A && topTag->name) ; /* ok */
1951
stringAndString(&new, &l, hnum);
1955
topTag->clickable = true;
1957
topTag->href = cloneString("#");
1959
sprintf(hnum, "%c%d{", InternalCodeChar, topTag->seqno);
1960
stringAndString(&new, &l, hnum);
1962
} /* loop over html string */
1965
stringAndString(&new, &l, "\2000}");
1969
/* Run the various onload functions */
1970
/* Turn the onunload functions into hyperlinks */
1971
if(!cw->jsdead && !onload_done) {
1972
const struct htmlTag *lasttag;
1973
onloadGo(jwin, 0, "window");
1974
onloadGo(jdoc, 0, "document");
1975
lasttag = htmlStack.prev;
1976
foreach(t, htmlStack) {
1979
/* in case the option has disappeared */
1980
if(t->action == TAGACT_OPTION)
1986
jsrc = htmlAttrVal(t->attrib, "onunload");
1987
onloadGo(ev, jsrc, t->info->name);
1992
} /* loop over tags */
1996
/* The onload function can, and often does, invoke document.write() */
2005
if(browseLocal == 1) { /* no errors yet */
2006
foreach(t, htmlStack) {
2008
if(t->info->nest && !t->slash && !t->balanced) {
2009
browseError(MSG_TagNotClosed, t->info->desc);
2013
/* Make sure the internal links are defined. */
2014
if(t->action != TAGACT_A)
2015
continue; /* not anchor */
2023
a = h + 1; /* this is what we're looking for */
2024
foreach(v, htmlStack) {
2025
if(v->action != TAGACT_A)
2026
continue; /* not achor */
2028
continue; /* no name */
2029
if(stringEqual(a, v->name))
2032
if(v == (struct htmlTag *)&htmlStack) { /* end of list */
2033
browseError(MSG_NoLable2, a);
2036
} /* loop over all tags */
2042
nzFree(radioChecked);
2046
h = allocMem(preamble_l + l + 2);
2047
strcpy(h, preamble);
2048
h[preamble_l] = '\f';
2049
strcpy(h + preamble_l + 1, new);
2060
preFormatCheck(int tagno, bool * pretag, bool * slash)
2062
const struct htmlTag *t;
2064
i_printfExit(MSG_ErrCallPreFormat);
2065
*pretag = *slash = false;
2066
if(tagno >= 0 && tagno < ntags) {
2067
t = tagArray[tagno];
2068
*pretag = (t->action == TAGACT_PRE);
2071
} /* preFormatCheck */
2074
htmlParse(char *buf, int remote)
2079
i_printfExit(MSG_HtmlNotreentrant);
2082
browseLocal = !remote;
2083
buf = encodeTags(buf);
2084
debugPrint(7, "%s", buf);
2088
newbuf = andTranslate(buf, false);
2092
debugPrint(7, "%s", buf);
2094
newbuf = htmlReformat(buf);
2100
/* In case one of the onload functions called document.write() */
2107
findField(const char *line, int ftype, int n,
2108
int *total, int *realtotal, int *tagno, char **href, void **evp)
2110
const struct htmlTag *t, **list = cw->tags;
2111
int nt = 0; /* number of fields total */
2112
int nrt = 0; /* the real total, for input fields */
2113
int nm = 0; /* number match */
2118
static const char urlok[] =
2119
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,./?@#%&-_+=:~";
2126
if(cw->browseMode) {
2128
while((c = *s) != '\n') {
2130
if(c != (char)InternalCodeChar)
2132
j = strtol(s, (char **)&s, 10);
2156
if(ftype == 1 && t->itype <= INP_SUBMIT)
2158
if(ftype == 2 && t->itype > INP_SUBMIT)
2166
} /* loop over line */
2180
*href = cloneString(t->href);
2184
/* defer to the java variable for the reference */
2185
const char *jh = get_property_url(t->jv, false);
2187
if(!*href || !stringEqual(*href, jh)) {
2189
*href = cloneString(jh);
2198
/* Second time through, maybe the url is in plain text. */
2202
/* skip past weird characters */
2203
while((c = *s) != '\n') {
2204
if(strchr(urlok, c))
2211
while(strchr(urlok, *s))
2213
h = pullString1(ss, s);
2238
} /* loop over line */
2253
findInputField(const char *line, int ftype, int n, int *total, int *realtotal,
2256
findField(line, ftype, n, total, realtotal, tagno, 0, 0);
2257
} /* findInputField */
2260
lineHasTag(const char *p, const char *s)
2262
const struct htmlTag *t, **list = cw->tags;
2265
while((c = *p++) != '\n') {
2266
if(c != (char)InternalCodeChar)
2268
j = strtol(p, (char **)&p, 10);
2272
if(t->action != TAGACT_A)
2276
if(stringEqual(t->name, s))
2282
/* See if there are simple tags like <p> or </font> */
2288
int fsize = 0; /* file size */
2290
bool firstline = true;
2292
for(ln = 1; ln <= cw->dol; ++ln) {
2293
char *p = (char *)fetchLine(ln, -1);
2297
while(isspaceByte(*p) && *p != '\n')
2300
continue; /* skip blank line */
2301
if(firstline && *p == '<') {
2302
/* check for <!doctype */
2303
if(memEqualCI(p + 1, "!doctype", 8))
2305
/* If it starts with <tag, for any tag we recognize,
2306
* we'll call it good. */
2307
for(j = 1; j < 10; ++j) {
2308
if(!isalnumByte(p[j]))
2313
if(j > 1 && (p[j] == '>' || isspaceByte(p[j]))) {
2314
/* something we recognize? */
2315
const struct tagInfo *ti;
2316
for(ti = elements; ti->name; ++ti)
2317
if(stringEqualCI(ti->name, look))
2323
/* count tags through the buffer */
2324
for(j = 0; (c = p[j]) != '\n'; ++j) {
2333
else if(isalphaByte(c))
2353
} /* loop over lines */
2355
/* we need at least one of these tags every 300 characters.
2356
* And we need at least 4 such tags.
2357
* Remember, you can always override by putting <html> at the top. */
2358
return (cnt >= 4 && cnt * 300 >= fsize);
2361
/* Show an input field */
2363
infShow(int tagno, const char *search)
2365
const struct htmlTag **list = cw->tags;
2366
const struct htmlTag *t = list[tagno], *v;
2371
s = inp_types[t->itype];
2377
if(t->itype >= INP_TEXT && t->itype <= INP_NUMBER && t->lic)
2378
printf("[%d]", t->lic);
2379
if(t->itype == INP_TA) {
2380
char *rows = htmlAttrVal(t->attrib, "rows");
2381
char *cols = htmlAttrVal(t->attrib, "cols");
2382
char *wrap = htmlAttrVal(t->attrib, "wrap");
2384
printf("[%sx%s", rows, cols);
2385
if(wrap && stringEqualCI(wrap, "virtual"))
2386
i_printf(MSG_Recommended);
2387
i_printf(MSG_Close);
2394
printf(" %s", t->name);
2396
if(t->itype != INP_SELECT)
2399
/* display the options in a pick list */
2400
/* If a search string is given, display the options containing that string. */
2403
for(j = 0; v = list[j]; ++j) {
2404
if(v->controller != t)
2409
if(*search && !strstrCI(v->name, search))
2412
printf("%3d %s\n", cnt, v->name);
2416
i_puts(MSG_NoOptions);
2418
i_printf(MSG_NoOptionsMatch, search);
2422
/* Update an input field. */
2424
infReplace(int tagno, const char *newtext, int notify)
2426
const struct htmlTag **list = cw->tags;
2427
const struct htmlTag *t = list[tagno], *v;
2428
const struct htmlTag *form = t->controller;
2430
int itype = t->itype;
2431
int newlen = strlen(newtext);
2433
/* sanity checks on the input */
2434
if(itype <= INP_SUBMIT) {
2435
int b = MSG_IsButton;
2436
if(itype == INP_SUBMIT || itype == INP_IMAGE)
2437
b = MSG_SubmitButton;
2438
if(itype == INP_RESET)
2439
b = MSG_ResetButton;
2444
if(itype == INP_TA) {
2445
setError(MSG_Textarea, t->lic);
2450
setError(MSG_Readonly);
2454
if(strchr(newtext, '\n')) {
2455
setError(MSG_InputNewline);
2459
if(itype >= INP_TEXT && itype <= INP_NUMBER && t->lic && newlen > t->lic) {
2460
setError(MSG_InputLong, t->lic);
2464
if(itype >= INP_RADIO) {
2465
if(newtext[0] != '+' && newtext[0] != '-' || newtext[1]) {
2466
setError(MSG_InputRadio);
2469
if(itype == INP_RADIO && newtext[0] == '-') {
2470
setError(MSG_ClearRadio);
2475
/* Two lines, clear the "other" radio button, and set this one. */
2481
if(itype == INP_SELECT) {
2482
if(!locateOptions(t, newtext, 0, 0, false))
2484
locateOptions(t, newtext, &display, 0, false);
2485
updateFieldInBuffer(tagno, display, notify, true);
2489
if(itype == INP_FILE) {
2490
if(!envFile(newtext, &newtext))
2492
if(newtext[0] && access(newtext, 4)) {
2493
setError(MSG_FileAccess, newtext);
2498
if(itype == INP_NUMBER) {
2499
if(*newtext && stringIsNum(newtext) < 0) {
2500
setError(MSG_NumberExpected);
2505
if(itype == INP_RADIO && form && t->name && *newtext == '+') {
2506
/* clear the other radio button */
2507
while(v = *list++) {
2508
if(v->controller != form)
2510
if(v->itype != INP_RADIO)
2514
if(!stringEqual(v->name, t->name))
2516
if(fieldIsChecked(v->seqno) == true)
2517
updateFieldInBuffer(v->seqno, "-", 0, false);
2521
if(itype != INP_SELECT) {
2522
updateFieldInBuffer(tagno, newtext, notify, true);
2525
if(itype >= INP_RADIO && tagHandler(t->seqno, "onclick")) {
2527
runningError(MSG_NJNoOnclick);
2530
handlerGo(t->jv, "onclick");
2537
if(itype >= INP_TEXT && itype <= INP_SELECT &&
2538
tagHandler(t->seqno, "onchange")) {
2540
runningError(MSG_NJNoOnchange);
2543
handlerGo(t->jv, "onchange");
2553
/*********************************************************************
2554
Reset or submit a form.
2555
This function could be called by javascript, as well as a human.
2556
It must therefore update the js variables and the text simultaneously.
2557
Most of this work is done by resetVar().
2558
To reset a variable, copy its original value, in the html tag,
2559
back to the text buffer, and over to javascript.
2560
*********************************************************************/
2563
resetVar(struct htmlTag *t)
2565
int itype = t->itype;
2566
const char *w = t->value;
2570
/* This is a kludge - option looks like INP_SELECT */
2571
if(t->action == TAGACT_OPTION)
2574
if(itype <= INP_SUBMIT)
2577
if(itype >= INP_SELECT) {
2580
w = bval ? "+" : "-";
2583
if(itype == INP_TA) {
2586
sideBuffer(cx, t->value, -1, 0, false);
2587
} else if(itype != INP_HIDDEN && itype != INP_SELECT)
2588
updateFieldInBuffer(t->seqno, w, 0, false);
2591
if(itype >= INP_RADIO) {
2592
set_property_bool(jv, "checked", bval);
2593
} else if(itype == INP_SELECT) {
2594
set_property_bool(jv, "selected", bval);
2595
if(bval && !t->controller->multiple)
2596
set_property_number(t->controller->jv, "selectedIndex", t->lic);
2598
set_property_string(jv, "value", w);
2603
formReset(const struct htmlTag *form)
2605
struct htmlTag **list = cw->tags, *t, *sel = 0;
2608
while(t = *list++) {
2609
if(t->action == TAGACT_OPTION) {
2612
if(t->controller != sel)
2618
if(t->action != TAGACT_INPUT)
2622
char *display = displayOptions(sel);
2623
updateFieldInBuffer(sel->seqno, display, 0, false);
2628
if(t->controller != form)
2631
if(itype != INP_SELECT) {
2637
set_property_number(t->jv, "selectedIndex", -1);
2638
} /* loop over tags */
2640
i_puts(MSG_FormReset);
2643
/* Fetch a field value (from a form) to post. */
2644
/* The result is allocated */
2646
fetchTextVar(const struct htmlTag *t)
2651
return cloneString(get_property_string(jv, "value"));
2653
if(t->itype > INP_HIDDEN) {
2654
v = getFieldFromBuffer(t->seqno);
2658
/* Revert to the default value */
2659
return cloneString(t->value);
2660
} /* fetchTextVar */
2663
fetchBoolVar(const struct htmlTag *t)
2668
return get_property_bool(jv,
2669
(t->action == TAGACT_OPTION ? "selected" : "checked"));
2671
checked = fieldIsChecked(t->seqno);
2673
checked = t->rchecked;
2675
} /* fetchBoolVar */
2677
/* Some information on posting forms can be found here.
2678
* http://www.w3.org/TR/REC-html40/interact/forms.html */
2681
postDelimiter(char fsep, const char *boundary, char **post, int *l)
2685
if(c == '?' || c == '\1')
2688
stringAndString(post, l, "--");
2689
stringAndString(post, l, boundary);
2690
stringAndChar(post, l, '\r');
2693
stringAndChar(post, l, fsep);
2694
} /* postDelimiter */
2697
postNameVal(const char *name, const char *val,
2698
char fsep, uchar isfile, const char *boundary, char **post, int *l)
2701
const char *ct, *ce; /* content type, content encoding */
2711
postDelimiter(fsep, boundary, post, l);
2714
enc = encodePostData(name);
2715
stringAndString(post, l, enc);
2716
stringAndChar(post, l, '=');
2720
stringAndString(post, l, name);
2721
stringAndString(post, l, "=\r\n");
2724
stringAndString(post, l, "Content-Disposition: form-data; name=\"");
2725
stringAndString(post, l, name);
2726
stringAndChar(post, l, '"');
2727
/* I'm leaving nl off, in case we need ; filename */
2740
enc = encodePostData(val);
2741
stringAndString(post, l, enc);
2745
stringAndString(post, l, val);
2746
stringAndString(post, l, eol);
2751
stringAndString(post, l, "; filename=\"");
2752
stringAndString(post, l, val);
2753
stringAndChar(post, l, '"');
2755
if(!encodeAttachment(val, 0, &ct, &ce, &enc))
2758
/* remember to free val in this case */
2762
/* Anything nonascii makes it 8bit */
2764
for(s = val; *s; ++s)
2770
stringAndString(post, l, "\r\nContent-Type: ");
2771
stringAndString(post, l, ct);
2772
stringAndString(post, l, "\r\nContent-Transfer-Encoding: ");
2773
stringAndString(post, l, ce);
2774
stringAndString(post, l, "\r\n\r\n");
2775
stringAndString(post, l, val);
2776
stringAndString(post, l, eol);
2786
formSubmit(const struct htmlTag *form, const struct htmlTag *submit,
2787
char **post, int *l)
2789
const struct htmlTag **list = cw->tags, *t;
2793
const char *boundary;
2794
char fsep = '&'; /* field separator */
2795
bool noname = false, rc;
2802
boundary = makeBoundary();
2803
stringAndString(post, l, "`mfd~");
2804
stringAndString(post, l, boundary);
2805
stringAndString(post, l, eol);
2808
while(t = *list++) {
2809
if(t->action != TAGACT_INPUT)
2811
if(t->controller != form)
2814
if(itype <= INP_SUBMIT && t != submit)
2818
if(t == submit) { /* the submit button you pushed */
2826
if(t->itype != INP_IMAGE)
2828
namelen = strlen(name);
2829
nx = allocMem(namelen + 3);
2831
strcpy(nx + namelen, ".x");
2832
postNameVal(nx, "0", fsep, false, boundary, post, l);
2833
nx[namelen + 1] = 'y';
2834
postNameVal(nx, "0", fsep, false, boundary, post, l);
2839
if(itype >= INP_RADIO) {
2841
bval = fetchBoolVar(t);
2848
if(value && !*value)
2850
if(itype == INP_CHECKBOX && value == 0)
2855
if(itype < INP_FILE) {
2856
/* Even a hidden variable can be adjusted by js.
2857
* fetchTextVar allows for this possibility.
2858
* I didn't allow for it in the above, the value of a radio button;
2859
* hope that's not a problem. */
2860
value = fetchTextVar(t);
2861
postNameVal(name, value, fsep, false, boundary, post, l);
2866
if(itype == INP_TA) {
2877
/* do this as an attachment */
2878
sprintf(cxstring, "%d", cx);
2879
if(!postNameVal(name, cxstring, fsep, 1, boundary, post, l))
2883
if(!unfoldBuffer(cx, textAreaDosNewlines, &cxbuf, &cxlen))
2885
for(j = 0; j < cxlen; ++j)
2887
setError(MSG_SessionNull, cx);
2891
if(j && cxbuf[j - 1] == '\n')
2893
if(j && cxbuf[j - 1] == '\r')
2896
rc = postNameVal(name, cxbuf, fsep, false, boundary, post, l);
2903
/* Just an empty string */
2904
postNameVal(name, 0, fsep, false, boundary, post, l);
2908
if(itype == INP_SELECT) {
2909
char *display = getFieldFromBuffer(t->seqno);
2911
if(!display) { /* off the air */
2912
struct htmlTag *v, **vl = cw->tags;
2913
/* revert back to reset state */
2915
if(v->controller == t)
2916
v->checked = v->rchecked;
2917
display = displayOptions(t);
2919
rc = locateOptions(t, display, 0, &value, false);
2922
goto fail; /* this should never happen */
2923
/* option could have an empty value, usually the null choice,
2924
* before you have made a selection. */
2926
postNameVal(name, value, fsep, false, boundary, post, l);
2929
/* Step through the options */
2930
for(s = value; *s; s = e) {
2934
e = strchr(s, '\1');
2938
postNameVal(name, s, fsep, false, boundary, post, l);
2946
if(itype == INP_FILE) { /* the only one left */
2947
value = fetchTextVar(t);
2952
if(!(form->post & form->mime)) {
2953
setError(MSG_FilePost);
2957
rc = postNameVal(name, value, fsep, 3, boundary, post, l);
2964
i_printfExit(MSG_UnexSubmitForm);
2967
postNameVal(name, value, fsep, false, boundary, post, l);
2968
} /* loop over tags */
2970
if(form->mime) { /* the last boundary */
2971
stringAndString(post, l, "--");
2972
stringAndString(post, l, boundary);
2973
stringAndString(post, l, "--\r\n");
2977
i_puts(MSG_UnnamedFields);
2978
i_puts(MSG_FormSubmit);
2985
/*********************************************************************
2986
Push the reset or submit button.
2987
This routine must be reentrant.
2988
You push submit, which calls this routine, which runs the onsubmit code,
2989
which checks the fields and calls form.submit(),
2990
which calls this routine. Happens all the time.
2991
*********************************************************************/
2994
infPush(int tagno, char **post_string)
2996
struct htmlTag **list = cw->tags;
2997
struct htmlTag *t = list[tagno];
2998
struct htmlTag *form;
3000
char *post, *section;
3002
const char *action = 0;
3008
/* If the tag is actually a form, then infPush() was invoked
3010
* Revert t back to 0, since there may be multiple submit buttons
3011
* on the form, and we don't know which one was pushed. */
3012
if(t->action == TAGACT_FORM) {
3017
form = t->controller;
3021
if(itype > INP_SUBMIT) {
3022
setError(MSG_NoButton);
3026
if(!form && itype != INP_BUTTON) {
3027
setError(MSG_NotInForm);
3031
if(t && tagHandler(t->seqno, "onclick")) {
3033
runningError(itype ==
3034
INP_BUTTON ? MSG_NJNoAction : MSG_NJNoOnclick);
3036
rc = handlerGo(t->jv, "onclick");
3045
if(itype == INP_BUTTON) {
3046
if(!handlerPresent(t->jv, "onclick")) {
3047
setError(MSG_ButtonNoJS);
3053
if(itype == INP_RESET) {
3054
/* Before we reset, run the onreset code */
3055
if(t && tagHandler(form->seqno, "onreset")) {
3057
runningError(MSG_NJNoReset);
3059
rc = handlerGo(form->jv, "onreset");
3071
/* Before we submit, run the onsubmit code */
3072
if(t && tagHandler(form->seqno, "onsubmit")) {
3074
runningError(MSG_NJNoSubmit);
3076
rc = handlerGo(form->jv, "onsubmit");
3085
action = form->href;
3086
/* But we defer to the java variable */
3088
const char *jh = get_property_url(form->jv, true);
3089
if(jh && (!action || !stringEqual(jh, action))) {
3090
/* Tie action to the form tag, to plug a small memory leak */
3092
form->href = resolveURL(getBaseHref(form->seqno), jh);
3093
action = form->href;
3097
/* if no action, the default is the current location */
3099
action = getBaseHref(form->seqno);
3103
setError(MSG_FormNoURL);
3107
debugPrint(2, "* %s", action);
3109
prot = getProtURL(action);
3111
setError(MSG_FormBadURL);
3115
if(stringEqualCI(prot, "javascript")) {
3117
setError(MSG_NJNoForm);
3120
javaParseExecute(form->jv, action, 0, 0);
3125
form->bymail = false;
3126
if(stringEqualCI(prot, "mailto")) {
3127
if(!validAccount(localAccount))
3129
form->bymail = true;
3130
} else if(stringEqualCI(prot, "http")) {
3132
setError(MSG_BecameInsecure);
3135
} else if(!stringEqualCI(prot, "https")) {
3136
setError(MSG_SubmitProtBad, prot);
3140
post = initString(&l);
3141
stringAndString(&post, &l, action);
3142
section = strchr(post, '#');
3144
i_printf(MSG_SectionIgnored, section);
3148
section = strpbrk(post, "?\1");
3150
if(*section == '\1' || !(form->bymail | form->post)) {
3152
"the url already specifies some data, which will be overwritten by the data in this form");
3158
stringAndChar(&post, &l, (form->post ? '\1' : '?'));
3161
if(!formSubmit(form, t, &post, &l)) {
3166
debugPrint(3, "%s %s", form->post ? "post" : "get", post + actlen);
3168
/* Handle the mail method here and now. */
3170
char *addr, *subj, *q;
3171
const char *tolist[2], *atlist[2];
3172
const char *name = form->name;
3173
int newlen = l - actlen; /* the new string could be longer than post */
3174
decodeMailURL(action, &addr, &subj, 0);
3178
newlen += 9; /* subject: \n */
3180
newlen += strlen(subj);
3182
newlen += 11 + (name ? strlen(name) : 1);
3183
++newlen; /* null */
3184
++newlen; /* encodeAttachment might append another nl */
3185
q = allocMem(newlen);
3187
sprintf(q, "subject:%s\n", subj);
3189
sprintf(q, "subject:html form(%s)\n", name ? name : "?");
3190
strcpy(q + strlen(q), post + actlen);
3192
i_printf(MSG_MailSending, addr);
3194
rc = sendMail(localAccount, tolist, q, -1, atlist, 0, false);
3196
i_puts(MSG_MailSent);
3204
*post_string = post;
3208
/* I don't have any reverse pointers, so I'm just going to scan the list */
3209
static struct htmlTag *
3210
tagFromJavaVar(void *v)
3212
struct htmlTag **list = cw->tags;
3215
i_printfExit(MSG_NullListInform);
3220
runningError(MSG_LostTag);
3222
} /* tagFromJavaVar */
3224
/* Javascript has changed an input field */
3226
javaSetsTagVar(void *v, const char *val)
3230
t = tagFromJavaVar(v);
3233
/* ok, we found it */
3234
if(t->itype == INP_HIDDEN || t->itype == INP_RADIO) {
3237
if(t->itype == INP_TA) {
3238
runningError(MSG_JSTextarea);
3241
updateFieldInBuffer(t->seqno, val, parsePage ? 0 : 2, false);
3242
} /* javaSetsTagVar */
3244
/* Return false to stop javascript, due to a url redirect */
3246
javaSubmitsForm(void *v, bool reset)
3253
t = tagFromJavaVar(v);
3262
rc = infPush(t->seqno, &post);
3267
gotoLocation(post, 0, false);
3268
} /* javaSubmitsForm */
3271
javaOpensWindow(const char *href, const char *name)
3277
if(!href || !*href) {
3278
browseError(MSG_JSBlankWindow);
3282
copy = cloneString(href);
3284
r = resolveURL(getBaseHref(-1), copy);
3287
gotoLocation(r, 0, false);
3294
/* I'll assume this is more helpful than the name of the window */
3297
toPreamble(t->seqno, "Popup", 0, name);
3298
} /* javaOpensWindow */
3301
javaSetsTimeout(int n, const char *jsrc, void *to, bool isInterval)
3303
struct htmlTag *t = newTag("a");
3307
strcpy(timedesc, (isInterval ? "Interval" : "Timer"));
3308
l = strlen(timedesc);
3310
sprintf(timedesc + l, " %d", n / 1000);
3312
sprintf(timedesc + l, " %dms", n);
3315
t->href = cloneString("#");
3316
toPreamble(t->seqno, timedesc, jsrc, 0);
3317
} /* javaSetsTimeout */