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