1 /* buffers.c
2  * Text buffer support routines, manage text and edit sessions.
3  * This file is part of the edbrowse project, released under GPL.
4  */
5 
6 #include "eb.h"
7 
8 #ifndef DOSLIKE
9 #include <sys/select.h>
10 #endif
11 
12 /* If this include file is missing, you need the pcre package,
13  * and the pcre-devel package. */
14 #include <pcre.h>
15 static bool pcre_utf8_error_stop = false;
16 
17 #include <readline/readline.h>
18 #include <readline/history.h>
19 
20 // rename() in linux is all inclusive, moving either files or directories.
21 // The comparable function in windows is MoveFile.
22 #ifdef DOSLIKE
23 #define rename(a, b) MoveFile(a, b)
24 #endif
25 
26 /* temporary, set the frame whenever you set the window. */
27 /* We're one frame per window for now. */
28 #define selfFrame() ( cf = &(cw->f0), cf->owner = cw )
29 
30 /* Static variables for this file. */
31 
32 static uchar dirWrite;		/* directories read write */
33 static bool endMarks;		/* ^ $ on listed lines */
34 /* The valid edbrowse commands. */
35 static const char valid_cmd[] = "aAbBcdDefghHijJklmMnpqrstuvwXz=^&<";
36 /* Commands that can be done in browse mode. */
37 static const char browse_cmd[] = "AbBdDefghHiklMnpqsvwXz=^&<";
38 /* Commands for sql mode. */
39 static const char sql_cmd[] = "AadDefghHiklmnpqrsvwXz=^<";
40 /* Commands for directory mode. */
41 static const char dir_cmd[] = "AbdDefghHklMmnpqstvwXz=^<";
42 /* Commands that work at line number 0, in an empty file. */
43 static const char zero_cmd[] = "aAbefhHMqruwz=^<";
44 /* Commands that expect a space afterward. */
45 static const char spaceplus_cmd[] = "befrw";
46 /* Commands that should have no text after them. */
47 static const char nofollow_cmd[] = "aAcdDhHjlmnptuX=";
48 /* Commands that can be done after a g// global directive. */
49 static const char global_cmd[] = "dDijJlmnpstX";
50 
51 static int startRange, endRange;	/* as in 57,89p */
52 static int destLine;		/* as in 57,89m226 */
53 static int last_z = 1;
54 static char cmd, scmd;
55 static uchar subPrint;		/* print lines after substitutions */
56 static bool noStack;		/* don't stack up edit sessions */
57 static bool globSub;		/* in the midst of a g// command */
58 static bool inscript;		/* run from inside an edbrowse function */
59 static int lastq, lastqq;
60 static char icmd;		/* input command, usually the same as cmd */
61 static bool uriEncoded;
62 
63 /*********************************************************************
64  * If a rendered line contains a hyperlink, the link is indicated
65  * by a code that is stored inline.
66  * If the hyperlink is number 17 on the list of hyperlinks for this window,
67  * it is indicated by InternalCodeChar 17 { text }.
68  * The "text" is what you see on the page, what you click on.
69  * {Click here for more information}.
70  * And the braces tell you it's a hyperlink.
71  * That's just my convention.
72  * The prior chars are for internal use only.
73  * I'm assuming these chars don't/won't appear on the rendered page.
74  * Yeah, sometimes nonascii chars appear, especially if the page is in
75  * a European language, but I just assume a rendered page will not contain
76  * the sequence: InternalCodeChar number {
77  * In fact I assume the rendered text won't contain InternalCodeChar at all.
78  * So I use this char to demark encoded constructs within the lines.
79  * And why do I encode things right in the text?
80  * Well it took me a few versions to reach this point.
81  * But it makes so much sense!
82  * If I move a line, the referenced hyperlink moves with it.
83  * I don't have to update some other structure that says,
84  * "At line 73, characters 29 through 47, that's hyperlink 17."
85  * I use to do it that way, and wow, what a lot of overhead
86  * when you move lines about, or delete them, or make substitutions.
87  * Yes, you really need to change rendered html text,
88  * because that's how you fill out forms.
89  * Add just one letter to the first name in your fill out form,
90  * and the hyperlink that comes later on in the line shifts down.
91  * I use to go back and update the pointers,
92  * so that the hyperlink started at offset 30, rather than 29.
93  * That was a lot of work, and very error prone.
94  * Finally I got smart, and coded the html tags inline.
95  * They can't get lost as text is modified.  They move with the text.
96  *
97  * So now, certain sequences in the text are for internal use only.
98  * This routine strips out these sequences, for display.
99  * After all, you don't want to see those code characters.
100  * You just want to see {Click here for more information}.
101  *
102  * This also checks for special input fields that are masked and
103  * displays stars instead, whenever we would display formated text */
104 
removeHiddenNumbers(pst p,uchar terminate)105 void removeHiddenNumbers(pst p, uchar terminate)
106 {
107 	pst s, t, u;
108 	uchar c, d;
109 	int field;
110 
111 	s = t = p;
112 	while ((c = *s) != terminate) {
113 		if (c != InternalCodeChar) {
114 addchar:
115 			*t++ = c;
116 			++s;
117 			continue;
118 		}
119 		u = s + 1;
120 		d = *u;
121 		if (!isdigitByte(d))
122 			goto addchar;
123 		field = strtol((char *)u, (char **)&u, 10);
124 		d = *u;
125 		if (d == '*') {
126 			s = u + 1;
127 			continue;
128 		}
129 		if (d == '<') {
130 			if (tagList[field]->masked) {
131 				*t++ = d;
132 				while (*++u != InternalCodeChar)
133 					*t++ = '*';
134 			}
135 			s = u;
136 			continue;
137 		}
138 		if (strchr(">{}", d)) {
139 			s = u;
140 			continue;
141 		}
142 /* This is not a code sequence I recognize. */
143 /* This should never happen; just move along. */
144 		goto addchar;
145 	}			/* loop over p */
146 	*t = c;			/* terminating character */
147 }				/* removeHiddenNumbers */
148 
149 /* Fetch line n from the current buffer, or perhaps another buffer.
150  * This returns an allocated copy of the string,
151  * and you need to free it when you're done.
152  * Good grief, how inefficient!
153  * I know, but perl use to do it, and I never noticed the slowdown.
154  * This is not the Linux kernel, we don't need to be ultra-efficient.
155  * We just need to be consistent in our programming efforts.
156  * Sometimes the returned line is changed,
157  * and if it happens sometimes, we may as well make the new copy
158  * all the time, even if the line doesn't change, to be consistent.
159  * You can suppress the copy feature with -1. */
160 
fetchLineContext(int n,int show,int cx)161 static pst fetchLineContext(int n, int show, int cx)
162 {
163 	struct ebWindow *lw = sessionList[cx].lw;
164 	struct lineMap *map, *t;
165 	int dol;
166 	pst p;			/* the resulting copy of the string */
167 
168 	if (!lw)
169 		i_printfExit(MSG_InvalidSession, cx);
170 	map = lw->map;
171 	dol = lw->dol;
172 	if (n <= 0 || n > dol)
173 		i_printfExit(MSG_InvalidLineNb, n);
174 
175 	t = map + n;
176 	if (show < 0)
177 		return t->text;
178 	p = clonePstring(t->text);
179 	if (show && lw->browseMode)
180 		removeHiddenNumbers(p, '\n');
181 	return p;
182 }				/* fetchLineContext */
183 
fetchLine(int n,int show)184 pst fetchLine(int n, int show)
185 {
186 	return fetchLineContext(n, show, context);
187 }				/* fetchLine */
188 
apparentSizeW(const struct ebWindow * w,bool browsing)189 static int apparentSizeW(const struct ebWindow *w, bool browsing)
190 {
191 	int ln, size = 0;
192 	pst p;
193 	if (!w)
194 		return -1;
195 	for (ln = 1; ln <= w->dol; ++ln) {
196 		p = w->map[ln].text;
197 		while (*p != '\n') {
198 			if (*p == InternalCodeChar && browsing && w->browseMode) {
199 				++p;
200 				while (isdigitByte(*p))
201 					++p;
202 				if (strchr("<>{}", *p))
203 					++size;
204 				++p;
205 				continue;
206 			}
207 			++p, ++size;
208 		}
209 		++size;
210 	}			/* loop over lines */
211 	if (w->nlMode)
212 		--size;
213 	return size;
214 }				/* apparentSizeW */
215 
apparentSize(int cx,bool browsing)216 static int apparentSize(int cx, bool browsing)
217 {
218 	const struct ebWindow *w;
219 	if (cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) {
220 		setError(MSG_SessionInactive, cx);
221 		return -1;
222 	}
223 	return apparentSizeW(w, browsing);
224 }				/* apparentSize */
225 
226 /* get the directory suffix for a file.
227  * This only makes sense in directory mode. */
dirSuffixContext(int n,int cx)228 static char *dirSuffixContext(int n, int cx)
229 {
230 	static char suffix[4];
231 	struct ebWindow *lw = sessionList[cx].lw;
232 
233 	suffix[0] = 0;
234 	if (lw->dirMode) {
235 		struct lineMap *s = lw->map + n;
236 		suffix[0] = s->ds1;
237 		suffix[1] = s->ds2;
238 		suffix[2] = 0;
239 	}
240 	return suffix;
241 }				/* dirSuffixContext */
242 
dirSuffix(int n)243 static char *dirSuffix(int n)
244 {
245 	return dirSuffixContext(n, context);
246 }				/* dirSuffix */
247 
248 /* Display a line to the screen, with a limit on output length. */
displayLine(int n)249 void displayLine(int n)
250 {
251 	pst line = fetchLine(n, 1);
252 	pst s = line;
253 	int cnt = 0;
254 	uchar c;
255 	int output_l = 0;
256 	char *output = initString(&output_l);
257 	char buf[10];
258 
259 	if (cmd == 'n') {
260 		stringAndNum(&output, &output_l, n);
261 		stringAndChar(&output, &output_l, ' ');
262 	}
263 	if (endMarks && cmd == 'l')
264 		stringAndChar(&output, &output_l, '^');
265 
266 	while ((c = *s++) != '\n') {
267 		bool expand = false;
268 		if (c == 0 || c == '\r' || c == '\x1b' || c == '\x0e'
269 		    || c == '\x0f')
270 			expand = true;
271 		if (cmd == 'l') {
272 /* show tabs and backspaces, ed style */
273 			if (c == '\b')
274 				c = '<';
275 			if (c == '\t')
276 				c = '>';
277 			if (c < ' ' || c == 0x7f || (c >= 0x80 && listNA))
278 				expand = true;
279 		}
280 		if (expand) {
281 			sprintf(buf, "~%02X", c), cnt += 3;
282 			stringAndString(&output, &output_l, buf);
283 		} else
284 			stringAndChar(&output, &output_l, c), ++cnt;
285 		if (cnt >= displayLength)
286 			break;
287 	}			/* loop over line */
288 
289 	if (cnt >= displayLength)
290 		stringAndString(&output, &output_l, "...");
291 	if (cw->dirMode) {
292 		stringAndString(&output, &output_l, dirSuffix(n));
293 		if (cw->r_map) {
294 			s = cw->r_map[n].text;
295 			if (*s) {
296 				stringAndChar(&output, &output_l, ' ');
297 				stringAndString(&output, &output_l, (char *)s);
298 			}
299 		}
300 	}
301 	if (endMarks && cmd == 'l')
302 		stringAndChar(&output, &output_l, '$');
303 	eb_puts(output);
304 
305 	free(line);
306 	nzFree(output);
307 }				/* displayLine */
308 
printDot(void)309 static void printDot(void)
310 {
311 	if (cw->dot)
312 		displayLine(cw->dot);
313 	else
314 		i_puts(MSG_Empty);
315 }				/* printDot */
316 
317 // These commands pass through jdb and on to normal edbrowse processing.
jdb_passthrough(const char * s)318 static bool jdb_passthrough(const char *s)
319 {
320 	static const char *const oklist[] = {
321 		"dberr", "dberr+", "dberr-",
322 		"dbcn", "dbcn+", "dbcn-",
323 		"dbev", "dbev+", "dbev-",
324 		"dbcss", "dbcss+", "dbcss-",
325 		"timers", "timers+", "timers-",
326 		"demin", "demin+", "demin-",
327 		"bflist", "bglist", "help", 0
328 	};
329 	int i;
330 	if (s[0] == '!')
331 		return true;
332 	if (s[0] == 'd' && s[1] == 'b' && isdigit(s[2]) && s[3] == 0)
333 		return true;
334 	if (stringInList(oklist, s) >= 0)
335 		return true;
336 	if (s[0] == 'e' && isdigit(s[1])) {
337 		for (i = 2; s[i]; ++i)
338 			if (!isdigit(s[i]))
339 				break;
340 		if (!s[i])
341 			return true;
342 	}
343 	return false;
344 }
345 
346 /* By default, readline's filename completion appends a single space
347  * character to a filename when there are no alternative completions.
348  * Since the r, w, and e commands treat spaces literally, this default
349  * causes tab completion to behave unintuitively in edbrowse.
350  * Solution: write our own completion function,
351  * which wraps readline's filename completer.  */
352 
edbrowse_completion(const char * text,int state)353 static char *edbrowse_completion(const char *text, int state)
354 {
355 	rl_completion_append_character = '\0';
356 	return rl_filename_completion_function(text, state);
357 }				/* edbrowse_completion */
358 
initializeReadline(void)359 void initializeReadline(void)
360 {
361 	rl_completion_entry_function = edbrowse_completion;
362 }				/* initializeReadline */
363 
364 #ifdef DOSLIKE
365 /* unix can use the select function on a file descriptor, like stdin
366    this function provides a work around for windows */
select_stdin(struct timeval * ptv)367 int select_stdin(struct timeval *ptv)
368 {
369 	int ms_delay = 55;
370 	int delay_secs = ptv->tv_sec;
371 	int delay_ms = ptv->tv_usec / 1000;
372 	int res = _kbhit();
373 	while (!res && (delay_secs || delay_ms)) {
374 		if (!delay_secs && (delay_ms < ms_delay))
375 			ms_delay = delay_ms;	// reduce this last sleep
376 		Sleep(ms_delay);
377 		if (delay_ms >= ms_delay)
378 			delay_ms -= ms_delay;
379 		else {
380 			if (delay_secs) {
381 				delay_ms += 1000;
382 				delay_secs--;
383 			}
384 			if (delay_ms >= ms_delay)
385 				delay_ms -= ms_delay;
386 			else
387 				delay_ms = 0;
388 		}
389 		res = _kbhit();
390 	}
391 	return res;
392 }
393 #endif
394 
395 /*********************************************************************
396 Get a line from standard in.  Need not be a terminal.
397 This routine returns the line in a string, which is allocated,
398 but nonetheless you should not free it; I free it upon the next input call.
399 It has to be allocated because of readline().
400 
401 ~xx is converted from hex, a way to enter nonascii chars.
402 This is the opposite of displayLine() above.
403 But you can't enter a newline this way; I won't permit it.
404 The only newline is the one corresponding to the end of your text,
405 when you hit enter. This terminates the line.
406 As we described earlier, this is a perl string.
407 It may contain nulls, and is terminated by newline.
408 Enter ~u hex digits for higher unicodes, emojis etc.
409 The program exits on EOF.
410 If you hit interrupt at this point, I print a message
411 and ask for your line again.
412 
413 Here are some thoughts on when to rerender and announce any changes to the user.
414 I rerender here because this is the foreground window, and I'm looking for input.
415 Other buffers could be changing, but that can wait; you're not looking at those right now.
416 If you switch to one of those, you may get a change message after the context switch.
417 So here's what I want.
418 If you have pushed a button or made a change to a form that invokes js,
419 then you want to see any changes, right now, that result from that action.
420 However, timers can also change the buffer in the background.
421 A line could be updating every second, but no one wants to see that.
422 So only rerender every 20 seconds.
423 mustrender is set if any js happens, and cleared after rerender.
424 It starts out clear when the buffer is first browsed.
425 nextrender is the suggested time to render again, if js changes have been made.
426 It is generally 20 seconds after the last render.
427 *********************************************************************/
428 
inputLine(void)429 pst inputLine(void)
430 {
431 	static char line[MAXTTYLINE];
432 	int i, j, len;
433 	uchar c, d, e;
434 	static char *last_rl, *s;
435 	int delay_sec, delay_ms;
436 
437 top:
438 	intFlag = false;
439 	inInput = true;
440 	intStart = 0;
441 	nzFree(last_rl);
442 	last_rl = 0;
443 	s = 0;
444 
445 // I guess this is as good a place as any to collect dead tags.
446 #if 0
447 // Not ready to do this yet, have to coordinate this with rerender.
448 	tag_gc();
449 #endif
450 
451 	if (timerWait(&delay_sec, &delay_ms)) {
452 /* timers are pending, use select to wait on input or run the first timer. */
453 		fd_set channels;
454 		int rc;
455 		struct timeval tv;
456 
457 /* a false timer will fire when time to rerender */
458 		if (cw->mustrender) {
459 			time_t now;
460 			time(&now);
461 			if (now >= cw->nextrender) {
462 				jSyncup(true);
463 				rerender(false);
464 			}
465 		}
466 
467 		tv.tv_sec = delay_sec;
468 		tv.tv_usec = delay_ms * 1000;
469 #ifdef DOSLIKE
470 		rc = select_stdin(&tv);
471 #else
472 		memset(&channels, 0, sizeof(channels));
473 		FD_SET(0, &channels);
474 		rc = select(1, &channels, 0, 0, &tv);
475 #endif
476 		if (rc < 0)
477 			goto interrupt;
478 		if (rc == 0) {	/* timeout */
479 			inInput = false;
480 			runTimer();
481 			inInput = true;
482 			if (newlocation && intFlag) {
483 				i_puts(MSG_RedirectionInterrupted);
484 				goto top;
485 			}
486 			intFlag = false;
487 
488 /* in case a timer set document.location to a new page, or opens a new window */
489 			if (newlocation) {
490 				debugPrint(2, "redirect %s", newlocation);
491 				if (newloc_f->owner != cw) {
492 /* background window; we shouldn't even be here! */
493 /* Such redirections are turned into hyperlinks on the page. */
494 #if 0
495 					printf((newloc_r ?
496 						"redirection of a background window to %s is not implemented\n"
497 						:
498 						"background window opening %s is not implemented\n"),
499 					       newlocation);
500 #endif
501 					nzFree(newlocation);
502 					newlocation = 0;
503 				} else {
504 					s = allocMem(strlen(newlocation) + 8);
505 					sprintf(s, "%sb %s\n",
506 						(newloc_r ? "ReF@" : ""),
507 						newlocation);
508 					nzFree(newlocation);
509 					newlocation = 0;
510 					return (uchar *) s;
511 				}
512 			}
513 			goto top;
514 		}
515 	}
516 
517 	jClearSync();
518 	if (cw->mustrender) {
519 /* in case jSyncup runs again */
520 		rebuildSelectors();
521 	}
522 
523 	if (inputReadLine && isInteractive) {
524 		last_rl = readline("");
525 		if ((last_rl != NULL) && *last_rl)
526 			add_history(last_rl);
527 		s = last_rl;
528 	} else {
529 		while (fgets(line, sizeof(line), stdin)) {
530 /* A bug in my keyboard causes nulls to be entered from time to time. */
531 			c = 0;
532 			i = 0;
533 			while (i < sizeof(line) - 1 && (c = line[i]) != '\n') {
534 				if (c == 0)
535 					line[i] = ' ';
536 				++i;
537 			}
538 			if (last_rl) {
539 // paste this line piece onto the growing line.
540 // This is not very efficient, but it hardly ever happens.
541 				len = strlen(last_rl);
542 // with nulls transliterated, strlen() returns the right answer
543 				i = strlen(line);
544 				last_rl = reallocMem(last_rl, len + i + 1);
545 				strcpy(last_rl + len, line);
546 			}
547 			if (c == '\n')
548 				goto tty_complete;
549 			if (!last_rl)
550 				last_rl = cloneString(line);
551 		}
552 		goto interrupt;
553 tty_complete:
554 		if (last_rl)
555 			s = last_rl;
556 		else
557 			s = line;
558 	}
559 
560 	if (!s) {
561 interrupt:
562 		if (intFlag)
563 			goto top;
564 		i_puts(MSG_EndFile);
565 		ebClose(1);
566 	}
567 	inInput = false;
568 	intFlag = false;
569 
570 	i = j = 0;
571 	if (last_rl) {
572 		len = strlen(s);
573 	} else {
574 		len = sizeof(line) - 1;
575 	}
576 
577 	if (debugFile)
578 		fprintf(debugFile, "* ");
579 	while (i < len && (c = s[i]) != '\n') {
580 		if (debugFile)
581 			fputc(c, debugFile);
582 		if (c != '~') {
583 addchar:
584 			s[j++] = c;
585 			++i;
586 			continue;
587 		}
588 		d = s[i + 1];
589 		if (d == '~') {
590 			++i;
591 			goto addchar;
592 		}
593 		e = 0;
594 		if (d)
595 			e = s[i + 2];
596 		if (d == 'u' && isxdigit(e)) {
597 			unsigned int unicode;
598 			char *t;
599 			int l;
600 			unicode = strtol((char *)s + i + 2, &t, 16);
601 			if (*t == ';')
602 				++t;
603 			i = t - s;
604 			if (cons_utf8) {
605 				t = uni2utf8(unicode);
606 				l = strlen(t);
607 				memcpy(s + j, t, l);
608 				j += l;
609 			} else {
610 				t[j++] = (c <= 0xff ? c : '?');
611 			}
612 			continue;
613 		}
614 		if (!isxdigit(d) || !isxdigit(e))
615 			goto addchar;
616 		c = fromHex(d, e);
617 		if (c == '\n')
618 			c = 0;
619 		i += 2;
620 		goto addchar;
621 	}			/* loop over input chars */
622 	s[j] = 0;
623 	if (debugFile)
624 		fputc('\n', debugFile);
625 
626 	if (cw->jdb_frame) {
627 // some edbrowse commands pass through.
628 		if (jdb_passthrough(s))
629 			goto eb_line;
630 		cf = cw->jdb_frame;
631 		if (stringEqual(s, ".") || stringEqual(s, "bye")) {
632 			cw->jdb_frame = NULL;
633 			puts("bye");
634 			jSideEffects();
635 // in case you changed objects that in turn change the screen.
636 			rerender(false);
637 		} else {
638 			char *resfile = NULL;
639 			char *result;
640 			FILE *f = NULL;
641 // ^> indicates redirection, since > might be a greater than operator.
642 			resfile = strstr(s, "^>");
643 			if (resfile) {
644 				*resfile = 0;
645 				resfile += 2;
646 				while (isspace(*resfile))
647 					++resfile;
648 			}
649 			result = jsRunScriptResult(cf, cf->winobj, s, "jdb", 1);
650 			if (resfile)
651 				f = fopen(resfile, "w");
652 			if (result) {
653 				if (f) {
654 					fprintf(f, "%s\n", result);
655 					printf("%zu bytes\n", strlen(result));
656 				} else
657 					puts(result);
658 			}
659 			nzFree(result);
660 			if (f)
661 				fclose(f);
662 			if (newlocation) {
663 				puts("sorry, page redirection is not honored under jdb.");
664 				nzFree(newlocation);
665 				newlocation = 0;
666 				newlocation = 0;
667 			}
668 		}
669 		goto top;
670 	}
671 
672 eb_line:
673 /* rest of edbrowse expects this line to be nl terminated */
674 	s[j] = '\n';
675 	return (uchar *) s;
676 }				/* inputLine */
677 
678 static struct {
679 	char lhs[MAXRE], rhs[MAXRE];
680 	bool lhs_yes, rhs_yes;
681 } globalSubs;
682 
saveSubstitutionStrings(void)683 static void saveSubstitutionStrings(void)
684 {
685 	if (!searchStringsAll)
686 		return;
687 	if (!cw)
688 		return;
689 	globalSubs.lhs_yes = cw->lhs_yes;
690 	strcpy(globalSubs.lhs, cw->lhs);
691 	globalSubs.rhs_yes = cw->rhs_yes;
692 	strcpy(globalSubs.rhs, cw->rhs);
693 }				/* saveSubstitutionStrings */
694 
restoreSubstitutionStrings(struct ebWindow * nw)695 static void restoreSubstitutionStrings(struct ebWindow *nw)
696 {
697 	if (!searchStringsAll)
698 		return;
699 	if (!nw)
700 		return;
701 	nw->lhs_yes = globalSubs.lhs_yes;
702 	strcpy(nw->lhs, globalSubs.lhs);
703 	nw->rhs_yes = globalSubs.rhs_yes;
704 	strcpy(nw->rhs, globalSubs.rhs);
705 }				/* restoreSubstitutionStrings */
706 
707 /* Create a new window, with default variables. */
createWindow(void)708 static struct ebWindow *createWindow(void)
709 {
710 	struct ebWindow *nw;	/* the new window */
711 	nw = allocZeroMem(sizeof(struct ebWindow));
712 	saveSubstitutionStrings();
713 	restoreSubstitutionStrings(nw);
714 	nw->f0.gsn = ++gfsn;
715 	return nw;
716 }				/* createWindow */
717 
718 /* for debugging */
print_pst(pst p)719 static void print_pst(pst p)
720 {
721 	do {
722 		if (debugFile)
723 			fprintf(debugFile, "%c", *p);
724 		else
725 			printf("%c", *p);
726 	} while (*p++ != '\n');
727 }				/* print_pst */
728 
freeLine(struct lineMap * t)729 static void freeLine(struct lineMap *t)
730 {
731 	if (debugLevel >= 8) {
732 		if (debugFile)
733 			fprintf(debugFile, "free ");
734 		else
735 			printf("free ");
736 		print_pst(t->text);
737 	}
738 	nzFree(t->text);
739 }				/* freeLine */
740 
freeWindowLines(struct lineMap * map)741 static void freeWindowLines(struct lineMap *map)
742 {
743 	struct lineMap *t;
744 	int cnt = 0;
745 
746 	if ((t = map)) {
747 		for (++t; t->text; ++t) {
748 			freeLine(t);
749 			++cnt;
750 		}
751 		free(map);
752 	}
753 
754 	debugPrint(6, "freeWindowLines = %d", cnt);
755 }				/* freeWindowLines */
756 
757 /*********************************************************************
758 Garbage collection for text lines.
759 There is an undo window that holds a snapshot of the buffer as it was before.
760 not a copy of all the text, but a copy of map,
761 and dot and dollar and some other things.
762 This starts out with map = 0.
763 The u command swaps undoWindow and current window (cw).
764 Thus another u undoes your undo.
765 You can step back through all your changes,
766 popping a stack like a modern editor. Sorry.
767 Just don't screw up more than once,
768 and don't save your file til you're sure it's ok.
769 No autosave feature here, I never liked that anyways.
770 So at the start of every command not under g//, set madeChanges = false.
771 If we're about to change something in the buffer, set madeChanges = true.
772 But if madeChanges was false, i.e. this is the first change coming,
773 call undoPush().
774 This pushes the undo window off the cliff,
775 and that means we have to free the text first.
776 Call undoCompare().
777 This finds any lines in the undo window that aren't in cw and frees them.
778 That is a quicksort on both maps and then a comm -23.
779 Then it frees undoWindow.map just to make sure we don't free things twice.
780 Then undoPush copies cw onto undoWindow, ready for the u command.
781 Return, and the calling function makes its change.
782 But we call undoCompare at other times, like switching buffers,
783 pop the window stack, browse, or quit.
784 These make undo impossible, so free the lines in the undo window.
785 *********************************************************************/
786 
787 static bool madeChanges;
788 static struct ebWindow undoWindow;
789 
790 /* quick sort compare */
qscmp(const void * s,const void * t)791 static int qscmp(const void *s, const void *t)
792 {
793 	return memcmp(s, t, sizeof(char *));
794 }				/* qscmp */
795 
796 /* Free undo lines not used by the current session. */
undoCompare(void)797 static void undoCompare(void)
798 {
799 	const struct lineMap *cmap = cw->map;
800 	struct lineMap *map = undoWindow.map;
801 	struct lineMap *cmap2;
802 	struct lineMap *s, *t;
803 	int diff, cnt = 0;
804 
805 	if (!cmap) {
806 		debugPrint(6, "undoCompare no current map");
807 		freeWindowLines(undoWindow.map);
808 		undoWindow.map = 0;
809 		return;
810 	}
811 
812 	if (!map) {
813 		debugPrint(6, "undoCompare no undo map");
814 		return;
815 	}
816 
817 /* sort both arrays, run comm, and find out which lines are not needed any more,
818 then free them.
819  * Use quick sort; some files are a million lines long.
820  * I have to copy the second array, so I can sort it.
821  * The first I can sort in place, cause it's going to get thrown away. */
822 
823 	cmap2 = allocMem((cw->dol + 2) * LMSIZE);
824 	memcpy(cmap2, cmap, (cw->dol + 2) * LMSIZE);
825 	debugPrint(8, "qsort %d %d", undoWindow.dol, cw->dol);
826 	qsort(map + 1, undoWindow.dol, LMSIZE, qscmp);
827 	qsort(cmap2 + 1, cw->dol, LMSIZE, qscmp);
828 
829 	s = map + 1;
830 	t = cmap2 + 1;
831 	while (s->text && t->text) {
832 		diff = memcmp(s, t, sizeof(char *));
833 		if (!diff) {
834 			++s, ++t;
835 			continue;
836 		}
837 		if (diff > 0) {
838 			++t;
839 			continue;
840 		}
841 		freeLine(s);
842 		++s;
843 		++cnt;
844 	}
845 
846 	while (s->text) {
847 		freeLine(s);
848 		++s;
849 		++cnt;
850 	}
851 
852 	free(cmap2);
853 	free(map);
854 	undoWindow.map = 0;
855 	debugPrint(6, "undoCompare strip %d", cnt);
856 }				/* undoCompare */
857 
undoPush(void)858 static void undoPush(void)
859 {
860 	struct ebWindow *uw;
861 
862 /* if in browse mode, we really shouldn't be here at all!
863  * But we could if substituting on an input field, since substitute is also
864  * a regular ed command. */
865 	if (cw->browseMode)
866 		return;
867 
868 	if (madeChanges)
869 		return;
870 	madeChanges = true;
871 
872 	cw->undoable = true;
873 	if (!cw->quitMode)
874 		cw->changeMode = true;
875 
876 	undoCompare();
877 
878 	uw = &undoWindow;
879 	uw->dot = cw->dot;
880 	uw->dol = cw->dol;
881 	memcpy(uw->labels, cw->labels, MARKLETTERS * sizeof(int));
882 	uw->binMode = cw->binMode;
883 	uw->nlMode = cw->nlMode;
884 	uw->dirMode = cw->dirMode;
885 	if (cw->map) {
886 		uw->map = allocMem((cw->dol + 2) * LMSIZE);
887 		memcpy(uw->map, cw->map, (cw->dol + 2) * LMSIZE);
888 	}
889 }				/* undoPush */
890 
freeWindow(struct ebWindow * w)891 static void freeWindow(struct ebWindow *w)
892 {
893 	Frame *f, *fnext;
894 	struct histLabel *label, *lnext;
895 	freeTags(w);
896 	for (f = &w->f0; f; f = fnext) {
897 		fnext = f->next;
898 		delTimers(f);
899 		freeJavaContext(f);
900 		nzFree(f->dw);
901 		nzFree(f->hbase);
902 		nzFree(f->fileName);
903 		nzFree(f->firstURL);
904 		if (f != &w->f0)
905 			free(f);
906 	}
907 	lnext = w->histLabel;
908 	while ((label = lnext)) {
909 		lnext = label->prev;
910 		free(label);
911 	}
912 	freeWindowLines(w->map);
913 	freeWindowLines(w->r_map);
914 	nzFree(w->htmltitle);
915 	nzFree(w->htmldesc);
916 	nzFree(w->htmlkey);
917 	nzFree(w->saveURL);
918 	nzFree(w->mailInfo);
919 	nzFree(w->referrer);
920 	nzFree(w->baseDirName);
921 	free(w);
922 }				/* freeWindow */
923 
924 /*********************************************************************
925 Here are a few routines to switch contexts from one buffer to another.
926 This is how the user edits multiple sessions, or browses multiple
927 web pages, simultaneously.
928 *********************************************************************/
929 
cxCompare(int cx)930 bool cxCompare(int cx)
931 {
932 	if (cx == 0) {
933 		setError(MSG_Session0);
934 		return false;
935 	}
936 	if (cx >= MAXSESSION) {
937 		setError(MSG_SessionHigh, cx, MAXSESSION - 1);
938 		return false;
939 	}
940 	if (cx != context)
941 		return true;	/* ok */
942 	setError(MSG_SessionCurrent, cx);
943 	return false;
944 }				/*cxCompare */
945 
946 /* is a context active? */
cxActive(int cx)947 bool cxActive(int cx)
948 {
949 	if (cx <= 0 || cx >= MAXSESSION)
950 		i_printfExit(MSG_SessionOutRange, cx);
951 	if (sessionList[cx].lw)
952 		return true;
953 	setError(MSG_SessionInactive, cx);
954 	return false;
955 }				/* cxActive */
956 
cxInit(int cx)957 static void cxInit(int cx)
958 {
959 	struct ebWindow *lw = createWindow();
960 	if (sessionList[cx].lw)
961 		i_printfExit(MSG_DoubleInit, cx);
962 	sessionList[cx].fw = sessionList[cx].lw = lw;
963 	if (cx > maxSession)
964 		maxSession = cx;
965 }				/* cxInit */
966 
cxQuit(int cx,int action)967 bool cxQuit(int cx, int action)
968 {
969 	struct ebWindow *w = sessionList[cx].lw;
970 	if (!w)
971 		i_printfExit(MSG_QuitNoActive, cx);
972 
973 /* action = 3 means we can trash data */
974 	if (action == 3) {
975 		w->changeMode = false;
976 		action = 2;
977 	}
978 
979 /* We might be trashing data, make sure that's ok. */
980 	if (w->changeMode &&
981 /* something in the buffer has changed */
982 	    lastq != cx &&
983 /* last command was not q */
984 	    !ismc &&
985 /* not in fetchmail mode, which uses the same buffer over and over again */
986 	    !(w->dirMode | w->sqlMode) &&
987 /* not directory or sql mode */
988 	    (!w->f0.fileName || !isURL(w->f0.fileName))) {
989 /* not changing a url */
990 		lastqq = cx;
991 		setError(MSG_ExpectW);
992 		if (cx != context)
993 			setError(MSG_ExpectWX, cx);
994 		return false;
995 	}
996 
997 	if (!action)
998 		return true;	/* just a test */
999 
1000 	if (cx == context) {
1001 /* Don't need to retain the undo lines. */
1002 		undoCompare();
1003 	}
1004 
1005 	if (action == 2) {
1006 		while (w) {
1007 			struct ebWindow *p = w->prev;
1008 			freeWindow(w);
1009 			w = p;
1010 		}
1011 		sessionList[cx].fw = sessionList[cx].lw = 0;
1012 	} else
1013 		freeWindow(w);
1014 
1015 	if (cx == context) {
1016 		cw = 0;
1017 		cf = 0;
1018 	}
1019 
1020 	return true;
1021 }				/* cxQuit */
1022 
1023 /* Switch to another edit session.
1024  * This assumes cxCompare has succeeded - we're moving to a different context.
1025  * Pass the context number and an interactive flag. */
cxSwitch(int cx,bool interactive)1026 void cxSwitch(int cx, bool interactive)
1027 {
1028 	bool created = false;
1029 	struct ebWindow *nw = sessionList[cx].lw;	/* the new window */
1030 	if (!nw) {
1031 		cxInit(cx);
1032 		nw = sessionList[cx].lw;
1033 		created = true;
1034 	} else {
1035 		saveSubstitutionStrings();
1036 		restoreSubstitutionStrings(nw);
1037 	}
1038 
1039 	if (cw) {
1040 		undoCompare();
1041 		cw->undoable = false;
1042 	}
1043 	cw = nw;
1044 	selfFrame();
1045 	cs = sessionList + cx;
1046 	context = cx;
1047 	if (interactive && debugLevel) {
1048 		if (created)
1049 			i_printf(MSG_SessionNew);
1050 		else if (cf->fileName)
1051 			i_printf(MSG_String, cf->fileName);
1052 		else
1053 			i_printf(MSG_NoFile);
1054 		if (cw->jdb_frame)
1055 			i_printf(MSG_String, " jdb");
1056 		eb_puts("");
1057 	}
1058 
1059 /* The next line is required when this function is called from main(),
1060  * when the first arg is a url and there is a second arg. */
1061 	startRange = endRange = cw->dot;
1062 }				/* cxSwitch */
1063 
1064 static struct lineMap *newpiece;
1065 
1066 /* Adjust the map of line numbers -- we have inserted text.
1067  * Also shift the downstream labels.
1068  * Pass the string containing the new line numbers, and the dest line number. */
addToMap(int nlines,int destl)1069 static void addToMap(int nlines, int destl)
1070 {
1071 	struct lineMap *newmap;
1072 	int i, ln;
1073 	int svdol = cw->dol;
1074 
1075 	if (nlines == 0)
1076 		i_printfExit(MSG_EmptyPiece);
1077 
1078 	if (sizeof(int) == 4) {
1079 		if (nlines > MAXLINES - cw->dol)
1080 			i_printfExit(MSG_LineLimit);
1081 	}
1082 
1083 /* browse has no undo command */
1084 	if (!(cw->browseMode | cw->dirMode))
1085 		undoPush();
1086 
1087 /* adjust labels */
1088 	for (i = 0; i < MARKLETTERS; ++i) {
1089 		ln = cw->labels[i];
1090 		if (ln <= destl)
1091 			continue;
1092 		cw->labels[i] += nlines;
1093 	}
1094 	cw->dot = destl + nlines;
1095 	cw->dol += nlines;
1096 
1097 	newmap = allocMem((cw->dol + 2) * LMSIZE);
1098 	if (destl)
1099 		memcpy(newmap, cw->map, (destl + 1) * LMSIZE);
1100 	else
1101 		memset(newmap, 0, LMSIZE);
1102 /* insert new piece here */
1103 	memcpy(newmap + destl + 1, newpiece, nlines * LMSIZE);
1104 /* put on the last piece */
1105 	if (destl < svdol)
1106 		memcpy(newmap + destl + nlines + 1, cw->map + destl + 1,
1107 		       (svdol - destl + 1) * LMSIZE);
1108 	else
1109 		memset(newmap + destl + nlines + 1, 0, LMSIZE);
1110 
1111 	nzFree(cw->map);
1112 	cw->map = newmap;
1113 	free(newpiece);
1114 	newpiece = 0;
1115 }				/* addToMap */
1116 
1117 /* Add a block of text into the buffer; uses addToMap(). */
addTextToBuffer(const pst inbuf,int length,int destl,bool showtrail)1118 bool addTextToBuffer(const pst inbuf, int length, int destl, bool showtrail)
1119 {
1120 	int i, j, linecount = 0;
1121 	struct lineMap *t;
1122 
1123 	if (!length)		// nothing to add
1124 		return true;
1125 
1126 	for (i = 0; i < length; ++i)
1127 		if (inbuf[i] == '\n') {
1128 			++linecount;
1129 			if (sizeof(int) == 4) {
1130 				if (linecount + cw->dol > MAXLINES)
1131 					i_printfExit(MSG_LineLimit);
1132 			}
1133 		}
1134 
1135 	if (destl == cw->dol)
1136 		cw->nlMode = false;
1137 	if (inbuf[length - 1] != '\n') {
1138 /* doesn't end in newline */
1139 		++linecount;	/* last line wasn't counted */
1140 		if (destl == cw->dol) {
1141 			cw->nlMode = true;
1142 			if (cmd != 'b' && !cw->binMode && showtrail)
1143 				i_puts(MSG_NoTrailing);
1144 		}
1145 	}
1146 
1147 	newpiece = t = allocZeroMem(linecount * LMSIZE);
1148 	i = 0;
1149 	while (i < length) {	/* another line */
1150 		j = i;
1151 		while (i < length)
1152 			if (inbuf[i++] == '\n')
1153 				break;
1154 		if (inbuf[i - 1] == '\n') {
1155 /* normal line */
1156 			t->text = allocMem(i - j);
1157 		} else {
1158 /* last line with no nl */
1159 			t->text = allocMem(i - j + 1);
1160 			t->text[i - j] = '\n';
1161 		}
1162 		memcpy(t->text, inbuf + j, i - j);
1163 		++t;
1164 	}			/* loop breaking inbuf into lines */
1165 
1166 	addToMap(linecount, destl);
1167 	return true;
1168 }				/* addTextToBuffer */
1169 
1170 /* Pass input lines straight into the buffer, until the user enters . */
1171 
inputLinesIntoBuffer(void)1172 static bool inputLinesIntoBuffer(void)
1173 {
1174 	pst line;
1175 	int linecount = 0, cap;
1176 	struct lineMap *t;
1177 /* I would use the static variable newpiece to build the new map of lines,
1178  * as other routines do, but this one is multiline input, and a javascript
1179  * timer can sneak in and add text, thus clobbering newpiece,
1180  * so I need a local variable. */
1181 	struct lineMap *np;
1182 
1183 	cap = 128;
1184 	np = t = allocZeroMem(cap * LMSIZE);
1185 
1186 	if (linePending)
1187 		line = linePending;
1188 	else
1189 		line = inputLine();
1190 
1191 	while (line[0] != '.' || line[1] != '\n') {
1192 		if (linecount == cap) {
1193 			cap *= 2;
1194 			np = reallocMem(np, cap * LMSIZE);
1195 			t = np + linecount;
1196 		}
1197 		t->text = clonePstring(line);
1198 		t->ds1 = t->ds2 = 0;
1199 		++t, ++linecount;
1200 		line = inputLine();
1201 	}
1202 
1203 	nzFree(linePending);
1204 	linePending = 0;
1205 
1206 	if (!linecount) {	/* no lines entered */
1207 		free(np);
1208 		cw->dot = endRange;
1209 		if (!cw->dot && cw->dol)
1210 			cw->dot = 1;
1211 		return true;
1212 	}
1213 
1214 	if (endRange == cw->dol)
1215 		cw->nlMode = false;
1216 	newpiece = np;
1217 	addToMap(linecount, endRange);
1218 	return true;
1219 }				/* inputLinesIntoBuffer */
1220 
1221 /* create the full pathname for a file that you are viewing in directory mode. */
1222 /* This is static, with a limit on path length. */
makeAbsPath(const char * f)1223 static char *makeAbsPath(const char *f)
1224 {
1225 	static char path[ABSPATH + 200];
1226 	if (strlen(cw->baseDirName) + strlen(f) > ABSPATH - 2) {
1227 		setError(MSG_PathNameLong, ABSPATH);
1228 		return 0;
1229 	}
1230 	sprintf(path, "%s/%s", cw->baseDirName, f);
1231 	return path;
1232 }				/* makeAbsPath */
1233 
1234 // Compress a/b/.. back to a, when going to a parent directory
stripDotDot(char * path)1235 static void stripDotDot(char *path)
1236 {
1237 	int l = strlen(path);
1238 	char *u;
1239 	if (l < 3 || strncmp(path + l - 3, "/..", 3))
1240 		return;
1241 	if (l == 3) {		// parent of root is root
1242 		path[1] = 0;
1243 		return;
1244 	}
1245 	if (stringEqual(path, "./..")) {
1246 		strcpy(path, "..");
1247 		return;
1248 	}
1249 // lop it off; I may have to put it back later
1250 	l -= 3;
1251 	path[l] = 0;
1252 	if (l >= 2 && path[l - 1] == '.' && path[l - 2] == '.' &&
1253 	    (l == 2 || path[l - 3] == '/')) {
1254 		path[l] = '/';
1255 		return;
1256 	}
1257 	u = strrchr(path, '/');
1258 	if (u && u[1]) {
1259 // in case it was /bin
1260 		if (u == path)
1261 			u[1] = 0;
1262 		else
1263 			u[0] = 0;
1264 		return;
1265 	}
1266 // at this point it should be a directory in the current directory.
1267 	strcpy(path, ".");
1268 }
1269 
nextLabel(int * label)1270 static int *nextLabel(int *label)
1271 {
1272 	if (label == NULL)
1273 		return cw->labels;
1274 
1275 	if (label >= cw->labels && label - cw->labels < MARKLETTERS)
1276 		return label + 1;
1277 
1278 	/* first history label */
1279 	if (label - cw->labels == MARKLETTERS)
1280 		return (int *)cw->histLabel;
1281 
1282 	/* previous history label. */
1283 	/* in both case we rely on label being first element of the struct */
1284 	return (int *)((struct histLabel *)label)->prev;
1285 }
1286 
1287 /* Delete a block of text. */
delText(int start,int end)1288 void delText(int start, int end)
1289 {
1290 	int i, ln;
1291 	int *label = NULL;
1292 
1293 /* browse has no undo command */
1294 	if (cw->browseMode) {
1295 		for (ln = start; ln <= end; ++ln)
1296 			nzFree(cw->map[ln].text);
1297 	} else {
1298 		undoPush();
1299 	}
1300 
1301 	if (end == cw->dol)
1302 		cw->nlMode = false;
1303 	i = end - start + 1;
1304 	memmove(cw->map + start, cw->map + end + 1,
1305 		(cw->dol - end + 1) * LMSIZE);
1306 
1307 	if (cw->dirMode && cw->r_map) {
1308 // if you are looking at directories with ls-s or some such,
1309 // we have to delete the corresponding stat information.
1310 		memmove(cw->r_map + start, cw->r_map + end + 1,
1311 			(cw->dol - end + 1) * LMSIZE);
1312 	}
1313 
1314 /* move the labels */
1315 	while ((label = nextLabel(label))) {
1316 		ln = *label;
1317 		if (ln < start)
1318 			continue;
1319 		if (ln <= end) {
1320 			*label = 0;
1321 			continue;
1322 		}
1323 		*label -= i;
1324 	}
1325 
1326 	cw->dol -= i;
1327 	cw->dot = start;
1328 	if (cw->dot > cw->dol)
1329 		cw->dot = cw->dol;
1330 /* by convention an empty buffer has no map */
1331 	if (!cw->dol) {
1332 		free(cw->map);
1333 		cw->map = 0;
1334 		if (cw->dirMode && cw->r_map) {
1335 			free(cw->r_map);
1336 			cw->r_map = 0;
1337 		}
1338 	}
1339 }				/* delText */
1340 
1341 /* Delete files from a directory as you delete lines.
1342  * Set dw to move them to your recycle bin.
1343  * Set dx to delete them outright. */
1344 
delFiles(void)1345 static bool delFiles(void)
1346 {
1347 	int ln, cnt, j;
1348 
1349 	if (!dirWrite) {
1350 		setError(MSG_DirNoWrite);
1351 		return false;
1352 	}
1353 
1354 	if (dirWrite == 1 && !recycleBin) {
1355 		setError(MSG_NoRecycle);
1356 		return false;
1357 	}
1358 
1359 	cmd = 'e';		// show errors
1360 
1361 	ln = startRange;
1362 	cnt = endRange - startRange + 1;
1363 	while (cnt--) {
1364 		char *file, *t, *path, *ftype, *a;
1365 		char qc = '\''; // quote character
1366 		file = (char *)fetchLine(ln, 0);
1367 		t = strchr(file, '\n');
1368 		if (!t)
1369 			i_printfExit(MSG_NoNlOnDir, file);
1370 		*t = 0;
1371 		path = makeAbsPath(file);
1372 		if (!path) {
1373 			free(file);
1374 			return false;
1375 		}
1376 
1377 // check formeta chars in path
1378 		if(strchr(path, qc)) {
1379 			qc = '"';
1380 			if(strpbrk(path, "\"$"))
1381 // I can't easily turn this into a shell command, so just hang it.
1382 				qc = 0;
1383 		}
1384 		if(strstr(path, "\\\\"))
1385 			qc = 0;
1386 
1387 		ftype = dirSuffix(ln);
1388 		if (dirWrite == 2 || (*ftype && strchr("@<*^|", *ftype)))
1389 			debugPrint(1, "%s%s ↓", file, ftype);
1390 		else
1391 			debugPrint(1, "%s%s → ��", file, ftype);
1392 
1393 		if (dirWrite == 2 && *ftype == '/') {
1394 			if(!qc) {
1395 				setError(MSG_MetaChar);
1396 				free(file);
1397 				return false;
1398 			}
1399 			asprintf(&a, "rm -rf %c%s%c",
1400 			qc, path, qc);
1401 			j = system(a);
1402 			free(a);
1403 			if(!j)
1404 				goto gone;
1405 			setError(MSG_NoDirDelete);
1406 			free(file);
1407 			return false;
1408 		}
1409 
1410 		if (dirWrite == 2 || (*ftype && strchr("@<*^|", *ftype))) {
1411 unlink:
1412 			if (unlink(path)) {
1413 				setError(MSG_NoRemove, file);
1414 				free(file);
1415 				return false;
1416 			}
1417 		} else {
1418 			char bin[ABSPATH];
1419 			sprintf(bin, "%s/%s", recycleBin, file);
1420 			if (rename(path, bin)) {
1421 				if (errno == EXDEV) {
1422 					char *rmbuf;
1423 					int rmlen;
1424 					if (*ftype == '/' ||
1425 					fileSizeByName(path) > 200000000) {
1426 // let mv do the work
1427 						if(!qc) {
1428 							setError(MSG_MetaChar);
1429 							free(file);
1430 							return false;
1431 						}
1432 						asprintf(&a, "mv -n %c%s%c",
1433 						qc, path, qc);
1434 						j = system(a);
1435 						free(a);
1436 						if(!j)
1437 							goto gone;
1438 						setError(MSG_MoveFileSystem , path);
1439 						free(file);
1440 						return false;
1441 					}
1442 					if (!fileIntoMemory
1443 					    (path, &rmbuf, &rmlen)) {
1444 						free(file);
1445 						return false;
1446 					}
1447 					if (!memoryOutToFile(bin, rmbuf, rmlen,
1448 							     MSG_TempNoCreate2,
1449 							     MSG_NoWrite2)) {
1450 						free(file);
1451 						nzFree(rmbuf);
1452 						return false;
1453 					}
1454 					nzFree(rmbuf);
1455 					goto unlink;
1456 				}
1457 
1458 // some other rename error
1459 				setError(MSG_NoMoveToTrash, file);
1460 				free(file);
1461 				return false;
1462 			}
1463 		}
1464 
1465 gone:
1466 		free(file);
1467 		delText(ln, ln);
1468 	}
1469 
1470 // if you type D instead of d, I don't want to lose that.
1471 	cmd = icmd;
1472 	return true;
1473 }				/* delFiles */
1474 
1475 // Move or copy files from one directory to another
moveFiles(void)1476 static bool moveFiles(void)
1477 {
1478 	struct ebWindow *cw1 = cw;
1479 	struct ebWindow *cw2 = sessionList[destLine].lw;
1480 	char *path1, *path2;
1481 	int ln, cnt, dol;
1482 
1483 	if (!dirWrite) {
1484 		setError(MSG_DirNoWrite);
1485 		return false;
1486 	}
1487 
1488 	cmd = 'e';		// show error messages
1489 	ln = startRange;
1490 	cnt = endRange - startRange + 1;
1491 	while (cnt--) {
1492 		char *file, *t, *ftype;
1493 		bool iswin = false;
1494 #ifdef DOSLIKE
1495 		iswin = true;
1496 #endif
1497 		file = (char *)fetchLine(ln, 0);
1498 		t = strchr(file, '\n');
1499 		if (!t)
1500 			i_printfExit(MSG_NoNlOnDir, file);
1501 		*t = 0;
1502 		ftype = dirSuffix(ln);
1503 
1504 		debugPrint(1, "%s%s %s %s",
1505 		file, ftype, (icmd == 'm' ? "→" : "≡"), cw2->baseDirName);
1506 
1507 		path1 = makeAbsPath(file);
1508 		if (!path1) {
1509 			free(file);
1510 			return false;
1511 		}
1512 		path1 = cloneString(path1);
1513 
1514 		cw = cw2;
1515 		path2 = makeAbsPath(file);
1516 		cw = cw1;
1517 		if (!path2) {
1518 			free(file);
1519 			free(path1);
1520 			return false;
1521 		}
1522 
1523 		if (!access(path2, 0)) {
1524 			setError(MSG_DestFileExists);
1525 			free(file);
1526 			free(path1);
1527 			return false;
1528 		}
1529 
1530 		errno = EXDEV;
1531 		if (icmd == 't' || rename(path1, path2)) {
1532 			if (errno == EXDEV) {
1533 				char *rmbuf;
1534 				int rmlen, j;
1535 				if (!iswin || *ftype || fileSizeByName(path1) > 200000000) {
1536 // let mv or cp do the work
1537 					char *a, qc = '\'';
1538 					if(strchr(path1, qc) || strchr(path2, qc)) {
1539 						qc = '"';
1540 						if(strpbrk(path1, "\"$") || strpbrk(path2, "\"$"))
1541 // I can't easily turn this into a shell command, so just hang it.
1542 							qc = 0;
1543 					}
1544 					if(strstr(path1, "\\\\") || strstr(path2, "\\\\"))
1545 						qc = 0;
1546 					if(!qc) {
1547 						setError(MSG_MetaChar);
1548 						free(file);
1549 						free(path1);
1550 						return false;
1551 					}
1552 					asprintf(&a, "%s %c%s%c %c%s%c",
1553 					(icmd == 'm' ? "mv -n" : "cp -an"),
1554 					qc, path1, qc, qc, cw2->baseDirName, qc);
1555 					j = system(a);
1556 					free(a);
1557 					if(j) {
1558 						setError((icmd == 'm' ? MSG_MoveFileSystem : MSG_CopyFail), file);
1559 						free(file);
1560 						free(path1);
1561 						return false;
1562 					}
1563 					if(icmd == 'm')
1564 						unlink(path1);
1565 					goto moved;
1566 				}
1567 // A small file, copy it ourselves.
1568 				if (!fileIntoMemory(path1, &rmbuf, &rmlen)) {
1569 					free(file);
1570 					free(path1);
1571 					return false;
1572 				}
1573 				if (!memoryOutToFile(path2, rmbuf, rmlen,
1574 						     MSG_TempNoCreate2,
1575 						     MSG_NoWrite2)) {
1576 					free(file);
1577 					free(path1);
1578 					nzFree(rmbuf);
1579 					return false;
1580 				}
1581 				nzFree(rmbuf);
1582 				if(icmd == 'm')
1583 					unlink(path1);
1584 				goto moved;
1585 			}
1586 
1587 			setError(MSG_MoveError, file, errno);
1588 			free(file);
1589 			free(path1);
1590 			return false;
1591 		}
1592 
1593 moved:
1594 		free(path1);
1595 		if(icmd == 'm')
1596 			delText(ln, ln);
1597 // add it to the other directory
1598 *t++ = '\n';
1599 		cw = cw2;
1600 		dol = cw->dol;
1601 		addTextToBuffer((pst)file, t-file, dol, false);
1602 		free(file);
1603 		cw->dot = ++dol;
1604 		cw->map[dol].ds1 = ftype[0];
1605 		if(ftype[0])
1606 			cw->map[dol].ds2 = ftype[1];
1607 // if attributes were displayed in that directory - more work to do.
1608 // I just leave a space for them; I don't try to derive them.
1609 		if(cw->r_map) {
1610 			cw->r_map = reallocMem(cw->r_map, LMSIZE * (dol + 2));
1611 			memset(cw->r_map + dol, 0, LMSIZE*2);
1612 			cw->r_map[dol].text = (uchar*)emptyString;
1613 		}
1614 		cw = cw1; // put it back
1615 	}
1616 
1617 	return true;
1618 }
1619 
1620 /* Move or copy a block of text. */
1621 /* Uses range variables, hence no parameters. */
moveCopy(void)1622 static bool moveCopy(void)
1623 {
1624 	int sr = startRange;
1625 	int er = endRange + 1;
1626 	int dl = destLine + 1;
1627 	int i_sr = sr * LMSIZE;	/* indexes into map */
1628 	int i_er = er * LMSIZE;
1629 	int i_dl = dl * LMSIZE;
1630 	int n_lines = er - sr;
1631 	struct lineMap *map = cw->map;
1632 	struct lineMap *newmap, *t;
1633 	int lowcut, highcut, diff, i, ln;
1634 	int *label = NULL;
1635 
1636 	if (dl > sr && dl < er) {
1637 		setError(MSG_DestInBlock);
1638 		return false;
1639 	}
1640 	if (cmd == 'm' && (dl == er || dl == sr)) {
1641 		if (globSub)
1642 			setError(MSG_NoChange);
1643 		return false;
1644 	}
1645 
1646 	undoPush();
1647 
1648 	if (cmd == 't') {
1649 		newpiece = t = allocZeroMem(n_lines * LMSIZE);
1650 		for (i = sr; i < er; ++i, ++t)
1651 			t->text = fetchLine(i, 0);
1652 		addToMap(n_lines, destLine);
1653 		return true;
1654 	}
1655 
1656 	if (destLine == cw->dol || endRange == cw->dol)
1657 		cw->nlMode = false;
1658 
1659 /* All we really need do is rearrange the map. */
1660 	newmap = allocMem((cw->dol + 2) * LMSIZE);
1661 	memcpy(newmap, map, (cw->dol + 2) * LMSIZE);
1662 	if (dl < sr) {
1663 		memcpy(newmap + dl, map + sr, i_er - i_sr);
1664 		memcpy(newmap + dl + er - sr, map + dl, i_sr - i_dl);
1665 	} else {
1666 		memcpy(newmap + sr, map + er, i_dl - i_er);
1667 		memcpy(newmap + sr + dl - er, map + sr, i_er - i_sr);
1668 	}
1669 	free(cw->map);
1670 	cw->map = newmap;
1671 
1672 /* now for the labels */
1673 	if (dl < sr) {
1674 		lowcut = dl;
1675 		highcut = er;
1676 		diff = sr - dl;
1677 	} else {
1678 		lowcut = sr;
1679 		highcut = dl;
1680 		diff = dl - er;
1681 	}
1682 	while ((label = nextLabel(label))) {
1683 		ln = *label;
1684 		if (ln < lowcut)
1685 			continue;
1686 		if (ln >= highcut)
1687 			continue;
1688 		if (ln >= startRange && ln <= endRange) {
1689 			ln += (dl < sr ? -diff : diff);
1690 		} else {
1691 			ln += (dl < sr ? n_lines : -n_lines);
1692 		}
1693 		*label = ln;
1694 	}			/* loop over labels */
1695 
1696 	cw->dot = endRange;
1697 	cw->dot += (dl < sr ? -diff : diff);
1698 	return true;
1699 }				/* moveCopy */
1700 
1701 /* Join lines from startRange to endRange. */
joinText(void)1702 static bool joinText(void)
1703 {
1704 	int j, size;
1705 	pst newline, t;
1706 
1707 	if (startRange == endRange) {
1708 		setError(MSG_Join1);
1709 		return false;
1710 	}
1711 
1712 	size = 0;
1713 	for (j = startRange; j <= endRange; ++j)
1714 		size += pstLength(fetchLine(j, -1));
1715 	t = newline = allocMem(size);
1716 	for (j = startRange; j <= endRange; ++j) {
1717 		pst p = fetchLine(j, -1);
1718 		size = pstLength(p);
1719 		memcpy(t, p, size);
1720 		t += size;
1721 		if (j < endRange) {
1722 			t[-1] = ' ';
1723 			if (cmd == 'j')
1724 				--t;
1725 		}
1726 	}
1727 
1728 	delText(startRange, endRange);
1729 
1730 	newpiece = allocZeroMem(LMSIZE);
1731 	newpiece->text = newline;
1732 	addToMap(1, startRange - 1);
1733 
1734 	cw->dot = startRange;
1735 	return true;
1736 }				/* joinText */
1737 
1738 // directory sort record, for nonalphabetical sorts.
1739 struct DSR {
1740 	int idx;
1741 	union {
1742 		time_t t;
1743 		off_t z;
1744 	} u;
1745 };
1746 static struct DSR *dsr_list;
1747 extern struct stat this_stat;
1748 static char ls_sort;		// sort method for directory listing
1749 static bool ls_reverse;		// reverse sort
1750 static char lsformat[12];	/* size date etc on a directory listing */
1751 
1752 /* compare routine for quicksort directory scan */
dircmp(const void * s,const void * t)1753 static int dircmp(const void *s, const void *t)
1754 {
1755 	const struct DSR *q = s;
1756 	const struct DSR *r = t;
1757 	int rc;
1758 	if (ls_sort == 1) {
1759 		rc = 0;
1760 		if (q->u.z < r->u.z)
1761 			rc = -1;
1762 		if (q->u.z > r->u.z)
1763 			rc = 1;
1764 	}
1765 	if (ls_sort == 2) {
1766 		rc = 0;
1767 		if (q->u.t < r->u.t)
1768 			rc = -1;
1769 		if (q->u.t > r->u.t)
1770 			rc = 1;
1771 	}
1772 	if (ls_reverse)
1773 		rc = -rc;
1774 	return rc;
1775 }
1776 
1777 /* Read the contents of a directory into the current buffer */
readDirectory(const char * filename)1778 static bool readDirectory(const char *filename)
1779 {
1780 	int len, j, linecount;
1781 	char *v;
1782 	struct lineMap *mptr;
1783 	struct lineMap *backpiece = 0;
1784 
1785 	cw->baseDirName = cloneString(filename);
1786 /* get rid of trailing slash */
1787 	len = strlen(cw->baseDirName);
1788 	if (len && cw->baseDirName[len - 1] == '/')
1789 		cw->baseDirName[len - 1] = 0;
1790 /* Understand that the empty string now means / */
1791 
1792 /* get the files, or fail if there is a problem */
1793 	if (!sortedDirList
1794 	    (filename, &newpiece, &linecount, ls_sort, ls_reverse))
1795 		return false;
1796 	if (!cw->dol) {
1797 		cw->dirMode = true;
1798 		i_puts(MSG_DirMode);
1799 		if (lsformat[0])
1800 			backpiece = allocZeroMem(LMSIZE * (linecount + 2));
1801 	}
1802 
1803 	if (!linecount) {	/* empty directory */
1804 		cw->dot = endRange;
1805 		fileSize = 0;
1806 		free(newpiece);
1807 		newpiece = 0;
1808 		nzFree(backpiece);
1809 		goto success;
1810 	}
1811 
1812 	if (ls_sort)
1813 		dsr_list = allocZeroMem(sizeof(struct DSR) * linecount);
1814 
1815 /* change 0 to nl and count bytes */
1816 	fileSize = 0;
1817 	mptr = newpiece;
1818 	for (j = 0; j < linecount; ++j, ++mptr) {
1819 		char c, ftype;
1820 		pst t = mptr->text;
1821 		char *abspath = makeAbsPath((char *)t);
1822 
1823 // make sure this gets done.
1824 		if (backpiece)
1825 			backpiece[j + 1].text = (uchar *) emptyString;
1826 
1827 		while (*t) {
1828 			if (*t == '\n')
1829 				*t = '\t';
1830 			++t;
1831 		}
1832 		*t = '\n';
1833 		len = t - mptr->text;
1834 		fileSize += len + 1;
1835 		if (ls_sort)
1836 			dsr_list[j].idx = j;
1837 		if (!abspath)
1838 			continue;	/* should never happen */
1839 
1840 		ftype = fileTypeByName(abspath, true);
1841 		if (!ftype)
1842 			continue;
1843 		if (isupperByte(ftype)) {	/* symbolic link */
1844 			if (!cw->dirMode)
1845 				*t = '@', *++t = '\n';
1846 			else
1847 				mptr->ds1 = '@';
1848 			++fileSize;
1849 		}
1850 		ftype = tolower(ftype);
1851 		c = 0;
1852 		if (ftype == 'd')
1853 			c = '/';
1854 		if (ftype == 's')
1855 			c = '^';
1856 		if (ftype == 'c')
1857 			c = '<';
1858 		if (ftype == 'b')
1859 			c = '*';
1860 		if (ftype == 'p')
1861 			c = '|';
1862 		if (c) {
1863 			if (!cw->dirMode)
1864 				*t = c, *++t = '\n';
1865 			else if (mptr->ds1)
1866 				mptr->ds2 = c;
1867 			else
1868 				mptr->ds1 = c;
1869 			++fileSize;
1870 		}
1871 // If sorting a different way, get the attribute.
1872 		if (ls_sort) {
1873 			if (ls_sort == 1)
1874 				dsr_list[j].u.z = this_stat.st_size;
1875 			if (ls_sort == 2)
1876 				dsr_list[j].u.t = this_stat.st_mtime;
1877 		}
1878 
1879 /* extra stat entries on the line */
1880 		if (!lsformat[0])
1881 			continue;
1882 		v = lsattr(abspath, lsformat);
1883 		if (!*v)
1884 			continue;
1885 		len = strlen(v);
1886 		fileSize += len + 1;
1887 		if (cw->dirMode) {
1888 			backpiece[j + 1].text = (uchar *) cloneString(v);
1889 		} else {
1890 /* have to realloc at this point */
1891 			int l1 = t - mptr->text;
1892 			mptr->text = reallocMem(mptr->text, l1 + len + 3);
1893 			t = mptr->text + l1;
1894 			*t++ = ' ';
1895 			strcpy((char *)t, v);
1896 			t += len;
1897 			*t++ = '\n';
1898 			*t = 0;
1899 		}
1900 	}			/* loop fixing files in the directory scan */
1901 
1902 	if (ls_sort) {
1903 		struct lineMap *tmp;
1904 		qsort(dsr_list, linecount, sizeof(struct DSR), dircmp);
1905 // Now I have to remap everything.
1906 		tmp = allocMem(LMSIZE * linecount);
1907 		for (j = 0; j < linecount; ++j)
1908 			tmp[j] = newpiece[dsr_list[j].idx];
1909 		free(newpiece);
1910 		newpiece = tmp;
1911 		if (backpiece) {
1912 			tmp = allocMem(LMSIZE * (linecount + 2));
1913 			for (j = 0; j < linecount; ++j)
1914 				tmp[j + 1] = backpiece[dsr_list[j].idx + 1];
1915 			memset(tmp, 0, LMSIZE);
1916 			memset(tmp + linecount + 1, 0, LMSIZE);
1917 			free(backpiece);
1918 			backpiece = tmp;
1919 		}
1920 		free(dsr_list);
1921 	}
1922 
1923 	addToMap(linecount, endRange);
1924 	cw->r_map = backpiece;
1925 
1926 success:
1927 	if (cmd == 'r')
1928 		debugPrint(1, "%d", fileSize);
1929 	return true;
1930 }				/* readDirectory */
1931 
1932 /* Read a file, or url, into the current buffer.
1933  * Post/get data is passed, via the second parameter, if it's a URL. */
readFile(const char * filename,const char * post,bool newwin,int fromframe,const char * fromthis)1934 static bool readFile(const char *filename, const char *post, bool newwin,
1935 		     int fromframe, const char *fromthis)
1936 {
1937 	char *rbuf;		/* read buffer */
1938 	int readSize;		/* should agree with fileSize */
1939 	bool rc;		/* return code */
1940 	bool fileprot = false;
1941 	char *nopound;
1942 	char filetype;
1943 
1944 	serverData = 0;
1945 	serverDataLen = 0;
1946 
1947 	if (newwin) {
1948 		nzFree(cw->saveURL);
1949 		cw->saveURL = cloneString(filename);
1950 	}
1951 
1952 	if (memEqualCI(filename, "file://", 7)) {
1953 		filename += 7;
1954 		if (!*filename) {
1955 			setError(MSG_MissingFileName);
1956 			return false;
1957 		}
1958 		fileprot = true;
1959 		changeFileName = cloneString(filename);
1960 		unpercentString(changeFileName);
1961 		if (stringEqual(filename, changeFileName)) {
1962 			free(changeFileName);
1963 			changeFileName = 0;
1964 		} else
1965 			filename = changeFileName;
1966 		goto fromdisk;
1967 	}
1968 
1969 	if (isURL(filename)) {
1970 		const char *newfile;
1971 		const struct MIMETYPE *mt;
1972 		uchar sxfirst;
1973 		struct i_get g;
1974 		memset(&g, 0, sizeof(g));
1975 		if (newwin)
1976 			g.down_ok = true;
1977 		if (fromframe <= 1) {
1978 			g.foreground = true;
1979 			g.pg_ok = pluginsOn;
1980 		}
1981 		if (g.pg_ok && fromframe == 1)
1982 			g.playonly = true;
1983 // If the url matches on protocol, (and you should never write a plugin
1984 // that matches on the standard transport protocols), then we need this plugin
1985 // just to get the data.
1986 		if (!g.pg_ok && !fromframe) {
1987 			sxfirst = 2;
1988 			if ((mt = findMimeByURL(filename, &sxfirst))
1989 			    && mt->outtype)
1990 				g.pg_ok = true;
1991 		}
1992 		if (fromframe)
1993 			g.uriEncoded = true;
1994 		else
1995 			g.uriEncoded = uriEncoded;
1996 		g.url = filename;
1997 		g.thisfile = fromthis;
1998 		rc = httpConnect(&g);
1999 		serverData = g.buffer;
2000 		serverDataLen = g.length;
2001 		if (!rc)
2002 			return false;
2003 		changeFileName = g.cfn;	// allocated
2004 		if (newwin) {
2005 			cw->referrer = g.referrer;	// allocated
2006 			if (g.cfn) {
2007 				nzFree(cw->saveURL);
2008 				cw->saveURL = cloneString(g.cfn);
2009 			}
2010 		} else
2011 			nzFree(g.referrer);
2012 
2013 /* We got some data.  Any warnings along the way have been printed,
2014  * like 404 file not found, but it's still worth continuing. */
2015 		rbuf = serverData;
2016 		fileSize = readSize = serverDataLen;
2017 		if (g.code != 200 && g.code != 210)
2018 			cf->render1 = cf->render2 = true;
2019 
2020 // Don't print "this doesn't look like browsable text"
2021 // if the content type is plain text.
2022 		if (memEqualCI(g.content, "text/plain", 10) && cmd == 'b')
2023 			cmd = 'e';
2024 
2025 		if (fileSize == 0) {	/* empty file */
2026 			nzFree(rbuf);
2027 			if (!fromframe)
2028 				cw->dot = endRange;
2029 			return true;
2030 		}
2031 
2032 		if (g.csp) {
2033 			cf->mt = 0;
2034 			cf->render1 = cf->render2 = true;
2035 		}
2036 // acid says a frame has to be text/html, not even text/plain.
2037 		if (fromframe && g.content[0]
2038 		    && !stringEqual(g.content, "text/html")) {
2039 			debugPrint(3,
2040 				   "frame suppressed because content type is %s",
2041 				   g.content);
2042 			nzFree(serverData);
2043 			serverData = 0;
2044 			serverDataLen = 0;
2045 			return false;
2046 		}
2047 
2048 		newfile = (changeFileName ? changeFileName : filename);
2049 		if (cf->mt && cf->mt->outtype &&
2050 		    pluginsOn && !cf->render1 && cmd == 'b' && newwin) {
2051 			sxfirst = 0;
2052 			rc = runPluginCommand(cf->mt, newfile, 0,
2053 					      rbuf, readSize, &rbuf, &readSize);
2054 			if (!rc) {
2055 				nzFree(rbuf);
2056 				fileSize = -1;
2057 				return false;
2058 			}
2059 			cf->render1 = true;
2060 // we have to look again, to see if it matched by suffix.
2061 // This call should always succeed.
2062 			if (findMimeByURL(cf->fileName, &sxfirst) && sxfirst)
2063 				cf->render2 = true;
2064 // browse command ran the plugin, but if it generates text,
2065 // then there's no need to browse the result.
2066 			if (cf->mt->outtype == 't')
2067 				cmd = 'e';
2068 			fileSize = readSize;
2069 		}
2070 
2071 /*********************************************************************
2072 Almost all music players take a url, but if one doesn't, whence down_url is set,
2073 then here we are with data in buffer.
2074 Yes, browseCurrent Buffer checks the suffix, but only to render, not to play.
2075 We have to play here.
2076 This also comes up when the protocol is used to get the data into buffer,
2077 like extracting from a zip or tar archive.
2078 Again the data is in buffer and we need to play it here.
2079 *********************************************************************/
2080 
2081 		sxfirst = 1;
2082 		mt = findMimeByURL(newfile, &sxfirst);
2083 		if (mt && !mt->outtype && sxfirst &&
2084 		    pluginsOn && (g.code == 200 || g.code == 201) &&
2085 		    cmd == 'b' && newwin) {
2086 			rc = runPluginCommand(mt, newfile, 0,
2087 					      rbuf, readSize, 0, 0);
2088 // rbuf has been freed by this command even if it didn't succeed.
2089 			serverData = NULL;
2090 			serverDataLen = 0;
2091 			cf->render2 = true;
2092 			return rc;
2093 		}
2094 // If we would browse this data but plugins are off then turn b into e.
2095 		if (mt && mt->outtype && sxfirst &&
2096 		    !pluginsOn &&
2097 		    (g.code == 200 || g.code == 201) && cmd == 'b' && newwin)
2098 			cmd = 'e';
2099 		if (mt && !mt->outtype && !pluginsOn && cmd == 'b' && newwin)
2100 			cf->render2 = true;
2101 
2102 		goto gotdata;
2103 	}
2104 
2105 	if (isSQL(filename) && !fromframe) {
2106 		const char *t1, *t2;
2107 		if (!cw->sqlMode) {
2108 			setError(MSG_DBOtherFile);
2109 			return false;
2110 		}
2111 		t1 = strchr(cf->fileName, ']');
2112 		t2 = strchr(filename, ']');
2113 		if (t1 - cf->fileName != t2 - filename ||
2114 		    memcmp(cf->fileName, filename, t2 - filename)) {
2115 			setError(MSG_DBOtherTable);
2116 			return false;
2117 		}
2118 		rc = sqlReadRows(filename, &rbuf);
2119 		if (!rc) {
2120 			nzFree(rbuf);
2121 			if (!cw->dol && newwin) {
2122 				cw->sqlMode = false;
2123 				nzFree(cf->fileName);
2124 				cf->fileName = 0;
2125 			}
2126 			return false;
2127 		}
2128 		serverData = rbuf;
2129 		fileSize = strlen(rbuf);
2130 		if (rbuf == emptyString)
2131 			return true;
2132 		goto intext;
2133 	}
2134 
2135 fromdisk:
2136 // reading a file from disk.
2137 	fileSize = 0;
2138 // for security reasons, this cannot be a frame in a web page.
2139 	if (!frameSecurityFile(filename)) {
2140 badfile:
2141 		nzFree(changeFileName);
2142 		changeFileName = 0;
2143 		return false;
2144 	}
2145 
2146 	filetype = fileTypeByName(filename, false);
2147 	if (filetype == 'd') {
2148 		if (!fromframe)
2149 			return readDirectory(filename);
2150 		setError(MSG_FrameNotHTML);
2151 		goto badfile;
2152 	}
2153 
2154 	if (newwin && !fileprot && !cf->mt)
2155 		cf->mt = findMimeByFile(filename);
2156 
2157 // Optimize; don't read a file into buffer if you're
2158 // just going to process it.
2159 	if (cf->mt && cf->mt->outtype && pluginsOn && !access(filename, 4)
2160 	    && !fileprot && cmd == 'b' && newwin) {
2161 		rc = runPluginCommand(cf->mt, 0, filename, 0, 0, &rbuf,
2162 				      &fileSize);
2163 		cf->render1 = cf->render2 = true;
2164 // browse command ran the plugin, but if it generates text,
2165 // then there's no need to browse the result.
2166 		if (cf->mt->outtype == 't')
2167 			cmd = 'e';
2168 	} else {
2169 
2170 		nopound = cloneString(filename);
2171 		rbuf = findHash(nopound);
2172 		if (rbuf && !filetype)
2173 			*rbuf = 0;
2174 		rc = fileIntoMemory(nopound, &rbuf, &fileSize);
2175 		nzFree(nopound);
2176 	}
2177 
2178 	if (!rc)
2179 		goto badfile;
2180 	serverData = rbuf;
2181 	serverDataLen = fileSize;
2182 	if (fileSize == 0) {	/* empty file */
2183 		if (!fromframe) {
2184 			cw->dot = endRange;
2185 			nzFree(rbuf);
2186 		}
2187 		return true;
2188 	}
2189 
2190 gotdata:
2191 
2192 	if (!looksBinary((uchar *) rbuf, fileSize)) {
2193 		char *tbuf;
2194 		int i, j;
2195 		bool crlf_yes = false, crlf_no = false, dosmode = false;
2196 
2197 /* looks like text.  In DOS, we should compress crlf.
2198  * Let's do that now. */
2199 #ifdef DOSLIKE
2200 		for (i = j = 0; i < fileSize; ++i) {
2201 			char c = rbuf[i];
2202 			if (c == '\r' && rbuf[i + 1] == '\n')
2203 				continue;
2204 			rbuf[j++] = c;
2205 		}
2206 		rbuf[j] = 0;
2207 		fileSize = j;
2208 		serverDataLen = fileSize;
2209 
2210 // if a utf32 file has unicode 2572, or 2572*256+x, it looks like \r\n,
2211 // and removing \r mungs the file from this point on.
2212 // This is a corner case that somebody needs to fix some day!
2213 
2214 #else
2215 
2216 // convert in unix, only if each \n has \r preceeding.
2217 		if (iuConvert) {
2218 			for (i = 0; i < fileSize; ++i) {
2219 				char c = rbuf[i];
2220 				if (c != '\n')
2221 					continue;
2222 				if (i && rbuf[i - 1] == '\r')
2223 					crlf_yes = true;
2224 				else
2225 					crlf_no = true;
2226 			}
2227 			if (crlf_yes && !crlf_no)
2228 				dosmode = true;
2229 		}
2230 
2231 		if (dosmode) {
2232 			if (debugLevel >= 2 || (debugLevel == 1
2233 						&& !isURL(filename)))
2234 				i_puts(MSG_ConvUnix);
2235 			for (i = j = 0; i < fileSize; ++i) {
2236 				char c = rbuf[i];
2237 				if (c == '\r' && rbuf[i + 1] == '\n')
2238 					continue;
2239 				rbuf[j++] = c;
2240 			}
2241 			rbuf[j] = 0;
2242 			fileSize = j;
2243 			serverDataLen = fileSize;
2244 		}
2245 #endif
2246 
2247 		if (iuConvert) {
2248 			bool is8859 = false, isutf8 = false;
2249 /* Classify this incoming text as ascii or 8859 or utf-x */
2250 			int bom = byteOrderMark((uchar *) rbuf, fileSize);
2251 			if (bom) {
2252 				debugPrint(3, "text type is %s%s",
2253 					   ((bom & 4) ? "big " : ""),
2254 					   ((bom & 2) ? "utf32" : "utf16"));
2255 				if (debugLevel >= 2 || (debugLevel == 1
2256 							&& !isURL(filename)))
2257 					i_puts(cons_utf8 ? MSG_ConvUtf8 :
2258 					       MSG_Conv8859);
2259 				utfLow(rbuf, fileSize, &tbuf, &fileSize, bom);
2260 				nzFree(rbuf);
2261 				rbuf = tbuf;
2262 				serverData = rbuf;
2263 				serverDataLen = fileSize;
2264 			} else {
2265 				looks_8859_utf8((uchar *) rbuf, fileSize,
2266 						&is8859, &isutf8);
2267 				debugPrint(3, "text type is %s",
2268 					   (isutf8 ? "utf8"
2269 					    : (is8859 ? "8859" : "ascii")));
2270 				if (cons_utf8 && is8859) {
2271 					if (debugLevel >= 2 || (debugLevel == 1
2272 								&&
2273 								!isURL
2274 								(filename)))
2275 						i_puts(MSG_ConvUtf8);
2276 					iso2utf((uchar *) rbuf, fileSize,
2277 						(uchar **) & tbuf, &fileSize);
2278 					nzFree(rbuf);
2279 					rbuf = tbuf;
2280 					serverData = rbuf;
2281 					serverDataLen = fileSize;
2282 				}
2283 				if (!cons_utf8 && isutf8) {
2284 					if (debugLevel >= 2 || (debugLevel == 1
2285 								&&
2286 								!isURL
2287 								(filename)))
2288 						i_puts(MSG_Conv8859);
2289 					utf2iso((uchar *) rbuf, fileSize,
2290 						(uchar **) & tbuf, &fileSize);
2291 					nzFree(rbuf);
2292 					rbuf = tbuf;
2293 					serverData = rbuf;
2294 					serverDataLen = fileSize;
2295 				}
2296 				if (cons_utf8 && isutf8) {
2297 // Strip off the leading bom, if any, and no we're not going to put it back.
2298 					if (fileSize >= 3 &&
2299 					    !memcmp(rbuf, "\xef\xbb\xbf", 3)) {
2300 						fileSize -= 3;
2301 						memmove(rbuf, rbuf + 3,
2302 							fileSize);
2303 						serverDataLen = fileSize - 3;
2304 					}
2305 				}
2306 			}
2307 
2308 // if reading into an empty buffer, set the mode and print message
2309 			if (!cw->dol) {
2310 				if (bom & 2) {
2311 					cw->utf32Mode = true;
2312 					debugPrint(3, "setting utf32 mode");
2313 				}
2314 				if (bom & 1) {
2315 					cw->utf16Mode = true;
2316 					debugPrint(3, "setting utf16 mode");
2317 				}
2318 				if (bom & 4)
2319 					cw->bigMode = true;
2320 				if (isutf8) {
2321 					cw->utf8Mode = true;
2322 					debugPrint(3, "setting utf8 mode");
2323 				}
2324 				if (is8859) {
2325 					cw->iso8859Mode = true;
2326 					debugPrint(3, "setting 8859 mode");
2327 				}
2328 				if (dosmode) {
2329 					cw->dosMode = true;
2330 					debugPrint(3, "setting dos mode");
2331 				}
2332 			}
2333 
2334 		}
2335 	} else if (fromframe) {
2336 		nzFree(rbuf);
2337 		setError(MSG_FrameNotHTML);
2338 		goto badfile;
2339 	} else if (binaryDetect & !cw->binMode) {
2340 		i_puts(MSG_BinaryData);
2341 		cw->binMode = true;
2342 	}
2343 
2344 	if (fromframe) {
2345 /* serverData holds the html text to browse */
2346 		return true;
2347 	}
2348 
2349 intext:
2350 	rc = addTextToBuffer((const pst)rbuf, fileSize, endRange,
2351 			     !isURL(filename));
2352 	nzFree(rbuf);
2353 	return rc;
2354 }				/* readFile */
2355 
2356 /* from the command line */
readFileArgv(const char * filename,int fromframe)2357 bool readFileArgv(const char *filename, int fromframe)
2358 {
2359 	bool newwin = !fromframe;
2360 	cmd = 'e';
2361 	return readFile(filename, emptyString, newwin, fromframe,
2362 			(newwin ? 0 : cw->f0.fileName));
2363 }				/* readFileArgv */
2364 
2365 /* Write a range to a file. */
writeFile(const char * name,int mode)2366 static bool writeFile(const char *name, int mode)
2367 {
2368 	int i;
2369 	FILE *fh;
2370 	char *modeString;
2371 	int modeString_l;
2372 
2373 	fileSize = 0;
2374 
2375 	if (memEqualCI(name, "file://", 7))
2376 		name += 7;
2377 
2378 	if (!*name) {
2379 		setError(MSG_MissingFileName);
2380 		return false;
2381 	}
2382 
2383 	if (isURL(name)) {
2384 		setError(MSG_NoWriteURL);
2385 		return false;
2386 	}
2387 
2388 	if (isSQL(name)) {
2389 		setError(MSG_WriteDB);
2390 		return false;
2391 	}
2392 
2393 	if (!cw->dol) {
2394 		setError(MSG_WriteEmpty);
2395 		return false;
2396 	}
2397 
2398 /* mode should be TRUNC or APPEND */
2399 	modeString = initString(&modeString_l);
2400 	if (mode & O_APPEND)
2401 		stringAndChar(&modeString, &modeString_l, 'a');
2402 	else
2403 		stringAndChar(&modeString, &modeString_l, 'w');
2404 	if (cw->binMode | cw->utf16Mode | cw->utf32Mode)
2405 		stringAndChar(&modeString, &modeString_l, 'b');
2406 
2407 	fh = fopen(name, modeString);
2408 	nzFree(modeString);
2409 	if (fh == NULL) {
2410 		setError(MSG_NoCreate2, name);
2411 		return false;
2412 	}
2413 // If writing to the same file and converting, print message,
2414 // and perhaps write the byte order mark.
2415 	if (name == cf->fileName && iuConvert) {
2416 		if (cw->iso8859Mode && cons_utf8)
2417 			if (debugLevel >= 1)
2418 				i_puts(MSG_Conv8859);
2419 		if (cw->utf8Mode && !cons_utf8)
2420 			if (debugLevel >= 1)
2421 				i_puts(MSG_ConvUtf8);
2422 		if (cw->utf16Mode) {
2423 			if (debugLevel >= 1)
2424 				i_puts(MSG_ConvUtf16);
2425 			if (fwrite
2426 			    ((cw->bigMode ? "\xfe\xff" : "\xff\xfe"), 2, 1,
2427 			     fh) <= 0) {
2428 badwrite:
2429 				setError(MSG_NoWrite2, name);
2430 				fclose(fh);
2431 				return false;
2432 			}
2433 		}
2434 		if (cw->utf32Mode) {
2435 			if (debugLevel >= 1)
2436 				i_puts(MSG_ConvUtf32);
2437 			if (fwrite
2438 			    ((cw->bigMode ? "\x00\x00\xfe\xff" :
2439 			      "\xff\xfe\x00\x00"), 4, 1, fh) <= 0)
2440 				goto badwrite;
2441 		}
2442 		if (cw->dosMode && debugLevel >= 1)
2443 			i_puts(MSG_ConvDos);
2444 	}
2445 
2446 	for (i = startRange; i <= endRange; ++i) {
2447 		pst p = fetchLine(i, (cw->browseMode ? 1 : -1));
2448 		int len = pstLength(p);
2449 		char *suf = dirSuffix(i);
2450 		char *tp;
2451 		int tlen;
2452 		bool alloc_p = cw->browseMode;
2453 		bool rc = true;
2454 
2455 		if (!cw->dirMode) {
2456 			if (i == cw->dol && cw->nlMode)
2457 				--len;
2458 
2459 			if (name == cf->fileName && iuConvert) {
2460 				if (cw->dosMode && len && p[len - 1] == '\n') {
2461 // dos mode should not be set with utf16 or utf32; I hope.
2462 					tp = allocMem(len + 2);
2463 					memcpy(tp, p, len - 1);
2464 					memcpy(tp + len - 1, "\r\n", 3);
2465 					if (alloc_p)
2466 						free(p);
2467 					alloc_p = true;
2468 					p = (pst) tp;
2469 					++len;
2470 				}
2471 
2472 				if (cw->iso8859Mode && cons_utf8) {
2473 					utf2iso((uchar *) p, len,
2474 						(uchar **) & tp, &tlen);
2475 					if (alloc_p)
2476 						free(p);
2477 					alloc_p = true;
2478 					p = (pst) tp;
2479 					if (fwrite(p, tlen, 1, fh) <= 0)
2480 						rc = false;
2481 					len = tlen;
2482 					goto endline;
2483 				}
2484 
2485 				if (cw->utf8Mode && !cons_utf8) {
2486 					iso2utf((uchar *) p, len,
2487 						(uchar **) & tp, &tlen);
2488 					if (alloc_p)
2489 						free(p);
2490 					alloc_p = true;
2491 					p = (pst) tp;
2492 					if (fwrite(p, tlen, 1, fh) <= 0)
2493 						rc = false;
2494 					len = tlen;
2495 					goto endline;
2496 				}
2497 
2498 				if (cw->utf16Mode || cw->utf32Mode) {
2499 					utfHigh((char *)p, len, &tp, &tlen,
2500 						cons_utf8, cw->utf32Mode,
2501 						cw->bigMode);
2502 					if (alloc_p)
2503 						free(p);
2504 					alloc_p = true;
2505 					p = (pst) tp;
2506 					if (fwrite(p, tlen, 1, fh) <= 0)
2507 						rc = false;
2508 					len = tlen;
2509 					goto endline;
2510 				}
2511 			}
2512 
2513 			if (fwrite(p, len, 1, fh) <= 0)
2514 				rc = false;
2515 			goto endline;
2516 		}
2517 
2518 /* Write this line with directory suffix, and possibly attributes */
2519 		--len;
2520 		if (fwrite(p, len, 1, fh) <= 0) {
2521 badline:
2522 			rc = false;
2523 			goto endline;
2524 		}
2525 		fileSize += len;
2526 
2527 		if (cw->r_map) {
2528 			int l;
2529 /* extra ls stats to write */
2530 			char *extra;
2531 			len = strlen(suf);
2532 			if (len && fwrite(suf, len, 1, fh) <= 0)
2533 				goto badline;
2534 			++len;	/* for nl */
2535 			extra = (char *)cw->r_map[i].text;
2536 			l = strlen(extra);
2537 			if (l) {
2538 				if (fwrite(" ", 1, 1, fh) <= 0)
2539 					goto badline;
2540 				++len;
2541 				if (fwrite(extra, l, 1, fh) <= 0)
2542 					goto badline;
2543 				len += l;
2544 			}
2545 			if (fwrite("\n", 1, 1, fh) <= 0)
2546 				goto badline;
2547 			goto endline;
2548 		}
2549 
2550 		strcat(suf, "\n");
2551 		len = strlen(suf);
2552 		if (fwrite(suf, len, 1, fh) <= 0)
2553 			goto badline;
2554 
2555 endline:
2556 		if (alloc_p)
2557 			free(p);
2558 		if (!rc)
2559 			goto badwrite;
2560 		fileSize += len;
2561 	}			/* loop over lines */
2562 
2563 	fclose(fh);
2564 /* This is not an undoable operation, nor does it change data.
2565  * In fact the data is "no longer modified" if we have written all of it. */
2566 	if (startRange == 1 && endRange == cw->dol)
2567 		cw->changeMode = false;
2568 	return true;
2569 }				/* writeFile */
2570 
readContext(int cx)2571 static bool readContext(int cx)
2572 {
2573 	struct ebWindow *lw;
2574 	int i, fardol;
2575 	struct lineMap *t;
2576 
2577 	if (!cxCompare(cx))
2578 		return false;
2579 	if (!cxActive(cx))
2580 		return false;
2581 
2582 	fileSize = 0;
2583 	lw = sessionList[cx].lw;
2584 	fardol = lw->dol;
2585 	if (!fardol)
2586 		return true;
2587 	if (cw->dol == endRange)
2588 		cw->nlMode = false;
2589 	newpiece = t = allocZeroMem(fardol * LMSIZE);
2590 	for (i = 1; i <= fardol; ++i, ++t) {
2591 		pst p = fetchLineContext(i, (lw->dirMode ? -1 : 1), cx);
2592 		int len = pstLength(p);
2593 		if (lw->dirMode) {
2594 			char *suf = dirSuffixContext(i, cx);
2595 			char *q;
2596 			if (lw->r_map) {
2597 				char *extra = (char *)lw->r_map[i].text;
2598 				int elen = strlen(extra);
2599 				q = allocMem(len + 4 + elen);
2600 				memcpy(q, p, len);
2601 				--len;
2602 				strcpy(q + len, suf);
2603 				if (elen) {
2604 					strcat(q, " ");
2605 					strcat(q, extra);
2606 				}
2607 				strcat(q, "\n");
2608 			} else {
2609 				q = allocMem(len + 3);
2610 				memcpy(q, p, len);
2611 				--len;
2612 				strcat(suf, "\n");
2613 				strcpy(q + len, suf);
2614 			}
2615 			len = strlen(q);
2616 			p = (pst) q;
2617 		}
2618 		t->text = p;
2619 		fileSize += len;
2620 	}			/* loop over lines in the "other" context */
2621 
2622 	addToMap(fardol, endRange);
2623 	if (lw->nlMode) {
2624 		--fileSize;
2625 		if (cw->dol == endRange)
2626 			cw->nlMode = true;
2627 	}
2628 	if (binaryDetect & !cw->binMode && lw->binMode) {
2629 		cw->binMode = true;
2630 		i_puts(MSG_BinaryData);
2631 	}
2632 	return true;
2633 }				/* readContext */
2634 
writeContext(int cx)2635 static bool writeContext(int cx)
2636 {
2637 	struct ebWindow *lw;
2638 	int i, len;
2639 	struct lineMap *newmap, *t;
2640 	pst p;
2641 	int fardol = endRange - startRange + 1;
2642 
2643 	if (!startRange)
2644 		fardol = 0;
2645 	if (!cxCompare(cx))
2646 		return false;
2647 	if (cxActive(cx) && !cxQuit(cx, 2))
2648 		return false;
2649 
2650 	cxInit(cx);
2651 	lw = sessionList[cx].lw;
2652 	fileSize = 0;
2653 	if (startRange) {
2654 		newmap = t = allocZeroMem((fardol + 2) * LMSIZE);
2655 		for (i = startRange, ++t; i <= endRange; ++i, ++t) {
2656 			p = fetchLine(i, (cw->dirMode ? -1 : 1));
2657 			len = pstLength(p);
2658 			if (cw->dirMode) {
2659 				char *q;
2660 				char *suf = dirSuffix(i);
2661 				if (cw->r_map) {
2662 					char *extra = (char *)cw->r_map[i].text;
2663 					int elen = strlen(extra);
2664 					q = allocMem(len + 4 + elen);
2665 					memcpy(q, p, len);
2666 					--len;
2667 					strcpy(q + len, suf);
2668 					if (elen) {
2669 						strcat(q, " ");
2670 						strcat(q, extra);
2671 					}
2672 					strcat(q, "\n");
2673 				} else {
2674 					q = allocMem(len + 3);
2675 					memcpy(q, p, len);
2676 					--len;
2677 					strcat(suf, "\n");
2678 					strcpy(q + len, suf);
2679 				}
2680 				len = strlen(q);
2681 				p = (pst) q;
2682 			}
2683 			t->text = p;
2684 			fileSize += len;
2685 		}
2686 		lw->map = newmap;
2687 		lw->binMode = cw->binMode;
2688 		if (cw->nlMode && endRange == cw->dol) {
2689 			lw->nlMode = true;
2690 			--fileSize;
2691 		}
2692 	}
2693 
2694 	lw->dot = lw->dol = fardol;
2695 	return true;
2696 }				/* writeContext */
2697 
debrowseSuffix(char * s)2698 static void debrowseSuffix(char *s)
2699 {
2700 	if (!s)
2701 		return;
2702 	while (*s) {
2703 		if (*s == '.' && stringEqual(s, ".browse")) {
2704 			*s = 0;
2705 			return;
2706 		}
2707 		++s;
2708 	}
2709 }				/* debrowseSuffix */
2710 
2711 // macro substitutions within the command line.
2712 // '_ filename, '. current line, '- last line, '+ next line,
2713 // 'x line labeled x. Replace only if there are no letters around it.
2714 // isn't will not become isn + the line labeled t.
apostropheMacros(const char * line)2715 static char *apostropheMacros(const char *line)
2716 {
2717 	char *newline, *s;
2718 	const char *t;
2719 	pst p;
2720 	char key;
2721 	int linesize = 0, pass, n;
2722 
2723 	for (pass = 1; pass <= 2; ++pass) {
2724 		for (t = line; *t; ++t) {
2725 			if (*t != '\'')
2726 				goto addchar;
2727 			if (t > line && isalnumByte(t[-1]))
2728 				goto addchar;
2729 			key = t[1];
2730 			if (key && isalnumByte(t[2]))
2731 				goto addchar;
2732 
2733 			if (key == '_') {
2734 				++t;
2735 				if (!cf->fileName)
2736 					continue;
2737 				if (pass == 1) {
2738 					linesize += strlen(cf->fileName);
2739 				} else {
2740 					strcpy(s, cf->fileName);
2741 					s += strlen(s);
2742 				}
2743 				continue;
2744 			}
2745 
2746 			if (key == '.' || key == '-' || key == '+') {
2747 				n = cw->dot;
2748 				if (key == '-')
2749 					--n;
2750 				if (key == '+')
2751 					++n;
2752 				if (n > cw->dol || n <= 0) {
2753 					setError(MSG_OutOfRange, key);
2754 					return NULL;
2755 				}
2756 frombuf:
2757 				++t;
2758 				if (pass == 1) {
2759 					p = fetchLine(n, -1);
2760 					linesize += pstLength(p) - 1;
2761 				} else {
2762 					p = fetchLine(n, 1);
2763 					if (perl2c((char *)p)) {
2764 						free(p);
2765 						setError(MSG_ShellNull);
2766 						return NULL;
2767 					}
2768 					strcpy(s, (char *)p);
2769 					s += strlen(s);
2770 					free(p);
2771 				}
2772 				continue;
2773 			}
2774 
2775 			if (islowerByte(key)) {
2776 				n = cw->labels[key - 'a'];
2777 				if (!n) {
2778 					setError(MSG_NoLabel, key);
2779 					return NULL;
2780 				}
2781 				goto frombuf;
2782 			}
2783 
2784 addchar:
2785 			if (pass == 1)
2786 				++linesize;
2787 			else
2788 				*s++ = *t;
2789 		}		/* loop over chars */
2790 
2791 		if (pass == 1)
2792 			s = newline = allocMem(linesize + 1);
2793 		else
2794 			*s = 0;
2795 	}			/* two passes */
2796 
2797 	return newline;
2798 }
2799 
get_interactive_shell(const char * sh)2800 static char *get_interactive_shell(const char *sh)
2801 {
2802 	char *ishell = NULL;
2803 #ifdef DOSLIKE
2804 	return cloneString(sh);
2805 #else
2806 	if (asprintf(&ishell, "exec %s -i", sh) == -1)
2807 		i_printfExit(MSG_NoMem);
2808 	return ishell;
2809 #endif
2810 }				/* get_interactive_shell */
2811 
shellEscape(const char * line)2812 static bool shellEscape(const char *line)
2813 {
2814 	char *sh, *newline;
2815 	char *interactive_shell_cmd = NULL;
2816 
2817 #ifdef DOSLIKE
2818 /* cmd.exe is the windows shell */
2819 	sh = "cmd";
2820 #else
2821 /* preferred shell */
2822 	sh = getenv("SHELL");
2823 	if (!sh || !*sh)
2824 		sh = "/bin/sh";
2825 #endif
2826 
2827 	if (!line[0]) {
2828 /* interactive shell */
2829 		if (!isInteractive) {
2830 			setError(MSG_SessionBackground);
2831 			return false;
2832 		}
2833 		interactive_shell_cmd = get_interactive_shell(sh);
2834 		eb_system(interactive_shell_cmd, true);
2835 		nzFree(interactive_shell_cmd);
2836 		return true;
2837 	}
2838 
2839 	newline = apostropheMacros(line);
2840 	if (!newline)
2841 		return false;
2842 
2843 /* Run the command.  Note that this routine returns success
2844  * even if the shell command failed.
2845  * Edbrowse succeeds if it is *able* to run the system command. */
2846 	eb_system(newline, true);
2847 	free(newline);
2848 	return true;
2849 }				/* shellEscape */
2850 
2851 /* Valid delimiters for search/substitute.
2852  * note that \ is conspicuously absent, not a valid delimiter.
2853  * I alsso avoid nestable delimiters such as parentheses.
2854  * And no alphanumerics please -- too confusing.
2855  * ed allows it, but I don't. */
2856 static const char valid_delim[] = "_=!;:`\"',/?@-";
2857 /* And a valid char for starting a line address */
2858 static const char valid_laddr[] = "0123456789-'.$+/?";
2859 
2860 /* Check the syntax of a regular expression, before we pass it to pcre.
2861  * The first char is the delimiter -- we stop at the next delimiter.
2862  * A pointer to the second delimiter is returned, along with the
2863  * (possibly reformatted) regular expression. */
2864 
2865 static bool
regexpCheck(const char * line,bool isleft,bool ebmuck,char ** rexp,const char ** split)2866 regexpCheck(const char *line, bool isleft, bool ebmuck,
2867 	    char **rexp, const char **split)
2868 {				/* result parameters */
2869 	static char re[MAXRE + 20];
2870 	const char *start;
2871 	char *e = re;
2872 	char c, d;
2873 /* Remember whether a char is "on deck", ready to be modified by * etc. */
2874 	bool ondeck = false;
2875 	bool was_ques = true;	/* previous modifier was ? */
2876 	bool cc = false;	/* are we in a [...] character class */
2877 	int mod;		/* length of modifier */
2878 	int paren = 0;		/* nesting level of parentheses */
2879 /* We wouldn't be here if the line was empty. */
2880 	char delim = *line++;
2881 
2882 	*rexp = re;
2883 	if (!strchr(valid_delim, delim)) {
2884 		setError(MSG_BadDelimit);
2885 		return false;
2886 	}
2887 	start = line;
2888 
2889 	c = *line;
2890 	if (ebmuck) {
2891 		if (isleft) {
2892 			if (c == delim || c == 0) {
2893 				if (!cw->lhs_yes) {
2894 					setError(MSG_NoSearchString);
2895 					return false;
2896 				}
2897 				strcpy(re, cw->lhs);
2898 				*split = line;
2899 				return true;
2900 			}
2901 /* Interpret lead * or lone [ as literal */
2902 			if (strchr("*?+", c) || (c == '[' && !line[1])) {
2903 				*e++ = '\\';
2904 				*e++ = c;
2905 				++line;
2906 				ondeck = true;
2907 			}
2908 		} else if (c == '%' && (line[1] == delim || line[1] == 0)) {
2909 			if (!cw->rhs_yes) {
2910 				setError(MSG_NoReplaceString);
2911 				return false;
2912 			}
2913 			strcpy(re, cw->rhs);
2914 			*split = line + 1;
2915 			return true;
2916 		}
2917 	}
2918 	/* ebmuck tricks */
2919 	while ((c = *line)) {
2920 		if (e >= re + MAXRE - 3) {
2921 			setError(MSG_RexpLong);
2922 			return false;
2923 		}
2924 		d = line[1];
2925 
2926 		if (c == '\\') {
2927 			line += 2;
2928 			if (d == 0) {
2929 				setError(MSG_LineBackslash);
2930 				return false;
2931 			}
2932 			ondeck = true;
2933 			was_ques = false;
2934 /* I can't think of any reason to remove the escaping \ from any character,
2935  * except ()|, where we reverse the sense of escape. */
2936 			if (ebmuck && isleft && !cc
2937 			    && (d == '(' || d == ')' || d == '|')) {
2938 				if (d == '|')
2939 					ondeck = false, was_ques = true;
2940 				if (d == '(')
2941 					++paren, ondeck = false, was_ques =
2942 					    false;
2943 				if (d == ')')
2944 					--paren;
2945 				if (paren < 0) {
2946 					setError(MSG_UnexpectedRight);
2947 					return false;
2948 				}
2949 				*e++ = d;
2950 				continue;
2951 			}
2952 			if (d == delim || (ebmuck && !isleft && d == '&')) {
2953 				*e++ = d;
2954 				continue;
2955 			}
2956 /* Nothing special; we retain the escape character. */
2957 			*e++ = c;
2958 			if (isleft && d >= '0' && d <= '7'
2959 			    && (*line < '0' || *line > '7'))
2960 				*e++ = '0';
2961 			*e++ = d;
2962 			continue;
2963 		}
2964 
2965 		/* escaping backslash */
2966 		/* Break out if we hit the delimiter. */
2967 		if (c == delim)
2968 			break;
2969 
2970 /* Remember, I reverse the sense of ()| */
2971 		if (isleft) {
2972 			if ((ebmuck && (c == '(' || c == ')' || c == '|'))
2973 			    || (c == '^' && line != start && !cc))
2974 				*e++ = '\\';
2975 			if (c == '$' && d && d != delim)
2976 				*e++ = '\\';
2977 		}
2978 
2979 		if (c == '$' && !isleft && isdigitByte(d)) {
2980 			if (d == '0' || isdigitByte(line[2])) {
2981 				setError(MSG_RexpDollar);
2982 				return false;
2983 			}
2984 		}
2985 		/* dollar digit on the right */
2986 		if (!isleft && c == '&' && ebmuck) {
2987 			*e++ = '$';
2988 			*e++ = '0';
2989 			++line;
2990 			continue;
2991 		}
2992 
2993 /* push the character */
2994 		*e++ = c;
2995 		++line;
2996 
2997 /* No more checks for the rhs */
2998 		if (!isleft)
2999 			continue;
3000 
3001 		if (cc) {	/* character class */
3002 			if (c == ']')
3003 				cc = false;
3004 			continue;
3005 		}
3006 		if (c == '[')
3007 			cc = true;
3008 
3009 /* Skip all these checks for javascript,
3010  * it probably has the expression right anyways. */
3011 		if (!ebmuck)
3012 			continue;
3013 
3014 /* Modifiers must have a preceding character.
3015  * Except ? which can reduce the greediness of the others. */
3016 		if (c == '?' && !was_ques) {
3017 			ondeck = false;
3018 			was_ques = true;
3019 			continue;
3020 		}
3021 
3022 		mod = 0;
3023 		if (c == '?' || c == '*' || c == '+')
3024 			mod = 1;
3025 		if (c == '{' && isdigitByte(d)) {
3026 			const char *t = line + 1;
3027 			while (isdigitByte(*t))
3028 				++t;
3029 			if (*t == ',')
3030 				++t;
3031 			while (isdigitByte(*t))
3032 				++t;
3033 			if (*t == '}')
3034 				mod = t + 2 - line;
3035 		}
3036 		if (mod) {
3037 			--mod;
3038 			if (mod) {
3039 				strncpy(e, line, mod);
3040 				e += mod;
3041 				line += mod;
3042 			}
3043 			if (!ondeck) {
3044 				*e = 0;
3045 				setError(MSG_RexpModifier, e - mod - 1);
3046 				return false;
3047 			}
3048 			ondeck = false;
3049 			continue;
3050 		}
3051 		/* modifier */
3052 		ondeck = true;
3053 		was_ques = false;
3054 	}			/* loop over chars in the pattern */
3055 	*e = 0;
3056 
3057 	*split = line;
3058 
3059 	if (ebmuck) {
3060 		if (cc) {
3061 			setError(MSG_NoBracket);
3062 			return false;
3063 		}
3064 		if (paren) {
3065 			setError(MSG_NoParen);
3066 			return false;
3067 		}
3068 
3069 		if (isleft) {
3070 			cw->lhs_yes = true;
3071 			strcpy(cw->lhs, re);
3072 		} else {
3073 			cw->rhs_yes = true;
3074 			strcpy(cw->rhs, re);
3075 		}
3076 	}
3077 
3078 	debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re);
3079 	return true;
3080 }				/* regexpCheck */
3081 
3082 /* regexp variables */
3083 static int re_count;
3084 static int re_vector[11 * 3];
3085 static pcre *re_cc;		/* compiled */
3086 static bool re_utf8 = true;
3087 
regexpCompile(const char * re,bool ci)3088 static void regexpCompile(const char *re, bool ci)
3089 {
3090 	static signed char try8 = 0;	/* 1 is utf8 on, -1 is utf8 off */
3091 	const char *re_error;
3092 	int re_offset;
3093 	int re_opt;
3094 
3095 top:
3096 /* Do we need PCRE_NO_AUTO_CAPTURE? */
3097 	re_opt = 0;
3098 	if (ci)
3099 		re_opt |= PCRE_CASELESS;
3100 
3101 	if (re_utf8) {
3102 		if (cons_utf8 && !cw->binMode && try8 >= 0) {
3103 			if (try8 == 0) {
3104 				const char *s = getenv("PCREUTF8");
3105 				if (s && stringEqual(s, "off")) {
3106 					try8 = -1;
3107 					goto top;
3108 				}
3109 			}
3110 			try8 = 1;
3111 			re_opt |= PCRE_UTF8;
3112 		}
3113 	}
3114 
3115 	re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
3116 	if (!re_cc && try8 > 0 && strstr(re_error, "PCRE_UTF8 support")) {
3117 		i_puts(MSG_PcreUtf8);
3118 		try8 = -1;
3119 		goto top;
3120 	}
3121 	if (!re_cc && try8 > 0 && strstr(re_error, "invalid UTF-8 string")) {
3122 		i_puts(MSG_BadUtf8String);
3123 	}
3124 
3125 	if (!re_cc)
3126 		setError(MSG_RexpError, re_error);
3127 }				/* regexpCompile */
3128 
3129 /* Get the start or end of a range.
3130  * Pass the line containing the address. */
getRangePart(const char * line,int * lineno,const char ** split)3131 static bool getRangePart(const char *line, int *lineno, const char **split)
3132 {				/* result parameters */
3133 	int ln = cw->dot;	/* this is where we start */
3134 	char first = *line;
3135 
3136 	if (isdigitByte(first)) {
3137 		ln = strtol(line, (char **)&line, 10);
3138 	} else if (first == '.') {
3139 		++line;
3140 /* ln is already set */
3141 	} else if (first == '$') {
3142 		++line;
3143 		ln = cw->dol;
3144 	} else if (first == '\'' && islowerByte(line[1])) {
3145 		ln = cw->labels[line[1] - 'a'];
3146 		if (!ln) {
3147 			setError(MSG_NoLabel, line[1]);
3148 			return false;
3149 		}
3150 		line += 2;
3151 	} else if (first == '/' || first == '?') {
3152 
3153 		char *re;	/* regular expression */
3154 		bool ci = caseInsensitive;
3155 		signed char incr;	/* forward or back */
3156 /* Don't look through an empty buffer. */
3157 		if (cw->dol == 0) {
3158 			setError(MSG_EmptyBuffer);
3159 			return false;
3160 		}
3161 		if (!regexpCheck(line, true, true, &re, &line))
3162 			return false;
3163 		if (*line == first) {
3164 			++line;
3165 			if (*line == 'i')
3166 				ci = true, ++line;
3167 		}
3168 
3169 		/* second delimiter */
3170 		regexpCompile(re, ci);
3171 		if (!re_cc)
3172 			return false;
3173 /* We should probably study the pattern, if the file is large.
3174  * But then again, it's probably not worth it,
3175  * since the expressions are simple, and the lines are short. */
3176 		incr = (first == '/' ? 1 : -1);
3177 		while (true) {
3178 			char *subject;
3179 			ln += incr;
3180 			if (!searchWrap && (ln == 0 || ln > cw->dol)) {
3181 				pcre_free(re_cc);
3182 				setError(MSG_NotFound);
3183 				return false;
3184 			}
3185 			if (ln > cw->dol)
3186 				ln = 1;
3187 			if (ln == 0)
3188 				ln = cw->dol;
3189 			subject = (char *)fetchLine(ln, 1);
3190 			re_count =
3191 			    pcre_exec(re_cc, 0, subject,
3192 				      pstLength((pst) subject) - 1, 0, 0,
3193 				      re_vector, 33);
3194 			free(subject);
3195 // An error in evaluation is treated like text not found.
3196 // This usually happens because this particular line has bad binary, not utf8.
3197 			if (re_count < -1 && pcre_utf8_error_stop) {
3198 				pcre_free(re_cc);
3199 				setError(MSG_RexpError2, ln);
3200 				return (globSub = false);
3201 			}
3202 			if (re_count >= 0)
3203 				break;
3204 			if (ln == cw->dot) {
3205 				pcre_free(re_cc);
3206 				setError(MSG_NotFound);
3207 				return false;
3208 			}
3209 		}		/* loop over lines */
3210 		pcre_free(re_cc);
3211 /* and ln is the line that matches */
3212 	}
3213 	/* Now add or subtract from this number */
3214 	while ((first = *line) == '+' || first == '-') {
3215 		int add = 1;
3216 		++line;
3217 		if (isdigitByte(*line))
3218 			add = strtol(line, (char **)&line, 10);
3219 		ln += (first == '+' ? add : -add);
3220 	}
3221 
3222 	if (cw->dirMode && lineno == &destLine) {
3223 		if (ln >= MAXSESSION) {
3224 			setError(MSG_SessionHigh, ln, MAXSESSION - 1);
3225 			return false;
3226 		}
3227 		if (ln == context) {
3228 			setError(MSG_SessionCurrent, ln);
3229 			return false;
3230 		}
3231 		if (!sessionList[ln].lw || !sessionList[ln].lw->dirMode) {
3232 			char numstring[8];
3233 			sprintf(numstring, "%d", ln);
3234 			setError(MSG_NotDir, numstring);
3235 			return false;
3236 		}
3237 		*lineno = ln;
3238 		*split = line;
3239 		return true;
3240 	}
3241 
3242 	if (ln > cw->dol) {
3243 		setError(MSG_LineHigh);
3244 		return false;
3245 	}
3246 	if (ln < 0) {
3247 		setError(MSG_LineLow);
3248 		return false;
3249 	}
3250 
3251 	*lineno = ln;
3252 	*split = line;
3253 	return true;
3254 }				/* getRangePart */
3255 
3256 /* Apply a regular expression to each line, and then execute
3257  * a command for each matching, or nonmatching, line.
3258  * This is the global feature, g/re/p, which gives us the word grep. */
doGlobal(const char * line)3259 static bool doGlobal(const char *line)
3260 {
3261 	int gcnt = 0;		/* global count */
3262 	bool ci = caseInsensitive;
3263 	bool change;
3264 	char delim = *line;
3265 	struct lineMap *t;
3266 	char *re;		/* regular expression */
3267 	int i, origdot, yesdot, nodot;
3268 
3269 	if (!delim) {
3270 		setError(MSG_RexpMissing, icmd);
3271 		return false;
3272 	}
3273 
3274 	if (!regexpCheck(line, true, true, &re, &line))
3275 		return false;
3276 	if (*line != delim) {
3277 		setError(MSG_NoDelimit);
3278 		return false;
3279 	}
3280 	++line;
3281 	if (*line == 'i')
3282 		++line, ci = true;
3283 	skipWhite(&line);
3284 
3285 /* clean up any previous global flags.
3286  * Also get ready for javascript, as in g/<->/ i=+
3287  * which I use in web based gmail to clear out spam etc. */
3288 	for (t = cw->map + 1; t->text; ++t)
3289 		t->gflag = false;
3290 
3291 /* Find the lines that match the pattern. */
3292 	regexpCompile(re, ci);
3293 	if (!re_cc)
3294 		return false;
3295 	for (i = startRange; i <= endRange; ++i) {
3296 		char *subject = (char *)fetchLine(i, 1);
3297 		re_count =
3298 		    pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1,
3299 			      0, 0, re_vector, 33);
3300 		free(subject);
3301 		if (re_count < -1 && pcre_utf8_error_stop) {
3302 			pcre_free(re_cc);
3303 			setError(MSG_RexpError2, i);
3304 			return false;
3305 		}
3306 		if ((re_count < 0 && cmd == 'v')
3307 		    || (re_count >= 0 && cmd == 'g')) {
3308 			++gcnt;
3309 			cw->map[i].gflag = true;
3310 		}
3311 	}			/* loop over line */
3312 	pcre_free(re_cc);
3313 
3314 	if (!gcnt) {
3315 		setError((cmd == 'v') + MSG_NoMatchG);
3316 		return false;
3317 	}
3318 
3319 /* apply the subcommand to every line with a star */
3320 	globSub = true;
3321 	setError(-1);
3322 	if (!*line)
3323 		line = "p";
3324 	origdot = cw->dot;
3325 	yesdot = nodot = 0;
3326 	change = true;
3327 	while (gcnt && change) {
3328 		change = false;	/* kinda like bubble sort */
3329 		for (i = 1; i <= cw->dol; ++i) {
3330 			t = cw->map + i;
3331 			if (!t->gflag)
3332 				continue;
3333 			if (intFlag)
3334 				goto done;
3335 			change = true, --gcnt;
3336 			t->gflag = false;
3337 			cw->dot = i;	/* so we can run the command at this line */
3338 			if (runCommand(line)) {
3339 				yesdot = cw->dot;
3340 /* try this line again, in case we deleted or moved it somewhere else */
3341 				--i;
3342 			} else {
3343 /* error in subcommand might turn global flag off */
3344 				if (!globSub) {
3345 					nodot = i, yesdot = 0;
3346 					goto done;
3347 				}	/* serious error */
3348 			}	/* subcommand succeeds or fails */
3349 		}		/* loop over lines */
3350 	}			/* loop making changes */
3351 
3352 done:
3353 	globSub = false;
3354 /* yesdot could be 0, even on success, if all lines are deleted via g/re/d */
3355 	if (yesdot || !cw->dol) {
3356 		cw->dot = yesdot;
3357 		if ((cmd == 's' || cmd == 'i') && subPrint == 1)
3358 			printDot();
3359 	} else if (nodot) {
3360 		cw->dot = nodot;
3361 	} else {
3362 		cw->dot = origdot;
3363 		if (!errorMsg[0])
3364 			setError(MSG_NotModifiedG);
3365 	}
3366 	if (!errorMsg[0] && intFlag)
3367 		setError(MSG_Interrupted);
3368 	return (errorMsg[0] == 0);
3369 }				/* doGlobal */
3370 
fieldNumProblem(int desc,char * c,int n,int nt,int nrt)3371 static void fieldNumProblem(int desc, char *c, int n, int nt, int nrt)
3372 {
3373 	if (!nrt) {
3374 		setError(MSG_NoInputFields + desc);
3375 		return;
3376 	}
3377 	if (!n) {
3378 		setError(MSG_ManyInputFields + desc, c, c, nt);
3379 		return;
3380 	}
3381 	if (nt > 1)
3382 		setError(MSG_InputRange, n, c, c, nt);
3383 	else
3384 		setError(MSG_InputRange2, n, c, c);
3385 }				/* fieldNumProblem */
3386 
3387 /* Perform a substitution on a given line.
3388  * The lhs has been compiled, and the rhs is passed in for replacement.
3389  * Refer to the static variable re_cc for the compiled lhs.
3390  * Return true for a replacement, false for no replace, and -1 for a problem. */
3391 
3392 static char *replaceString;
3393 static int replaceStringLength;
3394 static char *replaceStringEnd;
3395 
3396 static int
replaceText(const char * line,int len,const char * rhs,bool ebmuck,int nth,bool global,int ln)3397 replaceText(const char *line, int len, const char *rhs,
3398 	    bool ebmuck, int nth, bool global, int ln)
3399 {
3400 	int offset = 0, lastoffset, instance = 0;
3401 	int span;
3402 	char *r;
3403 	int rlen;
3404 	const char *s = line, *s_end, *t;
3405 	char c, d;
3406 
3407 	r = initString(&rlen);
3408 
3409 	while (true) {
3410 /* find the next match */
3411 		re_count =
3412 		    pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33);
3413 		if (re_count < -1 &&
3414 		    (pcre_utf8_error_stop || startRange == endRange)) {
3415 			setError(MSG_RexpError2, ln);
3416 			nzFree(r);
3417 			return -1;
3418 		}
3419 
3420 		if (re_count < 0)
3421 			break;
3422 		++instance;	/* found another match */
3423 		lastoffset = offset;
3424 		offset = re_vector[1];	/* ready for next iteration */
3425 		if (offset == lastoffset && (nth > 1 || global)) {
3426 			setError(MSG_ManyEmptyStrings);
3427 			nzFree(r);
3428 			return -1;
3429 		}
3430 
3431 		if (!global &&instance != nth)
3432 			continue;
3433 
3434 /* copy up to the match point */
3435 		s_end = line + re_vector[0];
3436 		span = s_end - s;
3437 		stringAndBytes(&r, &rlen, s, span);
3438 		s = line + offset;
3439 
3440 /* Now copy over the rhs */
3441 /* Special case lc mc uc */
3442 		if (ebmuck && (rhs[0] == 'l' || rhs[0] == 'm' || rhs[0] == 'u')
3443 		    && rhs[1] == 'c' && rhs[2] == 0) {
3444 			int savelen = rlen;
3445 			span = re_vector[1] - re_vector[0];
3446 			stringAndBytes(&r, &rlen, line + re_vector[0], span);
3447 			caseShift(r + savelen, rhs[0]);
3448 			if (!global)
3449 				break;
3450 			continue;
3451 		}
3452 
3453 		/* copy rhs, watching for $n */
3454 		t = rhs;
3455 		while ((c = *t)) {
3456 			d = t[1];
3457 			if (c == '\\') {
3458 				t += 2;
3459 				if (d == '$') {
3460 					stringAndChar(&r, &rlen, d);
3461 					continue;
3462 				}
3463 				if (d == 'n') {
3464 					stringAndChar(&r, &rlen, '\n');
3465 					continue;
3466 				}
3467 				if (d == 't') {
3468 					stringAndChar(&r, &rlen, '\t');
3469 					continue;
3470 				}
3471 				if (d == 'b') {
3472 					stringAndChar(&r, &rlen, '\b');
3473 					continue;
3474 				}
3475 				if (d == 'r') {
3476 					stringAndChar(&r, &rlen, '\r');
3477 					continue;
3478 				}
3479 				if (d == 'f') {
3480 					stringAndChar(&r, &rlen, '\f');
3481 					continue;
3482 				}
3483 				if (d == 'a') {
3484 					stringAndChar(&r, &rlen, '\a');
3485 					continue;
3486 				}
3487 				if (d >= '0' && d <= '7') {
3488 					int octal = d - '0';
3489 					d = *t;
3490 					if (d >= '0' && d <= '7') {
3491 						++t;
3492 						octal = 8 * octal + d - '0';
3493 						d = *t;
3494 						if (d >= '0' && d <= '7') {
3495 							++t;
3496 							octal =
3497 							    8 * octal + d - '0';
3498 						}
3499 					}
3500 					stringAndChar(&r, &rlen, octal);
3501 					continue;
3502 				}	/* octal */
3503 				if (!ebmuck)
3504 					stringAndChar(&r, &rlen, '\\');
3505 				stringAndChar(&r, &rlen, d);
3506 				continue;
3507 			}	// \ cases
3508 
3509 			if (c == '$' && isdigitByte(d)) {
3510 				int y, z;
3511 				t += 2;
3512 				d -= '0';
3513 				if (d > re_count)
3514 					continue;
3515 				y = re_vector[2 * d];
3516 				z = re_vector[2 * d + 1];
3517 				if (y < 0)
3518 					continue;
3519 				span = z - y;
3520 				stringAndBytes(&r, &rlen, line + y, span);
3521 				continue;
3522 			}
3523 
3524 			if (c == '%' && d == 'l' &&
3525 			    !strncmp(t + 2, "ine", 3) && !isalnum(t[5])) {
3526 				char numstring[12];
3527 				sprintf(numstring, "%d", ln);
3528 				stringAndString(&r, &rlen, numstring);
3529 				t += 5;
3530 				continue;
3531 			}
3532 
3533 			stringAndChar(&r, &rlen, c);
3534 			++t;
3535 		}
3536 
3537 		if (!global)
3538 			break;
3539 	}			/* loop matching the regular expression */
3540 
3541 	if (!instance) {
3542 		nzFree(r);
3543 		return false;
3544 	}
3545 
3546 	if (!global &&instance < nth) {
3547 		nzFree(r);
3548 		return false;
3549 	}
3550 
3551 /* We got a match, copy the last span. */
3552 	s_end = line + len;
3553 	span = s_end - s;
3554 	stringAndBytes(&r, &rlen, s, span);
3555 
3556 	replaceString = r;
3557 	replaceStringLength = rlen;
3558 	return true;
3559 }				/* replaceText */
3560 
3561 static void
findField(const char * line,int ftype,int n,int * total,int * realtotal,int * tagno,char ** href,const Tag ** tagp)3562 findField(const char *line, int ftype, int n,
3563 	  int *total, int *realtotal, int *tagno, char **href,
3564 	  const Tag **tagp)
3565 {
3566 	const Tag *t;
3567 	int nt = 0;		/* number of fields total */
3568 	int nrt = 0;		/* the real total, for input fields */
3569 	int nm = 0;		/* number match */
3570 	int j;
3571 	const char *s, *ss;
3572 	char *h, *nmh;
3573 	char c;
3574 	static const char urlok[] =
3575 	    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,./?@#%&-_+=:~";
3576 
3577 	if (href)
3578 		*href = 0;
3579 	if (tagp)
3580 		*tagp = 0;
3581 
3582 	if (cw->browseMode) {
3583 
3584 		s = line;
3585 		while ((c = *s) != '\n') {
3586 			++s;
3587 			if (c != InternalCodeChar)
3588 				continue;
3589 			j = strtol(s, (char **)&s, 10);
3590 			if (!ftype) {
3591 				if (*s != '{')
3592 					continue;
3593 				++nt, ++nrt;
3594 				if (n == nt || n < 0)
3595 					nm = j;
3596 				if (!n) {
3597 					if (!nm)
3598 						nm = j;
3599 					else
3600 						nm = -1;
3601 				}
3602 			} else {
3603 				if (*s != '<')
3604 					continue;
3605 				if (n > 0) {
3606 					++nt, ++nrt;
3607 					if (n == nt)
3608 						nm = j;
3609 					continue;
3610 				}
3611 				if (n < 0) {
3612 					nm = j;
3613 					++nt, ++nrt;
3614 					continue;
3615 				}
3616 				++nrt;
3617 				t = tagList[j];
3618 				if (ftype == 1 && t->itype <= INP_SUBMIT)
3619 					continue;
3620 				if (ftype == 2 && t->itype > INP_SUBMIT)
3621 					continue;
3622 				++nt;
3623 				if (!nm)
3624 					nm = j;
3625 				else
3626 					nm = -1;
3627 			}
3628 		}		/* loop over line */
3629 	}
3630 
3631 	if (nm < 0)
3632 		nm = 0;
3633 	if (total)
3634 		*total = nrt;
3635 	if (realtotal)
3636 		*realtotal = nt;
3637 	if (tagno)
3638 		*tagno = nm;
3639 	if (!ftype && nm) {
3640 		t = tagList[nm];
3641 		if (tagp)
3642 			*tagp = t;
3643 		if (t->action == TAGACT_A || t->action == TAGACT_FRAME ||
3644 		    t->action == TAGACT_MUSIC) {
3645 			if (href)
3646 				*href = cloneString(t->href);
3647 			if (href && isJSAlive && t->jv) {
3648 /* defer to the js variable for the reference */
3649 				char *jh = get_property_url(cf, t->jv, false);
3650 				if (jh) {
3651 					if (!*href || !stringEqual(*href, jh)) {
3652 						nzFree(*href);
3653 						*href = jh;
3654 					} else
3655 						nzFree(jh);
3656 				}
3657 			}
3658 		} else {
3659 // This link is not an anchor or frame, it's onclick on something else.
3660 			if (href)
3661 				*href = cloneString("#");
3662 		}
3663 	}
3664 
3665 	if (nt || ftype)
3666 		return;
3667 
3668 /* Second time through, maybe the url is in plain text. */
3669 	nmh = 0;
3670 	s = line;
3671 	while (true) {
3672 /* skip past weird characters */
3673 		while ((c = *s) != '\n') {
3674 			if (strchr(urlok, c))
3675 				break;
3676 			++s;
3677 		}
3678 		if (c == '\n')
3679 			break;
3680 		ss = s;
3681 		while (strchr(urlok, *s))
3682 			++s;
3683 		h = pullString1(ss, s);
3684 // When a url ends in period, that is almost always the end of the sentence,
3685 // Please check out www.foobar.com/snork.
3686 // and rarely part of the url.
3687 		if (s[-1] == '.')
3688 			h[s - ss - 1] = 0;
3689 		unpercentURL(h);
3690 		if (!isURL(h)) {
3691 			free(h);
3692 			continue;
3693 		}
3694 		++nt;
3695 		if (n == nt || n < 0) {
3696 			nm = nt;
3697 			nzFree(nmh);
3698 			nmh = h;
3699 			continue;
3700 		}
3701 		if (n) {
3702 			free(h);
3703 			continue;
3704 		}
3705 		if (!nm) {
3706 			nm = nt;
3707 			nmh = h;
3708 			continue;
3709 		}
3710 		free(h);
3711 		nm = -1;
3712 		free(nmh);
3713 		nmh = 0;
3714 	}			/* loop over line */
3715 
3716 	if (nm < 0)
3717 		nm = 0;
3718 	if (total)
3719 		*total = nt;
3720 	if (realtotal)
3721 		*realtotal = nt;
3722 	if (href)
3723 		*href = nmh;
3724 	else
3725 		nzFree(nmh);
3726 }				/* findField */
3727 
3728 static void
findInputField(const char * line,int ftype,int n,int * total,int * realtotal,int * tagno)3729 findInputField(const char *line, int ftype, int n, int *total, int *realtotal,
3730 	       int *tagno)
3731 {
3732 	findField(line, ftype, n, total, realtotal, tagno, 0, 0);
3733 }				/* findInputField */
3734 
3735 /* Substitute text on the lines in startRange through endRange.
3736  * We could be changing the text in an input field.
3737  * If so, we'll call infReplace().
3738  * Also, we might be indirectory mode, whence we must rename the file.
3739  * This is a complicated function!
3740  * The return can be true or false, with the usual meaning,
3741  * but also a return of -1, which is failure,
3742  * and an indication that we need to abort any g// in progress.
3743  * It's a serious problem. */
3744 
substituteText(const char * line)3745 static int substituteText(const char *line)
3746 {
3747 	int whichField = 0;
3748 	bool bl_mode = false;	/* running the bl command */
3749 	bool g_mode = false;	/* s/x/y/g */
3750 	bool ci = caseInsensitive;
3751 	bool save_nlMode;
3752 	char c, *s, *t;
3753 	int nth = 0;		/* s/x/y/7 */
3754 	int lastSubst = 0;	/* last successful substitution */
3755 	char *re;		/* the parsed regular expression */
3756 	int ln;			/* line number */
3757 	int j, linecount, slashcount, nullcount, tagno, total, realtotal;
3758 	char lhs[MAXRE], rhs[MAXRE];
3759 	struct lineMap *mptr;
3760 
3761 	replaceString = 0;
3762 
3763 	subPrint = 1;		/* default is to print the last line substituted */
3764 	re_cc = 0;
3765 	if (stringEqual(line, "`bl"))
3766 		bl_mode = true, breakLineSetup();
3767 
3768 	if (!bl_mode) {
3769 /* watch for s2/x/y/ for the second input field */
3770 		if (isdigitByte(*line))
3771 			whichField = strtol(line, (char **)&line, 10);
3772 		else if (*line == '$')
3773 			whichField = -1, ++line;
3774 		if (!*line) {
3775 			setError(MSG_RexpMissing2, icmd);
3776 			return -1;
3777 		}
3778 
3779 		if (cw->dirMode && !dirWrite) {
3780 			setError(MSG_DirNoWrite);
3781 			return -1;
3782 		}
3783 
3784 		if (!regexpCheck(line, true, true, &re, &line))
3785 			return -1;
3786 		strcpy(lhs, re);
3787 		if (!*line) {
3788 			setError(MSG_NoDelimit);
3789 			return -1;
3790 		}
3791 		if (!regexpCheck(line, false, true, &re, &line))
3792 			return -1;
3793 		strcpy(rhs, re);
3794 
3795 		if (*line) {	/* third delimiter */
3796 			++line;
3797 			subPrint = 0;
3798 			while ((c = *line)) {
3799 				if (c == 'g') {
3800 					g_mode = true;
3801 					++line;
3802 					continue;
3803 				}
3804 				if (c == 'i') {
3805 					ci = true;
3806 					++line;
3807 					continue;
3808 				}
3809 				if (c == 'p') {
3810 					subPrint = 2;
3811 					++line;
3812 					continue;
3813 				}
3814 				if (isdigitByte(c)) {
3815 					if (nth) {
3816 						setError(MSG_SubNumbersMany);
3817 						return -1;
3818 					}
3819 					nth = strtol(line, (char **)&line, 10);
3820 					continue;
3821 				}	/* number */
3822 				setError(MSG_SubSuffixBad);
3823 				return -1;
3824 			}	/* loop gathering suffix flags */
3825 			if (g_mode && nth) {
3826 				setError(MSG_SubNumberG);
3827 				return -1;
3828 			}
3829 		}		/* closing delimiter */
3830 		if (nth == 0 && !g_mode)
3831 			nth = 1;
3832 
3833 		regexpCompile(lhs, ci);
3834 		if (!re_cc)
3835 			return -1;
3836 	} else {
3837 
3838 		subPrint = 0;
3839 	}			/* bl_mode or not */
3840 
3841 	if (!globSub)
3842 		setError(-1);
3843 
3844 	for (ln = startRange; ln <= endRange && !intFlag; ++ln) {
3845 		char *p;
3846 		int len;
3847 
3848 		replaceString = 0;
3849 
3850 		p = (char *)fetchLine(ln, -1);
3851 		len = pstLength((pst) p);
3852 
3853 		if (bl_mode) {
3854 			int newlen;
3855 			if (!breakLine(p, len, &newlen)) {
3856 /* you just should never be here */
3857 				setError(MSG_BreakLong, 0);
3858 				nzFree(breakLineResult);
3859 				breakLineResult = 0;
3860 				return -1;
3861 			}
3862 /* empty line is not allowed */
3863 			if (!newlen)
3864 				breakLineResult[newlen++] = '\n';
3865 /* perhaps no changes were made */
3866 			if (newlen == len && !memcmp(p, breakLineResult, len)) {
3867 				nzFree(breakLineResult);
3868 				breakLineResult = 0;
3869 				continue;
3870 			}
3871 			replaceString = breakLineResult;
3872 /* But the regular substitute doesn't have the \n on the end.
3873  * We need to make this one conform. */
3874 			replaceStringLength = newlen - 1;
3875 		} else {
3876 
3877 			if (cw->browseMode) {
3878 				char search[20];
3879 				char searchend[4];
3880 				undoPush();
3881 				findInputField(p, 1, whichField, &total,
3882 					       &realtotal, &tagno);
3883 				if (!tagno) {
3884 					fieldNumProblem(0, "i", whichField,
3885 							total, realtotal);
3886 					continue;
3887 				}
3888 				sprintf(search, "%c%d<", InternalCodeChar,
3889 					tagno);
3890 				sprintf(searchend, "%c0>", InternalCodeChar);
3891 /* Ok, if the line contains a null, this ain't gonna work. */
3892 				s = strstr(p, search);
3893 				if (!s)
3894 					continue;
3895 				s = strchr(s, '<') + 1;
3896 				t = strstr(s, searchend);
3897 				if (!t)
3898 					continue;
3899 				j = replaceText(s, t - s, rhs, true, nth,
3900 						g_mode, ln);
3901 			} else {
3902 				j = replaceText(p, len - 1, rhs, true, nth,
3903 						g_mode, ln);
3904 			}
3905 			if (j < 0)
3906 				goto abort;
3907 			if (!j)
3908 				continue;
3909 		}
3910 
3911 /* Did we split this line into many lines? */
3912 		replaceStringEnd = replaceString + replaceStringLength;
3913 		linecount = slashcount = nullcount = 0;
3914 		for (t = replaceString; t < replaceStringEnd; ++t) {
3915 			c = *t;
3916 			if (c == '\n')
3917 				++linecount;
3918 			if (c == 0)
3919 				++nullcount;
3920 			if (c == '/')
3921 				++slashcount;
3922 		}
3923 
3924 		if (cw->sqlMode) {
3925 			if (linecount) {
3926 				setError(MSG_ReplaceNewline);
3927 				goto abort;
3928 			}
3929 
3930 			if (nullcount) {
3931 				setError(MSG_ReplaceNull);
3932 				goto abort;
3933 			}
3934 
3935 			*replaceStringEnd = '\n';
3936 			if (!sqlUpdateRow((pst) p, len - 1, (pst) replaceString,
3937 					  replaceStringLength))
3938 				goto abort;
3939 		}
3940 
3941 		if (cw->dirMode) {
3942 /* move the file, then update the text */
3943 			char src[ABSPATH], *dest;
3944 			if (slashcount + nullcount + linecount) {
3945 				setError(MSG_DirNameBad);
3946 				goto abort;
3947 			}
3948 			p[len - 1] = 0;	/* temporary */
3949 			t = makeAbsPath(p);
3950 			p[len - 1] = '\n';
3951 			if (!t)
3952 				goto abort;
3953 			strcpy(src, t);
3954 			*replaceStringEnd = 0;
3955 			dest = makeAbsPath(replaceString);
3956 			if (!dest)
3957 				goto abort;
3958 			if (!stringEqual(src, dest)) {
3959 				if (fileTypeByName(dest, true)) {
3960 					setError(MSG_DestFileExists);
3961 					goto abort;
3962 				}
3963 				if (rename(src, dest)) {
3964 					setError(MSG_NoRename, dest);
3965 					goto abort;
3966 				}
3967 			}	/* source and dest are different */
3968 		}
3969 
3970 		if (cw->browseMode) {
3971 			if (nullcount) {
3972 				setError(MSG_InputNull2);
3973 				goto abort;
3974 			}
3975 			if (linecount) {
3976 				setError(MSG_InputNewline2);
3977 				goto abort;
3978 			}
3979 			*replaceStringEnd = 0;
3980 /* We're managing our own printing, so leave notify = 0 */
3981 			if (!infReplace(tagno, replaceString, false))
3982 				goto abort;
3983 			undoCompare();
3984 			cw->undoable = false;
3985 		} else {
3986 
3987 			*replaceStringEnd = '\n';
3988 			if (!linecount) {
3989 /* normal substitute */
3990 				undoPush();
3991 				mptr = cw->map + ln;
3992 				mptr->text = allocMem(replaceStringLength + 1);
3993 				memcpy(mptr->text, replaceString,
3994 				       replaceStringLength + 1);
3995 				if (cw->dirMode || cw->sqlMode) {
3996 					undoCompare();
3997 					cw->undoable = false;
3998 				}
3999 			} else {
4000 /* Becomes many lines, this is the tricky case. */
4001 				save_nlMode = cw->nlMode;
4002 				delText(ln, ln);
4003 				addTextToBuffer((pst) replaceString,
4004 						replaceStringLength + 1, ln - 1,
4005 						false);
4006 				cw->nlMode = save_nlMode;
4007 				endRange += linecount;
4008 				ln += linecount;
4009 /* There's a quirk when adding newline to the end of a buffer
4010  * that had no newline at the end before. */
4011 				if (cw->nlMode && ln == cw->dol
4012 				    && replaceStringEnd[-1] == '\n') {
4013 					delText(ln, ln);
4014 					--ln, --endRange;
4015 				}
4016 			}
4017 		}		/* browse or not */
4018 
4019 		if (subPrint == 2)
4020 			displayLine(ln);
4021 		lastSubst = ln;
4022 		nzFree(replaceString);
4023 /* we may have just freed the result of a breakline command */
4024 		breakLineResult = 0;
4025 	}			/* loop over lines in the range */
4026 
4027 	if (re_cc)
4028 		pcre_free(re_cc);
4029 
4030 	if (intFlag) {
4031 		setError(MSG_Interrupted);
4032 		return -1;
4033 	}
4034 
4035 	if (!lastSubst) {
4036 		if (!globSub) {
4037 			if (!errorMsg[0])
4038 				setError(bl_mode ? MSG_NoChange : MSG_NoMatch);
4039 		}
4040 		return false;
4041 	}
4042 	cw->dot = lastSubst;
4043 	if (subPrint == 1 && !globSub)
4044 		printDot();
4045 	return true;
4046 
4047 abort:
4048 	if (re_cc)
4049 		pcre_free(re_cc);
4050 	nzFree(replaceString);
4051 /* we may have just freed the result of a breakline command */
4052 	breakLineResult = 0;
4053 	return -1;
4054 }				/* substituteText */
4055 
4056 /*********************************************************************
4057 Implement various two letter commands.
4058 Most of these set and clear modes.
4059 Return 1 or 0 for success or failure as usual.
4060 But return 2 if there is a new command to run.
4061 The second parameter is a result parameter, the new command to run.
4062 In rare cases we might allocate a new (longer) command line to run,
4063 like rf (refresh), which could be a long url.
4064 *********************************************************************/
4065 
4066 static char *allocatedLine = 0;
4067 /*********************************************************************
4068 Uses of allocatedLine:
4069 rf sets allocatedLine to b currentFile
4070 f/  becomes  f lastComponent
4071 w/  becomes  w lastComponent
4072 g becomes b url  (going to a hyperlink)
4073 i<7 becomes i=contents of session 7
4074 i<file becomes i=contents of file
4075 f<file becomes f contents of file
4076 e<file becomes e contents of file
4077 b<file becomes b contents of file
4078 w<file becomes w contents of file
4079 r<file becomes r contents of file
4080 i* becomes b url  for a submit button
4081 new location from javascript becomes b new url,
4082 	This one frees the old allocatedLine if it was present.
4083 	g could allocate a line for b url but then after browse
4084 	there is a new location to go to so must free that allocated line
4085 	and set the next one.
4086 e url without http:// becomes http://url
4087 	This one frees the old allocatedLine if it was present.
4088 	e<file could have made a new line with the contents of file,
4089 	now we have to free it and build another one.
4090 Speaking of e<file and its ilk, here is the function that
4091 reads in the data.
4092 *********************************************************************/
4093 
lessFile(const char * line)4094 static char *lessFile(const char *line)
4095 {
4096 	bool fromfile = false;
4097 	int j, n;
4098 	char *line2;
4099 	skipWhite(&line);
4100 	if (!*line) {
4101 		setError(MSG_NoFileSpecified);
4102 		return 0;
4103 	}
4104 	n = stringIsNum(line);
4105 	if (n >= 0) {
4106 		char *p;
4107 		int plen, dol;
4108 		if (!cxCompare(n) || !cxActive(n))
4109 			return 0;
4110 		dol = sessionList[n].lw->dol;
4111 		if (!dol) {
4112 			setError(MSG_BufferXEmpty, n);
4113 			return 0;
4114 		}
4115 		if (dol > 1) {
4116 			setError(MSG_BufferXLines, n);
4117 			return 0;
4118 		}
4119 		p = (char *)fetchLineContext(1, 1, n);
4120 		plen = pstLength((pst) p);
4121 		line2 = allocMem(plen + 1);
4122 		memcpy(line2, p, plen);
4123 		line2[plen] = 0;
4124 		n = plen;
4125 		nzFree(p);
4126 	} else {
4127 		if (!envFile(line, &line))
4128 			return 0;
4129 		if (!fileIntoMemory(line, &line2, &n))
4130 			return 0;
4131 		fromfile = true;
4132 	}
4133 	for (j = 0; j < n; ++j) {
4134 		if (line2[j] == 0) {
4135 			setError(MSG_LessNull);
4136 			return 0;
4137 		}
4138 		if (line2[j] == '\r'
4139 		    && !fromfile && j < n - 1 && line2[j + 1] != '\n') {
4140 			setError(MSG_InputCR);
4141 			return 0;
4142 		}
4143 		if (line2[j] == '\r' || line2[j] == '\n')
4144 			break;
4145 	}
4146 	line2[j] = 0;
4147 	return line2;
4148 }
4149 
twoLetter(const char * line,const char ** runThis)4150 static int twoLetter(const char *line, const char **runThis)
4151 {
4152 	static char shortline[60];
4153 	char c;
4154 	bool rc, ub;
4155 	int i, n;
4156 
4157 	*runThis = shortline;
4158 
4159 	if (stringEqual(line, "qt"))
4160 		ebClose(0);
4161 
4162 	if (line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2])
4163 	    && !line[3]) {
4164 		debugLevel = line[2] - '0';
4165 		return true;
4166 	}
4167 
4168 	if (!strncmp(line, "db>", 3)) {
4169 		setDebugFile(line + 3);
4170 		return true;
4171 	}
4172 
4173 	if (stringEqual(line, "bw")) {
4174 		cw->changeMode = false;
4175 		cw->quitMode = true;
4176 		return true;
4177 	}
4178 
4179 	if (stringEqual(line, "rr")) {
4180 		cmd = 'e';
4181 		if (!cw->browseMode) {
4182 			setError(MSG_NoBrowse);
4183 			return false;
4184 		}
4185 		if (!isJSAlive) {
4186 			setError(MSG_JavaOff);
4187 			return false;
4188 		}
4189 		rerender(true);
4190 		return true;
4191 	}
4192 
4193 	if (!strncmp(line, "rr ", 3) && isdigit(line[3])) {
4194 		rr_interval = atoi(line + 3);
4195 		return true;
4196 	}
4197 
4198 	if (line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2])
4199 	    && (!line[3] || (isdigitByte(line[3]) && !line[4]))) {
4200 		char *t = 0;
4201 		n = atoi(line + 2);
4202 		if (n < MAXAGENT)
4203 			t = userAgents[n];
4204 		cmd = 'e';
4205 		if (!t) {
4206 			setError(MSG_NoAgent, n);
4207 			return false;
4208 		}
4209 		currentAgent = t;
4210 		if (helpMessagesOn || debugLevel >= 1)
4211 			eb_puts(currentAgent);
4212 		return true;
4213 	}
4214 
4215 	if (stringEqual(line, "re") || stringEqual(line, "rea")) {
4216 		undoCompare();
4217 		cw->undoable = false;
4218 		cmd = 'e';	/* so error messages are printed */
4219 		rc = setupReply(line[2] == 'a');
4220 		if (rc && cw->browseMode) {
4221 			ub = false;
4222 			cw->browseMode = false;
4223 			goto et_go;
4224 		}
4225 		return rc;
4226 	}
4227 
4228 /* ^^^^ is the same as ^4; same with & */
4229 	if ((line[0] == '^' || line[0] == '&') && line[1] == line[0]) {
4230 		const char *t = line + 2;
4231 		while (*t == line[0])
4232 			++t;
4233 		if (!*t) {
4234 			sprintf(shortline, "%c%ld", line[0], (long)(t - line));
4235 			return 2;
4236 		}
4237 	}
4238 
4239 	if (line[0] == 'l' && line[1] == 's') {
4240 		char lsmode[12];
4241 		bool setmode = false;
4242 		char *file, *path, *t;
4243 		const char *s = line + 2;
4244 		cmd = 'e';	// show error messages
4245 		skipWhite(&s);
4246 		if (*s == '=') {
4247 			setmode = true;
4248 			++s;
4249 			skipWhite(&s);
4250 		} else {
4251 			if (!cw->dirMode) {
4252 				setError(MSG_NoDir);
4253 				return false;
4254 			}
4255 			if (stringEqual(s, "X"))
4256 				return true;
4257 			if (cw->dot == 0) {
4258 				setError(MSG_AtLine0);
4259 				return false;
4260 			}
4261 		}
4262 		if (!lsattrChars(s, lsmode)) {
4263 			setError(MSG_LSBadChar);
4264 			return false;
4265 		}
4266 		if (setmode) {
4267 			strcpy(lsformat, lsmode);
4268 			return true;
4269 		}
4270 /* default ls mode is size time */
4271 		if (!lsmode[0])
4272 			strcpy(lsmode, "st");
4273 		file = (char *)fetchLine(cw->dot, -1);
4274 		t = strchr(file, '\n');
4275 		if (!t)
4276 			i_printfExit(MSG_NoNlOnDir, file);
4277 		*t = 0;
4278 		path = makeAbsPath(file);
4279 		*t = '\n';	// put it back
4280 		t = emptyString;
4281 		if (path && fileTypeByName(path, true))
4282 			t = lsattr(path, lsmode);
4283 		if (*t)
4284 			eb_puts(t);
4285 		else
4286 			i_puts(MSG_Inaccess);
4287 		return true;
4288 	}
4289 
4290 	if (!strncmp(line, "sort", 4)
4291 	    && (line[4] == '+' || line[4] == '-' || line[4] == '=') &&
4292 	    line[5] && !line[6] && strchr("ast", line[5])) {
4293 		ls_sort = 0;
4294 		if (line[5] == 's')
4295 			ls_sort = 1;
4296 		if (line[5] == 't')
4297 			ls_sort = 2;
4298 		ls_reverse = false;
4299 		if (line[4] == '-')
4300 			ls_reverse = true;
4301 		if (helpMessagesOn) {
4302 			if (ls_reverse)
4303 				i_printf(MSG_Reverse);
4304 			i_puts(MSG_SortAlpha + ls_sort);
4305 		}
4306 		return true;
4307 	}
4308 
4309 	if (line[0] == 'c' && line[1] == 'd') {
4310 		c = line[2];
4311 		if (!c || isspaceByte(c)) {
4312 			const char *t = line + 2;
4313 			skipWhite(&t);
4314 			c = *t;
4315 			cmd = 'e';	/* so error messages are printed */
4316 			if (!c) {
4317 				char cwdbuf[ABSPATH];
4318 pwd:
4319 				if (!getcwd(cwdbuf, sizeof(cwdbuf))) {
4320 					setError(c ? MSG_CDGetError :
4321 						 MSG_CDSetError);
4322 					return false;
4323 				}
4324 				eb_puts(cwdbuf);
4325 				return true;
4326 			}
4327 			if (!envFile(t, &t))
4328 				return false;
4329 			if (!chdir(t))
4330 				goto pwd;
4331 			setError(MSG_CDInvalid);
4332 			return false;
4333 		}
4334 	}
4335 
4336 	if (line[0] == 'l' && line[1] == 'l') {
4337 		c = line[2];
4338 		if (!c) {
4339 			printf("%d\n", displayLength);
4340 			return true;
4341 		}
4342 		if (isspaceByte(c) && isdigitByte(line[3])) {
4343 			displayLength = atoi(line + 3);
4344 			if (displayLength < 80)
4345 				displayLength = 80;
4346 			return true;
4347 		}
4348 		setError(MSG_NoSpaceAfter);
4349 		return false;
4350 	}
4351 
4352 	if (line[0] == 'f' && line[1] == 'l' && line[2] == 'l') {
4353 		char *s;
4354 		c = line[3];
4355 		if (!c) {
4356 			printf("%d%s\n", formatLineLength,
4357 			       (formatOverflow ? "+" : ""));
4358 			return true;
4359 		}
4360 		if (isspaceByte(c) && isdigitByte(line[4])) {
4361 			formatLineLength = strtol(line + 4, &s, 10);
4362 			if (formatLineLength < 32)
4363 				formatLineLength = 32;
4364 			formatOverflow = (*s == '+');
4365 			return true;
4366 		}
4367 		setError(MSG_NoSpaceAfter);
4368 		return false;
4369 	}
4370 
4371 	if (line[0] == 'p' && line[1] == 'b') {
4372 		rc = playBuffer(line, NULL);
4373 		if (rc == 2)
4374 			goto no_action;
4375 		cmd = 'e';	// to see the error right away
4376 		return rc;
4377 	}
4378 
4379 	if (stringEqual(line, "rf")) {
4380 		cmd = 'e';
4381 		selfFrame();
4382 		if (!cf->fileName) {
4383 			setError(MSG_NoRefresh);
4384 			return false;
4385 		}
4386 		if (cw->browseMode)
4387 			cmd = 'b';
4388 		noStack = true;
4389 		allocatedLine = allocMem(strlen(cf->fileName) + 3);
4390 		sprintf(allocatedLine, "%c %s", cmd, cf->fileName);
4391 		debrowseSuffix(allocatedLine);
4392 		*runThis = allocatedLine;
4393 		uriEncoded = cf->uriEncoded;
4394 		return 2;
4395 	}
4396 
4397 	if (stringEqual(line, "config")) {
4398 		readConfigFile();
4399 		setupEdbrowseCache();
4400 		if (curlActive) {
4401 			if (cookieFile)
4402 				curl_easy_setopt(global_http_handle,
4403 						 CURLOPT_COOKIEJAR, cookieFile);
4404 		}
4405 		return true;
4406 	}
4407 
4408 	if (stringEqual(line, "shc")) {
4409 		if (!cw->sqlMode) {
4410 			setError(MSG_NoDB);
4411 			return false;
4412 		}
4413 		showColumns();
4414 		return true;
4415 	}
4416 
4417 	if (stringEqual(line, "shf")) {
4418 		if (!cw->sqlMode) {
4419 			setError(MSG_NoDB);
4420 			return false;
4421 		}
4422 		showForeign();
4423 		return true;
4424 	}
4425 
4426 	if (stringEqual(line, "sht")) {
4427 		if (!ebConnect())
4428 			return false;
4429 		return showTables();
4430 	}
4431 
4432 	if (stringEqual(line, "jdb")) {
4433 		const Tag *t;
4434 		cmd = 'e';
4435 		if (!cw->browseMode) {
4436 			setError(MSG_NoBrowse);
4437 			return false;
4438 		}
4439 		if (!isJSAlive) {
4440 			setError(MSG_JavaOff);
4441 			return false;
4442 		}
4443 /* debug the js context of the frame you are in */
4444 		t = line2frame(cw->dot);
4445 		if (t)
4446 			cf = t->f1;
4447 		else
4448 			selfFrame();
4449 		cw->jdb_frame = cf;
4450 		jSyncup(false);
4451 		return true;
4452 	}
4453 
4454 	if (stringEqual(line, "ub") || stringEqual(line, "et")) {
4455 		Frame *f, *fnext;
4456 		struct histLabel *label, *lnext;
4457 		ub = (line[0] == 'u');
4458 		rc = true;
4459 		cmd = 'e';
4460 		if (!cw->browseMode) {
4461 			setError(MSG_NoBrowse);
4462 			return false;
4463 		}
4464 		undoCompare();
4465 		cw->undoable = false;
4466 		cw->browseMode = false;
4467 		cf->render2 = false;
4468 		if (cf->render1b)
4469 			cf->render1 = cf->render1b = false;
4470 		if (ub) {
4471 			debrowseSuffix(cf->fileName);
4472 			cw->nlMode = cw->rnlMode;
4473 			cw->dot = cw->r_dot, cw->dol = cw->r_dol;
4474 			memcpy(cw->labels, cw->r_labels, sizeof(cw->labels));
4475 			freeWindowLines(cw->map);
4476 			cw->map = cw->r_map;
4477 			cw->r_map = 0;
4478 		} else {
4479 et_go:
4480 			for (i = 1; i <= cw->dol; ++i)
4481 				removeHiddenNumbers(cw->map[i].text, '\n');
4482 			freeWindowLines(cw->r_map);
4483 			cw->r_map = 0;
4484 		}
4485 		freeTags(cw);
4486 		cw->mustrender = false;
4487 		for (f = &cw->f0; f; f = fnext) {
4488 			fnext = f->next;
4489 			delTimers(f);
4490 			freeJavaContext(f);
4491 			nzFree(f->dw);
4492 			nzFree(f->hbase);
4493 			nzFree(f->firstURL);
4494 			if (f != &cw->f0) {
4495 				nzFree(f->fileName);
4496 				free(f);
4497 			} else {
4498 				f->cx = f->winobj = f->docobj = 0;
4499 				f->dw = 0;
4500 				f->dw_l = 0;
4501 				f->hbase = 0;
4502 				f->firstURL = 0;
4503 			}
4504 		}
4505 		cw->f0.next = 0;
4506 		lnext = cw->histLabel;
4507 		while ((label = lnext)) {
4508 			lnext = label->prev;
4509 			free(label);
4510 		}
4511 		cw->histLabel = 0;
4512 		nzFree(cw->htmltitle);
4513 		cw->htmltitle = 0;
4514 		nzFree(cw->htmldesc);
4515 		cw->htmldesc = 0;
4516 		nzFree(cw->htmlkey);
4517 		cw->htmlkey = 0;
4518 		nzFree(cw->mailInfo);
4519 		cw->mailInfo = 0;
4520 		if (ub)
4521 			fileSize = apparentSize(context, false);
4522 		return rc;
4523 	}
4524 
4525 	if (stringEqual(line, "ib")) {
4526 		cmd = 'e';
4527 		if (!cw->browseMode) {
4528 			setError(MSG_NoBrowse);
4529 			return false;
4530 		}
4531 		if (!cw->dot) {	// should never happen
4532 			setError(MSG_EmptyBuffer);
4533 			return false;
4534 		}
4535 		itext();
4536 		return true;
4537 	}
4538 
4539 	if (stringEqual(line, "f/") || stringEqual(line, "w/")) {
4540 		char *t;
4541 		cmd = line[0];
4542 		if (!cf->fileName) {
4543 			setError(MSG_NoRefresh);
4544 			return false;
4545 		}
4546 		t = strrchr(cf->fileName, '/');
4547 		if (!t) {
4548 			setError(MSG_NoSlash);
4549 			return false;
4550 		}
4551 		++t;
4552 		if (!*t) {
4553 			setError(MSG_YesSlash);
4554 			return false;
4555 		}
4556 		t = getFileURL(cf->fileName, false);
4557 		allocatedLine = allocMem(strlen(t) + 8);
4558 /* ` prevents wildcard expansion, which normally happens on an f command */
4559 		sprintf(allocatedLine, "%c `%s", cmd, t);
4560 		*runThis = allocatedLine;
4561 		return 2;
4562 	}
4563 // If you want this feature, e< f< b< w< r<, uncomment this,
4564 // and be sure to document it in usersguide.
4565 #if 0
4566 	if (strchr("bwref", line[0]) && line[1] == '<') {
4567 		allocatedLine = lessFile(line + 2);
4568 		if (allocatedLine == 0)
4569 			return false;
4570 		n = strlen(allocatedLine);
4571 		allocatedLine = reallocMem(allocatedLine, n + 4);
4572 		strmove(allocatedLine + 3, allocatedLine);
4573 		allocatedLine[0] = line[0];
4574 		allocatedLine[1] = ' ';
4575 		allocatedLine[2] = ' ';
4576 		if (!isURL(allocatedLine + 3))
4577 			allocatedLine[2] = '`';	// suppress wildcard expansion
4578 		*runThis = allocatedLine;
4579 		return 2;
4580 	}
4581 #endif
4582 
4583 	if (stringEqual(line, "ft") || stringEqual(line, "fd") ||
4584 	    stringEqual(line, "fk") || stringEqual(line, "fu")) {
4585 		const char *s;
4586 		int t;
4587 		cmd = 'e';
4588 		if (!cw->browseMode) {
4589 			setError(MSG_NoBrowse);
4590 			return false;
4591 		}
4592 		if (line[1] == 't')
4593 			s = cw->htmltitle, t = MSG_NoTitle;
4594 		if (line[1] == 'd')
4595 			s = cw->htmldesc, t = MSG_NoDesc;
4596 		if (line[1] == 'k')
4597 			s = cw->htmlkey, t = MSG_NoKeywords;
4598 		if (line[1] == 'u')
4599 			s = cw->saveURL, t = MSG_NoFileName;
4600 		if (s)
4601 			eb_puts(s);
4602 		else
4603 			i_puts(t);
4604 		return true;
4605 	}
4606 
4607 	if (line[0] == 's' && line[1] == 'm') {
4608 		const char *t = line + 2;
4609 		bool dosig = true;
4610 		int account = 0;
4611 		cmd = 'e';
4612 		if (*t == '-') {
4613 			dosig = false;
4614 			++t;
4615 		}
4616 		if (isdigitByte(*t))
4617 			account = strtol(t, (char **)&t, 10);
4618 		if (!*t) {
4619 // In case we haven't started curl yet.
4620 			if (!curlActive) {
4621 				eb_curl_global_init();
4622 // we don't need cookies and cache for email, but http might follow.
4623 				cookiesFromJar();
4624 				setupEdbrowseCache();
4625 			}
4626 			return sendMailCurrent(account, dosig);
4627 		} else {
4628 			setError(MSG_SMBadChar);
4629 			return false;
4630 		}
4631 	}
4632 
4633 	if (stringEqual(line, "ci")) {
4634 		caseInsensitive ^= 1;
4635 		if (helpMessagesOn || debugLevel >= 1)
4636 			i_puts(caseInsensitive + MSG_CaseSen);
4637 		return true;
4638 	}
4639 
4640 	if (stringEqual(line, "ci+") || stringEqual(line, "ci-")) {
4641 		caseInsensitive = (line[2] == '+');
4642 		if (helpMessagesOn)
4643 			i_puts(caseInsensitive + MSG_CaseSen);
4644 		return true;
4645 	}
4646 
4647 	if (stringEqual(line, "sg")) {
4648 		searchStringsAll ^= 1;
4649 		if (helpMessagesOn || debugLevel >= 1)
4650 			i_puts(searchStringsAll + MSG_SubLocal);
4651 		return true;
4652 	}
4653 
4654 	if (stringEqual(line, "sg+") || stringEqual(line, "sg-")) {
4655 		searchStringsAll = (line[2] == '+');
4656 		if (helpMessagesOn)
4657 			i_puts(searchStringsAll + MSG_SubLocal);
4658 		return true;
4659 	}
4660 
4661 	if (stringEqual(line, "dr")) {
4662 		dirWrite = 0;
4663 		if (helpMessagesOn)
4664 			i_puts(MSG_DirReadonly);
4665 		return true;
4666 	}
4667 
4668 	if (stringEqual(line, "dw")) {
4669 		dirWrite = 1;
4670 		if (helpMessagesOn)
4671 			i_puts(MSG_DirWritable);
4672 		return true;
4673 	}
4674 
4675 	if (stringEqual(line, "dx")) {
4676 		dirWrite = 2;
4677 		if (helpMessagesOn)
4678 			i_puts(MSG_DirX);
4679 		return true;
4680 	}
4681 
4682 	if (stringEqual(line, "hr")) {
4683 		allowRedirection ^= 1;
4684 		if (helpMessagesOn || debugLevel >= 1)
4685 			i_puts(allowRedirection + MSG_RedirectionOff);
4686 		return true;
4687 	}
4688 
4689 	if (stringEqual(line, "hr+") || stringEqual(line, "hr-")) {
4690 		allowRedirection = (line[2] == '+');
4691 		if (helpMessagesOn)
4692 			i_puts(allowRedirection + MSG_RedirectionOff);
4693 		return true;
4694 	}
4695 
4696 	if (stringEqual(line, "pg")) {
4697 		pluginsOn ^= 1;
4698 		if (helpMessagesOn || debugLevel >= 1)
4699 			i_puts(pluginsOn + MSG_PluginsOff);
4700 		return true;
4701 	}
4702 
4703 	if (stringEqual(line, "pg+") || stringEqual(line, "pg-")) {
4704 		pluginsOn = (line[2] == '+');
4705 		if (helpMessagesOn)
4706 			i_puts(pluginsOn + MSG_PluginsOff);
4707 		return true;
4708 	}
4709 
4710 	if (stringEqual(line, "bg")) {
4711 		down_bg ^= 1;
4712 		if (helpMessagesOn || debugLevel >= 1)
4713 			i_puts(down_bg + MSG_DownForeground);
4714 		return true;
4715 	}
4716 
4717 	if (stringEqual(line, "bg+") || stringEqual(line, "bg-")) {
4718 		down_bg = (line[2] == '+');
4719 		if (helpMessagesOn)
4720 			i_puts(down_bg + MSG_DownForeground);
4721 		return true;
4722 	}
4723 	if (stringEqual(line, "jsbg")) {
4724 		down_jsbg ^= 1;
4725 		if (helpMessagesOn || debugLevel >= 1)
4726 			i_puts(down_jsbg + MSG_JSDownForeground);
4727 		return true;
4728 	}
4729 
4730 	if (stringEqual(line, "jsbg+") || stringEqual(line, "jsbg-")) {
4731 		down_jsbg = (line[4] == '+');
4732 		if (helpMessagesOn)
4733 			i_puts(down_jsbg + MSG_JSDownForeground);
4734 		return true;
4735 	}
4736 
4737 	if (stringEqual(line, "bglist")) {
4738 		bg_jobs(false);
4739 		return true;
4740 	}
4741 
4742 	if (stringEqual(line, "bflist")) {
4743 		for (n = 1; n <= maxSession; ++n) {
4744 			struct ebWindow *lw = sessionList[n].lw;
4745 			if (!lw)
4746 				continue;
4747 			printf("%d: ", n);
4748 			if (lw->htmltitle)
4749 				printf("%s", lw->htmltitle);
4750 			else if (lw->f0.fileName)
4751 				printf("%s", lw->f0.fileName);
4752 			nl();
4753 		}
4754 		return true;
4755 	}
4756 
4757 	if (stringEqual(line, "help")) {
4758 		return helpUtility();
4759 	}
4760 
4761 	if (stringEqual(line, "iu")) {
4762 		iuConvert ^= 1;
4763 		if (helpMessagesOn || debugLevel >= 1)
4764 			i_puts(iuConvert + MSG_IUConvertOff);
4765 		return true;
4766 	}
4767 
4768 	if (stringEqual(line, "iu+") || stringEqual(line, "iu-")) {
4769 		iuConvert = (line[2] == '+');
4770 		if (helpMessagesOn)
4771 			i_puts(iuConvert + MSG_IUConvertOff);
4772 		return true;
4773 	}
4774 
4775 	if (stringEqual(line, "sr")) {
4776 		sendReferrer ^= 1;
4777 		if (helpMessagesOn || debugLevel >= 1)
4778 			i_puts(sendReferrer + MSG_RefererOff);
4779 		return true;
4780 	}
4781 
4782 	if (stringEqual(line, "sr+") || stringEqual(line, "sr-")) {
4783 		sendReferrer = (line[2] == '+');
4784 		if (helpMessagesOn)
4785 			i_puts(sendReferrer + MSG_RefererOff);
4786 		return true;
4787 	}
4788 
4789 	if (stringEqual(line, "js")) {
4790 #if 0
4791 		if (blockJS) {
4792 			i_puts(MSG_JavaBlock);
4793 			return true;
4794 		}
4795 #endif
4796 		allowJS ^= 1;
4797 		if (helpMessagesOn || debugLevel >= 1)
4798 			i_puts(allowJS + MSG_JavaOff);
4799 		return true;
4800 	}
4801 
4802 	if (stringEqual(line, "js+") || stringEqual(line, "js-")) {
4803 #if 0
4804 		if (blockJS) {
4805 			i_puts(MSG_JavaBlock);
4806 			return true;
4807 		}
4808 #endif
4809 		allowJS = (line[2] == '+');
4810 		if (helpMessagesOn)
4811 			i_puts(allowJS + MSG_JavaOff);
4812 		return true;
4813 	}
4814 
4815 	if (stringEqual(line, "H")) {
4816 		if (helpMessagesOn ^= 1)
4817 			if (debugLevel >= 1)
4818 				i_puts(MSG_HelpOn);
4819 		return true;
4820 	}
4821 
4822 	if (stringEqual(line, "H+") || stringEqual(line, "H-")) {
4823 		helpMessagesOn = (line[1] == '+');
4824 		if (helpMessagesOn && debugLevel >= 1)
4825 			i_puts(MSG_HelpOn);
4826 		return true;
4827 	}
4828 
4829 	if (stringEqual(line, "bd")) {
4830 		binaryDetect ^= 1;
4831 		if (helpMessagesOn || debugLevel >= 1)
4832 			i_puts(binaryDetect + MSG_BinaryIgnore);
4833 		return true;
4834 	}
4835 
4836 	if (stringEqual(line, "bd+") || stringEqual(line, "bd-")) {
4837 		binaryDetect = (line[2] == '+');
4838 		if (helpMessagesOn)
4839 			i_puts(binaryDetect + MSG_BinaryIgnore);
4840 		return true;
4841 	}
4842 
4843 	if (stringEqual(line, "sw")) {
4844 		searchWrap ^= 1;
4845 		if (helpMessagesOn || debugLevel >= 1)
4846 			i_puts(searchWrap + MSG_WrapOff);
4847 		return true;
4848 	}
4849 
4850 	if (stringEqual(line, "sw+") || stringEqual(line, "sw-")) {
4851 		searchWrap = (line[2] == '+');
4852 		if (helpMessagesOn)
4853 			i_puts(searchWrap + MSG_WrapOff);
4854 		return true;
4855 	}
4856 
4857 	if (stringEqual(line, "rl")) {
4858 		inputReadLine ^= 1;
4859 		if (helpMessagesOn || debugLevel >= 1)
4860 			i_puts(inputReadLine + MSG_InputTTY);
4861 		return true;
4862 	}
4863 
4864 	if (stringEqual(line, "rl+") || stringEqual(line, "rl-")) {
4865 		inputReadLine = (line[2] == '+');
4866 		if (helpMessagesOn)
4867 			i_puts(inputReadLine + MSG_InputTTY);
4868 		return true;
4869 	}
4870 
4871 	if (stringEqual(line, "can")) {
4872 		curlAuthNegotiate ^= 1;
4873 		if (helpMessagesOn || debugLevel >= 1)
4874 			i_puts(curlAuthNegotiate + MSG_CurlNoAuthNegotiate);
4875 		return true;
4876 	}
4877 
4878 	if (stringEqual(line, "can+") || stringEqual(line, "can-")) {
4879 		curlAuthNegotiate = (line[3] == '+');
4880 		if (helpMessagesOn)
4881 			i_puts(curlAuthNegotiate + MSG_CurlNoAuthNegotiate);
4882 		return true;
4883 	}
4884 
4885 	if (stringEqual(line, "lna")) {
4886 		listNA ^= 1;
4887 		if (helpMessagesOn || debugLevel >= 1)
4888 			i_puts(listNA + MSG_ListControl);
4889 		return true;
4890 	}
4891 
4892 	if (stringEqual(line, "lna+") || stringEqual(line, "lna-")) {
4893 		listNA = (line[3] == '+');
4894 		if (helpMessagesOn)
4895 			i_puts(listNA + MSG_ListControl);
4896 		return true;
4897 	}
4898 
4899 	if (stringEqual(line, "ftpa")) {
4900 		ftpActive ^= 1;
4901 		if (helpMessagesOn || debugLevel >= 1)
4902 			i_puts(MSG_PassiveMode + ftpActive);
4903 		return true;
4904 	}
4905 
4906 	if (stringEqual(line, "ftpa+") || stringEqual(line, "ftpa-")) {
4907 		ftpActive = (line[4] == '+');
4908 		if (helpMessagesOn)
4909 			i_puts(ftpActive + MSG_PassiveMode);
4910 		return true;
4911 	}
4912 
4913 	if (line[0] == 'p' && line[1] == 'd' &&
4914 	    line[2] && strchr("qdc", line[2]) && !line[3]) {
4915 		showProgress = line[2];
4916 		if (helpMessagesOn || debugLevel >= 1) {
4917 			if (showProgress == 'q')
4918 				i_puts(MSG_ProgressQuiet);
4919 			if (showProgress == 'd')
4920 				i_puts(MSG_ProgressDots);
4921 			if (showProgress == 'c')
4922 				i_puts(MSG_ProgressCount);
4923 		}
4924 		return true;
4925 	}
4926 
4927 	if (stringEqual(line, "vs")) {
4928 		verifyCertificates ^= 1;
4929 		if (helpMessagesOn || debugLevel >= 1)
4930 			i_puts(verifyCertificates + MSG_CertifyOff);
4931 		return true;
4932 	}
4933 
4934 	if (stringEqual(line, "vs+") || stringEqual(line, "vs-")) {
4935 		verifyCertificates = (line[2] == '+');
4936 		if (helpMessagesOn)
4937 			i_puts(verifyCertificates + MSG_CertifyOff);
4938 		return true;
4939 	}
4940 
4941 	if (stringEqual(line, "dbcn")) {
4942 		debugClone ^= 1;
4943 		if (helpMessagesOn || debugLevel >= 1)
4944 			i_puts(debugClone + MSG_DebugCloneOff);
4945 		if (isJSAlive)
4946 			set_property_bool(cf, cf->winobj, "cloneDebug", debugClone);
4947 		return true;
4948 	}
4949 
4950 	if (stringEqual(line, "dbcn+") || stringEqual(line, "dbcn-")) {
4951 		debugClone = (line[4] == '+');
4952 		if (helpMessagesOn)
4953 			i_puts(debugClone + MSG_DebugCloneOff);
4954 		if (isJSAlive)
4955 			set_property_bool(cf, cf->winobj, "cloneDebug", debugClone);
4956 		return true;
4957 	}
4958 
4959 	if (stringEqual(line, "dbev")) {
4960 		debugEvent ^= 1;
4961 		if (helpMessagesOn || debugLevel >= 1)
4962 			i_puts(debugEvent + MSG_DebugEventOff);
4963 		if (isJSAlive)
4964 			set_property_bool(cf, cf->winobj, "eventDebug", debugEvent);
4965 		return true;
4966 	}
4967 
4968 	if (stringEqual(line, "dbev+") || stringEqual(line, "dbev-")) {
4969 		debugEvent = (line[4] == '+');
4970 		if (helpMessagesOn)
4971 			i_puts(debugEvent + MSG_DebugEventOff);
4972 		if (isJSAlive)
4973 			set_property_bool(cf, cf->winobj, "eventDebug", debugEvent);
4974 		return true;
4975 	}
4976 
4977 	if (stringEqual(line, "dberr")) {
4978 		debugThrow ^= 1;
4979 		if (helpMessagesOn || debugLevel >= 1)
4980 			i_puts(debugThrow + MSG_DebugThrowOff);
4981 		if (isJSAlive)
4982 			set_property_bool(cf, cf->winobj, "throwDebug", debugThrow);
4983 		return true;
4984 	}
4985 
4986 	if (stringEqual(line, "dberr+") || stringEqual(line, "dberr-")) {
4987 		debugThrow = (line[5] == '+');
4988 		if (helpMessagesOn)
4989 			i_puts(debugThrow + MSG_DebugThrowOff);
4990 		if (isJSAlive)
4991 			set_property_bool(cf, cf->winobj, "throwDebug", debugThrow);
4992 		return true;
4993 	}
4994 
4995 	if (stringEqual(line, "dbcss")) {
4996 		debugCSS ^= 1;
4997 		if (helpMessagesOn || debugLevel >= 1)
4998 			i_puts(debugCSS + MSG_DebugCSSOff);
4999 		if (debugCSS)
5000 			unlink("/tmp/css");
5001 		return true;
5002 	}
5003 
5004 	if (stringEqual(line, "dbcss+") || stringEqual(line, "dbcss-")) {
5005 		debugCSS = (line[5] == '+');
5006 		if (helpMessagesOn)
5007 			i_puts(debugCSS + MSG_DebugCSSOff);
5008 		if (debugCSS)
5009 			unlink("/tmp/css");
5010 		return true;
5011 	}
5012 
5013 	if (stringEqual(line, "demin")) {
5014 		demin ^= 1;
5015 		if (helpMessagesOn || debugLevel >= 1)
5016 			i_puts(demin + MSG_DeminOff);
5017 		return true;
5018 	}
5019 
5020 	if (stringEqual(line, "demin+") || stringEqual(line, "demin-")) {
5021 		demin = (line[5] == '+');
5022 		if (helpMessagesOn)
5023 			i_puts(demin + MSG_DeminOff);
5024 		return true;
5025 	}
5026 
5027 	if (stringEqual(line, "trace")) {
5028 		uvw ^= 1;
5029 		if (helpMessagesOn || debugLevel >= 1)
5030 			i_puts(uvw + MSG_DebugTraceOff);
5031 		return true;
5032 	}
5033 
5034 	if (stringEqual(line, "trace+") || stringEqual(line, "trace-")) {
5035 		uvw = (line[5] == '+');
5036 		if (helpMessagesOn)
5037 			i_puts(uvw + MSG_DebugTraceOff);
5038 		return true;
5039 	}
5040 
5041 	if (stringEqual(line, "timers")) {
5042 		gotimers ^= 1;
5043 		if (helpMessagesOn || debugLevel >= 1)
5044 			i_puts(gotimers + MSG_TimersOff);
5045 		return true;
5046 	}
5047 
5048 	if (stringEqual(line, "timers+") || stringEqual(line, "timers-")) {
5049 		gotimers = (line[6] == '+');
5050 		if (helpMessagesOn)
5051 			i_puts(gotimers + MSG_TimersOff);
5052 		return true;
5053 	}
5054 
5055 	if (stringEqual(line, "hf")) {
5056 		showHiddenFiles ^= 1;
5057 		if (helpMessagesOn || debugLevel >= 1)
5058 			i_puts(showHiddenFiles + MSG_HiddenOff);
5059 		return true;
5060 	}
5061 
5062 	if (stringEqual(line, "hf+") || stringEqual(line, "hf-")) {
5063 		showHiddenFiles = (line[2] == '+');
5064 		if (helpMessagesOn)
5065 			i_puts(showHiddenFiles + MSG_HiddenOff);
5066 		return true;
5067 	}
5068 
5069 	if (stringEqual(line, "showall")) {
5070 		showHover ^= 1;
5071 		if (helpMessagesOn || debugLevel >= 1)
5072 			i_puts(showHover + MSG_HoverOff);
5073 		if (cw->browseMode && isJSAlive)
5074 			rerender(false);
5075 		return true;
5076 	}
5077 
5078 	if (stringEqual(line, "showall+") || stringEqual(line, "showall-")) {
5079 		showHover = (line[5] == '+');
5080 		if (helpMessagesOn)
5081 			i_puts(showHover + MSG_HoverOff);
5082 		if (cw->browseMode && isJSAlive)
5083 			rerender(false);
5084 		return true;
5085 	}
5086 
5087 	if (stringEqual(line, "colors")) {
5088 		doColors ^= 1;
5089 		if (helpMessagesOn || debugLevel >= 1)
5090 			i_puts(doColors + MSG_ColorOff);
5091 		if (cw->browseMode && isJSAlive)
5092 			rerender(false);
5093 		return true;
5094 	}
5095 
5096 	if (stringEqual(line, "colors+") || stringEqual(line, "colors-")) {
5097 		doColors = (line[6] == '+');
5098 		if (helpMessagesOn)
5099 			i_puts(doColors + MSG_ColorOff);
5100 		if (cw->browseMode && isJSAlive)
5101 			rerender(false);
5102 		return true;
5103 	}
5104 
5105 	if (stringEqual(line, "su8")) {
5106 		re_utf8 ^= 1;
5107 		if (helpMessagesOn || debugLevel >= 1)
5108 			i_puts(re_utf8 + MSG_ReAscii);
5109 		return true;
5110 	}
5111 
5112 	if (stringEqual(line, "su8+") || stringEqual(line, "su8-")) {
5113 		re_utf8 = (line[3] == '+');
5114 		if (helpMessagesOn)
5115 			i_puts(re_utf8 + MSG_ReAscii);
5116 		return true;
5117 	}
5118 
5119 	if (!strncmp(line, "ds=", 3)) {
5120 		if (!line[3]) {
5121 			if (!dbarea || !*dbarea) {
5122 				i_puts(MSG_DBNoSource);
5123 			} else {
5124 				printf("%s", dbarea);
5125 				if (dblogin)
5126 					printf(",%s", dblogin);
5127 				if (dbpw)
5128 					printf(",%s", dbpw);
5129 				nl();
5130 			}
5131 			return true;
5132 		}
5133 		dbClose();
5134 		setDataSource(cloneString(line + 3));
5135 		return true;
5136 	}
5137 
5138 	if (stringEqual(line, "fbc")) {
5139 		fetchBlobColumns ^= 1;
5140 		if (helpMessagesOn || debugLevel >= 1)
5141 			i_puts(MSG_FetchBlobOff + fetchBlobColumns);
5142 		return true;
5143 	}
5144 
5145 	if (stringEqual(line, "fbc+") || stringEqual(line, "fbc-")) {
5146 		fetchBlobColumns = (line[3] == '+');
5147 		if (helpMessagesOn)
5148 			i_puts(fetchBlobColumns + MSG_FetchBlobOff);
5149 		return true;
5150 	}
5151 
5152 	if (stringEqual(line, "endm")) {
5153 		endMarks ^= 1;
5154 		if (helpMessagesOn || debugLevel >= 1)
5155 			i_puts(endMarks + MSG_MarkersOff);
5156 		return true;
5157 	}
5158 
5159 	if (stringEqual(line, "endm+") || stringEqual(line, "endm-")) {
5160 		endMarks = (line[4] == '+');
5161 		if (helpMessagesOn)
5162 			i_puts(endMarks + MSG_MarkersOff);
5163 		return true;
5164 	}
5165 
5166 no_action:
5167 	*runThis = line;
5168 	return 2;		/* no change */
5169 }				/* twoLetter */
5170 
5171 /* Return the number of unbalanced punctuation marks.
5172  * This is used by the next routine. */
unbalanced(char c,char d,int ln,int * back_p,int * for_p)5173 static void unbalanced(char c, char d, int ln, int *back_p, int *for_p)
5174 {				/* result parameters */
5175 	char *t, *open;
5176 	char *p = (char *)fetchLine(ln, 1);
5177 	bool change;
5178 	int backward, forward;
5179 
5180 	change = true;
5181 	while (change) {
5182 		change = false;
5183 		open = 0;
5184 		for (t = p; *t != '\n'; ++t) {
5185 			if (*t == c)
5186 				open = t;
5187 			if (*t == d && open) {
5188 				*open = 0;
5189 				*t = 0;
5190 				change = true;
5191 				open = 0;
5192 			}
5193 		}
5194 	}
5195 
5196 	backward = forward = 0;
5197 	for (t = p; *t != '\n'; ++t) {
5198 		if (*t == c)
5199 			++forward;
5200 		if (*t == d)
5201 			++backward;
5202 	}
5203 
5204 	free(p);
5205 	*back_p = backward;
5206 	*for_p = forward;
5207 }				/* unbalanced */
5208 
5209 /* Find the line that balances the unbalanced punctuation. */
balanceLine(const char * line)5210 static bool balanceLine(const char *line)
5211 {
5212 	char c, d;		/* open and close */
5213 	char selected;
5214 	static char openlist[] = "{([<`";
5215 	static char closelist[] = "})]>'";
5216 	static const char alllist[] = "{}()[]<>`'";
5217 	char *t;
5218 	int level = 0;
5219 	int i, direction, forward, backward;
5220 
5221 	if ((c = *line)) {
5222 		if (!strchr(alllist, c) || line[1]) {
5223 			setError(MSG_BalanceChar, alllist);
5224 			return false;
5225 		}
5226 		if ((t = strchr(openlist, c))) {
5227 			d = closelist[t - openlist];
5228 			direction = 1;
5229 		} else {
5230 			d = c;
5231 			t = strchr(closelist, d);
5232 			c = openlist[t - closelist];
5233 			direction = -1;
5234 		}
5235 		unbalanced(c, d, endRange, &backward, &forward);
5236 		if (direction > 0) {
5237 			if ((level = forward) == 0) {
5238 				setError(MSG_BalanceNoOpen, c);
5239 				return false;
5240 			}
5241 		} else {
5242 			if ((level = backward) == 0) {
5243 				setError(MSG_BalanceNoOpen, d);
5244 				return false;
5245 			}
5246 		}
5247 	} else {
5248 
5249 /* Look for anything unbalanced, probably a brace. */
5250 		for (i = 0; i <= 2; ++i) {
5251 			c = openlist[i];
5252 			d = closelist[i];
5253 			unbalanced(c, d, endRange, &backward, &forward);
5254 			if (backward && forward) {
5255 				setError(MSG_BalanceAmbig, c, d, c, d);
5256 				return false;
5257 			}
5258 			level = backward + forward;
5259 			if (!level)
5260 				continue;
5261 			direction = 1;
5262 			if (backward)
5263 				direction = -1;
5264 			break;
5265 		}
5266 		if (!level) {
5267 			setError(MSG_BalanceNothing);
5268 			return false;
5269 		}
5270 	}			/* explicit character passed in, or look for one */
5271 
5272 	selected = (direction > 0 ? c : d);
5273 
5274 /* search for the balancing line */
5275 	i = endRange;
5276 	while ((i += direction) > 0 && i <= cw->dol) {
5277 		unbalanced(c, d, i, &backward, &forward);
5278 		if ((direction > 0 && backward >= level) ||
5279 		    (direction < 0 && forward >= level)) {
5280 			cw->dot = i;
5281 			printDot();
5282 			return true;
5283 		}
5284 		level += (forward - backward) * direction;
5285 	}			/* loop over lines */
5286 
5287 	setError(MSG_Unbalanced, selected);
5288 	return false;
5289 }				/* balanceLine */
5290 
5291 /* Unfold the buffer into one long, allocated string. */
unfoldBufferW(const struct ebWindow * w,bool cr,char ** data,int * len)5292 bool unfoldBufferW(const struct ebWindow *w, bool cr, char **data, int *len)
5293 {
5294 	char *buf;
5295 	int l, ln;
5296 	int size = apparentSizeW(w, false);
5297 	if (size < 0)
5298 		return false;
5299 	if (w->dirMode) {
5300 		setError(MSG_SessionDir, context);
5301 		return false;
5302 	}
5303 	if (cr)
5304 		size += w->dol;
5305 /* a few bytes more, just for safety */
5306 	buf = allocMem(size + 4);
5307 	*data = buf;
5308 	for (ln = 1; ln <= w->dol; ++ln) {
5309 		pst line = w->map[ln].text;
5310 		l = pstLength(line) - 1;
5311 		if (l) {
5312 			memcpy(buf, line, l);
5313 			buf += l;
5314 		}
5315 		if (cr) {
5316 			*buf++ = '\r';
5317 			if (l && buf[-2] == '\r')
5318 				--buf, --size;
5319 		}
5320 		*buf++ = '\n';
5321 	}			/* loop over lines */
5322 	if (w->dol && w->nlMode) {
5323 		if (cr)
5324 			--size;
5325 	}
5326 	*len = size;
5327 	(*data)[size] = 0;
5328 	return true;
5329 }				/* unfoldBufferW */
5330 
unfoldBuffer(int cx,bool cr,char ** data,int * len)5331 bool unfoldBuffer(int cx, bool cr, char **data, int *len)
5332 {
5333 	const struct ebWindow *w = sessionList[cx].lw;
5334 	return unfoldBufferW(w, cr, data, len);
5335 }				/* unfoldBuffer */
5336 
showLinks(void)5337 static char *showLinks(void)
5338 {
5339 	int a_l;
5340 	char *a = initString(&a_l);
5341 	bool click, dclick;
5342 	char c, *p, *s, *t, *q, *line, *h, *h2;
5343 	int j, k = 0, tagno;
5344 	const Tag *tag;
5345 
5346 	if (cw->browseMode && endRange) {
5347 		line = (char *)fetchLine(endRange, -1);
5348 		for (p = line; (c = *p) != '\n'; ++p) {
5349 			if (c != InternalCodeChar)
5350 				continue;
5351 			if (!isdigitByte(p[1]))
5352 				continue;
5353 			j = strtol(p + 1, &s, 10);
5354 			if (*s != '{')
5355 				continue;
5356 			p = s;
5357 			++k;
5358 			findField(line, 0, k, 0, 0, &tagno, &h, &tag);
5359 			if (tagno != j)
5360 				continue;	/* should never happen */
5361 
5362 			click = tagHandler(tagno, "onclick");
5363 			dclick = tagHandler(tagno, "ondblclick");
5364 
5365 /* find the closing brace */
5366 /* It might not be there, could be on the next line. */
5367 			for (s = p + 1; (c = *s) != '\n'; ++s)
5368 				if (c == InternalCodeChar && s[1] == '0'
5369 				    && s[2] == '}')
5370 					break;
5371 /* Ok, everything between p and s exclusive is the description */
5372 			if (!h)
5373 				h = emptyString;
5374 			if (stringEqual(h, "#")) {
5375 				nzFree(h);
5376 				h = emptyString;
5377 			}
5378 
5379 			if (memEqualCI(h, "mailto:", 7)) {
5380 				stringAndBytes(&a, &a_l, p + 1, s - p - 1);
5381 				stringAndChar(&a, &a_l, ':');
5382 				s = h + 7;
5383 				t = s + strcspn(s, "?");
5384 				stringAndBytes(&a, &a_l, s, t - s);
5385 				stringAndChar(&a, &a_l, '\n');
5386 				nzFree(h);
5387 				continue;
5388 			}
5389 // quotes may be permitted in encoded urls, apostrophes never.
5390 			stringAndString(&a, &a_l, "<br><a href='");
5391 
5392 			if (memEqualCI(h, "javascript:", 11)) {
5393 				stringAndString(&a, &a_l, "javascript:'>\n");
5394 			} else if (!*h && (click | dclick)) {
5395 				char buf[20];
5396 				sprintf(buf, "%s'>\n",
5397 					click ? "onclick" : "ondblclick");
5398 				stringAndString(&a, &a_l, buf);
5399 			} else {
5400 				if (*h) {
5401 					h2 = htmlEscape(h);
5402 					stringAndString(&a, &a_l, h2);
5403 					nzFree(h2);
5404 				}
5405 				stringAndString(&a, &a_l, "'>\n");
5406 			}
5407 
5408 			nzFree(h);
5409 /* next line is the description of the bookmark */
5410 			h = pullString(p + 1, s - p - 1);
5411 			h2 = htmlEscape(h);
5412 			stringAndString(&a, &a_l, h2);
5413 			stringAndString(&a, &a_l, "\n</a>\n");
5414 			nzFree(h);
5415 			nzFree(h2);
5416 		}		/* loop looking for hyperlinks */
5417 	}
5418 
5419 	if (!a_l) {		/* nothing found yet */
5420 		if (!(h = cw->saveURL)) {
5421 			setError(MSG_NoFileName);
5422 			return 0;
5423 		}
5424 		h = htmlEscape(h);
5425 		stringAndString(&a, &a_l, "<br><a href='");
5426 		stringAndString(&a, &a_l, h);
5427 		stringAndString(&a, &a_l, "'>\n");
5428 /* get text from the html title if you can */
5429 		s = cw->htmltitle;
5430 		if (s && *s) {
5431 			h2 = htmlEscape(s);
5432 			stringAndString(&a, &a_l, h2);
5433 			nzFree(h2);
5434 		} else {
5435 /* no title - getting the text from the url, very kludgy */
5436 			s = (char *)getDataURL(h);
5437 			if (!s || !*s)
5438 				s = h;
5439 			t = findHash(s);
5440 			if (t)
5441 				*t = 0;
5442 			t = s + strcspn(s, "\1?");
5443 			if (t > s && t[-1] == '/')
5444 				--t;
5445 			*t = 0;
5446 			q = strrchr(s, '/');
5447 			if (q && q < t)
5448 				s = q + 1;
5449 			stringAndBytes(&a, &a_l, s, t - s);
5450 		}
5451 		stringAndString(&a, &a_l, "\n</a>\n");
5452 		nzFree(h);
5453 	}
5454 
5455 	removeHiddenNumbers((pst) a, 0);
5456 	return a;
5457 }				/* showLinks */
5458 
lineHasTag(const char * p,const char * s)5459 static bool lineHasTag(const char *p, const char *s)
5460 {
5461 	const Tag *t;
5462 	char c;
5463 	int j;
5464 
5465 	while ((c = *p++) != '\n') {
5466 		if (c != InternalCodeChar)
5467 			continue;
5468 		j = strtol(p, (char **)&p, 10);
5469 		t = tagList[j];
5470 		if (t->id && stringEqual(t->id, s))
5471 			return true;
5472 		if (t->action == TAGACT_A && t->name && stringEqual(t->name, s))
5473 			return true;
5474 	}
5475 
5476 	return false;
5477 }				/* lineHasTag */
5478 
5479 /*********************************************************************
5480 Run the entered edbrowse command.
5481 This is indirectly recursive, as in g/x/d
5482 Pass in the ed command, and return success or failure.
5483 We assume it has been turned into a C string.
5484 This means no embedded nulls.
5485 If you want to use null in a search or substitute, use \0.
5486 *********************************************************************/
5487 
runCommand(const char * line)5488 bool runCommand(const char *line)
5489 {
5490 	int i, j, n;
5491 	int writeMode = O_TRUNC;
5492 	struct ebWindow *w = NULL;
5493 	const Tag *tag = NULL;	/* event variables */
5494 	bool nogo = true, rc = true;
5495 	bool emode = false;	// force e, not browse
5496 	bool postSpace = false, didRange = false;
5497 	char first;
5498 	int cx = 0;		/* numeric suffix as in s/x/y/3 or w2 */
5499 	int tagno;
5500 	const char *s = NULL;
5501 	static char newline[MAXTTYLINE];
5502 	char *thisfile;
5503 
5504 	selfFrame();
5505 	nzFree(allocatedLine);
5506 	allocatedLine = 0;
5507 	js_redirects = false;
5508 	cmd = icmd = 'p';
5509 	uriEncoded = false;
5510 	skipWhite(&line);
5511 	first = *line;
5512 	noStack = false;
5513 
5514 	if (!strncmp(line, "ReF@b", 5)) {
5515 		line += 4;
5516 		noStack = true;
5517 		if (cf != newloc_f) {
5518 /* replace a frame, not the whole window */
5519 			newlocation = cloneString(line + 2);
5520 			goto replaceframe;
5521 		}
5522 	}
5523 
5524 	if (!globSub) {
5525 		madeChanges = false;
5526 
5527 /* Allow things like comment, or shell escape, but not if we're
5528  * in the midst of a global substitute, as in g/x/ !echo hello world */
5529 		if (first == '#')
5530 			return true;
5531 
5532 		if (first == '!')
5533 			return shellEscape(line + 1);
5534 
5535 /* Watch for successive q commands. */
5536 		lastq = lastqq, lastqq = 0;
5537 
5538 // force a noStack
5539 		if (!strncmp(line, "nostack ", 8))
5540 			noStack = true, line += 8, first = *line;
5541 
5542 /* special 2 letter commands - most of these change operational modes */
5543 		j = twoLetter(line, &line);
5544 		if (j != 2)
5545 			return j;
5546 	}
5547 
5548 	startRange = endRange = cw->dot;	/* default range */
5549 /* Just hit return to read the next line. */
5550 	first = *line;
5551 	if (first == 0) {
5552 		didRange = true;
5553 		++startRange, ++endRange;
5554 		if (endRange > cw->dol) {
5555 			setError(MSG_EndBuffer);
5556 			return false;
5557 		}
5558 	}
5559 
5560 	if (first == ',') {
5561 		didRange = true;
5562 		++line;
5563 		startRange = 1;
5564 		if (cw->dol == 0)
5565 			startRange = 0;
5566 		endRange = cw->dol;
5567 	}
5568 
5569 	if (first == ';') {
5570 		didRange = true;
5571 		++line;
5572 		startRange = cw->dot;
5573 		endRange = cw->dol;
5574 	}
5575 
5576 	if (first == 'j' || first == 'J') {
5577 		didRange = true;
5578 		endRange = startRange + 1;
5579 		if (endRange > cw->dol) {
5580 			setError(MSG_EndJoin);
5581 			return false;
5582 		}
5583 	}
5584 
5585 	if (first == '=') {
5586 		didRange = true;
5587 		startRange = endRange = cw->dol;
5588 	}
5589 
5590 	if (first == 'w' || first == 'v' || (first == 'g' && line[1]
5591 					     && strchr(valid_delim, line[1])
5592 					     && !stringEqual(line, "g-")
5593 					     && !stringEqual(line, "g?"))) {
5594 		didRange = true;
5595 		startRange = 1;
5596 		if (cw->dol == 0)
5597 			startRange = 0;
5598 		endRange = cw->dol;
5599 	}
5600 
5601 	if (!didRange) {
5602 		if (!getRangePart(line, &startRange, &line))
5603 			return (globSub = false);
5604 		endRange = startRange;
5605 		if (line[0] == ',') {
5606 			++line;
5607 			endRange = cw->dol;	/* new default */
5608 			first = *line;
5609 			if (first && strchr(valid_laddr, first)) {
5610 				if (!getRangePart(line, &endRange, &line))
5611 					return (globSub = false);
5612 			}
5613 		}
5614 	}
5615 	if (endRange < startRange) {
5616 		setError(MSG_BadRange);
5617 		return false;
5618 	}
5619 
5620 	skipWhite(&line);
5621 	first = *line;
5622 /* change uc into a substitute command, converting the whole line */
5623 	if ((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' &&
5624 	    line[2] == 0) {
5625 		sprintf(newline, "s/.*/%cc/", first);
5626 		line = newline;
5627 	}
5628 
5629 /* Breakline is actually a substitution of lines. */
5630 	if (stringEqual(line, "bl")) {
5631 		if (cw->dirMode) {
5632 			setError(MSG_BreakDir);
5633 			return false;
5634 		}
5635 		if (cw->sqlMode) {
5636 			setError(MSG_BreakDB);
5637 			return false;
5638 		}
5639 		if (cw->browseMode) {
5640 			setError(MSG_BreakBrowse);
5641 			return false;
5642 		}
5643 		line = "s`bl";
5644 	}
5645 
5646 expctr:
5647 /* special commands to expand and contract frames */
5648 	if (stringEqual(line, "exp") || stringEqual(line, "ctr")) {
5649 		if (globSub) {
5650 			cmd = 'g';
5651 			setError(MSG_GlobalCommand2, line);
5652 			return false;
5653 		}
5654 		cmd = 'e';
5655 		if (endRange == 0) {
5656 			setError(MSG_EmptyBuffer);
5657 			return false;
5658 		}
5659 		if (!cw->browseMode) {
5660 			setError(MSG_NoBrowse);
5661 			return false;
5662 		}
5663 		jSyncup(false);
5664 		cw->dot = startRange;
5665 		if (!frameExpand((line[0] == 'e'), startRange, endRange))
5666 			showError();
5667 /* meta http refresh could send to another page */
5668 		if (newlocation) {
5669 replaceframe:
5670 			if (!shortRefreshDelay(newlocation, newloc_d)) {
5671 				nzFree(newlocation);
5672 				newlocation = 0;
5673 			} else {
5674 				jSyncup(false);
5675 				if (!reexpandFrame())
5676 					showError();
5677 				if (newlocation)
5678 					goto replaceframe;
5679 			}
5680 		}
5681 /* even if one frame failed to expand, another might, so always rerender */
5682 		selfFrame();
5683 		rerender(false);
5684 		return true;
5685 	}
5686 
5687 	/* special command for hidden input */
5688 	if (!strncmp(line, "ipass", 5)) {
5689 		char *p;
5690 		char buffer[MAXUSERPASS];
5691 		int realtotal;
5692 		bool old_masked;
5693 		if (!cw->browseMode) {
5694 			setError(MSG_NoBrowse);
5695 			return false;
5696 		}
5697 		if (endRange > startRange) {
5698 			setError(MSG_RangeCmd, "ipass");
5699 			return false;
5700 		}
5701 
5702 		s = line + 5;
5703 		if (isdigitByte(*s))
5704 			cx = strtol(s, (char **)&s, 10);
5705 		else if (*s == '$')
5706 			cx = -1, ++s;
5707 		/* XXX try to guess cx if only one password input field? */
5708 
5709 		cw->dot = endRange;
5710 		p = (char *)fetchLine(cw->dot, -1);
5711 		findInputField(p, 1, cx, &n, &realtotal, &tagno);
5712 		debugPrint(5, "findField returns %d.%d", n, tagno);
5713 		if (!tagno) {
5714 			fieldNumProblem(0, "ipass", cx, n, realtotal);
5715 			return false;
5716 		}
5717 
5718 		prompt_and_read(MSG_Password, buffer, MAXUSERPASS,
5719 				MSG_PasswordLong, true);
5720 
5721 		old_masked = tagList[tagno]->masked;
5722 		tagList[tagno]->masked = true;
5723 
5724 		rc = infReplace(tagno, buffer, true);
5725 		if (!rc)
5726 			tagList[tagno]->masked = old_masked;
5727 		return rc;
5728 	}
5729 
5730 /* get the command */
5731 	cmd = *line;
5732 	if (cmd)
5733 		++line;
5734 	else
5735 		cmd = 'p';
5736 	icmd = cmd;
5737 
5738 	if (!strchr(valid_cmd, cmd)) {
5739 		setError(MSG_UnknownCommand, cmd);
5740 		return (globSub = false);
5741 	}
5742 
5743 	first = *line;
5744 	if (cmd == 'w' && first == '+')
5745 		writeMode = O_APPEND, first = *++line;
5746 
5747 	if (cw->dirMode && !strchr(dir_cmd, cmd)) {
5748 		setError(MSG_DirCommand, icmd);
5749 		return (globSub = false);
5750 	}
5751 
5752 	if (cw->sqlMode && !strchr(sql_cmd, cmd)) {
5753 		setError(MSG_DBCommand, icmd);
5754 		return (globSub = false);
5755 	}
5756 
5757 	if (cw->browseMode && !strchr(browse_cmd, cmd)) {
5758 		setError(MSG_BrowseCommand, icmd);
5759 		return (globSub = false);
5760 	}
5761 
5762 	if (startRange == 0 && !strchr(zero_cmd, cmd)) {
5763 		setError(MSG_AtLine0);
5764 		return (globSub = false);
5765 	}
5766 
5767 	while (isspaceByte(first))
5768 		postSpace = true, first = *++line;
5769 
5770 	if (strchr(spaceplus_cmd, cmd) && !postSpace && first) {
5771 		s = line;
5772 		while (isdigitByte(*s))
5773 			++s;
5774 		if (*s) {
5775 			setError(MSG_NoSpaceAfter);
5776 			return (globSub = false);
5777 		}
5778 	}
5779 
5780 	if (globSub && !strchr(global_cmd, cmd)) {
5781 		setError(MSG_GlobalCommand, icmd);
5782 		return (globSub = false);
5783 	}
5784 
5785 /* move/copy destination, the third address */
5786 	if (cmd == 't' || cmd == 'm') {
5787 		if (!first) {
5788 			if (cw->dirMode) {
5789 				setError(MSG_BadDest);
5790 				return (globSub = false);
5791 			}
5792 			destLine = cw->dot;
5793 		} else {
5794 			if (!strchr(valid_laddr, first)) {
5795 				setError(MSG_BadDest);
5796 				return (globSub = false);
5797 			}
5798 			if (cw->dirMode && !isdigitByte(first)) {
5799 				setError(MSG_BadDest);
5800 				return (globSub = false);
5801 			}
5802 			if (!getRangePart(line, &destLine, &line))
5803 				return (globSub = false);
5804 			first = *line;
5805 		}		/* was there something after m or t */
5806 	}
5807 
5808 /* -c is the config file */
5809 	if ((cmd == 'b' || cmd == 'e') && stringEqual(line, "-c"))
5810 		line = configFile;
5811 
5812 /* env variable and wild card expansion */
5813 	if (strchr("brewf", cmd) && first && !isURL(line) && !isSQL(line)) {
5814 		if (cmd != 'r' || !cw->sqlMode) {
5815 			if (!envFile(line, &line))
5816 				return false;
5817 			first = *line;
5818 		}
5819 	}
5820 
5821 	if (cmd == 'z') {
5822 		if (isdigitByte(first)) {
5823 			last_z = strtol(line, (char **)&line, 10);
5824 			if (!last_z)
5825 				last_z = 1;
5826 			first = *line;
5827 		}
5828 		startRange = endRange + 1;
5829 		endRange = startRange;
5830 		if (startRange > cw->dol) {
5831 			startRange = endRange = 0;
5832 			setError(MSG_LineHigh);
5833 			return false;
5834 		}
5835 		cmd = 'p';
5836 		endRange += last_z - 1;
5837 		if (endRange > cw->dol)
5838 			endRange = cw->dol;
5839 	}
5840 
5841 	/* the a+ feature, when you thought you were in append mode */
5842 	if (cmd == 'a') {
5843 		if (stringEqual(line, "+"))
5844 			++line, first = 0;
5845 		else {
5846 			nzFree(linePending);
5847 			linePending = 0;
5848 		}
5849 	} else {
5850 		nzFree(linePending);
5851 		linePending = 0;
5852 	}
5853 
5854 	if (first && strchr(nofollow_cmd, cmd)) {
5855 		setError(MSG_TextAfter, icmd);
5856 		return (globSub = false);
5857 	}
5858 
5859 	if (cmd == 'h') {
5860 		showError();
5861 		return true;
5862 	}
5863 
5864 	if (cmd == 'X') {
5865 		cw->dot = endRange;
5866 		return true;
5867 	}
5868 
5869 	if (strchr("Llpn", cmd)) {
5870 		for (i = startRange; i <= endRange; ++i) {
5871 			displayLine(i);
5872 			cw->dot = i;
5873 			if (intFlag)
5874 				break;
5875 		}
5876 		return true;
5877 	}
5878 
5879 	if (cmd == '=') {
5880 		printf("%d\n", endRange);
5881 		return true;
5882 	}
5883 
5884 	if (cmd == 'B') {
5885 		return balanceLine(line);
5886 	}
5887 
5888 	if (cmd == 'u') {
5889 		struct ebWindow *uw = &undoWindow;
5890 		struct lineMap *swapmap;
5891 		if (!cw->undoable) {
5892 			setError(MSG_NoUndo);
5893 			return false;
5894 		}
5895 /* swap, so we can undo our undo, if need be */
5896 		i = uw->dot, uw->dot = cw->dot, cw->dot = i;
5897 		i = uw->dol, uw->dol = cw->dol, cw->dol = i;
5898 		for (j = 0; j < MARKLETTERS; ++j) {
5899 			i = uw->labels[j], uw->labels[j] =
5900 			    cw->labels[j], cw->labels[j] = i;
5901 		}
5902 		swapmap = uw->map, uw->map = cw->map, cw->map = swapmap;
5903 		return true;
5904 	}
5905 
5906 	if (cmd == 'k') {
5907 		if (!islowerByte(first) || line[1]) {
5908 			setError(MSG_EnterKAZ);
5909 			return false;
5910 		}
5911 		if (startRange < endRange) {
5912 			setError(MSG_RangeLabel);
5913 			return false;
5914 		}
5915 		cw->labels[first - 'a'] = endRange;
5916 		return true;
5917 	}
5918 
5919 	/* Find suffix, as in 27,59w2 */
5920 	if (!postSpace) {
5921 		cx = stringIsNum(line);
5922 		if (!cx) {
5923 			setError((cmd == '^'
5924 				  || cmd == '&') ? MSG_Backup0 : MSG_Session0);
5925 			return false;
5926 		}
5927 		if (cx < 0)
5928 			cx = 0;
5929 	}
5930 
5931 	if (cmd == 'q') {
5932 		if (cx) {
5933 			if (!cxCompare(cx))
5934 				return false;
5935 			if (!cxActive(cx))
5936 				return false;
5937 		} else {
5938 			cx = context;
5939 			if (first) {
5940 				setError(MSG_QAfter);
5941 				return false;
5942 			}
5943 		}
5944 		saveSubstitutionStrings();
5945 		if (!cxQuit(cx, 2))
5946 			return false;
5947 		if (cx != context)
5948 			return true;
5949 /* look around for another active session */
5950 		while (true) {
5951 			if (++cx > maxSession)
5952 				cx = 1;
5953 			if (cx == context)
5954 				ebClose(0);
5955 			if (!sessionList[cx].lw)
5956 				continue;
5957 			cxSwitch(cx, true);
5958 			return true;
5959 		}		/* loop over sessions */
5960 	}
5961 
5962 	if (cmd == 'f') {
5963 		selfFrame();
5964 		if (cx) {
5965 			if (!cxCompare(cx))
5966 				return false;
5967 			if (!cxActive(cx))
5968 				return false;
5969 			s = sessionList[cx].lw->f0.fileName;
5970 			if (s)
5971 				i_printf(MSG_String, s);
5972 			else
5973 				i_printf(MSG_NoFile);
5974 			if (sessionList[cx].lw->binMode)
5975 				i_printf(MSG_BinaryBrackets);
5976 			if (cw->jdb_frame)
5977 				i_printf(MSG_String, " jdb");
5978 			eb_puts("");
5979 			return true;
5980 		}		/* another session */
5981 		if (first) {
5982 			if (cw->dirMode) {
5983 				setError(MSG_DirRename);
5984 				return false;
5985 			}
5986 			if (cw->sqlMode) {
5987 				setError(MSG_TableRename);
5988 				return false;
5989 			}
5990 			nzFree(cf->fileName);
5991 			cf->fileName = cloneString(line);
5992 		}
5993 		s = cf->fileName;
5994 		if (s)
5995 			printf("%s", s);
5996 		else
5997 			i_printf(MSG_NoFile);
5998 		if (cw->binMode)
5999 			i_printf(MSG_BinaryBrackets);
6000 		nl();
6001 		return true;
6002 	}
6003 
6004 	if (cmd == 'w') {
6005 		if (cx) {	/* write to another buffer */
6006 			if (writeMode == O_APPEND) {
6007 				setError(MSG_BufferAppend);
6008 				return false;
6009 			}
6010 			return writeContext(cx);
6011 		}
6012 		selfFrame();
6013 		if (!first)
6014 			line = cf->fileName;
6015 		if (!line) {
6016 			setError(MSG_NoFileSpecified);
6017 			return false;
6018 		}
6019 		if (cw->dirMode && stringEqual(line, cf->fileName)) {
6020 			setError(MSG_NoDirWrite);
6021 			return false;
6022 		}
6023 		if (cw->sqlMode && stringEqual(line, cf->fileName)) {
6024 			setError(MSG_NoDBWrite);
6025 			return false;
6026 		}
6027 		return writeFile(line, writeMode);
6028 	}
6029 
6030 	if (cmd == '&') {	/* jump back key */
6031 		if (first && !cx) {
6032 			setError(MSG_ArrowAfter);
6033 			return false;
6034 		}
6035 		if (!cx)
6036 			cx = 1;
6037 		while (cx) {
6038 			struct histLabel *label = cw->histLabel;
6039 			if (!label) {
6040 				setError(MSG_NoPrevious);
6041 				return false;
6042 			}
6043 			cw->histLabel = label->prev;
6044 			if (label->label)	/* could be 0 because of line deletion */
6045 				cw->dot = label->label;
6046 			free(label);
6047 			--cx;
6048 		}
6049 		printDot();
6050 		return true;
6051 
6052 	}
6053 
6054 	if (cmd == '^') {	/* back key, pop the stack */
6055 		if (first && !cx) {
6056 			setError(MSG_ArrowAfter);
6057 			return false;
6058 		}
6059 		if (!cx)
6060 			cx = 1;
6061 		while (cx) {
6062 			struct ebWindow *prev = cw->prev;
6063 			if (!prev) {
6064 				setError(MSG_NoPrevious);
6065 				return false;
6066 			}
6067 			saveSubstitutionStrings();
6068 			if (!cxQuit(context, 1))
6069 				return false;
6070 			sessionList[context].lw = cw = prev;
6071 			selfFrame();
6072 			restoreSubstitutionStrings(cw);
6073 			--cx;
6074 		}
6075 		printDot();
6076 		return true;
6077 	}
6078 
6079 	if (cmd == 'M') {	/* move this to another session */
6080 		if (first && !cx) {
6081 			setError(MSG_MAfter);
6082 			return false;
6083 		}
6084 		if (!cw->prev) {
6085 			setError(MSG_NoBackup);
6086 			return false;
6087 		}
6088 		if (cx) {
6089 			if (!cxCompare(cx))
6090 				return false;
6091 		} else {
6092 			cx = sideBuffer(0, emptyString, 0, NULL);
6093 			if (cx == 0)
6094 				return false;
6095 		}
6096 /* we likely just created it; now quit it */
6097 		if (cxActive(cx) && !cxQuit(cx, 2))
6098 			return false;
6099 /* If changes were made to this buffer, they are undoable after the move */
6100 		undoCompare();
6101 		cw->undoable = false;
6102 		i_printf(MSG_MovedSession, cx);
6103 /* Magic with pointers, hang on to your hat. */
6104 		sessionList[cx].fw = sessionList[cx].lw = cw;
6105 		cs->lw = cw->prev;
6106 		cw->prev = 0;
6107 		cw = cs->lw;
6108 		selfFrame();
6109 		printDot();
6110 		return true;
6111 	}
6112 
6113 	if (cmd == 'A') {
6114 		char *a;
6115 		if (!cxQuit(context, 0))
6116 			return false;
6117 		if (!(a = showLinks()))
6118 			return false;
6119 		undoCompare();
6120 		cw->undoable = cw->changeMode = false;
6121 		w = createWindow();
6122 		w->prev = cw;
6123 		cw = w;
6124 		selfFrame();
6125 		cs->lw = w;
6126 		rc = addTextToBuffer((pst) a, strlen(a), 0, false);
6127 		nzFree(a);
6128 		cw->changeMode = false;
6129 		fileSize = apparentSize(context, false);
6130 		return rc;
6131 	}
6132 
6133 	if (cmd == '<') {	/* run a function */
6134 		return runEbFunction(line);
6135 	}
6136 
6137 	/* go to a file in a directory listing */
6138 	if (cmd == 'g' && cw->dirMode && (!first || stringEqual(line, "-"))) {
6139 		char *p, *dirline;
6140 		const struct MIMETYPE *gmt = 0;	/* the go mime type */
6141 		emode = (first == '-');
6142 		if (endRange > startRange) {
6143 			setError(MSG_RangeCmd, "g");
6144 			return false;
6145 		}
6146 		cw->dot = endRange;
6147 		p = (char *)fetchLine(endRange, -1);
6148 		j = pstLength((pst) p);
6149 		--j;
6150 		p[j] = 0;	/* temporary */
6151 		dirline = makeAbsPath(p);
6152 		p[j] = '\n';
6153 		cmd = 'e';
6154 		if (!dirline)
6155 			return false;
6156 		stripDotDot(dirline);
6157 		if (!emode)
6158 			gmt = findMimeByFile(dirline);
6159 		if (pluginsOn && gmt) {
6160 			if (gmt->outtype)
6161 				cmd = 'b';
6162 			else
6163 				return playBuffer("pb", dirline);
6164 		}
6165 /* I don't think we need to make a copy here. */
6166 		line = dirline;
6167 		first = *line;
6168 	}
6169 
6170 	if (cmd == 'e') {
6171 		if (cx) {
6172 // switchsession:
6173 			if (!cxCompare(cx))
6174 				return false;
6175 			cxSwitch(cx, true);
6176 			return true;
6177 		}
6178 		if (!first) {
6179 			i_printf(MSG_SessionX, context);
6180 			return true;
6181 		}
6182 /* more e to come */
6183 	}
6184 
6185 	/* see if it's a go command */
6186 	if (cmd == 'g' && !(cw->sqlMode | cw->binMode)) {
6187 		char *p, *h;
6188 		int tagno;
6189 		bool click, dclick;
6190 		bool jsh, jsgo, jsdead;
6191 		bool lookmode = false;
6192 
6193 		j = strlen(line);
6194 		if (j && line[j - 1] == '?')
6195 			lookmode = true;
6196 		if (j && line[j - 1] == '-')
6197 			emode = true;
6198 
6199 		/* Check to see if g means run an sql command. */
6200 		if (!first) {
6201 			char *rbuf;
6202 			j = goSelect(&startRange, &rbuf);
6203 			if (j >= 0) {
6204 				cmd = 'e';	/* for autoprint of errors */
6205 				cw->dot = startRange;
6206 				if (*rbuf) {
6207 					int savedol = cw->dol;
6208 					addTextToBuffer((pst) rbuf,
6209 							strlen(rbuf), cw->dot,
6210 							true);
6211 					nzFree(rbuf);
6212 					if (cw->dol > savedol) {
6213 						cw->labels[0] = startRange + 1;
6214 						cw->labels[1] =
6215 						    startRange + cw->dol -
6216 						    savedol;
6217 					}
6218 					cw->dot = startRange;
6219 				}
6220 				return j ? true : false;
6221 			}
6222 		}
6223 
6224 /* Now try to go to a hyperlink */
6225 		s = line;
6226 		j = 0;
6227 		if (first) {
6228 			if (isdigitByte(first))
6229 				j = strtol(s, (char **)&s, 10);
6230 			else if (first == '$')
6231 				j = -1, ++s;
6232 		}
6233 		if (*s == '?' || *s == '-')
6234 			++s;
6235 		if (!*s) {
6236 			if (cw->sqlMode) {
6237 				setError(MSG_DBG);
6238 				return false;
6239 			}
6240 			jsh = jsgo = nogo = false;
6241 			jsdead = !isJSAlive;
6242 			click = dclick = false;
6243 			cmd = (emode ? 'e' : 'b');
6244 			uriEncoded = true;
6245 			if (endRange > startRange) {
6246 				setError(MSG_RangeCmd, "g");
6247 				return false;
6248 			}
6249 			p = (char *)fetchLine(endRange, -1);
6250 			findField(p, 0, j, &n, 0, &tagno, &h, &tag);
6251 			debugPrint(5, "findField returns %d, %s", tagno, h);
6252 
6253 			if (!h) {
6254 				fieldNumProblem(1, "g", j, n, n);
6255 				return false;
6256 			}
6257 			cw->dot = endRange;
6258 			if (cw->browseMode && h[0] == '#')
6259 				emode = false, cmd = 'b';
6260 			jsh = memEqualCI(h, "javascript:", 11);
6261 
6262 			if (lookmode) {
6263 				puts(jsh ? "javascript:" : h);
6264 				nzFree(h);
6265 				return true;
6266 			}
6267 
6268 			if (tag && tag->action == TAGACT_FRAME) {
6269 				nzFree(h);
6270 				line = "exp";
6271 				goto expctr;
6272 			}
6273 
6274 			if (tagno) {
6275 				click = tagHandler(tagno, "onclick");
6276 				dclick = tagHandler(tagno, "ondblclick");
6277 			}
6278 			if (click)
6279 				jsgo = true;
6280 			jsgo |= jsh;
6281 			nogo = stringEqual(h, "#");
6282 			if (!*h)
6283 				nogo = true;
6284 			nogo |= jsh;
6285 			debugPrint(5, "go %d nogo %d jsh %d dead %d", jsgo,
6286 				   nogo, jsh, jsdead);
6287 			debugPrint(5, "click %d dclick %d", click, dclick);
6288 			if (jsgo & jsdead) {
6289 				if (nogo)
6290 					i_puts(MSG_NJNoAction);
6291 				else
6292 					i_puts(MSG_NJGoing);
6293 				jsgo = jsh = false;
6294 			}
6295 // because I am setting allocatedLine to h, it will get freed on the next go round.
6296 			line = allocatedLine = h;
6297 			first = *line;
6298 			setError(-1);
6299 			rc = false;
6300 // The website should not depend on the mouseover code running first.
6301 // edbrowse is more like a touchscreen, and there are such devices, so just go.
6302 // No mouseEnter, mouseOver, mouseExit, etc.
6303 			if (!jsdead)
6304 				set_property_string(cf, cf->winobj, "status", h);
6305 			if (jsgo) {
6306 				jSyncup(false);
6307 				rc = bubble_event(tag, "onclick");
6308 				jSideEffects();
6309 				if (newlocation)
6310 					goto redirect;
6311 				if (!rc)
6312 					return true;
6313 			}
6314 			if (jsh) {
6315 				jSyncup(false);
6316 /* actually running the url, not passing it to http etc, need to unescape */
6317 				unpercentString(h);
6318 				cf = tag->f0;
6319 				jsRunScript(cf, cf->winobj, h, "a.href", 1);
6320 				jSideEffects();
6321 				if (newlocation)
6322 					goto redirect;
6323 				return true;
6324 			}
6325 			if (nogo)
6326 				return true;
6327 // to access local files
6328 			if (!isURL(h))
6329 				unpercentString(h);
6330 		}
6331 	}
6332 
6333 	if (cmd == 's') {
6334 /* Some shorthand, like s,2 to split the line at the second comma */
6335 		if (!first) {
6336 			strcpy(newline, "//%");
6337 			line = newline;
6338 		} else if (strchr(",.;:!?)-\"", first) &&
6339 			   (!line[1] || (isdigitByte(line[1]) && !line[2]))) {
6340 			char esc[2];
6341 			esc[0] = esc[1] = 0;
6342 			if (first == '.' || first == '?')
6343 				esc[0] = '\\';
6344 			sprintf(newline, "/%s%c +/%c\\n%s%s",
6345 				esc, first, first, (line[1] ? "/" : ""),
6346 				line + 1);
6347 			debugPrint(7, "shorthand regexp %s", newline);
6348 			line = newline;
6349 		}
6350 		first = *line;
6351 	}
6352 
6353 	scmd = ' ';
6354 	if ((cmd == 'i' || cmd == 's') && first) {
6355 		char c;
6356 		s = line;
6357 		if (isdigitByte(*s))
6358 			cx = strtol(s, (char **)&s, 10);
6359 		else if (*s == '$')
6360 			cx = -1, ++s;
6361 		c = *s;
6362 		if (c &&
6363 		    (strchr(valid_delim, c) ||
6364 		     (cmd == 'i' && strchr("*<?=", c)))) {
6365 			if (!cw->browseMode && (cmd == 'i' || cx)) {
6366 				setError(MSG_NoBrowse);
6367 				return false;
6368 			}
6369 			if (endRange > startRange && cmd == 'i') {
6370 				setError(MSG_RangeI, c);
6371 				return false;
6372 			}
6373 
6374 			if (cmd == 'i' && strchr("?=<*", c)) {
6375 				char *p;
6376 				int realtotal;
6377 				scmd = c;
6378 				line = s + 1;
6379 				first = *line;
6380 				debugPrint(5, "scmd = %c", scmd);
6381 				cw->dot = endRange;
6382 				p = (char *)fetchLine(cw->dot, -1);
6383 				j = 1;
6384 				if (scmd == '*')
6385 					j = 2;
6386 				if (scmd == '?')
6387 					j = 3;
6388 				findInputField(p, j, cx, &n, &realtotal,
6389 					       &tagno);
6390 				debugPrint(5, "findField returns %d.%d", n,
6391 					   tagno);
6392 				if (!tagno) {
6393 					fieldNumProblem((c == '*' ? 2 : 0), "i",
6394 							cx, n, realtotal);
6395 					return false;
6396 				}
6397 
6398 				if (scmd == '?') {
6399 					infShow(tagno, line);
6400 					return true;
6401 				}
6402 
6403 				cw->undoable = false;
6404 
6405 				if (c == '<') {
6406 					if (globSub) {
6407 						setError(MSG_IG);
6408 						return (globSub = false);
6409 					}
6410 					allocatedLine = lessFile(line);
6411 					if (!allocatedLine)
6412 						return false;
6413 					prepareForField(allocatedLine);
6414 					line = allocatedLine;
6415 					scmd = '=';
6416 				}
6417 
6418 				if (scmd == '=') {
6419 					rc = infReplace(tagno, line, true);
6420 					if (newlocation)
6421 						goto redirect;
6422 					return rc;
6423 				}
6424 
6425 				if (c == '*') {
6426 					Frame *save_cf = cf;
6427 					jSyncup(false);
6428 					c = infPush(tagno, &allocatedLine);
6429 					jSideEffects();
6430 					cf = save_cf;
6431 					if (!c)
6432 						return false;
6433 					if (newlocation)
6434 						goto redirect;
6435 /* No url means it was a reset button */
6436 					if (!allocatedLine)
6437 						return true;
6438 					line = allocatedLine;
6439 					first = *line;
6440 					cmd = 'b';
6441 					uriEncoded = true;
6442 				}
6443 
6444 			} else
6445 				cmd = 's';
6446 		} else {
6447 			setError(MSG_TextAfter, icmd);
6448 			return false;
6449 		}
6450 	}
6451 
6452 rebrowse:
6453 	if (cmd == 'e' || (cmd == 'b' && first && first != '#')) {
6454 //  printf("ifetch %d %s\n", uriEncoded, line);
6455 		if (!noStack && sameURL(line, cf->fileName)) {
6456 			if (stringEqual(line, cf->fileName)) {
6457 				setError(MSG_AlreadyInBuffer);
6458 				return false;
6459 			}
6460 /* Same url, but a different #section */
6461 			s = findHash(line);
6462 			if (!s) {	/* no section specified */
6463 				cw->dot = 1;
6464 				if (!cw->dol)
6465 					cw->dot = 0;
6466 				printDot();
6467 				return true;
6468 			}
6469 			line = s;
6470 			first = '#';
6471 			cmd = 'b';
6472 			emode = false;
6473 			goto browse;
6474 		}
6475 
6476 /* Different URL, go get it. */
6477 /* did you make changes that you didn't write? */
6478 		if (!cxQuit(context, 0))
6479 			return false;
6480 		undoCompare();
6481 		cw->undoable = cw->changeMode = false;
6482 		startRange = endRange = 0;
6483 		changeFileName = 0;	/* should already be zero */
6484 		thisfile = cf->fileName;
6485 		w = createWindow();
6486 		cw = w;		/* we might wind up putting this back */
6487 		selfFrame();
6488 		cf->uriEncoded = uriEncoded;
6489 /* Check for sendmail link */
6490 		if (cmd == 'b' && memEqualCI(line, "mailto:", 7)) {
6491 			char *addr, *subj, *body;
6492 			char *q;
6493 			int ql;
6494 			decodeMailURL(line, &addr, &subj, &body);
6495 			ql = strlen(addr);
6496 			ql += 4;	/* to:\n */
6497 			ql += subj ? strlen(subj) : 5;
6498 			ql += 9;	/* subject:\n */
6499 			if (body)
6500 				ql += strlen(body);
6501 			q = allocMem(ql + 1);
6502 			sprintf(q, "to:%s\nSubject:%s\n%s", addr,
6503 				subj ? subj : "Hello", body ? body : "");
6504 			j = addTextToBuffer((pst) q, ql, 0, false);
6505 			nzFree(q);
6506 			nzFree(addr);
6507 			nzFree(subj);
6508 			nzFree(body);
6509 			if (j)
6510 				i_puts(MSG_MailHowto);
6511 // serverData doesn't mean anything here, but it has to be not null
6512 // for some code that is coming up.
6513 			serverData = emptyString;
6514 		} else {
6515 			bool save_pg;
6516 
6517 /*********************************************************************
6518 Before we set the new file name, and before we call up the next web page,
6519 we have to make sure it has a protocol. Every url needs a protocol.
6520 *********************************************************************/
6521 
6522 			if (missingProtURL(line)) {
6523 				char *w = allocMem(strlen(line) + 8);
6524 				sprintf(w, "http://%s", line);
6525 				nzFree(allocatedLine);
6526 				line = allocatedLine = w;
6527 			}
6528 
6529 			cf->fileName = cloneString(line);
6530 			cf->firstURL = cloneString(line);
6531 			if (isSQL(line))
6532 				cw->sqlMode = true;
6533 			if (icmd == 'g' && !nogo && isURL(line))
6534 				debugPrint(2, "*%s", line);
6535 // emode suppresses plugins, as well as browsing
6536 			save_pg = pluginsOn;
6537 			if (emode)
6538 				pluginsOn = false;
6539 			j = readFile(line, emptyString, (cmd != 'r'), 0,
6540 				     thisfile);
6541 			pluginsOn = save_pg;
6542 		}
6543 		w->undoable = w->changeMode = false;
6544 		cw = cs->lw;	/* put it back, for now */
6545 		selfFrame();
6546 /* Don't push a new session if we were trying to read a url,
6547  * and didn't get anything. */
6548 		if (!serverData && (isURL(line) || isSQL(line))) {
6549 			fileSize = -1;
6550 			freeWindow(w);
6551 			if (noStack && cw->prev) {
6552 				w = cw;
6553 				cw = w->prev;
6554 				selfFrame();
6555 				cs->lw = cw;
6556 				freeWindow(w);
6557 			}
6558 			return j;
6559 		}
6560 		if (noStack) {
6561 			w->prev = cw->prev;
6562 			nzFree(w->f0.firstURL);
6563 			w->f0.firstURL = cf->firstURL;
6564 			cf->firstURL = 0;
6565 			cxQuit(context, 1);
6566 		} else {
6567 			w->prev = cw;
6568 		}
6569 		cs->lw = cw = w;
6570 		selfFrame();
6571 		if (!w->prev)
6572 			cs->fw = w;
6573 		if (!j)
6574 			return false;
6575 		if (changeFileName) {
6576 			nzFree(w->f0.fileName);
6577 			w->f0.fileName = changeFileName;
6578 			w->f0.uriEncoded = true;
6579 			changeFileName = 0;
6580 		}
6581 /* Some files we just can't browse */
6582 		if (!cw->dol || cw->dirMode)
6583 			cmd = 'e';
6584 		if (cw->binMode && (!cf->mt || !cf->mt->outtype))
6585 			cmd = 'e';
6586 		if (cmd == 'e')
6587 			return true;
6588 	}
6589 
6590 browse:
6591 	if (cmd == 'b') {
6592 		char *newhash;
6593 		if (cw->dirMode) {
6594 			setError(MSG_DirCommand, cmd);
6595 			return false;
6596 		}
6597 		if (!cw->browseMode) {
6598 			if (!cw->dol) {
6599 				setError(MSG_BrowseEmpty);
6600 				return false;
6601 			}
6602 			if (fileSize >= 0) {
6603 				debugPrint(1, "%d", fileSize);
6604 				fileSize = -1;
6605 			}
6606 			if (!browseCurrentBuffer()) {
6607 				if (icmd == 'b')
6608 					return false;
6609 				return true;
6610 			}
6611 		} else if (!first) {
6612 			setError(MSG_BrowseAlready);
6613 			return false;
6614 		}
6615 
6616 		if (newlocation) {
6617 			if (!shortRefreshDelay(newlocation, newloc_d)) {
6618 				nzFree(newlocation);
6619 				newlocation = 0;
6620 			} else {
6621 redirect:
6622 				selfFrame();
6623 				noStack = newloc_r;
6624 				if (newloc_f != cf)
6625 					goto replaceframe;
6626 				nzFree(allocatedLine);
6627 				line = allocatedLine = newlocation;
6628 				newlocation = 0;
6629 				debugPrint(2, "redirect %s", line);
6630 				icmd = cmd = 'b';
6631 				uriEncoded = true;
6632 				first = *line;
6633 				if (intFlag) {
6634 					i_puts(MSG_RedirectionInterrupted);
6635 					return true;
6636 				}
6637 				goto rebrowse;
6638 			}
6639 		}
6640 
6641 /* Jump to the #section if specified in the url */
6642 		s = findHash(line);
6643 		if (!s)
6644 			return true;
6645 		++s;
6646 /* Sometimes there's a # in the midst of a long url,
6647  * probably with post data.  It really screws things up.
6648  * Here is a kludge to avoid this problem.
6649  * Some day I need to figure this out. */
6650 		if (strpbrk(line, "?\1"))
6651 			return true;
6652 /* Print the file size before we print the line. */
6653 		if (fileSize >= 0) {
6654 			debugPrint(1, "%d", fileSize);
6655 			fileSize = -1;
6656 		}
6657 		newhash = cloneString(s);
6658 		unpercentString(newhash);
6659 		for (i = 1; i <= cw->dol; ++i) {
6660 			char *p = (char *)fetchLine(i, -1);
6661 			if (lineHasTag(p, newhash)) {
6662 				struct histLabel *label =
6663 				    allocMem(sizeof(struct histLabel));
6664 				label->label = cw->dot;
6665 				label->prev = cw->histLabel;
6666 				cw->histLabel = label;
6667 				cw->dot = i;
6668 				printDot();
6669 				nzFree(newhash);
6670 				return true;
6671 			}
6672 		}
6673 		setError(MSG_NoLable2, newhash);
6674 		nzFree(newhash);
6675 		return false;
6676 	}
6677 
6678 	if (cmd == 'g' || cmd == 'v') {
6679 		return doGlobal(line);
6680 	}
6681 
6682 	if ((cmd == 'm' || cmd == 't') && cw->dirMode) {
6683 		j = moveFiles();
6684 		undoCompare();
6685 		cw->undoable = false;
6686 		return j;
6687 	}
6688 
6689 	if (cmd == 'm' || cmd == 't')
6690 		return moveCopy();
6691 
6692 	if (cmd == 'i') {
6693 		if (cw->browseMode) {
6694 			setError(MSG_BrowseI);
6695 			return false;
6696 		}
6697 		cmd = 'a';
6698 		--startRange, --endRange;
6699 	}
6700 
6701 	if (cmd == 'c') {
6702 		delText(startRange, endRange);
6703 		endRange = --startRange;
6704 		cmd = 'a';
6705 	}
6706 
6707 	if (cmd == 'a') {
6708 		if (inscript) {
6709 			setError(MSG_InsertFunction);
6710 			return false;
6711 		}
6712 		if (cw->sqlMode) {
6713 			j = cw->dol;
6714 			rc = sqlAddRows(endRange);
6715 /* adjust dot */
6716 			j = cw->dol - j;
6717 			if (j)
6718 				cw->dot = endRange + j;
6719 			else if (!endRange && cw->dol)
6720 				cw->dot = 1;
6721 			else
6722 				cw->dot = endRange;
6723 			return rc;
6724 		}
6725 		return inputLinesIntoBuffer();
6726 	}
6727 
6728 	if (cmd == 'd' || cmd == 'D') {
6729 		if (cw->dirMode) {
6730 			j = delFiles();
6731 			undoCompare();
6732 			cw->undoable = false;
6733 			goto afterdelete;
6734 		}
6735 		if (cw->sqlMode) {
6736 			j = sqlDelRows(startRange, endRange);
6737 			undoCompare();
6738 			cw->undoable = false;
6739 			goto afterdelete;
6740 		}
6741 		if (cw->browseMode)
6742 			delTags(startRange, endRange);
6743 		delText(startRange, endRange);
6744 		j = 1;
6745 afterdelete:
6746 		if (!j)
6747 			globSub = false;
6748 		else if (cmd == 'D')
6749 			printDot();
6750 		return j;
6751 	}
6752 
6753 	if (cmd == 'j' || cmd == 'J') {
6754 		return joinText();
6755 	}
6756 
6757 	if (cmd == 'r') {
6758 		if (cx)
6759 			return readContext(cx);
6760 		if (first) {
6761 			if (cw->sqlMode && !isSQL(line)) {
6762 				strcpy(newline, cf->fileName);
6763 				strmove(strchr(newline, ']') + 1, line);
6764 				line = newline;
6765 			}
6766 			j = readFile(line, emptyString, (cmd != 'r'), 0, 0);
6767 			if (!serverData)
6768 				fileSize = -1;
6769 			return j;
6770 		}
6771 		setError(MSG_NoFileSpecified);
6772 		return false;
6773 	}
6774 
6775 	if (cmd == 's') {
6776 		pst p;
6777 		j = substituteText(line);
6778 // special case, if last line became empty and nlMode is true.
6779 		if (cw->dol && cw->nlMode &&
6780 		    (p = fetchLine(cw->dol, -1)) && p[0] == '\n')
6781 			delText(cw->dol, cw->dol);
6782 		if (j < 0) {
6783 			globSub = false;
6784 			j = false;
6785 		}
6786 		if (newlocation)
6787 			goto redirect;
6788 		return j;
6789 	}
6790 
6791 	setError(MSG_CNYI, icmd);
6792 	return (globSub = false);
6793 }				/* runCommand */
6794 
edbrowseCommand(const char * line,bool script)6795 bool edbrowseCommand(const char *line, bool script)
6796 {
6797 	bool rc;
6798 	globSub = intFlag = false;
6799 	inscript = script;
6800 	fileSize = -1;
6801 	skipWhite(&line);
6802 	rc = runCommand(line);
6803 	if (fileSize >= 0)
6804 		debugPrint(1, "%d", fileSize);
6805 	fileSize = -1;
6806 	if (!rc) {
6807 		if (!script)
6808 			showErrorConditional(cmd);
6809 		eeCheck();
6810 	}
6811 	return rc;
6812 }				/* edbrowseCommand */
6813 
6814 /* Take some text, usually empty, and put it in a side buffer. */
sideBuffer(int cx,const char * text,int textlen,const char * bufname)6815 int sideBuffer(int cx, const char *text, int textlen, const char *bufname)
6816 {
6817 	int svcx = context;
6818 	bool rc;
6819 	if (cx) {
6820 		cxQuit(cx, 3);
6821 	} else {
6822 		for (cx = 1; cx < MAXSESSION; ++cx)
6823 			if (!sessionList[cx].lw)
6824 				break;
6825 		if (cx == MAXSESSION) {
6826 			i_puts(MSG_NoBufferExtraWindow);
6827 			return 0;
6828 		}
6829 	}
6830 	cxSwitch(cx, false);
6831 	if (bufname) {
6832 		cf->fileName = cloneString(bufname);
6833 		debrowseSuffix(cf->fileName);
6834 	}
6835 	if (textlen < 0) {
6836 		textlen = strlen(text);
6837 	} else {
6838 		cw->binMode = looksBinary((uchar *) text, textlen);
6839 	}
6840 	if (textlen) {
6841 		rc = addTextToBuffer((pst) text, textlen, 0, false);
6842 		cw->changeMode = false;
6843 		if (!rc)
6844 			i_printf(MSG_BufferPreload, cx);
6845 	}
6846 	/* back to original context */
6847 	cxSwitch(svcx, false);
6848 	return cx;
6849 }				/* sideBuffer */
6850 
freeEmptySideBuffer(int n)6851 void freeEmptySideBuffer(int n)
6852 {
6853 	struct ebWindow *side;
6854 	if (!(side = sessionList[n].lw))
6855 		return;
6856 	if (side->f0.fileName)
6857 		return;
6858 	if (side->dol)
6859 		return;
6860 	if (side != sessionList[n].fw)
6861 		return;
6862 /* We could have added a line, then deleted it */
6863 	cxQuit(n, 3);
6864 }				/* freeEmptySideBuffer */
6865 
browseCurrentBuffer(void)6866 bool browseCurrentBuffer(void)
6867 {
6868 	char *rawbuf, *newbuf, *tbuf;
6869 	int rawsize, tlen, j;
6870 	bool rc, remote;
6871 	uchar sxfirst = 1;
6872 	bool save_ch = cw->changeMode;
6873 	uchar bmode = 0;
6874 	const struct MIMETYPE *mt = 0;
6875 
6876 	remote = isURL(cf->fileName);
6877 
6878 	if (!cf->render2 && cf->fileName) {
6879 		if (remote)
6880 			mt = findMimeByURL(cf->fileName, &sxfirst);
6881 		else
6882 			mt = findMimeByFile(cf->fileName);
6883 	}
6884 
6885 	if (mt && !mt->outtype) {
6886 		setError(MSG_NotConverter);
6887 		return false;
6888 	}
6889 
6890 	if (mt && mt->from_file) {
6891 		setError(MSG_PluginFile);
6892 		return false;
6893 	}
6894 
6895 	if (mt) {
6896 		if (cf->render1 && mt == cf->mt)
6897 			cf->render2 = true;
6898 		else
6899 			bmode = 3;
6900 	}
6901 
6902 	if (!bmode && cw->binMode) {
6903 		setError(MSG_BrowseBinary);
6904 		return false;
6905 	}
6906 
6907 	if (bmode) ;		// ok
6908 	else
6909 /* A mail message often contains lots of html tags,
6910  * so we need to check for email headers first. */
6911 	if (!remote && emailTest())
6912 		bmode = 1;
6913 	else if (htmlTest())
6914 		bmode = 2;
6915 	else {
6916 		setError(MSG_Unbrowsable);
6917 		return false;
6918 	}
6919 
6920 	if (!unfoldBuffer(context, false, &rawbuf, &rawsize))
6921 		return false;	/* should never happen */
6922 
6923 	if (bmode == 3) {
6924 /* convert raw text via a plugin */
6925 		if (remote)
6926 			rc = runPluginCommand(mt, cf->fileName, 0, rawbuf,
6927 					      rawsize, &rawbuf, &rawsize);
6928 		else
6929 			rc = runPluginCommand(mt, 0, cf->fileName, rawbuf,
6930 					      rawsize, &rawbuf, &rawsize);
6931 		if (!rc)
6932 			return false;
6933 		if (!cf->render1)
6934 			cf->render1b = true;
6935 		cf->render1 = cf->render2 = true;
6936 		iuReformat(rawbuf, rawsize, &tbuf, &tlen);
6937 		if (tbuf) {
6938 			nzFree(rawbuf);
6939 			rawbuf = tbuf;
6940 			rawsize = tlen;
6941 		}
6942 /* make it look like remote html, so we don't get a lot of errors printed */
6943 		remote = true;
6944 		bmode = (mt->outtype == 'h' ? 2 : 0);
6945 		if (!allowRedirection)
6946 			bmode = 0;
6947 	}
6948 
6949 /* this shouldn't do any harm if the output is text */
6950 	prepareForBrowse(rawbuf, rawsize);
6951 
6952 /* No harm in running this code in mail client, but no help either,
6953  * and it begs for bugs, so leave it out. */
6954 	if (!ismc) {
6955 		undoCompare();
6956 		cw->undoable = false;
6957 	}
6958 
6959 	if (bmode == 1) {
6960 		newbuf = emailParse(rawbuf);
6961 		j = strlen(newbuf);
6962 
6963 /* mail could need utf8 conversion, after qp decode */
6964 		iuReformat(newbuf, j, &tbuf, &tlen);
6965 		if (tbuf) {
6966 			nzFree(newbuf);
6967 			newbuf = tbuf;
6968 			j = tlen;
6969 		}
6970 
6971 		if (memEqualCI(newbuf, "<html>\n", 7) && allowRedirection) {
6972 /* double browse, mail then html */
6973 			bmode = 2;
6974 			remote = true;
6975 			rawbuf = newbuf;
6976 			rawsize = j;
6977 			prepareForBrowse(rawbuf, rawsize);
6978 		}
6979 	}
6980 
6981 	if (bmode == 2) {
6982 		if (javaOK(cf->fileName))
6983 			createJavaContext();
6984 		nzFree(newlocation);	/* should already be 0 */
6985 		newlocation = 0;
6986 		newbuf = htmlParse(rawbuf, remote);
6987 	}
6988 
6989 	if (bmode == 0)
6990 		newbuf = rawbuf;
6991 
6992 	cw->rnlMode = cw->nlMode;
6993 	cw->nlMode = false;
6994 /* I'm gonna assume it ain't binary no more */
6995 	cw->binMode = false;
6996 	cw->r_dot = cw->dot, cw->r_dol = cw->dol;
6997 	cw->dot = cw->dol = 0;
6998 	cw->r_map = cw->map;
6999 	cw->map = 0;
7000 	memcpy(cw->r_labels, cw->labels, sizeof(cw->labels));
7001 	memset(cw->labels, 0, sizeof(cw->labels));
7002 	j = strlen(newbuf);
7003 	rc = addTextToBuffer((pst) newbuf, j, 0, false);
7004 	free(newbuf);
7005 	cw->undoable = false;
7006 	cw->changeMode = save_ch;
7007 
7008 	if (cf->fileName) {
7009 		j = strlen(cf->fileName);
7010 		cf->fileName = reallocMem(cf->fileName, j + 8);
7011 		strcat(cf->fileName, ".browse");
7012 	}
7013 
7014 	if (!rc) {
7015 /* should never happen */
7016 		fileSize = -1;
7017 		cw->browseMode = true;
7018 		return false;
7019 	}
7020 
7021 	if (bmode == 2)
7022 		cw->dot = cw->dol;
7023 	cw->browseMode = true;
7024 	fileSize = apparentSize(context, true);
7025 	cw->mustrender = false;
7026 	time(&cw->nextrender);
7027 	cw->nextrender += 2;
7028 	return true;
7029 }				/* browseCurrentBuffer */
7030 
locateTagInBuffer(int tagno,int * ln_p,char ** p_p,char ** s_p,char ** t_p)7031 bool locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p)
7032 {
7033 	int ln, n;
7034 	char *p, *s, *t, c;
7035 	char search[20];
7036 	char searchend[4];
7037 
7038 	sprintf(search, "%c%d<", InternalCodeChar, tagno);
7039 	sprintf(searchend, "%c0>", InternalCodeChar);
7040 	n = strlen(search);
7041 	for (ln = 1; ln <= cw->dol; ++ln) {
7042 		p = (char *)fetchLine(ln, -1);
7043 		for (s = p; (c = *s) != '\n'; ++s) {
7044 			if (c != InternalCodeChar)
7045 				continue;
7046 			if (!strncmp(s, search, n))
7047 				break;
7048 		}
7049 		if (c == '\n')
7050 			continue;	/* not here, try next line */
7051 		s = strchr(s, '<') + 1;
7052 		t = strstr(s, searchend);
7053 		if (!t)
7054 			i_printfExit(MSG_NoClosingLine, ln);
7055 		*ln_p = ln;
7056 		*p_p = p;
7057 		*s_p = s;
7058 		*t_p = t;
7059 		return true;
7060 	}
7061 
7062 	return false;
7063 }				/* locateTagInBuffer */
7064 
getFieldFromBuffer(int tagno)7065 char *getFieldFromBuffer(int tagno)
7066 {
7067 	int ln;
7068 	char *p, *s, *t;
7069 	if (locateTagInBuffer(tagno, &ln, &p, &s, &t))
7070 		return pullString1(s, t);
7071 	/* line has been deleted, revert to the reset value */
7072 	return 0;
7073 }				/* getFieldFromBuffer */
7074 
fieldIsChecked(int tagno)7075 int fieldIsChecked(int tagno)
7076 {
7077 	int ln;
7078 	char *p, *s, *t;
7079 	if (locateTagInBuffer(tagno, &ln, &p, &s, &t))
7080 		return (*s == '+');
7081 	return -1;
7082 }				/* fieldIsChecked */
7083