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