1 /* html.c
2 * Parse html tags.
3 * This file is part of the edbrowse project, released under GPL.
4 */
5
6 #include "eb.h"
7
8 #ifdef _MSC_VER // sleep(secs) macro
9 #define SLEEP(a) Sleep(a * 1000)
10 extern int gettimeofday(struct timeval *tp, void *tzp); // from tidys.lib
11 #else // !_MSC_VER
12 #define SLEEP sleep
13 #endif // _MSC_VER y/n
14
15 // OSX has no pthread_tryjoin_np, so we can't do our
16 // asynchronous timers under OSX, which is really no great loss.
17 #if defined(__APPLE__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__DragonFly__)
18 #define pthread_tryjoin_np pthread_join
19 #endif
20
21 uchar browseLocal;
22 bool showHover, doColors;
23
24 static jsobjtype js_reset, js_submit;
25 static const int asyncTimer = 250;
26
tagHandler(int seqno,const char * name)27 bool tagHandler(int seqno, const char *name)
28 {
29 Tag *t = tagList[seqno];
30 /* check the htnl tag attributes first */
31 if (t->onclick && stringEqual(name, "onclick"))
32 return true;
33 if (t->onsubmit && stringEqual(name, "onsubmit"))
34 return true;
35 if (t->onreset && stringEqual(name, "onreset"))
36 return true;
37 if (t->onchange && stringEqual(name, "onchange"))
38 return true;
39
40 if (!t->jv)
41 return false;
42 if (!isJSAlive)
43 return false;
44 if (!handlerPresent(cf, t->jv, name))
45 return false;
46
47 if (stringEqual(name, "onclick"))
48 t->onclick = true;
49 if (stringEqual(name, "onsubmit"))
50 t->onsubmit = true;
51 if (stringEqual(name, "onreset"))
52 t->onreset = true;
53 if (stringEqual(name, "onchange"))
54 t->onchange = true;
55 return true;
56 } /* tagHandler */
57
58 static void formReset(const Tag *form);
59
60 /*********************************************************************
61 This function was originally written to incorporate any strings generated by
62 document.write(), and it still does that,
63 but now it does much more.
64 It handles any side effects that occur from running js.
65 innerHTML tags generated, form input values set, timers,
66 form.reset(), form.submit(), document.location = url, etc.
67 Every js activity should start with jSyncup() and end with jSideEffects().
68 WARNING: this routine mucks with cf, so you need to set it afterwards,
69 the button being pushed or the onclick code or whatever frame is appropriate.
70 *********************************************************************/
71
jSideEffects(void)72 void jSideEffects(void)
73 {
74 if (!cw->browseMode || !isJSAlive)
75 return;
76 debugPrint(4, "jSideEffects starts");
77 runScriptsPending(false);
78 cw->mustrender = true;
79 rebuildSelectors();
80 debugPrint(4, "jSideEffects ends");
81 } /* jSideEffects */
82
locateOptionByName(const Tag * sel,const char * name,int * pmc,bool exact)83 static Tag *locateOptionByName(const Tag *sel,
84 const char *name, int *pmc,
85 bool exact)
86 {
87 Tag *t, *em = 0, *pm = 0;
88 int pmcount = 0; /* partial match count */
89 const char *s;
90
91 for (t = cw->optlist; t; t = t->same) {
92 if (t->controller != sel)
93 continue;
94 if (!(s = t->textval))
95 continue;
96 if (stringEqualCI(s, name)) {
97 em = t;
98 continue;
99 }
100 if (exact)
101 continue;
102 if (strstrCI(s, name)) {
103 pm = t;
104 ++pmcount;
105 }
106 }
107 if (em)
108 return em;
109 if (pmcount == 1)
110 return pm;
111 *pmc = (pmcount > 0);
112 return 0;
113 } /* locateOptionByName */
114
locateOptionByNum(const Tag * sel,int n)115 static Tag *locateOptionByNum(const Tag *sel, int n)
116 {
117 Tag *t;
118 int cnt = 0;
119
120 for (t = cw->optlist; t; t = t->same) {
121 if (t->controller != sel)
122 continue;
123 if (!t->textval)
124 continue;
125 ++cnt;
126 if (cnt == n)
127 return t;
128 }
129 return 0;
130 } /* locateOptionByNum */
131
132 static bool
locateOptions(const Tag * sel,const char * input,char ** disp_p,char ** val_p,bool setcheck)133 locateOptions(const Tag *sel, const char *input,
134 char **disp_p, char **val_p, bool setcheck)
135 {
136 Tag *t;
137 char *disp, *val;
138 int disp_l, val_l;
139 int len = strlen(input);
140 int n, pmc;
141 const char *s, *e; /* start and end of an option */
142 char *iopt; /* individual option */
143 Frame *f = sel->f0;
144
145 iopt = (char *)allocMem(len + 1);
146 disp = initString(&disp_l);
147 val = initString(&val_l);
148
149 if (setcheck) {
150 /* Uncheck all existing options, then check the ones selected. */
151 if (sel->jv && allowJS)
152 set_property_number(f, sel->jv, "selectedIndex", -1);
153 for (t = cw->optlist; t; t = t->same) {
154 if (t->controller == sel && t->textval) {
155 t->checked = false;
156 if (t->jv && allowJS)
157 set_property_bool(f, t->jv, "selected", false);
158 }
159 }
160 }
161
162 s = input;
163 while (*s) {
164 e = 0;
165 if (sel->multiple)
166 e = strchr(s, ',');
167 if (!e)
168 e = s + strlen(s);
169 len = e - s;
170 strncpy(iopt, s, len);
171 iopt[len] = 0;
172 s = e;
173 if (*s == ',')
174 ++s;
175
176 t = locateOptionByName(sel, iopt, &pmc, true);
177 if (!t) {
178 n = stringIsNum(iopt);
179 if (n >= 0)
180 t = locateOptionByNum(sel, n);
181 }
182 if (!t)
183 t = locateOptionByName(sel, iopt, &pmc, false);
184 if (!t) {
185 if (n >= 0)
186 setError(MSG_XOutOfRange, n);
187 else
188 setError(pmc + MSG_OptMatchNone, iopt);
189 /* This should never happen when we're doing a set check */
190 if (setcheck) {
191 runningError(MSG_OptionSync, iopt);
192 continue;
193 }
194 goto fail;
195 }
196
197 if (val_p) {
198 if (*val)
199 stringAndChar(&val, &val_l, '\1');
200 stringAndString(&val, &val_l, t->value);
201 }
202
203 if (disp_p) {
204 if (*disp)
205 stringAndChar(&disp, &disp_l, ',');
206 stringAndString(&disp, &disp_l, t->textval);
207 }
208
209 if (setcheck) {
210 t->checked = true;
211 if (t->jv && allowJS) {
212 set_property_bool(f, t->jv, "selected", true);
213 if (sel->jv && allowJS)
214 set_property_number(f, sel->jv,
215 "selectedIndex",
216 t->lic);
217 }
218 }
219 } /* loop over multiple options */
220
221 if (val_p)
222 *val_p = val;
223 if (disp_p)
224 *disp_p = disp;
225 free(iopt);
226 return true;
227
228 fail:
229 free(iopt);
230 nzFree(val);
231 nzFree(disp);
232 if (val_p)
233 *val_p = 0;
234 if (disp_p)
235 *disp_p = 0;
236 return false;
237 } /* locateOptions */
238
239 /*********************************************************************
240 Sync up the javascript variables with the input fields.
241 This is required before running any javascript, e.g. an onclick function.
242 After all, the input fields may have changed.
243 You may have changed the last name from Flintstone to Rubble.
244 This has to propagate down to the javascript strings in the DOM.
245 This is quick and stupid; I just update everything.
246 Most of the time I'm setting the strings to what they were before;
247 that's the way it goes.
248 *********************************************************************/
249
jSyncup(bool fromtimer)250 void jSyncup(bool fromtimer)
251 {
252 Tag *t;
253 int itype, j, cx;
254 char *value, *cxbuf;
255 Frame *f;
256
257 if (!cw->browseMode)
258 return; /* not necessary */
259 if (cw->sank)
260 return; /* already done */
261 cw->sank = true;
262 if (!isJSAlive)
263 return;
264 debugPrint(4, "jSyncup starts");
265 if (!fromtimer)
266 cw->nextrender = 0;
267
268 for (t = cw->inputlist; t; t = t->same) {
269 itype = t->itype;
270 if (itype <= INP_HIDDEN)
271 continue;
272
273 /*********************************************************************
274 You could change input fields in several frames, and each item should be
275 passed down to its corresponding js context.
276 This line sets the frame, then we're ready to roll.
277 *********************************************************************/
278 f = t->f0;
279
280 if (itype >= INP_RADIO) {
281 int checked = fieldIsChecked(t->seqno);
282 if (checked < 0)
283 continue;
284 t->checked = checked;
285 set_property_bool(f, t->jv, "checked", checked);
286 continue;
287 }
288
289 value = getFieldFromBuffer(t->seqno);
290 /* If that line has been deleted from the user's buffer,
291 * indicated by value = 0,
292 * then don't do anything. */
293 if (!value)
294 continue;
295
296 if (itype == INP_SELECT) {
297 /* set option.selected in js based on the option(s) in value */
298 locateOptions(t, (value ? value : t->value), 0, 0,
299 true);
300 if (value) {
301 nzFree(t->value);
302 t->value = value;
303 }
304 continue;
305 }
306
307 if (itype == INP_TA) {
308 if (!value) {
309 set_property_string(f, t->jv, "value", 0);
310 continue;
311 }
312 /* Now value is just <buffer 3>, which is meaningless. */
313 nzFree(value);
314 cx = t->lic;
315 if (!cx)
316 continue;
317 // unfoldBuffer could fail if we have quit that session.
318 if (!unfoldBuffer(cx, false, &cxbuf, &j))
319 continue;
320 set_property_string(f, t->jv, "value", cxbuf);
321 nzFree(cxbuf);
322 continue;
323 }
324
325 if (value) {
326 set_property_string(f, t->jv, "value", value);
327 nzFree(t->value);
328 t->value = value;
329 }
330 } /* loop over tags */
331
332 debugPrint(4, "jSyncup ends");
333 } /* jSyncup */
334
jClearSync(void)335 void jClearSync(void)
336 {
337 if (cw->browseMode) {
338 cw->sank = false;
339 return;
340 }
341 /* when we are able to jSyncup windows other than the foreground window,
342 * which we can't do yet, then the rest of this will make sense. */
343 #if 0
344 for (cx = 1; cx <= maxSession; ++cx) {
345 w = sessionList[cx].lw;
346 while (w) {
347 w->sank = false;
348 w = w->prev;
349 }
350 }
351 #endif
352 } /* jClearSync */
353
354 /*********************************************************************
355 This function is called for a new web page, by http refresh,
356 or by document.location = new_url, or by new Window().
357 If delay is 0 or less then the action should happen now.
358 The refresh parameter means replace the current page.
359 This is false only if js creates a new window, which should stack up on top of the old.
360 *********************************************************************/
361
362 char *newlocation;
363 int newloc_d; /* possible delay */
364 bool newloc_r; /* replace the buffer */
365 Frame *newloc_f; /* frame calling for new web page */
366 bool js_redirects;
gotoLocation(char * url,int delay,bool rf)367 static void gotoLocation(char *url, int delay, bool rf)
368 {
369 if (!allowRedirection) {
370 debugPrint(1, "javascript redirection disabled: %s", url);
371 nzFree(url);
372 return;
373 }
374 if (newlocation && delay >= newloc_d) {
375 nzFree(url);
376 return;
377 }
378 nzFree(newlocation);
379 newlocation = url;
380 newloc_d = delay;
381 newloc_r = rf;
382 newloc_f = cf;
383 if (!delay)
384 js_redirects = true;
385 } /* gotoLocation */
386
387 /* helper function for meta tag */
htmlMetaHelper(Tag * t)388 void htmlMetaHelper(Tag *t)
389 {
390 char *name;
391 const char *content, *heq;
392 char **ptr;
393 char *copy = 0;
394
395 /* if we're generating a cookie, we better get the frame right,
396 * because that's the url that the cookie belongs to.
397 * I think the frame is correct anyways, because we are parsing html,
398 * but just to be safe ... */
399 cf = t->f0;
400
401 name = t->name;
402 content = attribVal(t, "content");
403 copy = cloneString(content);
404 heq = attribVal(t, "http-equiv");
405
406 if (heq && content) {
407 bool rc;
408 int delay;
409
410 /* It's not clear if we should process the http refresh command
411 * immediately, the moment we spot it, or if we finish parsing
412 * all the html first.
413 * Does it matter? It might.
414 * A subsequent meta tag could use http-equiv to set a cooky,
415 * and we won't see that cooky if we jump to the new page right now.
416 * And there's no telling what subsequent javascript might do.
417 * So I'm going to postpone the refresh until everything is parsed.
418 * Bear in mind, we really don't want to refresh if we're working
419 * on a local file. */
420
421 if (stringEqualCI(heq, "Set-Cookie")) {
422 rc = receiveCookie(cf->fileName, content);
423 debugPrint(3, rc ? "jar" : "rejected");
424 }
425
426 if (allowRedirection && !browseLocal
427 && stringEqualCI(heq, "Refresh")) {
428 if (parseRefresh(copy, &delay)) {
429 char *newcontent;
430 unpercentURL(copy);
431 newcontent = resolveURL(cf->hbase, copy);
432 gotoLocation(newcontent, delay, true);
433 }
434 }
435 }
436
437 if (name) {
438 ptr = 0;
439 if (stringEqualCI(name, "description"))
440 ptr = &cw->htmldesc;
441 if (stringEqualCI(name, "keywords"))
442 ptr = &cw->htmlkey;
443 if (ptr && !*ptr && content) {
444 stripWhite(copy);
445 *ptr = copy;
446 copy = 0;
447 }
448 }
449
450 nzFree(copy);
451 } /* htmlMetaHelper */
452
453 /* pre is the predecoration from edbrowse-js, if appropriate */
runGeneratedHtml(Tag * t,const char * h)454 static void runGeneratedHtml(Tag *t, const char *h)
455 {
456 int l = cw->numTags;
457
458 if (t)
459 debugPrint(4, "parse under %s %d", t->info->name, t->seqno);
460 else
461 debugPrint(4, "parse under top");
462 debugPrint(4, "Generated {%s}", h);
463
464 html2nodes(h, false);
465 htmlGenerated = true;
466 htmlNodesIntoTree(l, t);
467 prerender(false);
468 decorate(0);
469 } /* runGeneratedHtml */
470
471 /* helper function to prepare an html script.
472 * steps: 1 parsed as html, 2 decorated with a coresponding javascript object
473 * 3 downloading in background (not yet implemented),
474 * 4 data fetched and in the js world and possibly deminimized,
475 * 5 script has run, 6 script could not run. */
476
prepareScript(Tag * t)477 void prepareScript(Tag *t)
478 {
479 const char *js_file = "generated";
480 char *js_text = 0;
481 const char *filepart;
482 char *b;
483 int blen;
484 Frame *f = t->f0;
485
486 if (intFlag)
487 goto fail;
488
489 /* As per the starting line number, we cant distinguish between
490 * <script> foo </script> and
491 * <script>
492 * foo
493 * </script>
494 * so make a guess towards the first form, knowing we could be off by 1.
495 * Just leave it at t->js_ln */
496 if (f->fileName && !t->scriptgen)
497 js_file = f->fileName;
498
499 if (t->jv) {
500 // js might have set, or changed, the source url.
501 char *new_url = get_property_url(f, t->jv, false);
502 if (new_url && *new_url) {
503 if (t->href && !stringEqual(t->href, new_url))
504 debugPrint(3, "js replaces script %s with %s",
505 t->href, new_url);
506 nzFree(t->href);
507 t->href = new_url;
508 }
509 t->async = get_property_bool(f, t->jv, "async");
510 // A side effect of tidy + edbrowse is that the text of the script is a
511 // childNode of script, but I don't think it should be.
512 if (t->firstchild && t->firstchild->action == TAGACT_TEXT)
513 run_function_onearg(f, t->jv, "removeChild",
514 t->firstchild->jv);
515 }
516
517 if (t->href) { /* fetch the javascript page */
518 const char *altsource = 0;
519 bool from_data;
520 if (!javaOK(t->href))
521 goto fail;
522 from_data = isDataURI(t->href);
523 if (!from_data) {
524 altsource = fetchReplace(t->href);
525 if (!altsource)
526 altsource = t->href;
527 }
528 debugPrint(3, "js source %s",
529 !from_data ? altsource : "data URI");
530 if (from_data) {
531 char *mediatype;
532 int data_l = 0;
533 if (parseDataURI(t->href, &mediatype,
534 &js_text, &data_l)) {
535 prepareForBrowse(js_text, data_l);
536 nzFree(mediatype);
537 } else {
538 debugPrint(3,
539 "Unable to parse data URI containing JavaScript");
540 goto fail;
541 }
542 } else if (browseLocal && !isURL(altsource)) {
543 char *h = cloneString(altsource);
544 unpercentString(h);
545 if (!fileIntoMemory(h, &b, &blen)) {
546 if (debugLevel >= 1)
547 i_printf(MSG_GetLocalJS);
548 nzFree(h);
549 goto fail;
550 }
551 js_text = force_utf8(b, blen);
552 if (!js_text)
553 js_text = b;
554 else
555 nzFree(b);
556 nzFree(h);
557 } else {
558 struct i_get g;
559
560 // this has to happen before threads spin off
561 if (!curlActive) {
562 eb_curl_global_init();
563 cookiesFromJar();
564 setupEdbrowseCache();
565 }
566
567 if (down_jsbg && !demin && !uvw
568 && !pthread_create(&t->loadthread, NULL,
569 httpConnectBack2, (void *)t)) {
570 t->js_ln = 1;
571 js_file = altsource;
572 filepart = getFileURL(js_file, true);
573 t->js_file = cloneString(filepart);
574 // stop here and wait for the child process to download
575 t->step = 3;
576 return;
577 }
578 memset(&g, 0, sizeof(g));
579 g.thisfile = f->fileName;
580 g.uriEncoded = true;
581 g.url = t->href;
582 if (!httpConnect(&g)) {
583 if (debugLevel >= 3)
584 i_printf(MSG_GetJS2);
585 goto fail;
586 }
587 if (g.code == 200) {
588 js_text = force_utf8(g.buffer, g.length);
589 if (!js_text)
590 js_text = g.buffer;
591 else
592 nzFree(g.buffer);
593 } else {
594 nzFree(g.buffer);
595 if (debugLevel >= 3)
596 i_printf(MSG_GetJS, g.url, g.code);
597 }
598 }
599 t->js_ln = 1;
600 js_file = (!from_data ? altsource : "data_URI");
601 } else {
602 js_text = t->textval;
603 t->textval = 0;
604 }
605
606 if (!js_text) {
607 // we can only run if javascript has supplied the code forr this scrip,
608 // because we have none to offer.
609 // Such code cannot be deminimized.
610 goto success;
611 }
612 set_property_string(f, t->jv, "text", js_text);
613 nzFree(js_text);
614
615 filepart = getFileURL(js_file, true);
616 t->js_file = cloneString(filepart);
617
618 // deminimize the code if we're debugging.
619 if (demin)
620 run_function_onearg(f, f->winobj, "eb$demin", t->jv);
621 if (uvw)
622 run_function_onearg(f, f->winobj, "eb$watch", t->jv);
623
624 success:
625 t->step = 4;
626 return;
627
628 fail:
629 t->step = 6;
630 } /* prepareScript */
631
is_subframe(Frame * f1,Frame * f2)632 static bool is_subframe(Frame *f1, Frame *f2)
633 {
634 Tag *t;
635 int n;
636 if (f1 == f2)
637 return true;
638 while (true) {
639 for (n = 0; n < cw->numTags; ++n) {
640 t = tagList[n];
641 if (t->f1 == f1)
642 goto found;
643 }
644 return false;
645 found:
646 f1 = t->f0;
647 if (f1 == f2)
648 return true;
649 }
650 }
651
652 /*********************************************************************
653 Run pending scripts, and perform other actions that have been queued up by javascript.
654 This includes document.write, linkages, perhaps even form.submit.
655 Things stop however if we detect document.location = new_url,
656 i.e. a page replacement, as indicated by the newlocation variable being set.
657 The old page doesn't matter any more.
658 I run the scripts linked to the current frame.
659 That way the scripts in a subframe will run, then return, then the scripts
660 in the parent frame pick up where they left off.
661 The algorithm for which sripts to run when is far from obvious,
662 and nowhere documented.
663 I had to write several contrived web pages and run them through chrome
664 and firefox and document the results.
665
666 1. Scripts that come from the internet (src=url) are different from
667 inline scripts, i.e. those that are part of the home page, or generated
668 dynamically with s.text set.
669 An internet script is loaded, that is, fetched from the internet and then run,
670 and after that, it's onload handler is run.
671 An inline script does not run its onload handler, even if it has one.
672
673 2. <style> is inline, but <link href=url rel=stylesheet> is internet.
674 As above, onload code is run after an internet css page is fetched.
675 This is rather asynchronous, relative to the other scripts.
676 Just run the onload code when you can.
677
678 3. All the scripts on the home page run in sequence.
679 Each internet script must fetch and run before the next script runs.
680 Of course all the internet scripts can download in parallel, to save time,
681 and I do that if down_jsbg is true,
682 but still, we have to execute them in order.
683
684 4. A script could have async set, and in theory I could
685 skip that one and do the next one if it is available (postpone), or even do the
686 async script in another thread, but I can't, because duktape is not threadsafe,
687 as is clearly documented.
688 So I allow for postponement, that is, two passes,
689 the first pass runs scripts in order and skips async scripts,
690 the second pass runs the async scripts.
691 Pass 2 runs the async scripts in order, and it doesn't have to, but it's
692 the easiest way to go, and how often do we have several async scripts,
693 some ready several seconds before others? Not very often.
694
695 5. These scripts can generate other scripts, which run in the next wave.
696 However, if the generated script is inline, not from the internet,
697 it runs right now.
698 The first script pauses, runs the second script, then resumes.
699 I demonstrated this in my contrived web page,
700 but not sure it ever happens in the real world.
701 If your second script sets s.text = "some code here",
702 then why not embed that code in the first script and be done with it?
703 So I haven't gone to the bother of implementing this.
704 All generated scripts, inline and internet, run later.
705 But if we wanted to implement this, you can probably follow the pattern
706 set by URL and several other classes.
707 Create a member text$2, that's where the code lives.
708 A getter returns text$2 when you ask for text.
709 A setter runs the code through eval(),
710 then stores it in text$2 for future reference.
711 The inline script is executed now.
712 All the C code deals with text$2 so there are no unintended side effects.
713 So it's not too hard I suppose, but I haven't seen the need yet.
714
715 6. DOMContentLoaded is a strange event.
716 It fires when the first wave of scripts is complete, but before the second wave or any onload handlers etc.
717 The startbrowse argument tells runScriptsPending() we are calling it
718 because a new page is being browsed.
719 It should dispatch DOMContentLoaded after the first scripts have run.
720
721 7. Generated scripts do not run unless and until they are connected to the tree.
722 If they are connected later, that is when they run.
723 This is true of both inline and internet.
724 The first is problematic, because my implementation, in 5,
725 that just calls eval(s.text) on a setter,
726 would run all the time, whether the new script was connected or not.
727 So my 5 implementation would fix one problem and create another.
728 Not sure what to do about that.
729
730 8. Scripts are run in creation order, not in tree order.
731 Or maybe the order is arbitrary and not prescribed,
732 in which case creation order is fine.
733 That is easiest, and it's what I do.
734 *********************************************************************/
735
runScriptsPending(bool startbrowse)736 void runScriptsPending(bool startbrowse)
737 {
738 Tag *t, *up;
739 char *js_file;
740 const char *a;
741 int ln;
742 bool change, async;
743 jsobjtype v;
744 Frame *f, *save_cf = cf;
745
746 if (newlocation && newloc_r)
747 return;
748
749 // Not sure where document.write objects belong.
750 // For now I'm putting them under body.
751 // Each write corresponds to the frame containing document.write.
752 for (f = &(cw->f0); f; f = f->next) {
753 if (!f->dw)
754 continue;
755 cf = f;
756 stringAndString(&cf->dw, &cf->dw_l, "</body>\n");
757 runGeneratedHtml(cf->bodytag, cf->dw);
758 nzFree(cf->dw);
759 cf->dw = 0;
760 cf->dw_l = 0;
761 }
762
763 top:
764 change = false;
765
766 for (t = cw->scriptlist; t; t = t->same) {
767 if (t->dead || !t->jv || t->step >= 3)
768 continue;
769
770 /*********************************************************************
771 Scripts do not run unless connected to the tree; see point 7
772 in the earlier comments.
773 Climb up until we reach HEAD or BODY.
774 If neither of these, then don't run the script, and in fact it's probably
775 unsafe to prepare it.
776 1. Maybe it isn't ready, which is why it isn't linked into the tree.
777 Maybe script.src is still being constructed.
778 We should skip it and try again later.
779 2. Maybe it was just an exercise, and will never be used.
780 We move into prepareScript(), and set some property of the script object.
781 This trigggers garbage collection, and the object goes away.
782 Our tag is marked dead in response, but we don't check for that in every step
783 of preparation and execution.
784 The next time we try to use this object in any way, it blows up.
785 So it is best to skate past a script that is not linked into the tree.
786 We skip past it here, and it isn't prepared, so it won't execute later.
787 *********************************************************************/
788 for(up = t; up; up = up->parent)
789 if(up->action == TAGACT_HEAD || up->action == TAGACT_BODY)
790 break;
791 if (!up)
792 continue;
793
794 cf = t->f0;
795 prepareScript(t);
796 // step will now be 3, load in background, 4, loaded, or 6, failure.
797 }
798
799 async = false;
800 passes:
801
802 for (t = cw->scriptlist; t; t = t->same) {
803 if (t->dead || !t->jv || t->step >= 5 || t->step <= 2 || t->async != async)
804 continue;
805 cf = t->f0;
806 if (!is_subframe(cf, save_cf))
807 continue;
808 if (intFlag) {
809 t->step = 6;
810 continue;
811 }
812
813 if (async && asyncTimer && down_jsbg && cw->browseMode) {
814 if (!t->intimer) {
815 scriptSetsTimeout(t);
816 t->intimer = true;
817 }
818 continue;
819 }
820
821 if (t->step == 3) {
822 // waiting for background process to load
823 pthread_join(t->loadthread, NULL);
824 if (!t->loadsuccess) {
825 if (debugLevel >= 3)
826 i_printf(MSG_GetJS, t->href, t->hcode);
827 t->step = 6;
828 continue;
829 }
830 set_property_string(cf, t->jv,
831 (t->inxhr ? "$entire" : "text"),
832 t->value);
833 nzFree(t->value);
834 t->value = 0;
835 t->step = 4; // loaded
836 }
837
838 t->step = 5; // now running the script
839
840 if (t->inxhr) {
841 // xhr looks like an asynchronous script before browse
842 char *gc_name;
843 jsobjtype xo = t->jv; // xhr object
844 run_function_bool(cf, xo, "parseResponse");
845 /*********************************************************************
846 Ok this is subtle. I put it on a script tag, and t.jv.onload exists!
847 That is the function that is run by xhr.
848 So runOnload() comes along and runs it again, unless we do something.
849 I will disconnect here, and also check for inxhr in runOnload().
850 *********************************************************************/
851 disconnectTagObject(t);
852 t->dead = true;
853 // allow garbage collection to recapture xo if it wants to
854 gc_name = get_property_string(cf, xo, "backlink");
855 if (gc_name)
856 delete_property(cf, cf->winobj, gc_name);
857 goto afterscript;
858 }
859
860 // If no language is specified, javascript is default.
861 a = get_property_string(cf, t->jv, "language");
862 if (a && *a && (!memEqualCI(a, "javascript", 10) || isalphaByte(a[10]))) {
863 debugPrint(3, "script tag %d language %s not executed", t->seqno, a);
864 cnzFree(a);
865 goto afterscript;
866 }
867 cnzFree(a);
868 // Also reject a script if a type is specified and it is not JS.
869 // For instance, some JSON pairs in script tags on amazon.com
870 a = get_property_string(cf, t->jv, "type");
871 if (a && *a && (!memEqualCI(a, "javascript", 10))
872 && (!memEqualCI(a, "text/javascript", 15))) {
873 debugPrint(3, "script tag %d type %s not executed", t->seqno, a);
874 cnzFree(a);
875 goto afterscript;
876 }
877 cnzFree(a);
878
879 js_file = t->js_file;
880 if (!js_file)
881 js_file = "generated";
882 if (cf != save_cf)
883 debugPrint(4, "running script at a lower frame %s",
884 js_file);
885 ln = t->js_ln;
886 if (!ln)
887 ln = 1;
888 debugPrint(3, "exec %s at %d", js_file, ln);
889 /* if script is in the html it usually begins on the next line, so increment,
890 * and hope the error messages line up. */
891 if (ln > 1)
892 ++ln;
893 set_property_object(cf, cf->docobj, "currentScript", t->jv);
894 jsRunData(cf, t->jv, js_file, ln);
895
896 // I don't know when to run the onload function, before or after the script.
897 // And should we run it for a json script or other non js script?
898 // If so that's a problem, cause we already jumped ahead to afterscript:
899
900 if (t->js_file && !isDataURI(t->href) && handlerPresent(cf, t->jv, "onload"))
901 run_event_bool(cf, t->jv, "script", "onload");
902 delete_property(cf, cf->docobj, "currentScript");
903 debugPrint(3, "exec complete");
904
905 afterscript:
906 if (newlocation && newloc_r) {
907 cf = save_cf;
908 return;
909 }
910
911 /* look for document.write from this script */
912 if (cf->dw) {
913 stringAndString(&cf->dw, &cf->dw_l, "</body>\n");
914 runGeneratedHtml(t, cf->dw);
915 nzFree(cf->dw);
916 cf->dw = 0;
917 cf->dw_l = 0;
918 run_function_onearg(cf, cf->winobj, "eb$uplift", t->jv);
919 }
920
921 change = true;
922 }
923
924 // after each pass, see if there is a link onload to run.
925 for (t = cw->linklist; t; t = t->same) {
926 if(t->lic == 1 && t->jv && !t->dead) {
927 if(handlerPresent(t->f0, t->jv, "onload")) {
928 run_event_bool(t->f0, t->jv, "link", "onload");
929 change = true;
930 }
931 t->lic = 0;
932 }
933 }
934
935 if (!async) {
936 if(startbrowse)
937 // I think it's ok to use cf here, but let's be safe.
938 run_event_bool(save_cf, save_cf->docobj, "document", "onDOMContentLoaded");
939 startbrowse = false;
940 async = true;
941 goto passes;
942 }
943
944 if (change)
945 goto top;
946
947 if ((v = js_reset)) {
948 js_reset = 0;
949 if ((t = tagFromJavaVar(v)))
950 formReset(t);
951 }
952
953 if ((v = js_submit)) {
954 js_submit = 0;
955 if ((t = tagFromJavaVar(v))) {
956 char *post;
957 bool rc = infPush(t->seqno, &post);
958 if (rc)
959 gotoLocation(post, 0, false);
960 else
961 showError();
962 }
963 }
964
965 cf = save_cf;
966 } /* runScriptsPending */
967
preFormatCheck(int tagno,bool * pretag,bool * slash)968 void preFormatCheck(int tagno, bool * pretag, bool * slash)
969 {
970 const Tag *t;
971 *pretag = *slash = false;
972 if (tagno >= 0 && tagno < cw->numTags) {
973 t = tagList[tagno];
974 *pretag = (t->action == TAGACT_PRE);
975 *slash = t->slash;
976 }
977 } /* preFormatCheck */
978
979 /* is there a doorway from html to js? */
jsDoorway(void)980 static bool jsDoorway(void)
981 {
982 const Tag *t;
983 int j;
984 for (j = 0; j < cw->numTags; ++j) {
985 t = tagList[j];
986 if (t->doorway)
987 return true;
988 }
989 debugPrint(3, "no js doorway");
990 return false;
991 } /* jsDoorway */
992
htmlParse(char * buf,int remote)993 char *htmlParse(char *buf, int remote)
994 {
995 char *a, *newbuf;
996
997 if (tagList)
998 i_printfExit(MSG_HtmlNotreentrant);
999 if (remote >= 0)
1000 browseLocal = !remote;
1001 initTagArray();
1002 cf->baseset = false;
1003 cf->hbase = cloneString(cf->fileName);
1004
1005 /* call the tidy parser to build the html nodes */
1006 html2nodes(buf, true);
1007 nzFree(buf);
1008 htmlGenerated = false;
1009 htmlNodesIntoTree(0, NULL);
1010 prerender(false);
1011
1012 /* if the html doesn't use javascript, then there's
1013 * no point in generating it.
1014 * This is typical of generated html, from pdf for instance,
1015 * or the html that is in email. */
1016 if (cf->cx && !jsDoorway())
1017 freeJavaContext(cf);
1018
1019 if (isJSAlive) {
1020 // the "create handlers" messages aren't helpful here.
1021 if (debugEvent && debugLevel >= 3)
1022 set_property_bool(cf, cf->winobj, "eventDebug", false);
1023 decorate(0);
1024 if (debugEvent && debugLevel >= 3)
1025 set_property_bool(cf, cf->winobj, "eventDebug", true);
1026 set_basehref(cf->hbase);
1027 run_function_bool(cf, cf->winobj, "eb$qs$start");
1028 runScriptsPending(true);
1029 runOnload();
1030 runScriptsPending(false);
1031
1032 /*********************************************************************
1033 set_property_string(cf->docobj, "readyState", "complete");
1034 This should work, and does almost every time, except for
1035 https://www.digicatapult.org.uk/
1036 which calls https://ajax.cloudflare.com/cdn-cgi/scripts/7089c43e/cloudflare-static/rocket-loader.min.js
1037 which replaces readyState with a getter and no setter.
1038 If we try to set it using this unprotected duktape call it aborts.
1039 I could add protection around set_property_string, or write a protected version
1040 thereof, and some day I might have to do that,
1041 but this is called only once per browse,
1042 so no impact on performance if we invoke a script,
1043 so for now I'm taking the easy way out.
1044 jsRunScript is protected.
1045 *********************************************************************/
1046 jsRunScript(cf, cf->winobj, "document.readyState='complete'",
1047 "readyState", 1);
1048
1049 run_event_bool(cf, cf->docobj, "document", "onreadystatechange");
1050 run_event_bool(cf, cf->winobj, "window", "onfocus");
1051 run_event_bool(cf, cf->docobj, "document", "onfocus");
1052
1053 runScriptsPending(false);
1054 rebuildSelectors();
1055 }
1056
1057 a = render(0);
1058 debugPrint(6, "|%s|\n", a);
1059 newbuf = htmlReformat(a);
1060 nzFree(a);
1061
1062 return newbuf;
1063 } /* htmlParse */
1064
1065 /* See if there are simple tags like <p> or </font> */
htmlTest(void)1066 bool htmlTest(void)
1067 {
1068 int j, ln;
1069 int cnt = 0;
1070 int fsize = 0; /* file size */
1071 char look[12];
1072 bool firstline = true;
1073
1074 for (ln = 1; ln <= cw->dol; ++ln) {
1075 char *p = (char *)fetchLine(ln, -1);
1076 char c;
1077 int state = 0;
1078
1079 while (isspaceByte(*p) && *p != '\n')
1080 ++p;
1081 if (*p == '\n')
1082 continue; /* skip blank line */
1083 if (firstline && *p == '<') {
1084 /* check for <!doctype and other things */
1085 if (memEqualCI(p + 1, "!doctype", 8))
1086 return true;
1087 if (memEqualCI(p + 1, "?xml", 4))
1088 return true;
1089 if (memEqualCI(p + 1, "!--", 3))
1090 return true;
1091 /* If it starts with <tag, for any tag we recognize,
1092 * we'll call it good. */
1093 for (j = 1; j < 10; ++j) {
1094 if (!isalnumByte(p[j]))
1095 break;
1096 look[j - 1] = p[j];
1097 }
1098 look[j - 1] = 0;
1099 if (j > 1 && (p[j] == '>' || isspaceByte(p[j]))) {
1100 /* something we recognize? */
1101 const struct tagInfo *ti;
1102 for (ti = availableTags; ti->name[0]; ++ti)
1103 if (stringEqualCI(ti->name, look))
1104 return true;
1105 } /* leading tag */
1106 } /* leading < */
1107 firstline = false;
1108
1109 /* count tags through the buffer */
1110 for (j = 0; (c = p[j]) != '\n'; ++j) {
1111 if (state == 0) {
1112 if (c == '<')
1113 state = 1;
1114 continue;
1115 }
1116 if (state == 1) {
1117 if (c == '/')
1118 state = 2;
1119 else if (isalphaByte(c))
1120 state = 3;
1121 else
1122 state = 0;
1123 continue;
1124 }
1125 if (state == 2) {
1126 if (isalphaByte(c))
1127 state = 3;
1128 else
1129 state = 0;
1130 continue;
1131 }
1132 if (isalphaByte(c))
1133 continue;
1134 if (c == '>')
1135 ++cnt;
1136 state = 0;
1137 }
1138 fsize += j;
1139 } /* loop over lines */
1140
1141 /* we need at least one of these tags every 300 characters.
1142 * And we need at least 4 such tags.
1143 * Remember, you can always override by putting <html> at the top. */
1144 return (cnt >= 4 && cnt * 300 >= fsize);
1145 } /* htmlTest */
1146
1147 /* Show an input field */
infShow(int tagno,const char * search)1148 void infShow(int tagno, const char *search)
1149 {
1150 const Tag *t = tagList[tagno], *v;
1151 const char *s;
1152 int cnt;
1153 bool show;
1154
1155 s = inp_types[t->itype];
1156 if (*s == ' ')
1157 ++s;
1158 printf("%s", s);
1159 if (t->multiple)
1160 i_printf(MSG_Many);
1161 if (t->itype == INP_TEXT && t->lic)
1162 printf("[%d]", t->lic);
1163 if (t->itype_minor != INP_NO_MINOR)
1164 printf(" (%s)", inp_others[t->itype_minor]);
1165 if (t->itype == INP_TA) {
1166 const char *rows = attribVal(t, "rows");
1167 const char *cols = attribVal(t, "cols");
1168 const char *wrap = attribVal(t, "wrap");
1169 if (rows && cols) {
1170 printf("[%sx%s", rows, cols);
1171 if (wrap && stringEqualCI(wrap, "virtual"))
1172 i_printf(MSG_Recommended);
1173 i_printf(MSG_Close);
1174 }
1175 } /* text area */
1176 if (t->name)
1177 printf(" %s", t->name);
1178 nl();
1179 if (t->itype != INP_SELECT)
1180 return;
1181
1182 /* display the options in a pick list */
1183 /* If a search string is given, display the options containing that string. */
1184 cnt = 0;
1185 show = false;
1186 for (v = cw->optlist; v; v = v->same) {
1187 if (v->controller != t)
1188 continue;
1189 if (!v->textval)
1190 continue;
1191 ++cnt;
1192 if (*search && !strstrCI(v->textval, search))
1193 continue;
1194 show = true;
1195 printf("%3d %s\n", cnt, v->textval);
1196 }
1197 if (!show) {
1198 if (!*search)
1199 i_puts(MSG_NoOptions);
1200 else
1201 i_printf(MSG_NoOptionsMatch, search);
1202 }
1203 } /* infShow */
1204
inputDisabled(const Tag * t)1205 static bool inputDisabled(const Tag *t)
1206 {
1207 if (allowJS && t->jv)
1208 return get_property_bool(t->f0, t->jv, "disabled");
1209 return t->disabled;
1210 }
1211
1212 /*********************************************************************
1213 Update an input field in the current edbrowse buffer.
1214 This can be done for one of two reasons.
1215 First, the user has interactively entered a value in the form, such as
1216 i=foobar
1217 In this case fromForm will be set to true.
1218 I need to find the tag in the current buffer.
1219 He just modified it, so it ought to be there.
1220 If it isn't there, print an error and do nothing.
1221 The second case: the value has been changed by form reset,
1222 either the user has pushed the reset button or javascript has called form.reset.
1223 Here fromForm is false.
1224 I'm not sure why js would reset a form before the page was even rendered;
1225 that's the only way the line should not be found,
1226 or perhaps if that section of the web page was deleted.
1227 notify = true causes the line to be printed after the change is made.
1228 Notify true and fromForm false is impossible.
1229 You don't need to be notified as each variable is changed during a reset.
1230 The new line replaces the old, and the old is freed.
1231 This works because undo is disabled in browse mode.
1232 *********************************************************************/
1233
1234 static void
updateFieldInBuffer(int tagno,const char * newtext,bool notify,bool fromForm)1235 updateFieldInBuffer(int tagno, const char *newtext, bool notify, bool fromForm)
1236 {
1237 int ln, n, plen;
1238 char *p, *s, *t, *new;
1239
1240 if (locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
1241 n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s);
1242 new = allocMem(n);
1243 memcpy(new, p, s - p);
1244 strcpy(new + (s - p), newtext);
1245 memcpy(new + strlen(new), t, plen - (t - p));
1246 free(cw->map[ln].text);
1247 cw->map[ln].text = (pst) new;
1248 if (notify)
1249 displayLine(ln);
1250 return;
1251 }
1252
1253 if (fromForm)
1254 i_printf(MSG_NoTagFound, tagno, newtext);
1255 } /* updateFieldInBuffer */
1256
1257 /* Update an input field. */
infReplace(int tagno,const char * newtext,bool notify)1258 bool infReplace(int tagno, const char *newtext, bool notify)
1259 {
1260 const Tag *t = tagList[tagno], *v;
1261 const Tag *form = t->controller;
1262 char *display;
1263 int itype = t->itype;
1264 int itype_minor = t->itype_minor;
1265 int newlen = strlen(newtext);
1266
1267 /* sanity checks on the input */
1268 if (itype <= INP_SUBMIT) {
1269 int b = MSG_IsButton;
1270 if (itype == INP_SUBMIT || itype == INP_IMAGE)
1271 b = MSG_SubmitButton;
1272 if (itype == INP_RESET)
1273 b = MSG_ResetButton;
1274 setError(b);
1275 return false;
1276 }
1277
1278 if (itype == INP_TA) {
1279 setError(MSG_Textarea, t->lic);
1280 return false;
1281 }
1282
1283 if (t->rdonly) {
1284 setError(MSG_Readonly);
1285 return false;
1286 }
1287 if (inputDisabled(t)) {
1288 setError(MSG_Disabled);
1289 return false;
1290 }
1291
1292 if (strchr(newtext, '\n')) {
1293 setError(MSG_InputNewline);
1294 return false;
1295 }
1296
1297 if (itype >= INP_RADIO) {
1298 if ((newtext[0] != '+' && newtext[0] != '-') || newtext[1]) {
1299 setError(MSG_InputRadio);
1300 return false;
1301 }
1302 if (itype == INP_RADIO && newtext[0] == '-') {
1303 setError(MSG_ClearRadio);
1304 return false;
1305 }
1306 }
1307
1308 if (itype == INP_SELECT) {
1309 if (!locateOptions(t, newtext, 0, 0, false))
1310 return false;
1311 locateOptions(t, newtext, &display, 0, false);
1312 updateFieldInBuffer(tagno, display, notify, true);
1313 nzFree(display);
1314 }
1315
1316 if (itype == INP_FILE) {
1317 if (!envFile(newtext, &newtext))
1318 return false;
1319 if (newtext[0] && access(newtext, 4)) {
1320 setError(MSG_FileAccess, newtext);
1321 return false;
1322 }
1323 }
1324
1325 if (itype == INP_TEXT && t->lic && newlen > t->lic) {
1326 setError(MSG_InputLong, t->lic);
1327 return false;
1328 }
1329
1330 if (itype_minor == INP_NUMBER && (*newtext && stringIsNum(newtext) < 0)) {
1331 setError(MSG_NumberExpected);
1332 return false;
1333 }
1334
1335 if (itype_minor == INP_EMAIL && (*newtext && !isEmailAddress(newtext))) {
1336 setError(MSG_EmailInput);
1337 return false;
1338 }
1339
1340 if (itype_minor == INP_URL && (*newtext && !isURL(newtext))) {
1341 setError(MSG_UrlInput);
1342 return false;
1343 }
1344
1345 if (itype == INP_RADIO && form && t->name && *newtext == '+') {
1346 /* clear the other radio button */
1347 for (v = cw->inputlist; v; v = v->same) {
1348 if (v->controller != form)
1349 continue;
1350 if (v->itype != INP_RADIO)
1351 continue;
1352 if (!v->name)
1353 continue;
1354 if (!stringEqual(v->name, t->name))
1355 continue;
1356 if (fieldIsChecked(v->seqno) == true)
1357 updateFieldInBuffer(v->seqno, "-", false, true);
1358 }
1359 }
1360
1361 if (itype != INP_SELECT) {
1362 updateFieldInBuffer(tagno, newtext, notify, true);
1363 }
1364
1365 if (itype >= INP_TEXT) {
1366 jSyncup(false);
1367 cf = t->f0;
1368 if (itype >= INP_RADIO) {
1369 // The change has already been made;
1370 // if onclick returns false, should that have prevented the change??
1371 bubble_event(t, "onclick");
1372 if (js_redirects)
1373 return true;
1374 }
1375 if (itype != INP_SELECT)
1376 bubble_event(t, "oninput");
1377 if (js_redirects)
1378 return true;
1379 bubble_event(t, "onchange");
1380 if (js_redirects)
1381 return true;
1382 jSideEffects();
1383 }
1384
1385 return true;
1386 } /* infReplace */
1387
1388 /*********************************************************************
1389 Reset or submit a form.
1390 This function could be called by javascript, as well as a human.
1391 It must therefore update the js variables and the text simultaneously.
1392 Most of this work is done by resetVar().
1393 To reset a variable, copy its original value, in the html tag,
1394 back to the text buffer, and over to javascript.
1395 *********************************************************************/
1396
resetVar(Tag * t)1397 static void resetVar(Tag *t)
1398 {
1399 int itype = t->itype;
1400 Frame *f = t->f0;
1401 const char *w = t->rvalue;
1402 bool bval;
1403
1404 /* This is a kludge - option looks like INP_SELECT */
1405 if (t->action == TAGACT_OPTION)
1406 itype = INP_SELECT;
1407
1408 if (itype <= INP_SUBMIT)
1409 return;
1410
1411 if (itype >= INP_SELECT && itype != INP_TA) {
1412 bval = t->rchecked;
1413 t->checked = bval;
1414 w = bval ? "+" : "-";
1415 }
1416
1417 if (itype == INP_TA) {
1418 int cx = t->lic;
1419 if (cx)
1420 sideBuffer(cx, w, -1, 0);
1421 } else if (itype != INP_HIDDEN && itype != INP_SELECT)
1422 updateFieldInBuffer(t->seqno, w, false, false);
1423
1424 if ((itype >= INP_TEXT && itype <= INP_FILE) || itype == INP_TA) {
1425 nzFree(t->value);
1426 t->value = cloneString(t->rvalue);
1427 }
1428
1429 if (!t->jv || !allowJS)
1430 return;
1431
1432 if (itype >= INP_RADIO) {
1433 set_property_bool(f, t->jv, "checked", bval);
1434 } else if (itype == INP_SELECT) {
1435 /* remember this means option */
1436 set_property_bool(f, t->jv, "selected", bval);
1437 if (bval && !t->controller->multiple && t->controller->jv)
1438 set_property_number(f, t->controller->jv,
1439 "selectedIndex", t->lic);
1440 } else
1441 set_property_string(f, t->jv, "value", w);
1442 } /* resetVar */
1443
formReset(const Tag * form)1444 static void formReset(const Tag *form)
1445 {
1446 Tag *t;
1447 int i, itype;
1448 char *display;
1449 Frame *f = form->f0;
1450
1451 rebuildSelectors();
1452
1453 for (i = 0; i < cw->numTags; ++i) {
1454 t = tagList[i];
1455 if (t->action == TAGACT_OPTION) {
1456 resetVar(t);
1457 continue;
1458 }
1459
1460 if (t->action != TAGACT_INPUT)
1461 continue;
1462 if (t->controller != form)
1463 continue;
1464 itype = t->itype;
1465 if (itype != INP_SELECT) {
1466 resetVar(t);
1467 continue;
1468 }
1469 if (t->jv && allowJS)
1470 set_property_number(f, t->jv, "selectedIndex", -1);
1471 } /* loop over tags */
1472
1473 /* loop again to look for select, now that options are set */
1474 for (t = cw->inputlist; t; t = t->same) {
1475 if (t->controller != form)
1476 continue;
1477 itype = t->itype;
1478 if (itype != INP_SELECT)
1479 continue;
1480 display = displayOptions(t);
1481 updateFieldInBuffer(t->seqno, display, false, false);
1482 nzFree(t->value);
1483 t->value = display;
1484 /* this should now be the same as t->rvalue, but I guess I'm
1485 * not going to check for that, or take advantage of it. */
1486 } /* loop over tags */
1487
1488 i_puts(MSG_FormReset);
1489 } /* formReset */
1490
1491 /* Fetch a field value (from a form) to post. */
1492 /* The result is allocated */
fetchTextVar(const Tag * t)1493 static char *fetchTextVar(const Tag *t)
1494 {
1495 char *v;
1496
1497 // js must not muck with the value of a file field
1498 if (t->itype != INP_FILE) {
1499 if (t->jv && allowJS)
1500 return get_property_string(t->f0, t->jv, "value");
1501 }
1502
1503 if (t->itype > INP_HIDDEN) {
1504 v = getFieldFromBuffer(t->seqno);
1505 if (v)
1506 return v;
1507 }
1508
1509 /* Revert to the default value */
1510 return cloneString(t->value);
1511 } /* fetchTextVar */
1512
fetchBoolVar(const Tag * t)1513 static bool fetchBoolVar(const Tag *t)
1514 {
1515 int checked;
1516
1517 if (t->jv && isJSAlive)
1518 return get_property_bool(t->f0, t->jv,
1519 (t->action ==
1520 TAGACT_OPTION ? "selected" :
1521 "checked"));
1522
1523 checked = fieldIsChecked(t->seqno);
1524 if (checked < 0)
1525 checked = t->rchecked;
1526 return checked;
1527 } /* fetchBoolVar */
1528
1529 /* Some information on posting forms can be found here.
1530 * http://www.w3.org/TR/REC-html40/interact/forms.html */
1531
1532 static char *pfs; /* post form string */
1533 static int pfs_l;
1534 static const char *boundary;
1535
postDelimiter(char fsep)1536 static void postDelimiter(char fsep)
1537 {
1538 char c = pfs[strlen(pfs) - 1];
1539 if (c == '?' || c == '\1')
1540 return;
1541 if (fsep == '-') {
1542 stringAndString(&pfs, &pfs_l, "--");
1543 stringAndString(&pfs, &pfs_l, boundary);
1544 stringAndChar(&pfs, &pfs_l, '\r');
1545 fsep = '\n';
1546 }
1547 stringAndChar(&pfs, &pfs_l, fsep);
1548 } /* postDelimiter */
1549
1550 static bool
postNameVal(const char * name,const char * val,char fsep,uchar isfile)1551 postNameVal(const char *name, const char *val, char fsep, uchar isfile)
1552 {
1553 char *enc;
1554 const char *ct, *ce; /* content type, content encoding */
1555
1556 if (!name)
1557 name = emptyString;
1558 if (!val)
1559 val = emptyString;
1560 if (!*name && !*val)
1561 return true;
1562
1563 postDelimiter(fsep);
1564 switch (fsep) {
1565 case '&':
1566 enc = encodePostData(name, NULL);
1567 stringAndString(&pfs, &pfs_l, enc);
1568 stringAndChar(&pfs, &pfs_l, '=');
1569 nzFree(enc);
1570 break;
1571
1572 case '\n':
1573 stringAndString(&pfs, &pfs_l, name);
1574 stringAndString(&pfs, &pfs_l, "=\r\n");
1575 break;
1576
1577 case '-':
1578 stringAndString(&pfs, &pfs_l,
1579 "Content-Disposition: form-data; name=\"");
1580 stringAndString(&pfs, &pfs_l, name);
1581 stringAndChar(&pfs, &pfs_l, '"');
1582 /* I'm leaving nl off, in case we need ; filename */
1583 break;
1584 } /* switch */
1585
1586 if (!*val && fsep == '&')
1587 return true;
1588
1589 switch (fsep) {
1590 case '&':
1591 enc = encodePostData(val, NULL);
1592 stringAndString(&pfs, &pfs_l, enc);
1593 nzFree(enc);
1594 break;
1595
1596 case '\n':
1597 stringAndString(&pfs, &pfs_l, val);
1598 stringAndString(&pfs, &pfs_l, eol);
1599 break;
1600
1601 case '-':
1602 if (isfile) {
1603 if (isfile & 2) {
1604 stringAndString(&pfs, &pfs_l, "; filename=\"");
1605 stringAndString(&pfs, &pfs_l, val);
1606 stringAndChar(&pfs, &pfs_l, '"');
1607 }
1608 if (!encodeAttachment(val, 0, true, &ct, &ce, &enc))
1609 return false;
1610 val = enc;
1611 /* remember to free val in this case */
1612 } else {
1613 const char *s;
1614 ct = "text/plain";
1615 /* Anything nonascii makes it 8bit */
1616 ce = "7bit";
1617 for (s = val; *s; ++s)
1618 if (*s < 0) {
1619 ce = "8bit";
1620 break;
1621 }
1622 }
1623 stringAndString(&pfs, &pfs_l, "\r\nContent-Type: ");
1624 stringAndString(&pfs, &pfs_l, ct);
1625 stringAndString(&pfs, &pfs_l,
1626 "\r\nContent-Transfer-Encoding: ");
1627 stringAndString(&pfs, &pfs_l, ce);
1628 stringAndString(&pfs, &pfs_l, "\r\n\r\n");
1629 stringAndString(&pfs, &pfs_l, val);
1630 stringAndString(&pfs, &pfs_l, eol);
1631 if (isfile)
1632 nzFree(enc);
1633 break;
1634 } /* switch */
1635
1636 return true;
1637 } /* postNameVal */
1638
formSubmit(const Tag * form,const Tag * submit)1639 static bool formSubmit(const Tag *form, const Tag *submit)
1640 {
1641 const Tag *t;
1642 int j, itype;
1643 char *name, *dynamicvalue = NULL;
1644 /* dynamicvalue needs to be freed with nzFree. */
1645 const char *value;
1646 char fsep = '&'; /* field separator */
1647 bool rc;
1648 bool bval;
1649
1650 /* js could rebuild an option list then submit the form. */
1651 rebuildSelectors();
1652
1653 if (form->bymail)
1654 fsep = '\n';
1655 if (form->mime) {
1656 fsep = '-';
1657 boundary = makeBoundary();
1658 stringAndString(&pfs, &pfs_l, "`mfd~");
1659 stringAndString(&pfs, &pfs_l, boundary);
1660 stringAndString(&pfs, &pfs_l, eol);
1661 }
1662
1663 for (t = cw->inputlist; t; t = t->same) {
1664 if (t->controller != form)
1665 continue;
1666 itype = t->itype;
1667 if (itype <= INP_SUBMIT && t != submit)
1668 continue;
1669 name = t->name;
1670 if (!name)
1671 name = t->id;
1672
1673 if (t == submit) { /* the submit button you pushed */
1674 int namelen;
1675 char *nx;
1676 if (!name)
1677 continue;
1678 value = t->value;
1679 if (!value || !*value)
1680 value = "Submit";
1681 if (t->itype != INP_IMAGE)
1682 goto success;
1683 namelen = strlen(name);
1684 nx = (char *)allocMem(namelen + 3);
1685 strcpy(nx, name);
1686 strcpy(nx + namelen, ".x");
1687 postNameVal(nx, "0", fsep, false);
1688 nx[namelen + 1] = 'y';
1689 postNameVal(nx, "0", fsep, false);
1690 nzFree(nx);
1691 goto success;
1692 }
1693
1694 if (itype >= INP_RADIO) {
1695 value = t->value;
1696 bval = fetchBoolVar(t);
1697 if (!bval)
1698 continue;
1699 if (!name)
1700 if (value && !*value)
1701 value = 0;
1702 if (itype == INP_CHECKBOX && value == 0)
1703 value = "on";
1704 goto success;
1705 }
1706
1707 if (itype < INP_FILE) {
1708 /* Even a hidden variable can be adjusted by js.
1709 * fetchTextVar allows for this possibility.
1710 * I didn't allow for it in the above, the value of a radio button;
1711 * hope that's not a problem. */
1712 dynamicvalue = fetchTextVar(t);
1713 postNameVal(name, dynamicvalue, fsep, false);
1714 nzFree(dynamicvalue);
1715 dynamicvalue = NULL;
1716 continue;
1717 }
1718
1719 if (itype == INP_TA) {
1720 int cx = t->lic;
1721 char *cxbuf;
1722 int cxlen;
1723 if (cx) {
1724 if (fsep == '-') {
1725 char cxstring[12];
1726 /* do this as an attachment */
1727 sprintf(cxstring, "%d", cx);
1728 if (!postNameVal
1729 (name, cxstring, fsep, 1))
1730 goto fail;
1731 continue;
1732 } /* attach */
1733 if (!unfoldBuffer(cx, true, &cxbuf, &cxlen))
1734 goto fail;
1735 for (j = 0; j < cxlen; ++j)
1736 if (cxbuf[j] == 0) {
1737 setError(MSG_SessionNull, cx);
1738 nzFree(cxbuf);
1739 goto fail;
1740 }
1741 if (j && cxbuf[j - 1] == '\n')
1742 --j;
1743 if (j && cxbuf[j - 1] == '\r')
1744 --j;
1745 cxbuf[j] = 0;
1746 rc = postNameVal(name, cxbuf, fsep, false);
1747 nzFree(cxbuf);
1748 if (rc)
1749 continue;
1750 goto fail;
1751 }
1752
1753 postNameVal(name, 0, fsep, false);
1754 continue;
1755 }
1756
1757 if (itype == INP_SELECT) {
1758 char *display = getFieldFromBuffer(t->seqno);
1759 char *s, *e;
1760 if (!display) { /* off the air */
1761 Tag *v;
1762 /* revert back to reset state */
1763 for (v = cw->optlist; v; v = v->same)
1764 if (v->controller == t)
1765 v->checked = v->rchecked;
1766 display = displayOptions(t);
1767 }
1768 rc = locateOptions(t, display, 0, &dynamicvalue, false);
1769 nzFree(display);
1770 if (!rc)
1771 goto fail; /* this should never happen */
1772 /* option could have an empty value, usually the null choice,
1773 * before you have made a selection. */
1774 if (!*dynamicvalue) {
1775 if (!t->multiple)
1776 postNameVal(name, dynamicvalue, fsep,
1777 false);
1778 continue;
1779 }
1780 /* Step through the options */
1781 for (s = dynamicvalue; *s; s = e) {
1782 char more;
1783 e = 0;
1784 if (t->multiple)
1785 e = strchr(s, '\1');
1786 if (!e)
1787 e = s + strlen(s);
1788 more = *e, *e = 0;
1789 postNameVal(name, s, fsep, false);
1790 if (more)
1791 ++e;
1792 }
1793 nzFree(dynamicvalue);
1794 dynamicvalue = NULL;
1795 continue;
1796 }
1797
1798 if (itype == INP_FILE) { /* the only one left */
1799 dynamicvalue = fetchTextVar(t);
1800 if (!dynamicvalue)
1801 continue;
1802 if (!*dynamicvalue)
1803 continue;
1804 if (!(form->post & form->mime)) {
1805 setError(MSG_FilePost);
1806 nzFree(dynamicvalue);
1807 goto fail;
1808 }
1809 rc = postNameVal(name, dynamicvalue, fsep, 3);
1810 nzFree(dynamicvalue);
1811 dynamicvalue = NULL;
1812 if (rc)
1813 continue;
1814 goto fail;
1815 }
1816
1817 i_printfExit(MSG_UnexSubmitForm);
1818
1819 success:
1820 postNameVal(name, value, fsep, false);
1821 } /* loop over tags */
1822
1823 if (form->mime) { /* the last boundary */
1824 stringAndString(&pfs, &pfs_l, "--");
1825 stringAndString(&pfs, &pfs_l, boundary);
1826 stringAndString(&pfs, &pfs_l, "--\r\n");
1827 }
1828
1829 i_puts(MSG_FormSubmit);
1830 return true;
1831
1832 fail:
1833 return false;
1834 } /* formSubmit */
1835
1836 /*********************************************************************
1837 Push the reset or submit button.
1838 This routine must be reentrant.
1839 You push submit, which calls this routine, which runs the onsubmit code,
1840 which checks the fields and calls form.submit(),
1841 which calls this routine. Happens all the time.
1842 *********************************************************************/
1843
1844 /* jSyncup has been called before we enter this function */
infPush(int tagno,char ** post_string)1845 bool infPush(int tagno, char **post_string)
1846 {
1847 Tag *t = tagList[tagno];
1848 Frame *f = t->f0;
1849 Tag *form;
1850 int itype;
1851 int actlen;
1852 const char *action = 0;
1853 char *section;
1854 const char *prot;
1855 bool rc;
1856
1857 *post_string = 0;
1858
1859 /* If the tag is actually a form, then infPush() was invoked
1860 * by form.submit().
1861 * Revert t back to 0, since there may be multiple submit buttons
1862 * on the form, and we don't know which one was pushed. */
1863 if (t->action == TAGACT_FORM) {
1864 form = t;
1865 t = 0;
1866 itype = INP_SUBMIT;
1867 } else {
1868 form = t->controller;
1869 itype = t->itype;
1870 }
1871
1872 if (itype > INP_SUBMIT) {
1873 setError(MSG_NoButton);
1874 return false;
1875 }
1876
1877 if (t) {
1878 if (inputDisabled(t)) {
1879 setError(MSG_Disabled);
1880 return false;
1881 }
1882 if (tagHandler(t->seqno, "onclick") && !allowJS)
1883 runningError(itype ==
1884 INP_BUTTON ? MSG_NJNoAction :
1885 MSG_NJNoOnclick);
1886 bubble_event(t, "onclick");
1887 if (js_redirects)
1888 return true;
1889 // At this point onclick has run, be it button or submit or reset
1890 }
1891
1892 if (itype == INP_BUTTON) {
1893 /* I use to error here, but click could be captured by a node higher up in the tree
1894 and do what it is suppose to do, so we might not want an error here.
1895 if (allowJS && t->jv && !t->onclick) {
1896 setError(MSG_ButtonNoJS);
1897 return false;
1898 }
1899 */
1900 return true;
1901 }
1902 // Now submit or reset
1903 if (itype == INP_RESET) {
1904 if (!form) {
1905 setError(MSG_NotInForm);
1906 return false;
1907 }
1908 // Before we reset, run the onreset code.
1909 // I read somewhere that onreset and onsubmit only run if you
1910 // pushed the button - rather like onclick.
1911 // Thus t, the reset button, must be nonzero.
1912 if (t && tagHandler(form->seqno, "onreset")) {
1913 if (!allowJS)
1914 runningError(MSG_NJNoReset);
1915 else {
1916 rc = true;
1917 if (form->jv)
1918 rc = run_event_bool(f, form->jv,
1919 "form", "onreset");
1920 if (!rc)
1921 return true;
1922 if (js_redirects)
1923 return true;
1924 }
1925 } /* onreset */
1926 formReset(form);
1927 return true;
1928 }
1929 // now it's submit
1930 if (!form && !(t && t->onclick)) {
1931 setError(MSG_NotInForm);
1932 return false;
1933 }
1934 // <button> could turn into submit, which we don't want to do if it is not in a form.
1935 if (!form)
1936 return true;
1937 // Before we submit, run the onsubmit code
1938 if (t && tagHandler(form->seqno, "onsubmit")) {
1939 if (!allowJS)
1940 runningError(MSG_NJNoSubmit);
1941 else {
1942 rc = true;
1943 if (form->jv)
1944 rc = bubble_event(form, "onsubmit");
1945 if (!rc)
1946 return true;
1947 if (js_redirects)
1948 return true;
1949 }
1950 }
1951
1952 action = form->href;
1953 /* But we defer to the js variable */
1954 if (form->jv && allowJS) {
1955 char *jh = get_property_url(f, form->jv, true);
1956 if (jh && (!action || !stringEqual(jh, action))) {
1957 nzFree(form->href);
1958 action = form->href = jh;
1959 jh = NULL;
1960 }
1961 nzFree(jh);
1962 }
1963 // if no action, or action is "#", the default is the current location.
1964 // And yet, with onclick on the submit button, no action means no action,
1965 // and I believe the same is true for onsubmit.
1966 // Just assume javascript has done the submit.
1967 if (!action || !*action || stringEqual(action, "#")) {
1968 if (t && (t->onclick | form->onsubmit))
1969 return true;
1970 action = f->hbase;
1971 }
1972
1973 prot = getProtURL(action);
1974 if (!prot) {
1975 if (t && t->onclick)
1976 return true;
1977 setError(MSG_FormBadURL);
1978 return false;
1979 }
1980
1981 debugPrint(2, "* %s", action);
1982
1983 if (stringEqualCI(prot, "javascript")) {
1984 if (!allowJS) {
1985 setError(MSG_NJNoForm);
1986 return false;
1987 }
1988 jsRunScript(f, form->jv, action, 0, 0);
1989 return true;
1990 }
1991
1992 form->bymail = false;
1993 if (stringEqualCI(prot, "mailto")) {
1994 if (!validAccount(localAccount))
1995 return false;
1996 form->bymail = true;
1997 } else if (stringEqualCI(prot, "http")) {
1998 if (form->secure) {
1999 setError(MSG_BecameInsecure);
2000 return false;
2001 }
2002 } else if (!stringEqualCI(prot, "https") &&
2003 !stringEqualCI(prot, "gopher")) {
2004 setError(MSG_SubmitProtBad, prot);
2005 return false;
2006 }
2007
2008 pfs = initString(&pfs_l);
2009 stringAndString(&pfs, &pfs_l, action);
2010 section = findHash(pfs);
2011 if (section) {
2012 i_printf(MSG_SectionIgnored, section);
2013 *section = 0;
2014 pfs_l = section - pfs;
2015 }
2016 section = strpbrk(pfs, "?\1");
2017 if (section && (*section == '\1' || !(form->bymail | form->post))) {
2018 debugPrint(3,
2019 "the url already specifies some data, which will be overwritten by the data in this form");
2020 *section = 0;
2021 pfs_l = section - pfs;
2022 }
2023
2024 stringAndChar(&pfs, &pfs_l, (form->post ? '\1' : '?'));
2025 actlen = strlen(pfs);
2026
2027 if (!formSubmit(form, t)) {
2028 nzFree(pfs);
2029 return false;
2030 }
2031
2032 debugPrint(3, "%s %s", form->post ? "post" : "get", pfs + actlen);
2033
2034 /* Handle the mail method here and now. */
2035 if (form->bymail) {
2036 char *addr, *subj, *q;
2037 const char *tolist[2], *atlist[2];
2038 const char *name = form->name;
2039 int newlen = strlen(pfs) - actlen; /* the new string could be longer than post */
2040 decodeMailURL(action, &addr, &subj, 0);
2041 tolist[0] = addr;
2042 tolist[1] = 0;
2043 atlist[0] = 0;
2044 newlen += 9; /* subject: \n */
2045 if (subj)
2046 newlen += strlen(subj);
2047 else
2048 newlen += 11 + (name ? strlen(name) : 1);
2049 ++newlen; /* null */
2050 ++newlen; /* encodeAttachment might append another nl */
2051 q = (char *)allocMem(newlen);
2052 if (subj)
2053 sprintf(q, "subject:%s\n", subj);
2054 else
2055 sprintf(q, "subject:html form(%s)\n",
2056 name ? name : "?");
2057 strcpy(q + strlen(q), pfs + actlen);
2058 nzFree(pfs);
2059 i_printf(MSG_MailSending, addr);
2060 SLEEP(1);
2061 rc = sendMail(localAccount, tolist, q, -1, atlist, 0, 0, false);
2062 if (rc)
2063 i_puts(MSG_MailSent);
2064 nzFree(addr);
2065 nzFree(subj);
2066 nzFree(q);
2067 *post_string = 0;
2068 return rc;
2069 }
2070 // gopher submit is one input field with no name;
2071 // the leading = doesn't belong.
2072 if (pfs[actlen] == '=' && stringEqualCI(prot, "gopher"))
2073 strmove(pfs + actlen, pfs + actlen + 1);
2074
2075 *post_string = pfs;
2076 return true;
2077 } /* infPush */
2078
2079 /* I don't have any reverse pointers, so I'm just going to scan the list */
2080 /* This doesn't come up all that often. */
tagFromJavaVar(jsobjtype v)2081 Tag *tagFromJavaVar(jsobjtype v)
2082 {
2083 Tag *t = 0;
2084 int i;
2085
2086 if (!tagList)
2087 i_printfExit(MSG_NullListInform);
2088
2089 for (i = 0; i < cw->numTags; ++i) {
2090 t = tagList[i];
2091 if (t->jv == v && !t->dead)
2092 return t;
2093 }
2094 return 0;
2095 } /* tagFromJavaVar */
2096
2097 // Create a new tag for this pointer, only from document.createElement().
tagFromJavaVar2(jsobjtype v,const char * tagname)2098 static Tag *tagFromJavaVar2(jsobjtype v, const char *tagname)
2099 {
2100 Tag *t;
2101 if (!tagname)
2102 return 0;
2103 t = newTag(cf, tagname);
2104 if (!t) {
2105 debugPrint(3, "cannot create tag node %s", tagname);
2106 return 0;
2107 }
2108 connectTagObject(t, v);
2109 /* this node now has a js object, don't decorate it again. */
2110 t->step = 2;
2111 /* and don't render it unless it is linked into the active tree */
2112 t->deleted = true;
2113 return t;
2114 } /* tagFromJavaVar2 */
2115
2116 /* Return false to stop javascript, due to a url redirect */
javaSubmitsForm(jsobjtype v,bool reset)2117 void javaSubmitsForm(jsobjtype v, bool reset)
2118 {
2119 if (reset)
2120 js_reset = v;
2121 else
2122 js_submit = v;
2123 } /* javaSubmitsForm */
2124
bubble_event(const Tag * t,const char * name)2125 bool bubble_event(const Tag *t, const char *name)
2126 {
2127 Frame *f = t->f0;
2128 jsobjtype e; // the event object
2129 bool rc;
2130 if (!allowJS || !t->jv)
2131 return true;
2132 e = create_event(f, t->jv, name);
2133 rc = run_function_onearg(f, t->jv, "dispatchEvent", e);
2134 if (rc && get_property_bool(f, e, "prev$default"))
2135 rc = false;
2136 unlink_event(f, t->jv);
2137 return rc;
2138 } /* bubble_event */
2139
2140 /* Javascript errors, we need to see these no matter what. */
runningError(int msg,...)2141 void runningError(int msg, ...)
2142 {
2143 va_list p;
2144 if (ismc)
2145 return;
2146 if (debugLevel <= 2)
2147 return;
2148 va_start(p, msg);
2149 vprintf(i_getString(msg), p);
2150 va_end(p);
2151 nl();
2152 } /* runningError */
2153
2154 /*********************************************************************
2155 Diff the old screen with the new rendered screen.
2156 This is a simple front back diff algorithm.
2157 Compare the two strings from the start, how many lines are the same.
2158 Compare the two strings from the back, how many lines are the same.
2159 That zeros in on the line that has changed.
2160 Most of the time one line has changed,
2161 or a couple of adjacent lines, or a couple of nearby lines.
2162 So this should do it.
2163 sameFront counts the lines from the top that are the same.
2164 We're here because the buffers are different, so sameFront will not equal $.
2165 Lines after sameFront are different.
2166 Lines past sameBack1 and same back2 are the same to the bottom in the two buffers.
2167 To be a bit more sophisticated, front1z and front2z
2168 become nonzero if just one line was added, updated, or deleted at sameFront.
2169 they march on beyond this point, as long as lines are the same.
2170 In the same way, back1z and back2z march backwards
2171 past a one line anomaly.
2172 *********************************************************************/
2173
2174 static int sameFront, sameBack1, sameBack2;
2175 static int front1z, front2z, back1z, back2z;
2176 static const char *newChunkStart, *newChunkEnd;
2177
2178 // need a reverse strchr to help us out.
rstrchr(const char * s,const char * mark)2179 static const char *rstrchr(const char *s, const char *mark)
2180 {
2181 for (--s; s > mark; --s)
2182 if (s[-1] == '\n')
2183 return s;
2184 return (s == mark ? s : NULL);
2185 }
2186
frontBackDiff(const char * b1,const char * b2)2187 static void frontBackDiff(const char *b1, const char *b2)
2188 {
2189 const char *f1, *f2, *s1, *s2, *e1, *e2;
2190 const char *g1, *g2, *h1, *h2;
2191
2192 sameFront = front1z = front2z = 0;
2193 back1z = back2z = 0;
2194
2195 s1 = b1, s2 = b2;
2196 f1 = b1, f2 = b2;
2197 while (*s1 == *s2 && *s1) {
2198 if (*s1 == '\n') {
2199 f1 = s1 + 1, f2 = s2 + 1;
2200 ++sameFront;
2201 }
2202 ++s1, ++s2;
2203 }
2204
2205 g1 = strchr(f1, '\n');
2206 g2 = strchr(f2, '\n');
2207 if (g1 && g2) {
2208 ++g1, ++g2;
2209 h1 = strchr(g1, '\n');
2210 h2 = strchr(g2, '\n');
2211 if (h1 && h2) {
2212 ++h1, ++h2;
2213 if (g1 - f1 == h2 - g2 && !memcmp(f1, g2, g1 - f1)) {
2214 e1 = f1, e2 = g2;
2215 s1 = g1, s2 = h2;
2216 front1z = sameFront + 1;
2217 front2z = sameFront + 2;
2218 } else if (h1 - g1 == g2 - f2
2219 && !memcmp(g1, f2, h1 - g1)) {
2220 e1 = g1, e2 = f2;
2221 s1 = h1, s2 = g2;
2222 front1z = sameFront + 2;
2223 front2z = sameFront + 1;
2224 } else if (h1 - g1 == h2 - g2
2225 && !memcmp(g1, g2, h1 - g1)) {
2226 e1 = g1, e2 = g2;
2227 s1 = h1, s2 = h2;
2228 front1z = sameFront + 2;
2229 front2z = sameFront + 2;
2230 }
2231 }
2232 }
2233
2234 if (front1z || front2z) {
2235 sameBack1 = front1z - 1, sameBack2 = front2z - 1;
2236 while (*s1 == *s2 && *s1) {
2237 if (*s1 == '\n')
2238 ++front1z, ++front2z;
2239 ++s1, ++s2;
2240 }
2241 if (!*s1) {
2242 front1z = front2z = 0;
2243 goto done;
2244 }
2245 }
2246
2247 s1 = b1 + strlen(b1);
2248 s2 = b2 + strlen(b2);
2249 while (s1 > f1 && s2 > f2 && s1[-1] == s2[-1])
2250 --s1, --s2;
2251 if (s1 == f1 && s2[-1] == '\n')
2252 goto mark_e;
2253 if (s2 == f2 && s1[-1] == '\n')
2254 goto mark_e;
2255 /* advance both pointers to newline or null */
2256 while (*s1 && *s1 != '\n')
2257 ++s1, ++s2;
2258 /* these buffers should always end in newline, so the next if should always be true */
2259 if (*s1 == '\n')
2260 ++s1, ++s2;
2261 mark_e:
2262 e1 = s1, e2 = s2;
2263
2264 sameBack1 = sameFront;
2265 for (s1 = f1; s1 < e1; ++s1)
2266 if (*s1 == '\n')
2267 ++sameBack1;
2268 if (s1 > f1 && s1[-1] != '\n') // should never happen
2269 ++sameBack1;
2270
2271 sameBack2 = sameFront;
2272 for (s2 = f2; s2 < e2; ++s2)
2273 if (*s2 == '\n')
2274 ++sameBack2;
2275 if (s2 > f2 && s2[-1] != '\n') // should never happen
2276 ++sameBack2;
2277
2278 if (front1z || front2z) {
2279 // front2z can run past sameBack2 if lines are deleted.
2280 // This because front2z is computed before sameBack2.
2281 while (front1z > sameBack1 || front2z > sameBack2)
2282 --front1z, --front2z;
2283 if (front1z <= sameFront || front2z <= sameFront)
2284 front1z = front2z = 0;
2285 goto done;
2286 }
2287
2288 h1 = rstrchr(e1, f1);
2289 h2 = rstrchr(e2, f2);
2290 if (h1 && h2) {
2291 g1 = rstrchr(h1, f1);
2292 g2 = rstrchr(h2, f2);
2293 if (g1 && g2) {
2294 if (e1 - h1 == h2 - g2 && !memcmp(h1, g2, e1 - h1)) {
2295 s1 = h1, s2 = g2;
2296 back1z = sameBack1, back2z = sameBack2 - 1;
2297 } else if (h1 - g1 == e2 - h2
2298 && !memcmp(g1, h2, h1 - g1)) {
2299 s1 = g1, s2 = h2;
2300 back1z = sameBack1 - 1, back2z = sameBack2;
2301 } else if (h1 - g1 == h2 - g2
2302 && !memcmp(g1, g2, h1 - g1)) {
2303 s1 = g1, s2 = g2;
2304 back1z = sameBack1 - 1, back2z = sameBack2 - 1;
2305 }
2306 }
2307 }
2308
2309 if (back1z || back2z) {
2310 --s1, --s2;
2311 while (*s1 == *s2 && s1 >= f1 && s2 >= f2) {
2312 if (s1[-1] == '\n' && s2[-1] == '\n')
2313 --back1z, --back2z;
2314 --s1, --s2;
2315 }
2316 }
2317
2318 done:
2319 newChunkStart = f2;
2320 newChunkEnd = e2;
2321 } /* frontBackDiff */
2322
2323 // Believe it or not, I have exercised all the pathways in this routine.
2324 // It's rather mind numbing.
reportZ(void)2325 static bool reportZ(void)
2326 {
2327 // low and high operations are ad, update, delete
2328 char oplow, ophigh;
2329 // lines affected in the second group
2330 int act1, act2;
2331 int d_start, d_end;
2332
2333 if (!(front1z || front2z || back1z || back2z))
2334 return false;
2335
2336 if (front1z || front2z) {
2337 if (front2z > front1z)
2338 oplow = 1;
2339 if (front2z == front1z)
2340 oplow = 2;
2341 if (front2z < front1z)
2342 oplow = 3;
2343 act1 = sameBack1 - front1z;
2344 act2 = sameBack2 - front2z;
2345 ophigh = 2;
2346 if (!act1)
2347 ophigh = 1;
2348 if (!act2)
2349 ophigh = 3;
2350 // delete delete is the easy case, but very rare
2351 if (oplow == 3 && ophigh == 3) {
2352 if (act1 == 1)
2353 i_printf(MSG_LineDeleteZ1, sameFront + 1,
2354 sameBack1);
2355 else
2356 i_printf(MSG_LineDeleteZ2, sameFront + 1,
2357 front1z + 1, sameBack1);
2358 goto done;
2359 }
2360 // double add is more common, and also unambiguous.
2361 // If this algorithm says we added 100 lines, then we added 100 lines.
2362 if (oplow == 1 && ophigh == 1) {
2363 if (act2 == 1)
2364 i_printf(MSG_LineAddZ1, sameFront + 1,
2365 sameBack2);
2366 else
2367 i_printf(MSG_LineAddZ2, sameFront + 1,
2368 front2z + 1, sameBack2);
2369 goto done;
2370 }
2371 if (oplow == 3) {
2372 // delete mixed with something else, and I just don't care about the delete.
2373 if (ophigh == 1)
2374 i_printf(MSG_LineAdd2, front2z + 1, sameBack2);
2375 else if (act2 <= 10)
2376 i_printf(MSG_LineUpdate3, front2z + 1,
2377 sameBack2);
2378 else
2379 i_printf(MSG_LineUpdateRange, front2z + 1,
2380 sameBack2);
2381 goto done;
2382 }
2383 if (ophigh == 3) {
2384 // if the deleted block is big then report it, otherwise ignore it.
2385 if (act1 >= 10)
2386 i_printf(MSG_LineDelete2, act1, front1z);
2387 else if (oplow == 1)
2388 i_printf(MSG_LineAdd1, sameFront + 1);
2389 else
2390 i_printf(MSG_LineUpdate1, sameFront + 1);
2391 goto done;
2392 }
2393 // a mix of add and update, call it an update.
2394 // If the second group is big then switch to range message.
2395 if (act2 > 10 && ophigh == 2)
2396 i_printf(MSG_LineUpdateRange,
2397 (front2z - sameFront <
2398 10 ? sameFront + 1 : front2z + 1), sameBack2);
2399 else if (act2 == 1)
2400 i_printf(MSG_LineUpdateZ1, sameFront + 1, sameBack2);
2401 else
2402 i_printf(MSG_LineUpdateZ2, sameFront + 1, front2z + 1,
2403 sameBack2);
2404 goto done;
2405 }
2406 // At this point the single line change comes second,
2407 // we have to look at back1z and back2z.
2408 d_start = sameBack2 - sameBack1;
2409 d_end = back2z - back1z;
2410 ophigh = 2;
2411 if (d_end > d_start)
2412 ophigh = 3;
2413 if (d_end < d_start)
2414 ophigh = 1;
2415 act1 = back1z - sameFront - 1;
2416 act2 = back2z - sameFront - 1;
2417 oplow = 2;
2418 if (!act1)
2419 oplow = 1;
2420 if (!act2)
2421 oplow = 3;
2422 // delete delete is the easy case, but very rare
2423 if (oplow == 3 && ophigh == 3) {
2424 // act1 should never be 1, because then one line was deleted earlier,
2425 // and we would be in the front1z case.
2426 i_printf(MSG_LineDeleteZ3, sameFront + 1, back1z - 1,
2427 sameBack1);
2428 goto done;
2429 }
2430 // double add is more common, and also unambiguous.
2431 // If this algorithm says we added 100 lines, then we added 100 lines.
2432 if (oplow == 1 && ophigh == 1) {
2433 i_printf(MSG_LineAddZ3, sameFront + 1, back2z - 1, sameBack2);
2434 goto done;
2435 }
2436 if (ophigh == 3) {
2437 // delete mixed with something else, and I just don't care about the delete.
2438 if (oplow == 1)
2439 i_printf(MSG_LineAdd2, sameFront + 1, back2z - 1);
2440 else if (act2 <= 10)
2441 i_printf(MSG_LineUpdate3, sameFront + 1, back2z - 1);
2442 else
2443 i_printf(MSG_LineUpdateRange, sameFront + 1,
2444 back2z - 1);
2445 goto done;
2446 }
2447 if (oplow == 3) {
2448 // if the deleted block is big then report it, otherwise ignore it.
2449 if (act1 >= 10)
2450 i_printf(MSG_LineDelete2, act1, sameFront);
2451 else if (ophigh == 1)
2452 i_printf(MSG_LineAdd1, sameBack2);
2453 else
2454 i_printf(MSG_LineUpdate1, sameBack2);
2455 goto done;
2456 }
2457 // a mix of add and update, call it an update.
2458 // If the first group is big then switch to range message.
2459 if (act2 > 10 && oplow == 2)
2460 i_printf(MSG_LineUpdateRange,
2461 sameFront + 1,
2462 (sameBack2 - back2z < 10 ? sameBack2 : back2z - 1));
2463 else
2464 i_printf(MSG_LineUpdateZ3, sameFront + 1, back2z - 1,
2465 sameBack2);
2466
2467 done:
2468 return true;
2469 }
2470
2471 static time_t now_sec;
2472 static int now_ms;
currentTime(void)2473 static void currentTime(void)
2474 {
2475 struct timeval tv;
2476 gettimeofday(&tv, NULL);
2477 now_sec = tv.tv_sec;
2478 now_ms = tv.tv_usec / 1000;
2479 } /* currentTime */
2480
silent(int msg,...)2481 static void silent(int msg, ...)
2482 {
2483 }
2484
2485 // Is there an active tag below?
activeBelow(Tag * t)2486 static bool activeBelow(Tag *t)
2487 {
2488 bool rc;
2489 int action = t->action;
2490 if (action == TAGACT_INPUT || action == TAGACT_SELECT ||
2491 action == TAGACT_A || (action == TAGACT_SPAN && t->onclick))
2492 return true;
2493 t = t->firstchild;
2494 while (t) {
2495 rc = activeBelow(t);
2496 if (rc)
2497 return rc;
2498 t = t->sibling;
2499 }
2500 return false;
2501 }
2502
2503 static int hovcount, invcount, injcount;
2504
2505 /* Rerender the buffer and notify of any lines that have changed */
2506 int rr_interval = 20;
rerender(bool rr_command)2507 void rerender(bool rr_command)
2508 {
2509 char *a, *snap, *newbuf;
2510 int j;
2511 int markdot, wasdot, addtop;
2512 bool z;
2513 void (*say_fn) (int, ...);
2514
2515 debugPrint(4, "rerender");
2516 cw->mustrender = false;
2517 time(&cw->nextrender);
2518 cw->nextrender += rr_interval;
2519 hovcount = invcount = injcount = 0;
2520
2521 // not sure if we have to do this here
2522 rebuildSelectors();
2523
2524 if (rr_command) {
2525 // You might have changed some input fields on the screen, then typed rr
2526 jSyncup(true);
2527 }
2528 // screen snap, to compare with the new screen.
2529 if (!unfoldBufferW(cw, false, &snap, &j)) {
2530 snap = 0;
2531 puts("no screen snap available");
2532 return;
2533 }
2534
2535 /* and the new screen */
2536 a = render(0);
2537 newbuf = htmlReformat(a);
2538 nzFree(a);
2539
2540 if (rr_command && debugLevel >= 3) {
2541 char buf[120];
2542 buf[0] = 0;
2543 if (hovcount)
2544 sprintf(buf, "%d nodes under hover", hovcount);
2545 if (invcount) {
2546 if (buf[0])
2547 strcat(buf, ", ");
2548 sprintf(buf + strlen(buf),
2549 "%d nodes invisible", invcount);
2550 }
2551 if (injcount) {
2552 if (buf[0])
2553 strcat(buf, ", ");
2554 sprintf(buf + strlen(buf), "%d nodes injected by css",
2555 injcount);
2556 }
2557 if (buf[0])
2558 debugPrint(3, "%s", buf);
2559 }
2560
2561 /* the high runner case, most of the time nothing changes,
2562 * and we can check that efficiently with strcmp */
2563 if (stringEqual(newbuf, snap)) {
2564 if (rr_command)
2565 i_puts(MSG_NoChange);
2566 nzFree(newbuf);
2567 nzFree(snap);
2568 return;
2569 }
2570
2571 /* mark dot, so it stays in place */
2572 cw->labels[MARKDOT] = wasdot = cw->dot;
2573 frontBackDiff(snap, newbuf);
2574 addtop = 0;
2575 if (sameBack1 > sameFront)
2576 delText(sameFront + 1, sameBack1);
2577 if (sameBack2 > sameFront) {
2578 addTextToBuffer((pst) newChunkStart,
2579 newChunkEnd - newChunkStart, sameFront, false);
2580 addtop = sameFront + 1;
2581 }
2582 markdot = cw->labels[MARKDOT];
2583 if (markdot)
2584 cw->dot = markdot;
2585 else if (sameBack1 == sameBack2)
2586 cw->dot = wasdot;
2587 else if (addtop)
2588 cw->dot = addtop;
2589 cw->undoable = false;
2590
2591 /*********************************************************************
2592 It's almost easier to do it than to report it.
2593 First, run diff again with the hidden numbers gone, so we only report
2594 the visible differences. It's annoying to hear that line 27 has been updated,
2595 and it looks just like it did before.
2596 This happens when a periodic timer updates a section through innerHTML.
2597 If the text is the same every time that's fine, but it's new tags each time,
2598 and new internal numbers each time, and that use to trip this algorithm.
2599 *********************************************************************/
2600
2601 removeHiddenNumbers((pst) snap, 0);
2602 removeHiddenNumbers((pst) newbuf, 0);
2603 if (stringEqual(snap, newbuf)) {
2604 if (rr_command)
2605 i_puts(MSG_NoChange);
2606 goto done;
2607 }
2608 frontBackDiff(snap, newbuf);
2609 debugPrint(4, "front %d back %d,%d front z %d,%d back z %d,%d",
2610 sameFront, sameBack1, sameBack2,
2611 front1z, front2z, back1z, back2z);
2612 z = reportZ();
2613
2614 // Even if the change has been reported above,
2615 // I march on here because it puts dot back where it belongs.
2616 say_fn = (z ? silent : i_printf);
2617 if (sameBack2 == sameFront) { /* delete */
2618 if (sameBack1 == sameFront + 1)
2619 (*say_fn) (MSG_LineDelete1, sameFront);
2620 else
2621 (*say_fn) (MSG_LineDelete2, sameBack1 - sameFront,
2622 sameFront);
2623 } else if (sameBack1 == sameFront) {
2624 if (sameBack2 == sameFront + 1)
2625 (*say_fn) (MSG_LineAdd1, sameFront + 1);
2626 else {
2627 (*say_fn) (MSG_LineAdd2, sameFront + 1, sameBack2);
2628 /* put dot back to the start of the new block */
2629 if (!markdot)
2630 cw->dot = sameFront + 1;
2631 }
2632 } else {
2633 if (sameBack1 == sameFront + 1 && sameBack2 == sameFront + 1)
2634 (*say_fn) (MSG_LineUpdate1, sameFront + 1);
2635 else if (sameBack2 == sameFront + 1)
2636 (*say_fn) (MSG_LineUpdate2, sameBack1 - sameFront,
2637 sameFront + 1);
2638 else {
2639 if (sameBack2 - sameFront <= 10 ||
2640 sameBack1 - sameFront <= 10)
2641 (*say_fn) (MSG_LineUpdate3, sameFront + 1,
2642 sameBack2);
2643 else
2644 (*say_fn) (MSG_LineUpdateRange, sameFront + 1,
2645 sameBack2);
2646 /* put dot back to the start of the new block */
2647 if (!markdot && sameBack1 != sameBack2)
2648 cw->dot = sameFront + 1;
2649 }
2650 }
2651
2652 done:
2653 nzFree(newbuf);
2654 nzFree(snap);
2655 } /* rerender */
2656
2657 /* mark the tags on the deleted lines as deleted */
delTags(int startRange,int endRange)2658 void delTags(int startRange, int endRange)
2659 {
2660 pst p;
2661 int j, tagno, action;
2662 Tag *t;
2663
2664 /* no javascript, no cause to ever rerender */
2665 if (!cf->cx)
2666 return;
2667
2668 for (j = startRange; j <= endRange; ++j) {
2669 p = fetchLine(j, -1);
2670 for (; *p != '\n'; ++p) {
2671 if (*p != InternalCodeChar)
2672 continue;
2673 tagno = strtol((char *)p + 1, (char **)&p, 10);
2674 /* could be 0, but should never be negative */
2675 if (tagno <= 0)
2676 continue;
2677 t = tagList[tagno];
2678 /* Only mark certain tags as deleted.
2679 * If you mark <div> deleted, it could wipe out half the page. */
2680 action = t->action;
2681 if (action == TAGACT_TEXT ||
2682 action == TAGACT_HR ||
2683 action == TAGACT_LI || action == TAGACT_IMAGE)
2684 t->deleted = true;
2685 #if 0
2686 /* this seems to cause more trouble than it's worth */
2687 Tag *last_td = 0;
2688 if (action == TAGACT_TD) {
2689 printf("td%d\n", tagno);
2690 if (last_td)
2691 last_td->deleted = true;
2692 last_td = t;
2693 }
2694 #endif
2695 }
2696 }
2697 } /* delTags */
2698
2699 /* turn an onunload function into a clickable hyperlink */
unloadHyperlink(const char * js_function,const char * where)2700 static void unloadHyperlink(const char *js_function, const char *where)
2701 {
2702 dwStart();
2703 stringAndString(&cf->dw, &cf->dw_l, "<P>Onclose <A href='javascript:");
2704 stringAndString(&cf->dw, &cf->dw_l, js_function);
2705 stringAndString(&cf->dw, &cf->dw_l, "()'>");
2706 stringAndString(&cf->dw, &cf->dw_l, where);
2707 stringAndString(&cf->dw, &cf->dw_l, "</A><br>");
2708 } /* unloadHyperlink */
2709
2710 /* Run the various onload functions */
2711 /* Turn the onunload functions into hyperlinks */
2712 /* This runs after the page is parsed and before the various javascripts run, is that right? */
runOnload(void)2713 void runOnload(void)
2714 {
2715 int i, action;
2716 int fn; /* form number */
2717 Tag *t;
2718
2719 if (!isJSAlive)
2720 return;
2721 if (intFlag)
2722 return;
2723
2724 /* window and document onload */
2725 run_event_bool(cf, cf->winobj, "window", "onload");
2726 if (intFlag)
2727 return;
2728 run_event_bool(cf, cf->docobj, "document", "onload");
2729 if (intFlag)
2730 return;
2731
2732 fn = -1;
2733 for (i = 0; i < cw->numTags; ++i) {
2734 if (intFlag)
2735 return;
2736 t = tagList[i];
2737 if (t->slash)
2738 continue;
2739 if (t->f0 != cf)
2740 continue;
2741 action = t->action;
2742 if (action == TAGACT_FORM)
2743 ++fn;
2744 if (!t->jv)
2745 continue;
2746 if (action == TAGACT_BODY && handlerPresent(cf, t->jv, "onload"))
2747 run_event_bool(cf, t->jv, "body", "onload");
2748 if (action == TAGACT_BODY && t->onunload)
2749 unloadHyperlink("document.body.onunload", "Body");
2750 if (action == TAGACT_FORM && handlerPresent(cf, t->jv, "onload"))
2751 run_event_bool(cf, t->jv, "form", "onload");
2752 /* tidy5 says there is no form.onunload */
2753 if (action == TAGACT_FORM && t->onunload) {
2754 char formfunction[48];
2755 sprintf(formfunction, "document.forms[%d].onunload",
2756 fn);
2757 unloadHyperlink(formfunction, "Form");
2758 }
2759 if (action == TAGACT_H && handlerPresent(cf, t->jv, "onload"))
2760 run_event_bool(cf, t->jv, "h1", "onload");
2761 }
2762 } /* runOnload */
2763
2764 /*********************************************************************
2765 Manage js timers here.
2766 It's a simple list of timers, assuming there aren't too many.
2767 Store the seconds and milliseconds when the timer should fire,
2768 the code to execute, and the timer object, which becomes "this".
2769 *********************************************************************/
2770
2771 struct jsTimer {
2772 struct jsTimer *next, *prev;
2773 Frame *frame; /* edbrowse frame holding this timer */
2774 Tag *t; // for an asynchronous script
2775 time_t sec;
2776 int ms;
2777 bool isInterval;
2778 bool running;
2779 bool deleted;
2780 int jump_sec; /* for interval */
2781 int jump_ms;
2782 jsobjtype timerObject;
2783 };
2784
2785 /* list of pending timers */
2786 struct listHead timerList = {
2787 &timerList, &timerList
2788 };
2789
2790 /*********************************************************************
2791 the spec says you can't run a timer less than 10 ms but here we currently use
2792 900 ms. This really should be a configurable limit.
2793 If less than 200ms the load average jumps way up. e.g.nasa.gov
2794 We only rerender the screen every 20 seconds or so anyways.
2795 But, the acid test uses a timer to schedule each of its 100 tests,
2796 and is crazy slow if we throttle them.
2797 So ... the first few timers can run as fast as they like,and we're ok
2798 with that, then timers slow down as we proceed.
2799 *********************************************************************/
2800 int timerResolution = 900;
2801 static int tsn; // timer sequence number
2802
javaSetsTimeout(int n,const char * jsrc,jsobjtype to,bool isInterval)2803 void javaSetsTimeout(int n, const char *jsrc, jsobjtype to, bool isInterval)
2804 {
2805 struct jsTimer *jt;
2806 int seqno;
2807
2808 if (jsrc[0] == 0)
2809 return; /* nothing to run */
2810
2811 if (stringEqual(jsrc, "-")) {
2812 seqno = get_property_number(cf, to, "tsn");
2813 // delete a timer
2814 foreach(jt, timerList) {
2815 if (jt->timerObject == to) {
2816 debugPrint(4, "timer %d delete", seqno);
2817 // a running timer will often delete itself.
2818 if (jt->running) {
2819 jt->deleted = true;
2820 } else {
2821 char *gc_name =
2822 get_property_string(cf, jt->timerObject,
2823 "backlink");
2824 if (gc_name)
2825 delete_property(cf, cf->winobj,
2826 gc_name);
2827 delFromList(jt);
2828 nzFree(jt);
2829 }
2830 return;
2831 }
2832 }
2833 // not found, just return.
2834 return;
2835 }
2836
2837 jt = allocZeroMem(sizeof(struct jsTimer));
2838 if (n < timerResolution) {
2839 n = cf->jtmin;
2840 if (!n)
2841 n = 10;
2842 if (n < timerResolution)
2843 n += 3;
2844 cf->jtmin = n;
2845 }
2846 jt->sec = n / 1000;
2847 jt->ms = n % 1000;
2848 if ((jt->isInterval = isInterval))
2849 jt->jump_sec = n / 1000, jt->jump_ms = n % 1000;
2850 currentTime();
2851 jt->sec += now_sec;
2852 jt->ms += now_ms;
2853 if (jt->ms >= 1000)
2854 jt->ms -= 1000, ++jt->sec;
2855 jt->timerObject = to;
2856 jt->frame = cf;
2857 addToListBack(&timerList, jt);
2858 seqno = ++tsn;
2859 set_property_number(cf, to, "tsn", seqno);
2860 debugPrint(4, "timer %d %s", seqno, jsrc);
2861 } /* javaSetsTimeout */
2862
scriptSetsTimeout(Tag * t)2863 void scriptSetsTimeout(Tag *t)
2864 {
2865 struct jsTimer *jt = allocZeroMem(sizeof(struct jsTimer));
2866 jt->sec = 0;
2867 jt->ms = asyncTimer;
2868 jt->isInterval = true;
2869 jt->jump_sec = 0, jt->jump_ms = asyncTimer;
2870 currentTime();
2871 jt->sec += now_sec;
2872 jt->ms += now_ms;
2873 if (jt->ms >= 1000)
2874 jt->ms -= 1000, ++jt->sec;
2875 jt->t = t;
2876 jt->frame = cf;
2877 addToListBack(&timerList, jt);
2878 debugPrint(3, "timer %s%d=%s",
2879 (t->action == TAGACT_SCRIPT ? "script" : "xhr"),
2880 ++tsn, t->href);
2881 t->lic = tsn;
2882 } /* scriptSetsTimeout */
2883
soonest(void)2884 static struct jsTimer *soonest(void)
2885 {
2886 struct jsTimer *t, *best_t = 0;
2887 if (listIsEmpty(&timerList))
2888 return 0;
2889 foreach(t, timerList) {
2890 if (!best_t || t->sec < best_t->sec ||
2891 (t->sec == best_t->sec && t->ms < best_t->ms))
2892 best_t = t;
2893 }
2894 return best_t;
2895 } /* soonest */
2896
timerWait(int * delay_sec,int * delay_ms)2897 bool timerWait(int *delay_sec, int *delay_ms)
2898 {
2899 struct jsTimer *jt = soonest();
2900 time_t now;
2901 int remaining;
2902
2903 if (cw->mustrender) {
2904 time(&now);
2905 remaining = 0;
2906 if (now < cw->nextrender)
2907 remaining = cw->nextrender - now;
2908 }
2909
2910 if (!jt) {
2911 if (!cw->mustrender)
2912 return false;
2913 *delay_sec = remaining;
2914 *delay_ms = 0;
2915 return true;
2916 }
2917
2918 currentTime();
2919 if (now_sec > jt->sec || (now_sec == jt->sec && now_ms >= jt->ms))
2920 *delay_sec = *delay_ms = 0;
2921 else {
2922 *delay_sec = jt->sec - now_sec;
2923 *delay_ms = (jt->ms - now_ms);
2924 if (*delay_ms < 0)
2925 *delay_ms += 1000, --*delay_sec;
2926 }
2927
2928 if (cw->mustrender && remaining <= *delay_sec) {
2929 *delay_sec = remaining;
2930 *delay_ms = 0;
2931 }
2932
2933 return true;
2934 } /* timerWait */
2935
delTimers(Frame * f)2936 void delTimers(Frame *f)
2937 {
2938 int delcount = 0;
2939 struct jsTimer *jt, *jnext;
2940 for (jt = timerList.next; jt != (void *)&timerList; jt = jnext) {
2941 jnext = jt->next;
2942 if (jt->frame == f) {
2943 ++delcount;
2944 delFromList(jt);
2945 nzFree(jt);
2946 }
2947 }
2948 debugPrint(4, "%d timers deleted", delcount);
2949 } /* delTimers */
2950
runTimer(void)2951 void runTimer(void)
2952 {
2953 struct jsTimer *jt;
2954 struct ebWindow *save_cw = cw;
2955 Frame *save_cf = cf;
2956 Tag *t;
2957
2958 currentTime();
2959
2960 if ((jt = soonest())
2961 && !(jt->sec > now_sec || (jt->sec == now_sec && jt->ms > now_ms))) {
2962
2963 if (!gotimers)
2964 goto skip_execution;
2965
2966 cf = jt->frame;
2967 cw = cf->owner;
2968
2969 /*********************************************************************
2970 Only syncing the foreground window is right almost all the time,
2971 but not every time.
2972 The forground could be just text, buffer for a textarea in another window.
2973 You should sync that other window before running javascript, so it has
2974 the latest text, the text you are editing right now.
2975 I can't do that because jSyncup calls fetchLine() to pull text lines
2976 out of the buffer, which has to be the foreground window.
2977 We need to fix this someday, though it is a very rare low runner case.
2978 *********************************************************************/
2979 if (foregroundWindow)
2980 jSyncup(true);
2981 jt->running = true;
2982 if ((t = jt->t)) {
2983 // asynchronous script
2984 if (t->step == 3) { // background load
2985 int rc =
2986 pthread_tryjoin_np(t->loadthread, NULL);
2987 if (rc != 0 && rc != EBUSY) {
2988 // should never happen
2989 debugPrint(3,
2990 "script background thread test returns %d",
2991 rc);
2992 pthread_join(t->loadthread, NULL);
2993 rc = 0;
2994 }
2995 if (!rc) { // it's done
2996 if (!t->loadsuccess) {
2997 if (debugLevel >= 3)
2998 i_printf(MSG_GetJS,
2999 t->href,
3000 t->hcode);
3001 t->step = 6;
3002 } else {
3003 if (t->action == TAGACT_SCRIPT) {
3004 set_property_string(cf,
3005 t->jv, "text",
3006 t->value);
3007 nzFree(t->value);
3008 t->value = 0;
3009 }
3010 t->step = 4; // loaded
3011 }
3012 }
3013 }
3014 if (t->step == 4 && t->action == TAGACT_SCRIPT) {
3015 char *js_file = t->js_file;
3016 int ln = t->js_ln;
3017 t->step = 5; // running
3018 if (!js_file)
3019 js_file = "generated";
3020 if (!ln)
3021 ln = 1;
3022 if (ln > 1)
3023 ++ln;
3024 if (cf != save_cf)
3025 debugPrint(4,
3026 "running script at a lower frame %s",
3027 js_file);
3028 debugPrint(3, "async exec timer %d %s at %d",
3029 t->lic, js_file, ln);
3030 set_property_object(cf, cf->docobj, "currentScript",
3031 t->jv);
3032 jsRunData(cf, t->jv, js_file, ln);
3033 if (t->js_file && !isDataURI(t->href) && handlerPresent(cf, t->jv, "onload"))
3034 run_event_bool(cf, t->jv, "script", "onload");
3035 delete_property(cf, cf->docobj, "currentScript");
3036 debugPrint(3, "async exec complete");
3037 }
3038 if (t->step == 4 && t->action != TAGACT_SCRIPT) {
3039 t->step = 5;
3040 set_property_string(cf, t->jv, "$entire", t->value);
3041 // could be large; it's worth freeing
3042 nzFree(t->value);
3043 t->value = 0;
3044 debugPrint(3, "run xhr %d", t->lic);
3045 run_function_bool(cf, t->jv, "parseResponse");
3046 jt->timerObject = t->jv;
3047 }
3048 if (t->step >= 5)
3049 jt->deleted = true;
3050 } else {
3051 run_function_bool(cf, jt->timerObject, "ontimer");
3052 }
3053 jt->running = false;
3054 skip_execution:
3055
3056 if (!jt->isInterval || jt->deleted) {
3057 if (jt->timerObject) {
3058 char *gc_name =
3059 get_property_string(cf, jt->timerObject,
3060 "backlink");
3061 if (gc_name)
3062 delete_property(cf, cf->winobj, gc_name);
3063 }
3064 delFromList(jt);
3065 nzFree(jt);
3066 } else {
3067 jt->sec = now_sec + jt->jump_sec;
3068 jt->ms = now_ms + jt->jump_ms;
3069 if (jt->ms >= 1000)
3070 jt->ms -= 1000, ++jt->sec;
3071 }
3072
3073 if (gotimers)
3074 jSideEffects();
3075 }
3076
3077 cw = save_cw;
3078 cf = save_cf;
3079 } /* runTimer */
3080
javaOpensWindow(const char * href,const char * name)3081 void javaOpensWindow(const char *href, const char *name)
3082 {
3083 char *copy, *r;
3084 const char *a;
3085 bool replace = false;
3086
3087 if (*href == 'r')
3088 replace = true;
3089 ++href;
3090 if (!*href) {
3091 debugPrint(3, "javascript is opening a blank window");
3092 return;
3093 }
3094
3095 copy = cloneString(href);
3096 unpercentURL(copy);
3097 r = resolveURL(cf->hbase, copy);
3098 nzFree(copy);
3099 if ((replace || cw->browseMode) && foregroundWindow) {
3100 gotoLocation(r, 0, replace);
3101 return;
3102 }
3103
3104 /* Turn the new window into a hyperlink. */
3105 /* just shovel this onto dw, as though it came from document.write() */
3106 dwStart();
3107 stringAndString(&cf->dw, &cf->dw_l, "<P>");
3108 stringAndString(&cf->dw, &cf->dw_l,
3109 i_getString(replace ? MSG_Redirect : MSG_NewWindow));
3110 stringAndString(&cf->dw, &cf->dw_l, ": <A href=");
3111 stringAndString(&cf->dw, &cf->dw_l, r);
3112 stringAndChar(&cf->dw, &cf->dw_l, '>');
3113 a = altText(r);
3114 nzFree(r);
3115 /* I'll assume this is more helpful than the name of the window */
3116 if (a)
3117 name = a;
3118 r = htmlEscape(name);
3119 stringAndString(&cf->dw, &cf->dw_l, r);
3120 nzFree(r);
3121 stringAndString(&cf->dw, &cf->dw_l, "</A><br>\n");
3122 } /* javaOpensWindow */
3123
3124 /* Push an attribute onto an html tag. */
3125 /* Value is already allocated, name is not. */
3126 /* So far only used by javaSetsLinkage. */
setTagAttr(Tag * t,const char * name,char * val)3127 static void setTagAttr(Tag *t, const char *name, char *val)
3128 {
3129 int nattr = 0; /* number of attributes */
3130 int i = -1;
3131 if (!val)
3132 return;
3133 if (t->attributes) {
3134 for (nattr = 0; t->attributes[nattr]; ++nattr)
3135 if (stringEqualCI(name, t->attributes[nattr]))
3136 i = nattr;
3137 }
3138 if (i >= 0) {
3139 cnzFree(t->atvals[i]);
3140 t->atvals[i] = val;
3141 return;
3142 }
3143 /* push */
3144 if (!nattr) {
3145 t->attributes = allocMem(sizeof(char *) * 2);
3146 t->atvals = allocMem(sizeof(char *) * 2);
3147 } else {
3148 t->attributes =
3149 reallocMem(t->attributes, sizeof(char *) * (nattr + 2));
3150 t->atvals = reallocMem(t->atvals, sizeof(char *) * (nattr + 2));
3151 }
3152 t->attributes[nattr] = cloneString(name);
3153 t->atvals[nattr] = val;
3154 ++nattr;
3155 t->attributes[nattr] = 0;
3156 t->atvals[nattr] = 0;
3157 } /* setTagAttr */
3158
javaSetsLinkage(bool after,char type,jsobjtype p_j,const char * rest)3159 void javaSetsLinkage(bool after, char type, jsobjtype p_j, const char *rest)
3160 {
3161 Tag *parent, *add, *before, *c, *t;
3162 jsobjtype *a_j, *b_j;
3163 jsobjtype cx;
3164 char p_name[MAXTAGNAME], a_name[MAXTAGNAME], b_name[MAXTAGNAME];
3165 int action;
3166 char *jst; // javascript string
3167
3168 // Some functions in third.js create, link, and then remove nodes, before
3169 // there is a document. Don't run any side effects in this case.
3170 if (!cw->tags)
3171 return;
3172
3173 sscanf(rest, "%s %p,%s %p,%s ", p_name, &a_j, a_name, &b_j, b_name);
3174 if (type == 'c') { /* create */
3175 parent = tagFromJavaVar2(p_j, p_name);
3176 if (parent) {
3177 debugPrint(4, "linkage, %s %d created",
3178 p_name, parent->seqno);
3179 if (parent->action == TAGACT_INPUT) {
3180 // we need to establish the getter and setter for value
3181 set_property_string_0(parent->f0->cx,
3182 parent->jv, "value", emptyString);
3183 }
3184 }
3185 return;
3186 }
3187
3188 parent = tagFromJavaVar(p_j);
3189 /* options are relinked by rebuildSelectors, not here. */
3190 if (stringEqual(p_name, "option"))
3191 return;
3192
3193 if (stringEqual(a_name, "option"))
3194 return;
3195
3196 add = tagFromJavaVar(a_j);
3197 if (!parent || !add)
3198 return;
3199
3200 if (type == 'r') {
3201 /* add is a misnomer here, it's being removed */
3202 add->deleted = true;
3203 debugPrint(4, "linkage, %s %d removed from %s %d",
3204 a_name, add->seqno, p_name, parent->seqno);
3205 add->parent = NULL;
3206 if (parent->firstchild == add)
3207 parent->firstchild = add->sibling;
3208 else {
3209 c = parent->firstchild;
3210 if (c) {
3211 for (; c->sibling; c = c->sibling) {
3212 if (c->sibling != add)
3213 continue;
3214 c->sibling = add->sibling;
3215 break;
3216 }
3217 }
3218 }
3219 add->sibling = NULL;
3220 return;
3221 }
3222
3223 /* check and see if this link would turn the tree into a circle, whence
3224 * any subsequent traversal would fall into an infinite loop.
3225 * Child node must not have a parent, and, must not link into itself.
3226 * Oddly enough the latter seems to happen on acid3.acidtests.org,
3227 * linking body into body, and body at the top has no parent,
3228 * so passes the "no parent" test, whereupon I had to add the second test. */
3229 if (add->parent || add == parent) {
3230 if (debugLevel >= 3) {
3231 debugPrint(3,
3232 "linkage cycle, cannot link %s %d into %s %d",
3233 a_name, add->seqno, p_name, parent->seqno);
3234 if (type == 'b') {
3235 before = tagFromJavaVar(b_j);
3236 debugPrint(3, "before %s %d", b_name,
3237 (before ? before->seqno : -1));
3238 }
3239 if (add->parent)
3240 debugPrint(3,
3241 "the child already has parent %s %d",
3242 add->parent->info->name,
3243 add->parent->seqno);
3244 debugPrint(3,
3245 "Aborting the link, some data may not be rendered.");
3246 }
3247 return;
3248 }
3249
3250 if (type == 'b') { /* insertBefore */
3251 before = tagFromJavaVar(b_j);
3252 if (!before)
3253 return;
3254 debugPrint(4, "linkage, %s %d linked into %s %d before %s %d",
3255 a_name, add->seqno, p_name, parent->seqno,
3256 b_name, before->seqno);
3257 c = parent->firstchild;
3258 if (!c)
3259 return;
3260 if (c == before) {
3261 parent->firstchild = add;
3262 add->sibling = before;
3263 goto ab;
3264 }
3265 while (c->sibling && c->sibling != before)
3266 c = c->sibling;
3267 if (!c->sibling)
3268 return;
3269 c->sibling = add;
3270 add->sibling = before;
3271 goto ab;
3272 }
3273
3274 /* type = a, appendchild */
3275 debugPrint(4, "linkage, %s %d linked into %s %d",
3276 a_name, add->seqno, p_name, parent->seqno);
3277 if (!parent->firstchild)
3278 parent->firstchild = add;
3279 else {
3280 c = parent->firstchild;
3281 while (c->sibling)
3282 c = c->sibling;
3283 c->sibling = add;
3284 }
3285
3286 ab:
3287 add->parent = parent;
3288 add->deleted = false;
3289
3290 t = add;
3291 debugPrint(4, "fixup %s %d", a_name, t->seqno);
3292 action = t->action;
3293 cx = t->f0->cx;
3294 t->name = get_property_string_0(cx, t->jv, "name");
3295 t->id = get_property_string_0(cx, t->jv, "id");
3296 t->jclass = get_property_string_0(cx, t->jv, "class");
3297
3298 switch (action) {
3299 case TAGACT_INPUT:
3300 jst = get_property_string_0(cx, t->jv, "type");
3301 setTagAttr(t, "type", jst);
3302 t->value = get_property_string_0(cx, t->jv, "value");
3303 htmlInputHelper(t);
3304 break;
3305
3306 case TAGACT_OPTION:
3307 if (!t->value)
3308 t->value = emptyString;
3309 if (!t->textval)
3310 t->textval = emptyString;
3311 break;
3312
3313 case TAGACT_TA:
3314 t->action = TAGACT_INPUT;
3315 t->itype = INP_TA;
3316 t->value = get_property_string_0(cx, t->jv, "value");
3317 if (!t->value)
3318 t->value = emptyString;
3319 // Need to create the side buffer here.
3320 formControl(t, true);
3321 break;
3322
3323 case TAGACT_SELECT:
3324 t->action = TAGACT_INPUT;
3325 t->itype = INP_SELECT;
3326 if (typeof_property_0(cx, t->jv, "multiple"))
3327 t->multiple = true;
3328 formControl(t, true);
3329 break;
3330
3331 case TAGACT_TR:
3332 t->controller = findOpenTag(t, TAGACT_TABLE);
3333 break;
3334
3335 case TAGACT_TD:
3336 t->controller = findOpenTag(t, TAGACT_TR);
3337 break;
3338
3339 } /* switch */
3340 } /* javaSetsLinkage */
3341
3342 /* the new string, the result of the render operation */
3343 static char *ns;
3344 static int ns_l;
3345 static bool invisible, tdfirst;
3346 static Tag *inv2, *inv3; // invisible via css
3347 static int listnest; /* count nested lists */
3348 /* None of these tags nest, so it is reasonable to talk about
3349 * the current open tag. */
3350 static Tag *currentForm, *currentA;
3351
backArrow(char * s)3352 static char *backArrow(char *s)
3353 {
3354 if (!s)
3355 s = ns + ns_l;
3356 while (s > ns) {
3357 if ((uchar) (*--s) == 0xe2 && (uchar) s[1] == 0x89 &&
3358 ((uchar) s[2] == 0xaa || (uchar) s[2] == 0xab))
3359 return s;
3360 }
3361 return 0;
3362 }
3363
backColon(char * s)3364 static char *backColon(char *s)
3365 {
3366 while (s > ns)
3367 if (*--s == ':')
3368 break;
3369 return s;
3370 }
3371
swapArrow(void)3372 static void swapArrow(void)
3373 {
3374 char *s = ns + ns_l - 6;
3375 if (s > ns &&
3376 !strncmp(s, "≫\0020", 5) && (s[5] == '>' || s[5] == '}')) {
3377 strmove(s, s + 3);
3378 strcpy(s + 3, "≫");
3379 }
3380 }
3381
tagInStream(int tagno)3382 static void tagInStream(int tagno)
3383 {
3384 char buf[32];
3385 sprintf(buf, "%c%d*", InternalCodeChar, tagno);
3386 stringAndString(&ns, &ns_l, buf);
3387 } /* tagInStream */
3388
3389 /* see if a number or star is pending, waiting to be printed */
liCheck(Tag * t)3390 static void liCheck(Tag *t)
3391 {
3392 Tag *ltag; /* the list tag */
3393 if (listnest && (ltag = findOpenList(t)) && ltag->post) {
3394 char olbuf[32];
3395 if (ltag->ninp)
3396 tagInStream(ltag->ninp);
3397 if (ltag->action == TAGACT_OL) {
3398 int j = ++ltag->lic;
3399 sprintf(olbuf, "%d. ", j);
3400 } else {
3401 strcpy(olbuf, "* ");
3402 }
3403 if (!invisible)
3404 stringAndString(&ns, &ns_l, olbuf);
3405 ltag->post = false;
3406 }
3407 } /* liCheck */
3408
3409 static Tag *deltag;
3410
renderNode(Tag * t,bool opentag)3411 static void renderNode(Tag *t, bool opentag)
3412 {
3413 int tagno = t->seqno;
3414 Frame *f = t->f0;
3415 char hnum[40]; /* hidden number */
3416 #define ns_hnum() stringAndString(&ns, &ns_l, hnum)
3417 #define ns_ic() stringAndChar(&ns, &ns_l, InternalCodeChar)
3418 int j, l;
3419 int itype; /* input type */
3420 const struct tagInfo *ti = t->info;
3421 int action = t->action;
3422 char c;
3423 bool endcolor;
3424 bool retainTag;
3425 const char *a; /* usually an attribute */
3426 char *u;
3427 Tag *ltag; /* list tag */
3428
3429 debugPrint(6, "rend %c%s", (opentag ? ' ' : '/'), t->info->name);
3430
3431 if (deltag) {
3432 if (t == deltag && !opentag)
3433 deltag = 0;
3434 li_hide:
3435 /* we can skate past the li tag, but still need to increment the count */
3436 if (action == TAGACT_LI && opentag &&
3437 (ltag = findOpenList(t)) && ltag->action == TAGACT_OL)
3438 ++ltag->lic;
3439 return;
3440 }
3441 if (t->deleted) {
3442 deltag = t;
3443 goto li_hide;
3444 }
3445
3446 if (inv2) {
3447 if (inv2 == t)
3448 inv2 = NULL;
3449 return;
3450 }
3451
3452 if (inv3 == t) {
3453 inv3 = NULL;
3454 // I tried to remove an empty invisible section,
3455 // but it's never really empty due to tag markers.
3456 stringAndString(&ns, &ns_l, "\r}'\r");
3457 return;
3458 }
3459
3460 endcolor = false;
3461 if (doColors && !opentag && t->iscolor) {
3462 char *u0, *u1, *u3;
3463 // don't put a color around whitespace
3464 u1 = backArrow(0);
3465 // there should always be a previous color marker
3466 if (!u1)
3467 goto nocolorend;
3468 if ((uchar) u1[2] == 0xab) // close
3469 goto yescolorend;
3470 for (u3 = u1 + 3; *u3; ++u3) {
3471 if (*u3 == InternalCodeChar) {
3472 for (++u3; isdigit(*u3); ++u3) ;
3473 if (*u3 == '*' && !*++u3)
3474 break;
3475 }
3476 if (!isspace(*u3))
3477 goto yescolorend;
3478 }
3479 u0 = backColon(u1);
3480 if (*u0 != ':')
3481 goto yescolorend;
3482 for (u3 = u0 + 1; u3 < u1; ++u3)
3483 if (*u3 != ' ' && !isalpha(*u3))
3484 goto yescolorend;
3485 u1 += 3;
3486 strmove(u0, u1);
3487 ns_l -= (u1 - u0);
3488 goto nocolorend;
3489 yescolorend:
3490 stringAndString(&ns, &ns_l, "≫");
3491 endcolor = true;
3492 }
3493 nocolorend:
3494
3495 if (!opentag && ti->bits & TAG_NOSLASH)
3496 return;
3497
3498 if (opentag && t->jv) {
3499 // what is the visibility now?
3500 uchar v_now = 2;
3501 t->disval =
3502 run_function_onearg(f, f->winobj, "eb$visible", t->jv);
3503 if (t->disval == 1)
3504 v_now = 1;
3505 if (t->disval == 2)
3506 v_now = 3;
3507 if (t->action == TAGACT_TEXT && v_now == 2) {
3508 Tag *y = t;
3509 while (y && y->f0 == f) {
3510 uchar dv = y->disval;
3511 if (dv == DIS_TRANSPARENT)
3512 v_now = 1;
3513 if (dv == DIS_HOVERCOLOR)
3514 v_now = 3;
3515 if (dv >= DIS_COLOR)
3516 break;
3517 y = y->parent;
3518 }
3519 }
3520 // gather some stats
3521 if (v_now == 1)
3522 ++invcount;
3523 if (v_now == 3)
3524 ++hovcount;
3525 if (v_now == 1) {
3526 if (!showHover) {
3527 inv2 = t;
3528 return;
3529 }
3530 if (!inv3) {
3531 inv3 = t;
3532 // merge adjacent invisible sections together
3533 if (ns_l >= 4
3534 && stringEqual(ns + ns_l - 4, "\r}'\r"))
3535 ns_l -= 4;
3536 else
3537 stringAndString(&ns, &ns_l, "\r`{\r");
3538 }
3539 }
3540 if (!showHover && v_now == 3 && !activeBelow(t)) {
3541 inv2 = t;
3542 return;
3543 }
3544 if (action == TAGACT_TEXT && t->jv &&
3545 get_property_bool(f, t->jv, "inj$css")) {
3546 ++injcount;
3547 if (!showHover) {
3548 inv2 = t;
3549 return;
3550 }
3551 }
3552 }
3553
3554 retainTag = true;
3555 if (invisible)
3556 retainTag = false;
3557 if (ti->bits & TAG_INVISIBLE) {
3558 retainTag = false;
3559 invisible = opentag;
3560 /* special case for noscript with no js */
3561 if (action == TAGACT_NOSCRIPT && !f->cx)
3562 invisible = false;
3563 }
3564
3565 if (doColors && opentag) {
3566 char *u0, *u1, *u2, *u3;
3567 jsobjtype so; // style object
3568 char *color = 0, *recolor = 0;
3569 t->iscolor = false;
3570 so = get_property_object(f, t->jv, "style");
3571 if (so)
3572 color = get_property_string(f, so, "color");
3573 if (!color || !color[0])
3574 goto nocolor;
3575 caseShift(color, 'l');
3576 recolor = closeColor(color);
3577 if (!recolor) {
3578 nzFree(color);
3579 goto nocolor;
3580 }
3581 if (recolor != color)
3582 nzFree(color);
3583 if (stringEqual(recolor, "inherit")) { // not a color
3584 nzFree(recolor);
3585 goto nocolor;
3586 }
3587 // is this the same as the previous?
3588 u2 = backArrow(0);
3589 if (!u2)
3590 goto yescolor;
3591 if ((uchar) u2[2] == 0xaa) { // open
3592 u1 = u2;
3593 u2 = 0; // no closing
3594 } else {
3595 u1 = backArrow(u2);
3596 if (!u1 || (uchar) u1[2] != 0xaa)
3597 goto yescolor;
3598 }
3599 // back up to :
3600 u0 = backColon(u1);
3601 if (*u0++ != ':' ||
3602 u1 - u0 != strlen(recolor) || memcmp(u0, recolor, u1 - u0))
3603 goto yescolor;
3604 if (!u2) {
3605 // it's the same color, orange inside orange
3606 nzFree(recolor);
3607 goto nocolor;
3608 }
3609 // merge sections if there are no words in between
3610 for (u3 = u2; *u3; ++u3) {
3611 if (*u3 == InternalCodeChar)
3612 for (++u3; isdigit(*u3); ++u3) ;
3613 if (isalnum(*u3))
3614 goto yescolor;
3615 }
3616 strmove(u2, u2 + 3);
3617 ns_l -= 3;
3618 nzFree(recolor);
3619 t->iscolor = true;
3620 goto nocolor;
3621 yescolor:
3622 stringAndChar(&ns, &ns_l, ':');
3623 stringAndString(&ns, &ns_l, recolor);
3624 stringAndString(&ns, &ns_l, "≪");
3625 nzFree(recolor);
3626 t->iscolor = true;
3627 }
3628 nocolor:
3629
3630 switch (action) {
3631 case TAGACT_TEXT:
3632 if (t->jv) {
3633 // defer to the javascript text.
3634 // either we query js every time, on every piece of text, as we do now,
3635 // or we include a setter so that TextNode.data assignment has a side effect.
3636 char *u = get_property_string(f, t->jv, "data");
3637 if (u) {
3638 nzFree(t->textval);
3639 t->textval = u;
3640 }
3641 }
3642 if (!t->textval)
3643 break;
3644 liCheck(t);
3645 if (!invisible) {
3646 tagInStream(tagno);
3647 stringAndString(&ns, &ns_l, t->textval);
3648 }
3649 break;
3650
3651 case TAGACT_A:
3652 liCheck(t);
3653 currentA = (opentag ? t : 0);
3654 if (!retainTag)
3655 break;
3656 // Javascript might have set or changed this url.
3657 if (opentag && t->jv) {
3658 char *new_url = get_property_url(f, t->jv, false);
3659 if (new_url && *new_url) {
3660 nzFree(t->href);
3661 t->href = new_url;
3662 }
3663 }
3664 if (opentag && !t->href) {
3665 // onclick turns this into a hyperlink.
3666 if (tagHandler(tagno, "onclick"))
3667 t->href = cloneString("#");
3668 }
3669 if (t->href) {
3670 if (opentag) {
3671 sprintf(hnum, "%c%d{", InternalCodeChar, tagno);
3672 if (t->jv
3673 && (a =
3674 get_property_string(f, t->jv, "title"))) {
3675 ++hovcount;
3676 if (showHover) {
3677 stringAndString(&ns, &ns_l, a);
3678 stringAndChar(&ns, &ns_l, ' ');
3679 }
3680 cnzFree(a);
3681 }
3682 } else
3683 sprintf(hnum, "%c0}", InternalCodeChar);
3684 } else {
3685 if (opentag)
3686 sprintf(hnum, "%c%d*", InternalCodeChar, tagno);
3687 else
3688 hnum[0] = 0;
3689 }
3690 ns_hnum();
3691 if (endcolor)
3692 swapArrow();
3693 break;
3694
3695 // check for span onclick and make it look like a link.
3696 // Maybe we should do more than span, but just span for now.
3697 case TAGACT_SPAN:
3698 // If nothing in the span then the title becomes important.
3699 a = 0, u = 0;
3700 if (!t->firstchild && opentag) {
3701 a = attribVal(t, "title");
3702 if (allowJS && t->jv)
3703 u = get_property_string(f, t->jv, "title");
3704 }
3705 // If an onclick function, then turn this into a hyperlink, thus clickable.
3706 // At least one site adds the onclick function via javascript, not html.
3707 // But only at the start, so maybe we only need to check on the first render.
3708 // But maybe some other site adds onclick later. Do we have to check every time?
3709 // This rerender function is getting more and more js intensive!
3710 if (!t->onclick && t->jv && handlerPresent(f, t->jv, "onclick"))
3711 t->onclick = true;
3712 if (!t->onclick) {
3713 // regular span
3714 if (u)
3715 stringAndString(&ns, &ns_l, u), nzFree(u);
3716 else if (a)
3717 stringAndString(&ns, &ns_l, a);
3718 goto nop;
3719 }
3720 // this span has click, so turn into {text}
3721 if (opentag) {
3722 sprintf(hnum, "%c%d{", InternalCodeChar, tagno);
3723 ns_hnum();
3724 if (u)
3725 stringAndString(&ns, &ns_l, u), nzFree(u);
3726 else if (a)
3727 stringAndString(&ns, &ns_l, a);
3728 } else {
3729 sprintf(hnum, "%c0}", InternalCodeChar);
3730 ns_hnum();
3731 if (endcolor)
3732 swapArrow();
3733 }
3734 break;
3735
3736 case TAGACT_OL:
3737 case TAGACT_UL:
3738 t->lic = t->slic;
3739 t->post = false;
3740 if (opentag)
3741 ++listnest;
3742 else
3743 --listnest;
3744 case TAGACT_DL:
3745 case TAGACT_DT:
3746 case TAGACT_DD:
3747 case TAGACT_DIV:
3748 case TAGACT_OBJECT:
3749 case TAGACT_BR:
3750 case TAGACT_P:
3751 case TAGACT_H:
3752 case TAGACT_NOP:
3753 nop:
3754 if (invisible)
3755 break;
3756 j = ti->para;
3757 if (opentag)
3758 j &= 3;
3759 else
3760 j >>= 2;
3761 if (j) {
3762 c = '\f';
3763 if (j == 1) {
3764 c = '\r';
3765 if (action == TAGACT_BR)
3766 c = '\n';
3767 }
3768 stringAndChar(&ns, &ns_l, c);
3769 if (doColors && t->iscolor &&
3770 ns_l > 4 && !memcmp(ns + ns_l - 4, "≪", 3)) {
3771 // move the newline before the color
3772 char *u0 = ns + ns_l - 4;
3773 u0 = backColon(u0);
3774 if (*u0 == ':') {
3775 int j = strlen(u0);
3776 memmove(u0 + 1, u0, j);
3777 *u0 = c;
3778 }
3779 }
3780 if (opentag && action == TAGACT_H) {
3781 strcpy(hnum, ti->name);
3782 strcat(hnum, " ");
3783 ns_hnum();
3784 }
3785 }
3786 /* tags with id= have to be part of the screen, so you can jump to them */
3787 if (t->id && opentag && action != TAGACT_LI)
3788 tagInStream(tagno);
3789 break;
3790
3791 case TAGACT_PRE:
3792 if (!retainTag)
3793 break;
3794 /* one of those rare moments when I really need </tag> in the text stream */
3795 j = (opentag ? tagno : t->balance->seqno);
3796 /* I need to manage the paragraph breaks here, rather than t->info->para,
3797 * which would rule if I simply redirected to nop.
3798 * But the order is wrong if I do that. */
3799 if (opentag)
3800 stringAndChar(&ns, &ns_l, '\f');
3801 sprintf(hnum, "%c%d*", InternalCodeChar, j);
3802 ns_hnum();
3803 if (!opentag)
3804 stringAndChar(&ns, &ns_l, '\f');
3805 break;
3806
3807 case TAGACT_FORM:
3808 currentForm = (opentag ? t : 0);
3809 goto nop;
3810
3811 case TAGACT_INPUT:
3812 if (!retainTag)
3813 break;
3814 if (!opentag) {
3815 // button tag opens and closes, like anchor.
3816 // Check and make sure it's not </select>
3817 if (!stringEqual(t->info->name, "button"))
3818 break;
3819 // <button></button> with no text yields "push".
3820 while (ns_l && isspace(ns[ns_l - 1]))
3821 --ns_l;
3822 if (ns_l >= 3 && ns[ns_l - 1] == '<'
3823 && isdigit(ns[ns_l - 2]))
3824 stringAndString(&ns, &ns_l,
3825 i_getString(MSG_Push));
3826 ns_ic();
3827 stringAndString(&ns, &ns_l, "0>");
3828 if (endcolor)
3829 swapArrow();
3830 break;
3831 }
3832 // value has to be something.
3833 if (!t->value)
3834 t->value = emptyString;
3835 itype = t->itype;
3836 if (itype == INP_HIDDEN)
3837 break;
3838 liCheck(t);
3839 if (itype == INP_TA) {
3840 j = t->lic;
3841 if (j)
3842 sprintf(hnum, "%c%d<buffer %d%c0>",
3843 InternalCodeChar, t->seqno, j,
3844 InternalCodeChar);
3845 else if (t->value[0])
3846 sprintf(hnum, "%c%d<buffer text%c0>",
3847 InternalCodeChar, t->seqno,
3848 InternalCodeChar);
3849 else
3850 sprintf(hnum, "%c%d<buffer ?%c0>",
3851 InternalCodeChar, t->seqno,
3852 InternalCodeChar);
3853 ns_hnum();
3854 break;
3855 }
3856 sprintf(hnum, "%c%d<", InternalCodeChar, tagno);
3857 ns_hnum();
3858 // button stops here, until </button>
3859 if (stringEqual(t->info->name, "button"))
3860 break;
3861 if (itype < INP_RADIO) {
3862 if (t->value[0])
3863 stringAndString(&ns, &ns_l, t->value);
3864 else if (itype == INP_SUBMIT || itype == INP_IMAGE)
3865 stringAndString(&ns, &ns_l,
3866 i_getString(MSG_Submit));
3867 else if (itype == INP_RESET)
3868 stringAndString(&ns, &ns_l,
3869 i_getString(MSG_Reset));
3870 else if (itype == INP_BUTTON)
3871 stringAndString(&ns, &ns_l,
3872 i_getString(MSG_Push));
3873 } else {
3874 // in case js checked or unchecked
3875 if (allowJS && t->jv)
3876 t->checked =
3877 get_property_bool(f, t->jv, "checked");
3878 stringAndChar(&ns, &ns_l, (t->checked ? '+' : '-'));
3879 }
3880 if (currentForm && (itype == INP_SUBMIT || itype == INP_IMAGE)) {
3881 if (currentForm->secure)
3882 stringAndString(&ns, &ns_l,
3883 i_getString(MSG_Secure));
3884 if (currentForm->bymail)
3885 stringAndString(&ns, &ns_l,
3886 i_getString(MSG_Bymail));
3887 }
3888 ns_ic();
3889 stringAndString(&ns, &ns_l, "0>");
3890 break;
3891
3892 case TAGACT_LI:
3893 if ((ltag = findOpenList(t))) {
3894 ltag->post = true;
3895 /* borrow ninp to store the tag number of <li> */
3896 ltag->ninp = t->seqno;
3897 }
3898 goto nop;
3899
3900 case TAGACT_HR:
3901 liCheck(t);
3902 if (retainTag) {
3903 tagInStream(tagno);
3904 stringAndString(&ns, &ns_l, "\r----------\r");
3905 }
3906 break;
3907
3908 case TAGACT_TR:
3909 if (opentag)
3910 tdfirst = true;
3911 case TAGACT_TABLE:
3912 goto nop;
3913
3914 case TAGACT_TD:
3915 if (!retainTag)
3916 break;
3917 if (tdfirst)
3918 tdfirst = false;
3919 else {
3920 liCheck(t);
3921 j = ns_l;
3922 while (j && ns[j - 1] == ' ')
3923 --j;
3924 ns[j] = 0;
3925 ns_l = j;
3926 stringAndChar(&ns, &ns_l, TableCellChar);
3927 }
3928 tagInStream(tagno);
3929 break;
3930
3931 /* This is strictly for rendering math pages written with my particular css.
3932 * <span class=sup> becomes TAGACT_SUP, which means superscript.
3933 * sub is subscript and ovb is overbar.
3934 * Sorry to put my little quirks into this program, but hey,
3935 * it's my program. */
3936 case TAGACT_SUP:
3937 case TAGACT_SUB:
3938 case TAGACT_OVB:
3939 if (!retainTag)
3940 break;
3941 if (action == TAGACT_SUB)
3942 j = 1;
3943 if (action == TAGACT_SUP)
3944 j = 2;
3945 if (action == TAGACT_OVB)
3946 j = 3;
3947 if (opentag) {
3948 static const char *openstring[] = { 0,
3949 "[", "^(", "`"
3950 };
3951 t->lic = ns_l;
3952 liCheck(t);
3953 stringAndString(&ns, &ns_l, openstring[j]);
3954 break;
3955 }
3956 if (j == 3) {
3957 stringAndChar(&ns, &ns_l, '\'');
3958 break;
3959 }
3960 /* backup, and see if we can get rid of the parentheses or brackets */
3961 l = t->lic + j;
3962 u = ns + l;
3963 /* skip past <span> tag indicator */
3964 if (*u == InternalCodeChar) {
3965 ++u;
3966 while (isdigit(*u))
3967 ++u;
3968 ++u;
3969 }
3970 if (j == 2 && isalphaByte(u[0]) && !u[1])
3971 goto unparen;
3972 if (j == 2 && (stringEqual(u, "th") || stringEqual(u, "rd")
3973 || stringEqual(u, "nd") || stringEqual(u, "st"))) {
3974 strmove(ns + l - 2, ns + l);
3975 ns_l -= 2;
3976 break;
3977 }
3978 while (isdigitByte(*u))
3979 ++u;
3980 if (!*u)
3981 goto unparen;
3982 stringAndChar(&ns, &ns_l, (j == 2 ? ')' : ']'));
3983 break;
3984 unparen:
3985 /* ok, we can trash the original ( or [ */
3986 l = t->lic + j;
3987 strmove(ns + l - 1, ns + l);
3988 --ns_l;
3989 if (j == 2)
3990 stringAndChar(&ns, &ns_l, ' ');
3991 break;
3992
3993 case TAGACT_AREA:
3994 case TAGACT_FRAME:
3995 if (!retainTag)
3996 break;
3997
3998 if (t->f1 && !t->contracted) { /* expanded frame */
3999 sprintf(hnum, "\r%c%d*%s\r", InternalCodeChar, tagno,
4000 (opentag ? "`--" : "--`"));
4001 ns_hnum();
4002 break;
4003 }
4004
4005 /* back to unexpanded frame or area */
4006 if (!opentag)
4007 break;
4008 liCheck(t);
4009 stringAndString(&ns, &ns_l,
4010 (action == TAGACT_FRAME ? "\rFrame " : "\r"));
4011 a = 0;
4012 if (action == TAGACT_AREA)
4013 a = attribVal(t, "alt");
4014 u = (char *)a;
4015 if (!u) {
4016 u = t->name;
4017 if (!u)
4018 u = altText(t->href);
4019 }
4020 if (!u)
4021 u = (action == TAGACT_FRAME ? "???" : "area");
4022 if (t->href) {
4023 sprintf(hnum, "%c%d{", InternalCodeChar, tagno);
4024 ns_hnum();
4025 }
4026 if (t->href || action == TAGACT_FRAME)
4027 stringAndString(&ns, &ns_l, u);
4028 if (t->href) {
4029 ns_ic();
4030 stringAndString(&ns, &ns_l, "0}");
4031 }
4032 stringAndChar(&ns, &ns_l, '\r');
4033 if (t->f1 && t->contracted) /* contracted frame */
4034 deltag = t;
4035 break;
4036
4037 case TAGACT_MUSIC:
4038 liCheck(t);
4039 if (!retainTag)
4040 break;
4041 if (!t->href)
4042 break;
4043 sprintf(hnum, "\r%c%d{", InternalCodeChar, tagno);
4044 ns_hnum();
4045 stringAndString(&ns, &ns_l,
4046 (ti->name[0] ==
4047 'b' ? "Background Music" : "Audio passage"));
4048 sprintf(hnum, "%c0}\r", InternalCodeChar);
4049 ns_hnum();
4050 break;
4051
4052 case TAGACT_IMAGE:
4053 liCheck(t);
4054 tagInStream(tagno);
4055 if (!currentA) {
4056 if ((a = attribVal(t, "alt"))) {
4057 u = altText(a);
4058 a = NULL;
4059 /* see if js has changed the alt tag */
4060 if (allowJS && t->jv) {
4061 char *aa =
4062 get_property_string(f, t->jv, "alt");
4063 if (aa)
4064 u = altText(aa);
4065 nzFree(aa);
4066 }
4067 if (u && !invisible) {
4068 stringAndChar(&ns, &ns_l, '[');
4069 stringAndString(&ns, &ns_l, u);
4070 stringAndChar(&ns, &ns_l, ']');
4071 }
4072 }
4073 break;
4074 }
4075 /* image is part of a hyperlink */
4076 if (!retainTag || !currentA->href || currentA->textin)
4077 break;
4078 u = 0;
4079 a = attribVal(t, "alt");
4080 if (a)
4081 u = altText(a);
4082 if (!u)
4083 u = altText(t->name);
4084 if (!u)
4085 u = altText(currentA->href);
4086 if (!u)
4087 u = altText(t->href);
4088 if (!u)
4089 u = "image";
4090 stringAndString(&ns, &ns_l, u);
4091 break;
4092
4093 } /* switch */
4094 } /* renderNode */
4095
4096 /* returns an allocated string */
render(int start)4097 char *render(int start)
4098 {
4099 Frame *f;
4100 for (f = &cw->f0; f; f = f->next)
4101 if (f->cx)
4102 set_property_bool(f, f->winobj, "rr$start", true);
4103 ns = initString(&ns_l);
4104 invisible = false;
4105 inv2 = inv3 = NULL;
4106 listnest = 0;
4107 currentForm = currentA = NULL;
4108 traverse_callback = renderNode;
4109 traverseAll(start);
4110 return ns;
4111 } /* render */
4112
4113 // Create buffers for text areas, so the user can type in comments or whatever
4114 // and send them to the website in a fill-out form.
itext(void)4115 void itext(void)
4116 {
4117 int ln = cw->dot; // line number
4118 pst p; // the raw line to scan
4119 int n;
4120 Tag *t;
4121 char newtext[20];
4122 bool change = false, inp = false;
4123
4124 p = fetchLine(ln, -1);
4125 while (*p != '\n') {
4126 if (*p != InternalCodeChar) {
4127 ++p;
4128 continue;
4129 }
4130 n = strtol((char *)p + 1, (char **)&p, 10);
4131 if (*p != '<')
4132 continue;
4133 inp = true;
4134 t = tagList[n];
4135 if (t->itype != INP_TA || t->lic)
4136 continue;
4137 t->lic = sideBuffer(0, t->value, -1, 0);
4138 change = true;
4139 sprintf(newtext, "buffer %d", t->lic);
4140 // updateFieldInBuffer is crazy inefficient in that it searches through the
4141 // whole buffer, and we know it's on the current line, but really, how often
4142 // do you invoke this command?
4143 updateFieldInBuffer(n, newtext, false, false);
4144 // And now all the pointers are invalid so break out.
4145 // If there's another textarea on the same line you have to issue the command
4146 // again, but really, how often does that happen?
4147 break;
4148 }
4149
4150 if (change)
4151 displayLine(ln);
4152 else if (inp)
4153 i_puts(MSG_NoChange);
4154 else
4155 i_puts(MSG_NoInputFields);
4156 }
4157