~ubuntu-branches/ubuntu/karmic/edbrowse/karmic-security

« back to all changes in this revision

Viewing changes to src/buffers.c

  • Committer: Bazaar Package Importer
  • Author(s): Kapil Hari Paranjape
  • Date: 2008-04-09 18:55:23 UTC
  • mfrom: (1.1.4 upstream) (3.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080409185523-dqokcloumyn1ibn4
Tags: 3.3.4-1
* New upstream version (3.3.4).
 - Convert between iso8859-1 and utf-8 on the fly.
 - Support reading of pdf using pdftohtml.
 - French translation of html documentation.
 - Old html documentation renamed to usersguide.
 - Additional documentation on philosophy.
* debian/control:
 - Changed homepage to sourcefource site.
 - Moved homepage from description to its own field.
 - Added "poppler-utils | xpdf-utils" to Recommends.
 - Added "www-browser", "mail-reader" and "editor" to Provides. 
 - Removed "XS-" from Vcs-Svn tag.
 - Standards-Version: 3.7.3
* debian/docs: Added new documentation files
  from "doc/" subdirectory.
* debian/watch: Updated to use sourceforge site.
* debian/edbrowse.doc-base:
  - Changed name of upstream provided html documentation from
    "ebdoc.html" to "usersguide.html".
  - Changed section from "net" to "Network/Web Browsing".
* debian/install: Compiled binary is now in "src/".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* buffers.c
 
2
 * Text buffer support routines, manage text and edit sessions.
 
3
 * Copyright (c) Karl Dahlke, 2008
 
4
 * This file is part of the edbrowse project, released under GPL.
 
5
 */
 
6
 
 
7
#include "eb.h"
 
8
 
 
9
/* If this include file is missing, you need the pcre package,
 
10
 * and the pcre-devel package. */
 
11
#include <pcre.h>
 
12
 
 
13
/* Static variables for this file. */
 
14
 
 
15
/* The valid edbrowse commands. */
 
16
static const char valid_cmd[] = "aAbBcdDefghHijJklmMnpqrstuvwXz=^<";
 
17
/* Commands that can be done in browse mode. */
 
18
static const char browse_cmd[] = "AbBdDefghHijJklmMnpqsuvwXz=^<";
 
19
/* Commands for sql mode. */
 
20
static const char sql_cmd[] = "AadDefghHiklmnpqrsvwXz=^<";
 
21
/* Commands for directory mode. */
 
22
static const char dir_cmd[] = "AdDefghHklmnpqsvwXz=^<";
 
23
/* Commands that work at line number 0, in an empty file. */
 
24
static const char zero_cmd[] = "aAbefhHMqruw=^<";
 
25
/* Commands that expect a space afterward. */
 
26
static const char spaceplus_cmd[] = "befrw";
 
27
/* Commands that should have no text after them. */
 
28
static const char nofollow_cmd[] = "aAcdDhHjlmnptuX=";
 
29
/* Commands that can be done after a g// global directive. */
 
30
static const char global_cmd[] = "dDijJlmnpstX";
 
31
 
 
32
static struct ebWindow preWindow, undoWindow;
 
33
static int startRange, endRange;        /* as in 57,89p */
 
34
static int destLine;            /* as in 57,89m226 */
 
35
static int last_z = 1;
 
36
static char cmd, icmd, scmd;
 
37
static uchar subPrint;          /* print lines after substitutions */
 
38
static bool noStack;            /* don't stack up edit sessions */
 
39
static bool globSub;            /* in the midst of a g// command */
 
40
static bool inscript;           /* run from inside an edbrowse function */
 
41
static int lastq, lastqq;
 
42
static char icmd;               /* input command, usually the same as cmd */
 
43
 
 
44
 
 
45
/*********************************************************************
 
46
/* If a rendered line contains a hyperlink, the link is indicated
 
47
 * by a code that is stored inline.
 
48
 * If the hyperlink is number 17 on the list of hyperlinks for this window,
 
49
 * it is indicated by InternalCodeChar 17 { text }.
 
50
 * The "text" is what you see on the page, what you click on.
 
51
 * {Click here for more information}.
 
52
 * And the braces tell you it's a hyperlink.
 
53
 * That's just my convention.
 
54
 * The prior chars are for internal use only.
 
55
 * I'm assuming these chars don't/won't appear on the rendered page.
 
56
 * Yeah, sometimes nonascii chars appear, especially if the page is in
 
57
 * a European language, but I just assume a rendered page will not contain
 
58
 * the sequence: InternalCodeChar number {
 
59
 * In fact I assume the rendered text won't contain InternalCodeChar at all.
 
60
 * So I use this char to demark encoded constructs within the lines.
 
61
 * And why do I encode things right in the text?
 
62
 * Well it took me a few versions to reach this point.
 
63
 * But it makes so much sense!
 
64
 * If I move a line, the referenced hyperlink moves with it.
 
65
 * I don't have to update some other structure that says,
 
66
 * "At line 73, characters 29 through 47, that's hyperlink 17.
 
67
 * I use to do it that way, and wow, what a lot of overhead
 
68
 * when you move lines about, or delete them, or make substitutions.
 
69
 * Yes, you really need to change rendered html text,
 
70
 * because that's how you fill out forms.
 
71
 * Add just one letter to the first name in your fill out form,
 
72
 * and the hyperlink that comes later on in the line shifts down.
 
73
 * I use to go back and update the pointers,
 
74
 * so that the hyperlink started at offset 30, rather than 29.
 
75
 * That was a lot of work, and very error prone.
 
76
 * Finally I got smart, and coded the html tags inline.
 
77
 * They can't get lost as text is modified.  They move with the text.
 
78
 *
 
79
 * So now, certain sequences in the text are for internal use only.
 
80
 * This routine strips out these sequences, for display.
 
81
 * After all, you don't want to see those code characters.
 
82
 * You just want to see {Click here for more information}. */
 
83
 
 
84
static void
 
85
removeHiddenNumbers(pst p)
 
86
{
 
87
    pst s, t, u;
 
88
    uchar c, d;
 
89
 
 
90
    s = t = p;
 
91
    while((c = *s) != '\n') {
 
92
        if(c != InternalCodeChar) {
 
93
          addchar:
 
94
            *t++ = c;
 
95
            ++s;
 
96
            continue;
 
97
        }
 
98
        u = s + 1;
 
99
        d = *u;
 
100
        if(!isdigitByte(d))
 
101
            goto addchar;
 
102
        do {
 
103
            d = *++u;
 
104
        } while(isdigitByte(d));
 
105
        if(d == '*') {
 
106
            s = u + 1;
 
107
            continue;
 
108
        }
 
109
        if(strchr("<>{}", d)) {
 
110
            s = u;
 
111
            continue;
 
112
        }
 
113
/* This is not a code sequence I recognize. */
 
114
/* This should never happen; just move along. */
 
115
        goto addchar;
 
116
    }                           /* loop over p */
 
117
    *t = c;                     /* terminating newline */
 
118
}                               /* removeHiddenNumbers */
 
119
 
 
120
/* Fetch line n from the current buffer, or perhaps another buffer.
 
121
 * This returns an allocated copy of the string,
 
122
 * and you need to free it when you're done.
 
123
 * Good grief, how inefficient!
 
124
 * I know, but perl use to do it, and I never noticed the slowdown.
 
125
 * This is not the Linux kernel, we don't need to be ultra-efficient.
 
126
 * We just need to be consistent in our programming efforts.
 
127
 * Sometimes the returned line is changed,
 
128
 * and if it happens sometimes, we may as well make the new copy
 
129
 * all the time, even if the line doesn't change, to be consistent.
 
130
 * You can supress the copy feature with -1. */
 
131
 
 
132
static pst
 
133
fetchLineContext(int n, int show, int cx)
 
134
{
 
135
    struct ebWindow *lw = sessionList[cx].lw;
 
136
    char *map, *t;
 
137
    int dol, idx;
 
138
    unsigned len;
 
139
    pst p;                      /* the resulting copy of the string */
 
140
 
 
141
    if(!lw)
 
142
        i_printfExit(MSG_InvalidSession, cx);
 
143
    map = lw->map;
 
144
    dol = lw->dol;
 
145
    if(n <= 0 || n > dol)
 
146
        i_printfExit(MSG_InvalidLineNb, n);
 
147
 
 
148
    t = map + LNWIDTH * n;
 
149
    idx = atoi(t);
 
150
    if(!textLines[idx])
 
151
        i_printfExit(MSG_LineNull, n, idx);
 
152
    if(show < 0)
 
153
        return textLines[idx];
 
154
    p = clonePstring(textLines[idx]);
 
155
    if(show && lw->browseMode)
 
156
        removeHiddenNumbers(p);
 
157
    return p;
 
158
}                               /* fetchLineContext */
 
159
 
 
160
pst
 
161
fetchLine(int n, int show)
 
162
{
 
163
    return fetchLineContext(n, show, context);
 
164
}                               /* fetchLine */
 
165
 
 
166
static int
 
167
apparentSize(int cx, bool browsing)
 
168
{
 
169
    char c;
 
170
    int i, ln, size;
 
171
    struct ebWindow *w;
 
172
    if(cx <= 0 || cx >= MAXSESSION || (w = sessionList[cx].lw) == 0) {
 
173
        setError(MSG_SessionInactive, cx);
 
174
        return -1;
 
175
    }
 
176
    size = 0;
 
177
    for(ln = 1; ln <= w->dol; ++ln) {
 
178
        if(browsing && sessionList[cx].lw->browseMode) {
 
179
            pst line = fetchLineContext(ln, 1, cx);
 
180
            size += pstLength(line);
 
181
            free(line);
 
182
        } else {
 
183
            pst line = fetchLineContext(ln, -1, cx);
 
184
            size += pstLength(line);
 
185
        }
 
186
    }                           /* loop over lines */
 
187
    if(sessionList[cx].lw->nlMode)
 
188
        --size;
 
189
    return size;
 
190
}                               /* apparentSize */
 
191
 
 
192
int
 
193
currentBufferSize(void)
 
194
{
 
195
    return apparentSize(context, cw->browseMode);
 
196
}                               /* currentBufferSize */
 
197
 
 
198
/* get the directory suffix for a file.
 
199
 * This only makes sense in directory mode. */
 
200
static char *
 
201
dirSuffixContext(int n, int cx)
 
202
{
 
203
    static char suffix[4];
 
204
    struct ebWindow *lw = sessionList[cx].lw;
 
205
    suffix[0] = 0;
 
206
    if(lw->dirMode) {
 
207
        char *s = lw->map + LNWIDTH * n + 8;
 
208
        suffix[0] = s[0];
 
209
        if(suffix[0] == ' ')
 
210
            suffix[0] = 0;
 
211
        suffix[1] = s[1];
 
212
        if(suffix[1] == ' ')
 
213
            suffix[1] = 0;
 
214
        suffix[2] = 0;
 
215
    }
 
216
    return suffix;
 
217
}                               /* dirSuffixContext */
 
218
 
 
219
static char *
 
220
dirSuffix(int n)
 
221
{
 
222
    return dirSuffixContext(n, context);
 
223
}                               /* dirSuffix */
 
224
 
 
225
/* Display a line to the screen, but no more than 500 chars. */
 
226
void
 
227
displayLine(int n)
 
228
{
 
229
    pst line = fetchLine(n, 1);
 
230
    pst s = line;
 
231
    int cnt = 0;
 
232
    uchar c;
 
233
 
 
234
    if(cmd == 'n')
 
235
        printf("%d ", n);
 
236
    if(endMarks == 2 || endMarks && cmd == 'l')
 
237
        printf("^");
 
238
 
 
239
    while((c = *s++) != '\n') {
 
240
        bool expand = false;
 
241
        if(c == 0 || c == '\r' || c == '\x1b')
 
242
            expand = true;
 
243
        if(cmd == 'l') {
 
244
/* show tabs and backspaces, ed style */
 
245
            if(c == '\b')
 
246
                c = '<';
 
247
            if(c == '\t')
 
248
                c = '>';
 
249
            if(c < ' ' || c == 0x7f || c >= 0x80 && listNA)
 
250
                expand = true;
 
251
        }                       /* list */
 
252
        if(expand)
 
253
            printf("~%02X", c), cnt += 3;
 
254
        else
 
255
            printf("%c", c), ++cnt;
 
256
        if(cnt >= 500)
 
257
            break;
 
258
    }                           /* loop over line */
 
259
 
 
260
    if(cnt >= 500)
 
261
        printf("...");
 
262
    printf("%s", dirSuffix(n));
 
263
    if(endMarks == 2 || endMarks && cmd == 'l')
 
264
        printf("$");
 
265
    nl();
 
266
 
 
267
    free(line);
 
268
}                               /* displayLine */
 
269
 
 
270
static void
 
271
printDot(void)
 
272
{
 
273
    if(cw->dot)
 
274
        displayLine(cw->dot);
 
275
    else
 
276
        i_puts(MSG_Empty);
 
277
}                               /* printDot */
 
278
 
 
279
/* Get a line from standard in.  Need not be a terminal.
 
280
 * Each input line is limited to 255 chars.
 
281
 * On Unix cooked mode, that's as long as a line can be anyways.
 
282
 * This routine returns the line in a static string.
 
283
 * If you want to keep it, better make a copy.
 
284
 * ~xx is converted from hex, a way to enter nonascii chars.
 
285
 * This is the opposite of displayLine() above.
 
286
 * But you can't enter a newline this way; I won't permit it.
 
287
 * The only newline is the one corresponding to the end of your text,
 
288
 * when you hit enter. This terminates the line.
 
289
 * As we described earlier, this is a perl string.
 
290
 * It may contain nulls, and is terminated by newline.
 
291
 * The program exits on EOF.
 
292
 * If you hit interrupt at this point, I print a message
 
293
 * and ask for your line again. */
 
294
 
 
295
pst
 
296
inputLine(void)
 
297
{
 
298
    static uchar line[MAXTTYLINE];
 
299
    int i, j;
 
300
    uchar c, d, e;
 
301
 
 
302
  top:
 
303
    intFlag = false;
 
304
    inInput = true;
 
305
    if(!fgets((char *)line, sizeof (line), stdin)) {
 
306
        if(intFlag)
 
307
            goto top;
 
308
        i_puts(MSG_EOF);
 
309
        ebClose(1);
 
310
    }
 
311
    inInput = false;
 
312
    intFlag = false;
 
313
 
 
314
    i = j = 0;
 
315
    while(i < sizeof (line) - 1 && (c = line[i]) != '\n') {
 
316
/* A bug in my keyboard causes nulls to be entered from time to time. */
 
317
        if(c == 0)
 
318
            c = ' ';
 
319
        if(c != '~') {
 
320
          addchar:
 
321
            line[j++] = c;
 
322
            ++i;
 
323
            continue;
 
324
        }
 
325
        d = line[i + 1];
 
326
        if(d == '~') {
 
327
            ++i;
 
328
            goto addchar;
 
329
        }
 
330
        if(!isxdigit(d))
 
331
            goto addchar;
 
332
        e = line[i + 2];
 
333
        if(!isxdigit(e))
 
334
            goto addchar;
 
335
        c = fromHex(d, e);
 
336
        if(c == '\n')
 
337
            c = 0;
 
338
        i += 2;
 
339
        goto addchar;
 
340
    }                           /* loop over input chars */
 
341
    line[j] = '\n';
 
342
    return line;
 
343
}                               /* inputLine */
 
344
 
 
345
static struct {
 
346
    char lhs[MAXRE], rhs[MAXRE];
 
347
    bool lhs_yes, rhs_yes;
 
348
} globalSubs;
 
349
 
 
350
static void
 
351
saveSubstitutionStrings(void)
 
352
{
 
353
    if(!searchStringsAll)
 
354
        return;
 
355
    if(!cw)
 
356
        return;
 
357
    globalSubs.lhs_yes = cw->lhs_yes;
 
358
    strcpy(globalSubs.lhs, cw->lhs);
 
359
    globalSubs.rhs_yes = cw->rhs_yes;
 
360
    strcpy(globalSubs.rhs, cw->rhs);
 
361
}                               /* saveSubstitutionStrings */
 
362
 
 
363
static void
 
364
restoreSubstitutionStrings(struct ebWindow *nw)
 
365
{
 
366
    if(!searchStringsAll)
 
367
        return;
 
368
    if(!nw)
 
369
        return;
 
370
    nw->lhs_yes = globalSubs.lhs_yes;
 
371
    strcpy(nw->lhs, globalSubs.lhs);
 
372
    nw->rhs_yes = globalSubs.rhs_yes;
 
373
    strcpy(nw->rhs, globalSubs.rhs);
 
374
}                               /* restoreSubstitutionStrings */
 
375
 
 
376
/* Create a new window, with default variables. */
 
377
static struct ebWindow *
 
378
createWindow(void)
 
379
{
 
380
    struct ebWindow *nw;        /* the new window */
 
381
    nw = allocZeroMem(sizeof (struct ebWindow));
 
382
    saveSubstitutionStrings();
 
383
    restoreSubstitutionStrings(nw);
 
384
    return nw;
 
385
}                               /* createWindow */
 
386
 
 
387
static void
 
388
freeWindowLines(char *map)
 
389
{
 
390
    char *t;
 
391
    int cnt = 0;
 
392
    if(map) {
 
393
        for(t = map + LNWIDTH; *t; t += LNWIDTH) {
 
394
            int ln = atoi(t);
 
395
            if(textLines[ln]) {
 
396
                free(textLines[ln]);
 
397
                ++cnt;
 
398
                textLines[ln] = 0;
 
399
            }
 
400
        }
 
401
        free(map);
 
402
    }
 
403
    debugPrint(6, "freeWindowLines = %d", cnt);
 
404
}                               /* freeWindowLines */
 
405
 
 
406
/* quick sort compare */
 
407
static int
 
408
qscmp(const void *s, const void *t)
 
409
{
 
410
    return memcmp(s, t, 6);
 
411
}                               /* qscmp */
 
412
 
 
413
/* Free any lines not used by the snapshot of the current session. */
 
414
void
 
415
freeUndoLines(const char *cmap)
 
416
{
 
417
    char *map = undoWindow.map;
 
418
    char *cmap2;
 
419
    char *s, *t;
 
420
    int diff, ln, cnt = 0;
 
421
 
 
422
    if(!map) {
 
423
        debugPrint(6, "freeUndoLines = null");
 
424
        return;
 
425
    }
 
426
 
 
427
    if(!cmap) {
 
428
        debugPrint(6, "freeUndoLines = win");
 
429
        freeWindowLines(map);
 
430
        undoWindow.map = 0;
 
431
        return;
 
432
    }
 
433
 
 
434
/* sort both arrays, run comm, and find out which lines are not needed any more,
 
435
then free them.
 
436
 * Use quick sort; some files are 100,000 lines long.
 
437
 * I have to copy the second array, so I can sort it.
 
438
 * The first I can sort in place, cause it's going to get thrown away. */
 
439
 
 
440
    cmap2 = cloneString(cmap);
 
441
    qsort(map + LNWIDTH, (strlen(map) / LNWIDTH) - 1, LNWIDTH, qscmp);
 
442
    qsort(cmap2 + LNWIDTH, (strlen(cmap2) / LNWIDTH) - 1, LNWIDTH, qscmp);
 
443
 
 
444
    s = map + LNWIDTH;
 
445
    t = cmap2 + LNWIDTH;
 
446
    while(*s && *t) {
 
447
        diff = memcmp(s, t, 6);
 
448
        if(!diff) {
 
449
            s += LNWIDTH;
 
450
            t += LNWIDTH;
 
451
            continue;
 
452
        }
 
453
        if(diff > 0) {
 
454
            t += LNWIDTH;
 
455
            continue;
 
456
        }
 
457
/* This line isn't being used any more. */
 
458
        ln = atoi(s);
 
459
        if(textLines[ln]) {
 
460
            free(textLines[ln]);
 
461
            textLines[ln] = 0;
 
462
            ++cnt;
 
463
        }
 
464
        s += LNWIDTH;
 
465
    }
 
466
 
 
467
    while(*s) {
 
468
        ln = atoi(s);
 
469
        if(textLines[ln]) {
 
470
            free(textLines[ln]);
 
471
            textLines[ln] = 0;
 
472
            ++cnt;
 
473
        }
 
474
        s += LNWIDTH;
 
475
    }
 
476
 
 
477
    free(cmap2);
 
478
    free(map);
 
479
    undoWindow.map = 0;
 
480
    debugPrint(6, "freeUndoLines = %d", cnt);
 
481
}                               /* freeUndoLines */
 
482
 
 
483
static void
 
484
freeWindow(struct ebWindow *w)
 
485
{
 
486
/* The next few are designed to do nothing if not in browseMode */
 
487
    freeJavaContext(w->jsc);
 
488
    freeWindowLines(w->r_map);
 
489
    nzFree(w->dw);
 
490
    nzFree(w->ft);
 
491
    nzFree(w->fd);
 
492
    nzFree(w->fk);
 
493
    nzFree(w->mailInfo);
 
494
    freeTags(w->tags);
 
495
    freeWindowLines(w->map);
 
496
    nzFree(w->fileName);
 
497
    nzFree(w->referrer);
 
498
    nzFree(w->baseDirName);
 
499
    free(w);
 
500
}                               /* freeWindow */
 
501
 
 
502
/*********************************************************************
 
503
Here are a few routines to switch contexts from one buffer to another.
 
504
This is how the user edits multiple sessions, or browses multiple
 
505
web pages, simultaneously.
 
506
*********************************************************************/
 
507
 
 
508
bool
 
509
cxCompare(int cx)
 
510
{
 
511
    if(cx == 0) {
 
512
        setError(MSG_Session0);
 
513
        return false;
 
514
    }
 
515
    if(cx >= MAXSESSION) {
 
516
        setError(MSG_SessionHigh, cx, MAXSESSION - 1);
 
517
        return false;
 
518
    }
 
519
    if(cx != context)
 
520
        return true;            /* ok */
 
521
    setError(MSG_SessionCurrent, cx);
 
522
    return false;
 
523
}                               /*cxCompare */
 
524
 
 
525
/* is a context active? */
 
526
bool
 
527
cxActive(int cx)
 
528
{
 
529
    if(cx <= 0 || cx >= MAXSESSION)
 
530
        i_printfExit(MSG_SessionOutRange, cx);
 
531
    if(sessionList[cx].lw)
 
532
        return true;
 
533
    setError(MSG_SessionInactive, cx);
 
534
    return false;
 
535
}                               /* cxActive */
 
536
 
 
537
static void
 
538
cxInit(int cx)
 
539
{
 
540
    struct ebWindow *lw = createWindow();
 
541
    if(sessionList[cx].lw)
 
542
        i_printfExit(MSG_DoubleInit, cx);
 
543
    sessionList[cx].fw = sessionList[cx].lw = lw;
 
544
}                               /* cxInit */
 
545
 
 
546
bool
 
547
cxQuit(int cx, int action)
 
548
{
 
549
    struct ebWindow *w = sessionList[cx].lw;
 
550
    if(!w)
 
551
        i_printfExit(MSG_QuitNoActive, cx);
 
552
 
 
553
/* We might be trashing data, make sure that's ok. */
 
554
    if(w->changeMode &&
 
555
       !(w->dirMode | w->sqlMode) && lastq != cx && w->fileName &&
 
556
       !isURL(w->fileName)) {
 
557
        lastqq = cx;
 
558
        setError(MSG_ExpectW);
 
559
        if(cx != context)
 
560
            setError(MSG_ExpectWX, cx);
 
561
        return false;
 
562
    }
 
563
 
 
564
    if(!action)
 
565
        return true;            /* just a test */
 
566
 
 
567
    if(cx == context) {
 
568
/* Don't need to retain the undo lines. */
 
569
        freeWindowLines(undoWindow.map);
 
570
        undoWindow.map = 0;
 
571
        nzFree(preWindow.map);
 
572
        preWindow.map = 0;
 
573
    }
 
574
 
 
575
    if(action == 2) {
 
576
        while(w) {
 
577
            struct ebWindow *p = w->prev;
 
578
            freeWindow(w);
 
579
            w = p;
 
580
        }
 
581
        sessionList[cx].fw = sessionList[cx].lw = 0;
 
582
    } else
 
583
        freeWindow(w);
 
584
 
 
585
    if(cx == context)
 
586
        cw = 0;
 
587
 
 
588
    return true;
 
589
}                               /* cxQuit */
 
590
 
 
591
/* Switch to another edit session.
 
592
 * This assumes cxCompare has succeeded - we're moving to a different context.
 
593
 * Pass the context number and an interactive flag. */
 
594
void
 
595
cxSwitch(int cx, bool interactive)
 
596
{
 
597
    bool created = false;
 
598
    struct ebWindow *nw = sessionList[cx].lw;   /* the new window */
 
599
    if(!nw) {
 
600
        cxInit(cx);
 
601
        nw = sessionList[cx].lw;
 
602
        created = true;
 
603
    } else {
 
604
        saveSubstitutionStrings();
 
605
        restoreSubstitutionStrings(nw);
 
606
    }
 
607
 
 
608
    if(cw) {
 
609
        freeUndoLines(cw->map);
 
610
        cw->firstOpMode = false;
 
611
    }
 
612
    cw = nw;
 
613
    cs = sessionList + cx;
 
614
    context = cx;
 
615
    if(interactive && debugLevel) {
 
616
        if(created)
 
617
            i_puts(MSG_SessionNew);
 
618
        else if(cw->fileName)
 
619
            puts(cw->fileName);
 
620
        else
 
621
            i_puts(MSG_NoFile);
 
622
    }
 
623
 
 
624
/* The next line is required when this function is called from main(),
 
625
 * when the first arg is a url and there is a second arg. */
 
626
    startRange = endRange = cw->dot;
 
627
}                               /* cxSwitch */
 
628
 
 
629
/* Make sure we have room to store another n lines. */
 
630
 
 
631
void
 
632
linesReset(void)
 
633
{
 
634
    int j;
 
635
    if(!textLines)
 
636
        return;
 
637
    for(j = 0; j < textLinesCount; ++j)
 
638
        nzFree(textLines[j]);
 
639
    nzFree(textLines);
 
640
    textLines = 0;
 
641
    textLinesCount = textLinesMax = 0;
 
642
}                               /* linesReset */
 
643
 
 
644
bool
 
645
linesComing(int n)
 
646
{
 
647
    int need = textLinesCount + n;
 
648
    if(need > LNMAX) {
 
649
        setError(MSG_LineLimit);
 
650
        return false;
 
651
    }
 
652
    if(need > textLinesMax) {
 
653
        int newmax = textLinesMax * 3 / 2;
 
654
        if(need > newmax)
 
655
            newmax = need + 8192;
 
656
        if(newmax > LNMAX)
 
657
            newmax = LNMAX;
 
658
        if(textLinesMax) {
 
659
            debugPrint(4, "textLines realloc %d", newmax);
 
660
            textLines = reallocMem(textLines, newmax * sizeof (pst));
 
661
        } else {
 
662
            textLines = allocMem(newmax * sizeof (pst));
 
663
        }
 
664
        textLinesMax = newmax;
 
665
    }
 
666
    /* overflow requires realloc */
 
667
    /* We now have room for n new lines, but you have to add n
 
668
     * to textLines Count, once you have brought in the lines. */
 
669
    return true;
 
670
}                               /* linesComing */
 
671
 
 
672
/* This function is called for web redirection, by the refresh command,
 
673
 * or by window.location = new_url. */
 
674
static char *newlocation;
 
675
static int newloc_d;            /* possible delay */
 
676
static bool newloc_rf;          /* refresh the buffer */
 
677
bool js_redirects;
 
678
 
 
679
void
 
680
gotoLocation(char *url, int delay, bool rf)
 
681
{
 
682
    if(newlocation && delay >= newloc_d) {
 
683
        nzFree(url);
 
684
        return;
 
685
    }
 
686
    nzFree(newlocation);
 
687
    newlocation = url;
 
688
    newloc_d = delay;
 
689
    newloc_rf = rf;
 
690
    if(!delay)
 
691
        js_redirects = true;
 
692
}                               /* gotoLocation */
 
693
 
 
694
/* Adjust the map of line numbers -- we have inserted text.
 
695
 * Also shift the downstream labels.
 
696
 * Pass the string containing the new line numbers, and the dest line number. */
 
697
static void
 
698
addToMap(int start, int end, int destl)
 
699
{
 
700
    char *newmap;
 
701
    int i, j;
 
702
    int nlines = end - start;
 
703
    if(nlines == 0)
 
704
        i_printfExit(MSG_EmptyPiece);
 
705
    for(i = 0; i < 26; ++i) {
 
706
        int ln = cw->labels[i];
 
707
        if(ln <= destl)
 
708
            continue;
 
709
        cw->labels[i] += nlines;
 
710
    }
 
711
    cw->dot = destl + nlines;
 
712
    cw->dol += nlines;
 
713
    newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
 
714
    if(cw->map)
 
715
        strcpy(newmap, cw->map);
 
716
    else
 
717
        strcpy(newmap, LNSPACE);
 
718
    i = j = (destl + 1) * LNWIDTH;      /* insert new piece here */
 
719
    while(start < end) {
 
720
        sprintf(newmap + i, LNFORMAT, start);
 
721
        ++start;
 
722
        i += LNWIDTH;
 
723
    }
 
724
    if(cw->map)
 
725
        strcat(newmap, cw->map + j);
 
726
    nzFree(cw->map);
 
727
    cw->map = newmap;
 
728
    cw->firstOpMode = undoable = true;
 
729
    if(!cw->browseMode)
 
730
        cw->changeMode = true;
 
731
}                               /* addToMap */
 
732
 
 
733
/* Add a block of text into the buffer; uses addToMap(). */
 
734
bool
 
735
addTextToBuffer(const pst inbuf, int length, int destl)
 
736
{
 
737
    int i, j, linecount = 0;
 
738
    int start, end;
 
739
    for(i = 0; i < length; ++i)
 
740
        if(inbuf[i] == '\n')
 
741
            ++linecount;
 
742
    if(!linesComing(linecount + 1))
 
743
        return false;
 
744
    if(destl == cw->dol)
 
745
        cw->nlMode = false;
 
746
    if(inbuf[length - 1] != '\n') {
 
747
/* doesn't end in newline */
 
748
        ++linecount;            /* last line wasn't counted */
 
749
        if(destl == cw->dol) {
 
750
            cw->nlMode = true;
 
751
            if(cmd != 'b' && !cw->binMode && !ismc)
 
752
                i_puts(MSG_NoTrailing);
 
753
        }
 
754
    }                           /* missing newline */
 
755
    start = end = textLinesCount;
 
756
    i = 0;
 
757
    while(i < length) {         /* another line */
 
758
        j = i;
 
759
        while(i < length)
 
760
            if(inbuf[i++] == '\n')
 
761
                break;
 
762
        if(inbuf[i - 1] == '\n') {
 
763
/* normal line */
 
764
            textLines[end] = allocMem(i - j);
 
765
        } else {
 
766
/* last line with no nl */
 
767
            textLines[end] = allocMem(i - j + 1);
 
768
            textLines[end][i - j] = '\n';
 
769
        }
 
770
        memcpy(textLines[end], inbuf + j, i - j);
 
771
        ++end;
 
772
    }                           /* loop breaking inbuf into lines */
 
773
    textLinesCount = end;
 
774
    addToMap(start, end, destl);
 
775
    return true;
 
776
}                               /* addTextToBuffer */
 
777
 
 
778
/* Pass input lines straight into the buffer, until the user enters . */
 
779
 
 
780
static bool
 
781
inputLinesIntoBuffer(void)
 
782
{
 
783
    int start = textLinesCount;
 
784
    int end = start;
 
785
    pst line;
 
786
    if(linePending[0])
 
787
        line = linePending;
 
788
    else
 
789
        line = inputLine();
 
790
    while(line[0] != '.' || line[1] != '\n') {
 
791
        if(!linesComing(1))
 
792
            return false;
 
793
        textLines[end++] = clonePstring(line);
 
794
        line = inputLine();
 
795
    }
 
796
    if(end == start) {          /* no lines entered */
 
797
        cw->dot = endRange;
 
798
        if(!cw->dot && cw->dol)
 
799
            cw->dot = 1;
 
800
        return true;
 
801
    }
 
802
    if(endRange == cw->dol)
 
803
        cw->nlMode = false;
 
804
    textLinesCount = end;
 
805
    addToMap(start, end, endRange);
 
806
    return true;
 
807
}                               /* inputLinesIntoBuffer */
 
808
 
 
809
/* Delete a block of text. */
 
810
 
 
811
void
 
812
delText(int start, int end)
 
813
{
 
814
    int i, j;
 
815
    if(end == cw->dol)
 
816
        cw->nlMode = false;
 
817
    j = end - start + 1;
 
818
    strcpy(cw->map + start * LNWIDTH, cw->map + (end + 1) * LNWIDTH);
 
819
/* move the labels */
 
820
    for(i = 0; i < 26; ++i) {
 
821
        int ln = cw->labels[i];
 
822
        if(ln < start)
 
823
            continue;
 
824
        if(ln <= end) {
 
825
            cw->labels[i] = 0;
 
826
            continue;
 
827
        }
 
828
        cw->labels[i] -= j;
 
829
    }
 
830
    cw->dol -= j;
 
831
    cw->dot = start;
 
832
    if(cw->dot > cw->dol)
 
833
        cw->dot = cw->dol;
 
834
/* by convention an empty buffer has no map */
 
835
    if(!cw->dol) {
 
836
        free(cw->map);
 
837
        cw->map = 0;
 
838
    }
 
839
    cw->firstOpMode = undoable = true;
 
840
    if(!cw->browseMode)
 
841
        cw->changeMode = true;
 
842
}                               /* delText */
 
843
 
 
844
/* Delete files from a directory as you delete lines.
 
845
 * Set dw to move them to your recycle bin.
 
846
 * Set dx to delete them outright. */
 
847
 
 
848
static char *
 
849
makeAbsPath(char *f)
 
850
{
 
851
    static char path[ABSPATH];
 
852
    if(strlen(cw->baseDirName) + strlen(f) > ABSPATH - 2) {
 
853
        setError(MSG_PathNameLong, ABSPATH);
 
854
        return 0;
 
855
    }
 
856
    sprintf(path, "%s/%s", cw->baseDirName, f);
 
857
    return path;
 
858
}                               /* makeAbsPath */
 
859
 
 
860
static bool
 
861
delFiles(void)
 
862
{
 
863
    int ln, cnt;
 
864
 
 
865
    if(!dirWrite) {
 
866
        setError(MSG_DirNoWrite);
 
867
        return false;
 
868
    }
 
869
 
 
870
    if(dirWrite == 1 && !recycleBin) {
 
871
        setError(MSG_NoRecycle);
 
872
        return false;
 
873
    }
 
874
 
 
875
    ln = startRange;
 
876
    cnt = endRange - startRange + 1;
 
877
    while(cnt--) {
 
878
        char *file, *t, *path, *ftype;
 
879
        file = (char *)fetchLine(ln, 0);
 
880
        t = strchr(file, '\n');
 
881
        if(!t)
 
882
            i_printfExit(MSG_NoNlOnDir, file);
 
883
        *t = 0;
 
884
        path = makeAbsPath(file);
 
885
        if(!path) {
 
886
            free(file);
 
887
            return false;
 
888
        }
 
889
 
 
890
        ftype = dirSuffix(ln);
 
891
        if(dirWrite == 2 && *ftype == '/') {
 
892
            setError(MSG_NoDirDelete);
 
893
            free(file);
 
894
            return false;
 
895
        }
 
896
 
 
897
        if(dirWrite == 2 || *ftype && strchr("@<*^|", *ftype)) {
 
898
          unlink:
 
899
            if(unlink(path)) {
 
900
                setError(MSG_NoRemove, file);
 
901
                free(file);
 
902
                return false;
 
903
            }
 
904
        } else {
 
905
            char bin[ABSPATH];
 
906
            sprintf(bin, "%s/%s", recycleBin, file);
 
907
            if(rename(path, bin)) {
 
908
                if(errno == EXDEV) {
 
909
                    char *rmbuf;
 
910
                    int rmlen;
 
911
                    if(*ftype == '/') {
 
912
                        setError(MSG_CopyMoveDir);
 
913
                        free(file);
 
914
                        return false;
 
915
                    }
 
916
                    if(!fileIntoMemory(path, &rmbuf, &rmlen)) {
 
917
                        free(file);
 
918
                        return false;
 
919
                    }
 
920
                    if(!memoryOutToFile(bin, rmbuf, rmlen,
 
921
                       MSG_TempNoCreate2, MSG_NoWrite2)) {
 
922
                        free(file);
 
923
                        nzFree(rmbuf);
 
924
                        return false;
 
925
                    }
 
926
                    nzFree(rmbuf);
 
927
                    goto unlink;
 
928
                }
 
929
 
 
930
                setError(MSG_NoMoveToTrash, file);
 
931
                free(file);
 
932
                return false;
 
933
            }
 
934
        }
 
935
 
 
936
        free(file);
 
937
        delText(ln, ln);
 
938
    }
 
939
 
 
940
    return true;
 
941
}                               /* delFiles */
 
942
 
 
943
/* Move or copy a block of text. */
 
944
/* Uses range variables, hence no parameters. */
 
945
static bool
 
946
moveCopy(void)
 
947
{
 
948
    int sr = startRange;
 
949
    int er = endRange + 1;
 
950
    int dl = destLine + 1;
 
951
    int i_sr = sr * LNWIDTH;    /* indexes into map */
 
952
    int i_er = er * LNWIDTH;
 
953
    int i_dl = dl * LNWIDTH;
 
954
    int n_lines = er - sr;
 
955
    char *map = cw->map;
 
956
    char *newmap;
 
957
    int lowcut, highcut, diff, i;
 
958
 
 
959
    if(dl > sr && dl < er) {
 
960
        setError(MSG_DestInBlock);
 
961
        return false;
 
962
    }
 
963
    if(cmd == 'm' && (dl == er || dl == sr)) {
 
964
        if(globSub)
 
965
            setError(MSG_NoChange);
 
966
        return false;
 
967
    }
 
968
 
 
969
    if(cmd == 't') {
 
970
        if(!linesComing(n_lines))
 
971
            return false;
 
972
        for(i = sr; i < er; ++i)
 
973
            textLines[textLinesCount++] = fetchLine(i, 0);
 
974
        addToMap(textLinesCount - n_lines, textLinesCount, destLine);
 
975
        return true;
 
976
    }
 
977
    /* copy */
 
978
    if(destLine == cw->dol || endRange == cw->dol)
 
979
        cw->nlMode = false;
 
980
/* All we really need do is rearrange the map. */
 
981
    newmap = allocMem((cw->dol + 1) * LNWIDTH + 1);
 
982
    strcpy(newmap, map);
 
983
    if(dl < sr) {
 
984
        memcpy(newmap + i_dl, map + i_sr, i_er - i_sr);
 
985
        memcpy(newmap + i_dl + i_er - i_sr, map + i_dl, i_sr - i_dl);
 
986
    } else {
 
987
        memcpy(newmap + i_sr, map + i_er, i_dl - i_er);
 
988
        memcpy(newmap + i_sr + i_dl - i_er, map + i_sr, i_er - i_sr);
 
989
    }
 
990
    free(cw->map);
 
991
    cw->map = newmap;
 
992
 
 
993
/* now for the labels */
 
994
    if(dl < sr) {
 
995
        lowcut = dl;
 
996
        highcut = er;
 
997
        diff = sr - dl;
 
998
    } else {
 
999
        lowcut = sr;
 
1000
        highcut = dl;
 
1001
        diff = dl - er;
 
1002
    }
 
1003
    for(i = 0; i < 26; ++i) {
 
1004
        int ln = cw->labels[i];
 
1005
        if(ln < lowcut)
 
1006
            continue;
 
1007
        if(ln >= highcut)
 
1008
            continue;
 
1009
        if(ln >= startRange && ln <= endRange) {
 
1010
            ln += (dl < sr ? -diff : diff);
 
1011
        } else {
 
1012
            ln += (dl < sr ? n_lines : -n_lines);
 
1013
        }
 
1014
        cw->labels[i] = ln;
 
1015
    }                           /* loop over labels */
 
1016
 
 
1017
    cw->dot = endRange;
 
1018
    cw->dot += (dl < sr ? -diff : diff);
 
1019
    cw->firstOpMode = undoable = true;
 
1020
    if(!cw->browseMode)
 
1021
        cw->changeMode = true;
 
1022
    return true;
 
1023
}                               /* moveCopy */
 
1024
 
 
1025
/* Join lines from startRange to endRange. */
 
1026
static bool
 
1027
joinText(void)
 
1028
{
 
1029
    int j, size;
 
1030
    pst newline, t;
 
1031
    if(startRange == endRange) {
 
1032
        setError(MSG_Join1);
 
1033
        return false;
 
1034
    }
 
1035
    if(!linesComing(1))
 
1036
        return false;
 
1037
    size = 0;
 
1038
    for(j = startRange; j <= endRange; ++j)
 
1039
        size += pstLength(fetchLine(j, -1));
 
1040
    t = newline = allocMem(size);
 
1041
    for(j = startRange; j <= endRange; ++j) {
 
1042
        pst p = fetchLine(j, -1);
 
1043
        size = pstLength(p);
 
1044
        memcpy(t, p, size);
 
1045
        t += size;
 
1046
        if(j < endRange) {
 
1047
            t[-1] = ' ';
 
1048
            if(cmd == 'j')
 
1049
                --t;
 
1050
        }
 
1051
    }
 
1052
    textLines[textLinesCount] = newline;
 
1053
    sprintf(cw->map + startRange * LNWIDTH, LNFORMAT, textLinesCount);
 
1054
    ++textLinesCount;
 
1055
    delText(startRange + 1, endRange);
 
1056
    cw->dot = startRange;
 
1057
    return true;
 
1058
}                               /* joinText */
 
1059
 
 
1060
/* Read a file, or url, into the current buffer.
 
1061
 * Post/get data is passed, via the second parameter, if it's a URL. */
 
1062
bool
 
1063
readFile(const char *filename, const char *post)
 
1064
{
 
1065
    char *rbuf;                 /* read buffer */
 
1066
    int readSize;               /* should agree with fileSize */
 
1067
    int fh;                     /* file handle */
 
1068
    bool rc;                    /* return code */
 
1069
    bool is8859, isutf8;
 
1070
    char *nopound;
 
1071
 
 
1072
    serverData = 0;
 
1073
    serverDataLen = 0;
 
1074
 
 
1075
    if(memEqualCI(filename, "file://", 7)) {
 
1076
        filename += 7;
 
1077
        if(!*filename) {
 
1078
            setError(MSG_MissingFileName);
 
1079
            return false;
 
1080
        }
 
1081
        goto fromdisk;
 
1082
    }
 
1083
 
 
1084
    if(isURL(filename)) {
 
1085
        const char *domain = getHostURL(filename);
 
1086
        if(!domain)
 
1087
            return false;       /* some kind of error */
 
1088
        if(!*domain) {
 
1089
            setError(MSG_DomainEmpty);
 
1090
            return false;
 
1091
        }
 
1092
 
 
1093
        rc = httpConnect(0, filename);
 
1094
 
 
1095
        if(!rc) {
 
1096
/* The error could have occured after redirection */
 
1097
            nzFree(changeFileName);
 
1098
            changeFileName = 0;
 
1099
            return false;
 
1100
        }
 
1101
 
 
1102
/* We got some data.  Any warnings along the way have been printed,
 
1103
 * like 404 file not found, but it's still worth continuing. */
 
1104
        rbuf = serverData;
 
1105
        fileSize = readSize = serverDataLen;
 
1106
 
 
1107
        if(fileSize == 0) {     /* empty file */
 
1108
            nzFree(rbuf);
 
1109
            cw->dot = endRange;
 
1110
            return true;
 
1111
        }
 
1112
 
 
1113
        goto gotdata;
 
1114
    }
 
1115
 
 
1116
    if(isSQL(filename)) {
 
1117
        const char *t1, *t2;
 
1118
        if(!cw->sqlMode) {
 
1119
            setError(MSG_DBOtherFile);
 
1120
            return false;
 
1121
        }
 
1122
        t1 = strchr(cw->fileName, ']');
 
1123
        t2 = strchr(filename, ']');
 
1124
        if(t1 - cw->fileName != t2 - filename ||
 
1125
           memcmp(cw->fileName, filename, t2 - filename)) {
 
1126
            setError(MSG_DBOtherTable);
 
1127
            return false;
 
1128
        }
 
1129
        rc = sqlReadRows(filename, &rbuf);
 
1130
        if(!rc) {
 
1131
            nzFree(rbuf);
 
1132
            if(!cw->dol && cmd != 'r') {
 
1133
                cw->sqlMode = false;
 
1134
                nzFree(cw->fileName);
 
1135
                cw->fileName = 0;
 
1136
            }
 
1137
            return false;
 
1138
        }
 
1139
        serverData = rbuf;
 
1140
        fileSize = strlen(rbuf);
 
1141
        if(rbuf == EMPTYSTRING)
 
1142
            return true;
 
1143
        goto intext;
 
1144
    }
 
1145
 
 
1146
  fromdisk:
 
1147
/* reading a file from disk */
 
1148
    fileSize = 0;
 
1149
    if(fileTypeByName(filename, false) == 'd') {
 
1150
/* directory scan */
 
1151
        int len, j, start, end;
 
1152
        cw->baseDirName = cloneString(filename);
 
1153
/* get rid of trailing slash */
 
1154
        len = strlen(cw->baseDirName);
 
1155
        if(len && cw->baseDirName[len - 1] == '/')
 
1156
            cw->baseDirName[len - 1] = 0;
 
1157
/* Understand that the empty string now means / */
 
1158
/* get the files, or fail if there is a problem */
 
1159
        if(!sortedDirList(filename, &start, &end))
 
1160
            return false;
 
1161
        if(!cw->dol) {
 
1162
            cw->dirMode = true;
 
1163
            i_puts(MSG_DirMode);
 
1164
        }
 
1165
        if(start == end) {      /* empty directory */
 
1166
            cw->dot = endRange;
 
1167
            fileSize = 0;
 
1168
            return true;
 
1169
        }
 
1170
 
 
1171
        addToMap(start, end, endRange);
 
1172
 
 
1173
/* change 0 to nl and count bytes */
 
1174
        fileSize = 0;
 
1175
        for(j = start; j < end; ++j) {
 
1176
            char *s, c, ftype;
 
1177
            pst t = textLines[j];
 
1178
            char *abspath = makeAbsPath((char *)t);
 
1179
            while(*t) {
 
1180
                if(*t == '\n')
 
1181
                    *t = '\t';
 
1182
                ++t;
 
1183
            }
 
1184
            *t = '\n';
 
1185
            len = t - textLines[j];
 
1186
            fileSize += len + 1;
 
1187
            if(!abspath)
 
1188
                continue;       /* should never happen */
 
1189
            ftype = fileTypeByName(abspath, true);
 
1190
            if(!ftype)
 
1191
                continue;
 
1192
            s = cw->map + (endRange + 1 + j - start) * LNWIDTH + 8;
 
1193
            if(isupperByte(ftype)) {    /* symbolic link */
 
1194
                if(!cw->dirMode)
 
1195
                    *t = '@', *++t = '\n';
 
1196
                else
 
1197
                    *s++ = '@';
 
1198
                ++fileSize;
 
1199
            }
 
1200
            ftype = tolower(ftype);
 
1201
            c = 0;
 
1202
            if(ftype == 'd')
 
1203
                c = '/';
 
1204
            if(ftype == 's')
 
1205
                c = '^';
 
1206
            if(ftype == 'c')
 
1207
                c = '<';
 
1208
            if(ftype == 'b')
 
1209
                c = '*';
 
1210
            if(ftype == 'p')
 
1211
                c = '|';
 
1212
            if(!c)
 
1213
                continue;
 
1214
            if(!cw->dirMode)
 
1215
                *t = c, *++t = '\n';
 
1216
            else
 
1217
                *s++ = c;
 
1218
            ++fileSize;
 
1219
        }                       /* loop fixing files in the directory scan */
 
1220
        return true;
 
1221
    }
 
1222
    /* reading a directory */
 
1223
    nopound = cloneString(filename);
 
1224
    rbuf = strchr(nopound, '#');
 
1225
    if(rbuf)
 
1226
        *rbuf = 0;
 
1227
    rc = fileIntoMemory(nopound, &rbuf, &fileSize);
 
1228
    nzFree(nopound);
 
1229
    if(!rc)
 
1230
        return false;
 
1231
    serverData = rbuf;
 
1232
    if(fileSize == 0) {         /* empty file */
 
1233
        free(rbuf);
 
1234
        cw->dot = endRange;
 
1235
        return true;
 
1236
    }
 
1237
    /* empty */
 
1238
  gotdata:
 
1239
 
 
1240
    if(!looksBinary(rbuf, fileSize)) {
 
1241
        char *tbuf;
 
1242
/* looks like text.  In DOS, we should have compressed crlf.
 
1243
 * Let's do that now. */
 
1244
#ifdef DOSLIKE
 
1245
        for(i = j = 0; i < fileSize - 1; ++i) {
 
1246
            char c = rbuf[i];
 
1247
            if(c == '\r' && rbuf[i + 1] == '\n')
 
1248
                continue;
 
1249
            rbuf[j++] = c;
 
1250
        }
 
1251
        fileSize = j;
 
1252
#endif
 
1253
        if(iuConvert) {
 
1254
/* Classify this incoming text as ascii or 8859 or utf8 */
 
1255
            looks_8859_utf8(rbuf, fileSize, &is8859, &isutf8);
 
1256
            debugPrint(3, "text type is %s",
 
1257
               (isutf8 ? "utf8" : (is8859 ? "8859" : "ascii")));
 
1258
            if(cons_utf8 && is8859) {
 
1259
                if(debugLevel >= 2 || debugLevel == 1 && !isURL(filename))
 
1260
                    i_puts(MSG_ConvUtf8);
 
1261
                iso2utf(rbuf, fileSize, &tbuf, &fileSize);
 
1262
                nzFree(rbuf);
 
1263
                rbuf = tbuf;
 
1264
            }
 
1265
            if(!cons_utf8 && isutf8) {
 
1266
                if(debugLevel >= 2 || debugLevel == 1 && !isURL(filename))
 
1267
                    i_puts(MSG_Conv8859);
 
1268
                utf2iso(rbuf, fileSize, &tbuf, &fileSize);
 
1269
                nzFree(rbuf);
 
1270
                rbuf = tbuf;
 
1271
            }
 
1272
            if(!cw->dol) {
 
1273
                if(isutf8) {
 
1274
                    cw->utf8Mode = true;
 
1275
                    debugPrint(3, "setting utf8 mode");
 
1276
                }
 
1277
                if(is8859) {
 
1278
                    cw->iso8859Mode = true;
 
1279
                    debugPrint(3, "setting 8859 mode");
 
1280
                }
 
1281
            }
 
1282
        }
 
1283
    } else if(binaryDetect & !cw->binMode) {
 
1284
        i_puts(MSG_BinaryData);
 
1285
        cw->binMode = true;
 
1286
    }
 
1287
 
 
1288
  intext:
 
1289
    rc = addTextToBuffer((const pst)rbuf, fileSize, endRange);
 
1290
    free(rbuf);
 
1291
    if(cw->sqlMode)
 
1292
        undoable = false;
 
1293
    return rc;
 
1294
}                               /* readFile */
 
1295
 
 
1296
/* Write a range to a file. */
 
1297
static bool
 
1298
writeFile(const char *name, int mode)
 
1299
{
 
1300
    int i, fh;
 
1301
 
 
1302
    if(memEqualCI(name, "file://", 7))
 
1303
        name += 7;
 
1304
 
 
1305
    if(!*name) {
 
1306
        setError(MSG_MissingFileName);
 
1307
        return false;
 
1308
    }
 
1309
 
 
1310
    if(isURL(name)) {
 
1311
        setError(MSG_NoWriteURL);
 
1312
        return false;
 
1313
    }
 
1314
 
 
1315
    if(isSQL(name)) {
 
1316
        setError(MSG_WriteDB);
 
1317
        return false;
 
1318
    }
 
1319
 
 
1320
    if(!cw->dol) {
 
1321
        setError(MSG_WriteEmpty);
 
1322
        return false;
 
1323
    }
 
1324
 
 
1325
/* mode should be TRUNC or APPEND */
 
1326
    mode |= O_WRONLY | O_CREAT;
 
1327
    if(cw->binMode)
 
1328
        mode |= O_BINARY;
 
1329
 
 
1330
    fh = open(name, mode, 0666);
 
1331
    if(fh < 0) {
 
1332
        setError(MSG_NoCreate2, name);
 
1333
        return false;
 
1334
    }
 
1335
 
 
1336
    if(name == cw->fileName && iuConvert) {
 
1337
/* should we locale convert back? */
 
1338
        if(cw->iso8859Mode && cons_utf8)
 
1339
            if(debugLevel >= 1)
 
1340
                i_puts(MSG_Conv8859);
 
1341
        if(cw->utf8Mode && !cons_utf8)
 
1342
            if(debugLevel >= 1)
 
1343
                i_puts(MSG_ConvUtf8);
 
1344
    }
 
1345
 
 
1346
    fileSize = 0;
 
1347
    for(i = startRange; i <= endRange; ++i) {
 
1348
        pst p = fetchLine(i, (cw->browseMode ? 1 : -1));
 
1349
        int len = pstLength(p);
 
1350
        char *suf = dirSuffix(i);
 
1351
        char *tp;
 
1352
        int tlen;
 
1353
        bool alloc_p = cw->browseMode;
 
1354
        bool rc = true;
 
1355
 
 
1356
        if(!suf[0]) {
 
1357
            if(i == cw->dol && cw->nlMode)
 
1358
                --len;
 
1359
 
 
1360
            if(name == cw->fileName && iuConvert) {
 
1361
                if(cw->iso8859Mode && cons_utf8) {
 
1362
                    utf2iso((char *)p, len, &tp, &tlen);
 
1363
                    if(alloc_p)
 
1364
                        free(p);
 
1365
                    alloc_p = true;
 
1366
                    p = tp;
 
1367
                    if(write(fh, p, tlen) < tlen)
 
1368
                        rc = false;
 
1369
                    goto endline;
 
1370
                }
 
1371
 
 
1372
                if(cw->utf8Mode && !cons_utf8) {
 
1373
                    iso2utf((char *)p, len, &tp, &tlen);
 
1374
                    if(alloc_p)
 
1375
                        free(p);
 
1376
                    alloc_p = true;
 
1377
                    p = tp;
 
1378
                    if(write(fh, p, tlen) < tlen)
 
1379
                        rc = false;
 
1380
                    goto endline;
 
1381
                }
 
1382
            }
 
1383
 
 
1384
            if(write(fh, p, len) < len)
 
1385
                rc = false;
 
1386
            goto endline;
 
1387
        }
 
1388
 
 
1389
/* must write this line with the suffix on the end */
 
1390
        --len;
 
1391
        if(write(fh, p, len) < len) {
 
1392
          badwrite:
 
1393
            rc = false;
 
1394
            goto endline;
 
1395
        }
 
1396
        fileSize += len;
 
1397
        strcat(suf, "\n");
 
1398
        len = strlen(suf);
 
1399
        if(write(fh, suf, len) < len)
 
1400
            goto badwrite;
 
1401
 
 
1402
      endline:
 
1403
        if(alloc_p)
 
1404
            free(p);
 
1405
        if(!rc) {
 
1406
            setError(MSG_NoWrite2, name);
 
1407
            close(fh);
 
1408
            return false;
 
1409
        }
 
1410
        fileSize += len;
 
1411
    }                           /* loop over lines */
 
1412
 
 
1413
    close(fh);
 
1414
/* This is not an undoable operation, nor does it change data.
 
1415
 * In fact the data is "no longer modified" if we have written all of it. */
 
1416
    if(startRange == 1 && endRange == cw->dol)
 
1417
        cw->changeMode = false;
 
1418
    return true;
 
1419
}                               /* writeFile */
 
1420
 
 
1421
static bool
 
1422
readContext(int cx)
 
1423
{
 
1424
    struct ebWindow *lw;
 
1425
    int i, start, end, fardol;
 
1426
    if(!cxCompare(cx))
 
1427
        return false;
 
1428
    if(!cxActive(cx))
 
1429
        return false;
 
1430
    fileSize = 0;
 
1431
    lw = sessionList[cx].lw;
 
1432
    fardol = lw->dol;
 
1433
    if(!fardol)
 
1434
        return true;
 
1435
    if(!linesComing(fardol))
 
1436
        return false;
 
1437
    if(cw->dol == endRange)
 
1438
        cw->nlMode = false;
 
1439
    start = end = textLinesCount;
 
1440
    for(i = 1; i <= fardol; ++i) {
 
1441
        pst p = fetchLineContext(i, (lw->dirMode ? -1 : 1), cx);
 
1442
        int len = pstLength(p);
 
1443
        pst q;
 
1444
        if(lw->dirMode) {
 
1445
            char *suf = dirSuffixContext(i, cx);
 
1446
            char *q = allocMem(len + 3);
 
1447
            memcpy(q, p, len);
 
1448
            --len;
 
1449
            strcat(suf, "\n");
 
1450
            strcpy(q + len, suf);
 
1451
            len = strlen(q);
 
1452
            p = (pst) q;
 
1453
        }
 
1454
        textLines[end++] = p;
 
1455
        fileSize += len;
 
1456
    }                           /* loop over lines in the "other" context */
 
1457
    textLinesCount = end;
 
1458
    addToMap(start, end, endRange);
 
1459
    if(lw->nlMode) {
 
1460
        --fileSize;
 
1461
        if(cw->dol == endRange)
 
1462
            cw->nlMode = true;
 
1463
    }
 
1464
    if(binaryDetect & !cw->binMode && lw->binMode) {
 
1465
        cw->binMode = true;
 
1466
        i_puts(MSG_BinaryData);
 
1467
    }
 
1468
    return true;
 
1469
}                               /* readContext */
 
1470
 
 
1471
static bool
 
1472
writeContext(int cx)
 
1473
{
 
1474
    struct ebWindow *lw;
 
1475
    int i, j, len;
 
1476
    char *newmap;
 
1477
    pst p;
 
1478
    int fardol = endRange - startRange + 1;
 
1479
    if(!startRange)
 
1480
        fardol = 0;
 
1481
    if(!linesComing(fardol))
 
1482
        return false;
 
1483
    if(!cxCompare(cx))
 
1484
        return false;
 
1485
    if(cxActive(cx) && !cxQuit(cx, 2))
 
1486
        return false;
 
1487
 
 
1488
    cxInit(cx);
 
1489
    lw = sessionList[cx].lw;
 
1490
    fileSize = 0;
 
1491
    if(startRange) {
 
1492
        newmap = allocMem((fardol + 1) * LNWIDTH + 1);
 
1493
        strcpy(newmap, LNSPACE);
 
1494
        for(i = startRange, j = 1; i <= endRange; ++i, ++j) {
 
1495
            p = fetchLine(i, (cw->dirMode ? -1 : 1));
 
1496
            len = pstLength(p);
 
1497
            sprintf(newmap + j * LNWIDTH, LNFORMAT, textLinesCount);
 
1498
            if(cw->dirMode) {
 
1499
                pst q;
 
1500
                char *suf = dirSuffix(i);
 
1501
                q = allocMem(len + 3);
 
1502
                memcpy(q, p, len);
 
1503
                --len;
 
1504
                strcat(suf, "\n");
 
1505
                strcpy((char *)q + len, suf);
 
1506
                len = strlen((char *)q);
 
1507
                p = q;
 
1508
            }
 
1509
            textLines[textLinesCount++] = p;
 
1510
            fileSize += len;
 
1511
        }
 
1512
        lw->map = newmap;
 
1513
        lw->binMode = cw->binMode;
 
1514
        if(cw->nlMode && endRange == cw->dol) {
 
1515
            lw->nlMode = true;
 
1516
            --fileSize;
 
1517
        }
 
1518
    }                           /* nonempty range */
 
1519
    lw->dot = lw->dol = fardol;
 
1520
 
 
1521
    return true;
 
1522
}                               /* writeContext */
 
1523
 
 
1524
static void
 
1525
debrowseSuffix(char *s)
 
1526
{
 
1527
    if(!s)
 
1528
        return;
 
1529
    while(*s) {
 
1530
        if(*s == '.' && stringEqual(s, ".browse")) {
 
1531
            *s = 0;
 
1532
            return;
 
1533
        }
 
1534
        ++s;
 
1535
    }
 
1536
}                               /* debrowseSuffix */
 
1537
 
 
1538
static bool
 
1539
shellEscape(const char *line)
 
1540
{
 
1541
    char *newline, *s;
 
1542
    const char *t;
 
1543
    pst p;
 
1544
    char key;
 
1545
    int linesize, pass, n;
 
1546
    char *sh;
 
1547
    char subshell[ABSPATH];
 
1548
 
 
1549
/* preferred shell */
 
1550
    sh = getenv("SHELL");
 
1551
    if(!sh || !*sh)
 
1552
        sh = "/bin/sh";
 
1553
 
 
1554
    linesize = strlen(line);
 
1555
    if(!linesize) {
 
1556
/* interactive shell */
 
1557
        if(!isInteractive) {
 
1558
            setError(MSG_SessionBackground);
 
1559
            return false;
 
1560
        }
 
1561
#ifdef DOSLIKE
 
1562
        system(line);           /* don't know how to spawn a shell here */
 
1563
#else
 
1564
        sprintf(subshell, "exec %s -i", sh);
 
1565
        system(subshell);
 
1566
#endif
 
1567
        i_puts(MSG_OK);
 
1568
        return true;
 
1569
    }
 
1570
 
 
1571
/* Make substitutions within the command line. */
 
1572
    for(pass = 1; pass <= 2; ++pass) {
 
1573
        for(t = line; *t; ++t) {
 
1574
            if(*t != '\'')
 
1575
                goto addchar;
 
1576
            if(t > line && isalnumByte(t[-1]))
 
1577
                goto addchar;
 
1578
            key = t[1];
 
1579
            if(key && isalnumByte(t[2]))
 
1580
                goto addchar;
 
1581
 
 
1582
            if(key == '_') {
 
1583
                ++t;
 
1584
                if(!cw->fileName)
 
1585
                    continue;
 
1586
                if(pass == 1) {
 
1587
                    linesize += strlen(cw->fileName);
 
1588
                } else {
 
1589
                    strcpy(s, cw->fileName);
 
1590
                    s += strlen(s);
 
1591
                }
 
1592
                continue;
 
1593
            }
 
1594
            /* '_ filename */
 
1595
            if(key == '.' || key == '-' || key == '+') {
 
1596
                n = cw->dot;
 
1597
                if(key == '-')
 
1598
                    --n;
 
1599
                if(key == '+')
 
1600
                    ++n;
 
1601
                if(n > cw->dol || n == 0) {
 
1602
                    setError(MSG_OutOfRange, key);
 
1603
                    return false;
 
1604
                }
 
1605
              frombuf:
 
1606
                ++t;
 
1607
                if(pass == 1) {
 
1608
                    p = fetchLine(n, -1);
 
1609
                    linesize += pstLength(p) - 1;
 
1610
                } else {
 
1611
                    p = fetchLine(n, 1);
 
1612
                    if(perl2c((char *)p)) {
 
1613
                        free(p);
 
1614
                        setError(MSG_ShellNull);
 
1615
                        return false;
 
1616
                    }
 
1617
                    strcpy(s, (char *)p);
 
1618
                    s += strlen(s);
 
1619
                    free(p);
 
1620
                }
 
1621
                continue;
 
1622
            }
 
1623
            /* '. current line */
 
1624
            if(islowerByte(key)) {
 
1625
                n = cw->labels[key - 'a'];
 
1626
                if(!n) {
 
1627
                    setError(MSG_NoLabel, key);
 
1628
                    return false;
 
1629
                }
 
1630
                goto frombuf;
 
1631
            }
 
1632
            /* 'x the line labeled x */
 
1633
          addchar:
 
1634
            if(pass == 1)
 
1635
                ++linesize;
 
1636
            else
 
1637
                *s++ = *t;
 
1638
        }                       /* loop over chars */
 
1639
 
 
1640
        if(pass == 1)
 
1641
            s = newline = allocMem(linesize + 1);
 
1642
        else
 
1643
            *s = 0;
 
1644
    }                           /* two passes */
 
1645
 
 
1646
/* Run the command.  Note that this routine returns success
 
1647
 * even if the shell command failed.
 
1648
 * Edbrowse succeeds if it is *able* to run the system command. */
 
1649
    system(newline);
 
1650
    i_puts(MSG_OK);
 
1651
    free(newline);
 
1652
    return true;
 
1653
}                               /* shellEscape */
 
1654
 
 
1655
/* Valid delimiters for search/substitute.
 
1656
 * note that \ is conspicuously absent, not a valid delimiter.
 
1657
 * I alsso avoid nestable delimiters such as parentheses.
 
1658
 * And no alphanumerics please -- too confusing.
 
1659
 * ed allows it, but I don't. */
 
1660
static const char valid_delim[] = "_=!;:`\"',/?@-";
 
1661
/* And a valid char for starting a line address */
 
1662
static const char valid_laddr[] = "0123456789-'.$+/?";
 
1663
 
 
1664
/* Check the syntax of a regular expression, before we pass it to pcre.
 
1665
 * The first char is the delimiter -- we stop at the next delimiter.
 
1666
 * A pointer to the second delimiter is returned, along with the
 
1667
 * (possibly reformatted) regular expression. */
 
1668
 
 
1669
static bool
 
1670
regexpCheck(const char *line, bool isleft, bool ebmuck,
 
1671
   char **rexp, const char **split)
 
1672
{                               /* result parameters */
 
1673
    static char re[MAXRE + 20];
 
1674
    const char *start;
 
1675
    char *e = re;
 
1676
    char c, d;
 
1677
/* Remember whether a char is "on deck", ready to be modified by * etc. */
 
1678
    bool ondeck = false;
 
1679
    bool was_ques = true;       /* previous modifier was ? */
 
1680
    bool cc = false;            /* are we in a [...] character class */
 
1681
    int mod;                    /* length of modifier */
 
1682
    int paren = 0;              /* nesting level of parentheses */
 
1683
/* We wouldn't be here if the line was empty. */
 
1684
    char delim = *line++;
 
1685
 
 
1686
    *rexp = re;
 
1687
    if(!strchr(valid_delim, delim)) {
 
1688
        setError(MSG_BadDelimit);
 
1689
        return false;
 
1690
    }
 
1691
    start = line;
 
1692
 
 
1693
    c = *line;
 
1694
    if(ebmuck) {
 
1695
        if(isleft) {
 
1696
            if(c == delim || c == 0) {
 
1697
                if(!cw->lhs_yes) {
 
1698
                    setError(MSG_NoSearchString);
 
1699
                    return false;
 
1700
                }
 
1701
                strcpy(re, cw->lhs);
 
1702
                *split = line;
 
1703
                return true;
 
1704
            }
 
1705
/* Interpret lead * or lone [ as literal */
 
1706
            if(strchr("*?+", c) || c == '[' && !line[1]) {
 
1707
                *e++ = '\\';
 
1708
                *e++ = c;
 
1709
                ++line;
 
1710
                ondeck = true;
 
1711
            }
 
1712
        } else if(c == '%' && (line[1] == delim || line[1] == 0)) {
 
1713
            if(!cw->rhs_yes) {
 
1714
                setError(MSG_NoReplaceString);
 
1715
                return false;
 
1716
            }
 
1717
            strcpy(re, cw->rhs);
 
1718
            *split = line + 1;
 
1719
            return true;
 
1720
        }
 
1721
    }
 
1722
    /* ebmuck tricks */
 
1723
    while(c = *line) {
 
1724
        if(e >= re + MAXRE - 3) {
 
1725
            setError(MSG_RexpLong);
 
1726
            return false;
 
1727
        }
 
1728
        d = line[1];
 
1729
 
 
1730
        if(c == '\\') {
 
1731
            line += 2;
 
1732
            if(d == 0) {
 
1733
                setError(MSG_LineBackslash);
 
1734
                return false;
 
1735
            }
 
1736
            ondeck = true;
 
1737
            was_ques = false;
 
1738
/* I can't think of any reason to remove the escaping \ from any character,
 
1739
 * except ()|, where we reverse the sense of escape. */
 
1740
            if(ebmuck && isleft && !cc && (d == '(' || d == ')' || d == '|')) {
 
1741
                if(d == '|')
 
1742
                    ondeck = false, was_ques = true;
 
1743
                if(d == '(')
 
1744
                    ++paren, ondeck = false, was_ques = true;
 
1745
                if(d == ')')
 
1746
                    --paren;
 
1747
                if(paren < 0) {
 
1748
                    setError(MSG_UnexpectedRight);
 
1749
                    return false;
 
1750
                }
 
1751
                *e++ = d;
 
1752
                continue;
 
1753
            }
 
1754
            if(d == delim || ebmuck && !isleft && d == '&') {
 
1755
                *e++ = d;
 
1756
                continue;
 
1757
            }
 
1758
/* Nothing special; we retain the escape character. */
 
1759
            *e++ = c;
 
1760
            if(isleft && d >= '0' && d <= '7' && (*line < '0' || *line > '7'))
 
1761
                *e++ = '0';
 
1762
            *e++ = d;
 
1763
            continue;
 
1764
        }
 
1765
 
 
1766
        /* escaping backslash */
 
1767
        /* Break out if we hit the delimiter. */
 
1768
        if(c == delim)
 
1769
            break;
 
1770
 
 
1771
/* Remember, I reverse the sense of ()| */
 
1772
        if(isleft) {
 
1773
            if(ebmuck && (c == '(' || c == ')' || c == '|') || c == '^' && line != start && !cc)        /* don't know why we have to do this */
 
1774
                *e++ = '\\';
 
1775
            if(c == '$' && d && d != delim)
 
1776
                *e++ = '\\';
 
1777
        }
 
1778
 
 
1779
        if(c == '$' && !isleft && isdigitByte(d)) {
 
1780
            if(d == '0' || isdigitByte(line[2])) {
 
1781
                setError(MSG_RexpDollar);
 
1782
                return false;
 
1783
            }
 
1784
        }
 
1785
        /* dollar digit on the right */
 
1786
        if(!isleft && c == '&' && ebmuck) {
 
1787
            *e++ = '$';
 
1788
            *e++ = '0';
 
1789
            ++line;
 
1790
            continue;
 
1791
        }
 
1792
 
 
1793
/* push the character */
 
1794
        *e++ = c;
 
1795
        ++line;
 
1796
 
 
1797
/* No more checks for the rhs */
 
1798
        if(!isleft)
 
1799
            continue;
 
1800
 
 
1801
        if(cc) {                /* character class */
 
1802
            if(c == ']')
 
1803
                cc = false;
 
1804
            continue;
 
1805
        }
 
1806
        if(c == '[')
 
1807
            cc = true;
 
1808
 
 
1809
/* Skip all these checks for javascript,
 
1810
 * it probably has the expression right anyways. */
 
1811
        if(!ebmuck)
 
1812
            continue;
 
1813
 
 
1814
/* Modifiers must have a preceding character.
 
1815
 * Except ? which can reduce the greediness of the others. */
 
1816
        if(c == '?' && !was_ques) {
 
1817
            ondeck = false;
 
1818
            was_ques = true;
 
1819
            continue;
 
1820
        }
 
1821
 
 
1822
        mod = 0;
 
1823
        if(c == '?' || c == '*' || c == '+')
 
1824
            mod = 1;
 
1825
        if(c == '{' && isdigitByte(d)) {
 
1826
            const char *t = line + 1;
 
1827
            while(isdigitByte(*t))
 
1828
                ++t;
 
1829
            if(*t == ',')
 
1830
                ++t;
 
1831
            while(isdigitByte(*t))
 
1832
                ++t;
 
1833
            if(*t == '}')
 
1834
                mod = t + 2 - line;
 
1835
        }
 
1836
        if(mod) {
 
1837
            --mod;
 
1838
            if(mod) {
 
1839
                strncpy(e, line, mod);
 
1840
                e += mod;
 
1841
                line += mod;
 
1842
            }
 
1843
            if(!ondeck) {
 
1844
                *e = 0;
 
1845
                setError(MSG_RexpModifier, e - mod - 1);
 
1846
                return false;
 
1847
            }
 
1848
            ondeck = false;
 
1849
            continue;
 
1850
        }
 
1851
        /* modifier */
 
1852
        ondeck = true;
 
1853
        was_ques = false;
 
1854
    }                           /* loop over chars in the pattern */
 
1855
    *e = 0;
 
1856
 
 
1857
    *split = line;
 
1858
 
 
1859
    if(ebmuck) {
 
1860
        if(cc) {
 
1861
            setError(MSG_NoBracket);
 
1862
            return false;
 
1863
        }
 
1864
        if(paren) {
 
1865
            setError(MSG_NoParen);
 
1866
            return false;
 
1867
        }
 
1868
 
 
1869
        if(isleft) {
 
1870
            cw->lhs_yes = true;
 
1871
            strcpy(cw->lhs, re);
 
1872
        } else {
 
1873
            cw->rhs_yes = true;
 
1874
            strcpy(cw->rhs, re);
 
1875
        }
 
1876
    }
 
1877
 
 
1878
    debugPrint(7, "%s regexp %s", (isleft ? "search" : "replace"), re);
 
1879
    return true;
 
1880
}                               /* regexpCheck */
 
1881
 
 
1882
/* regexp variables */
 
1883
static int re_count;
 
1884
static int re_vector[11 * 3];
 
1885
static pcre *re_cc;             /* compiled */
 
1886
 
 
1887
static void
 
1888
regexpCompile(const char *re, bool ci)
 
1889
{
 
1890
    static char try8 = 0;       /* 1 is utf8 on, -1 is utf8 off */
 
1891
    const char *re_error;
 
1892
    int re_offset;
 
1893
    int re_opt;
 
1894
 
 
1895
  top:
 
1896
/* Do we need PCRE_NO_AUTO_CAPTURE? */
 
1897
    re_opt = 0;
 
1898
    if(ci)
 
1899
        re_opt |= PCRE_CASELESS;
 
1900
 
 
1901
    if(cons_utf8 && !cw->binMode && try8 >= 0) {
 
1902
        if(try8 == 0) {
 
1903
            const char *s = getenv("PCREUTF8");
 
1904
            if(s && stringEqual(s, "off")) {
 
1905
                try8 = -1;
 
1906
                goto top;
 
1907
            }
 
1908
        }
 
1909
        try8 = 1;
 
1910
        re_opt |= PCRE_UTF8;
 
1911
    }
 
1912
 
 
1913
    re_cc = pcre_compile(re, re_opt, &re_error, &re_offset, 0);
 
1914
    if(!re_cc && try8 > 0 && strstr(re_error, "PCRE_UTF8 support")) {
 
1915
        i_puts(MSG_PcreUtf8);
 
1916
        try8 = -1;
 
1917
        goto top;
 
1918
    }
 
1919
 
 
1920
    if(!re_cc)
 
1921
        setError(MSG_RexpError, re_error);
 
1922
}                               /* regexpCompile */
 
1923
 
 
1924
/* Get the start or end of a range.
 
1925
 * Pass the line containing the address. */
 
1926
static bool
 
1927
getRangePart(const char *line, int *lineno, const char **split)
 
1928
{                               /* result parameters */
 
1929
    int ln = cw->dot;           /* this is where we start */
 
1930
    char first = *line;
 
1931
 
 
1932
    if(isdigitByte(first)) {
 
1933
        ln = strtol(line, (char **)&line, 10);
 
1934
    } else if(first == '.') {
 
1935
        ++line;
 
1936
/* ln is already set */
 
1937
    } else if(first == '$') {
 
1938
        ++line;
 
1939
        ln = cw->dol;
 
1940
    } else if(first == '\'' && islowerByte(line[1])) {
 
1941
        ln = cw->labels[line[1] - 'a'];
 
1942
        if(!ln) {
 
1943
            setError(MSG_NoLabel, line[1]);
 
1944
            return false;
 
1945
        }
 
1946
        line += 2;
 
1947
    } else if(first == '/' || first == '?') {
 
1948
 
 
1949
        char *re;               /* regular expression */
 
1950
        bool ci = caseInsensitive;
 
1951
        char incr;              /* forward or back */
 
1952
/* Don't look through an empty buffer. */
 
1953
        if(cw->dol == 0) {
 
1954
            setError(MSG_EmptyBuffer);
 
1955
            return false;
 
1956
        }
 
1957
        if(!regexpCheck(line, true, true, &re, &line))
 
1958
            return false;
 
1959
        if(*line == first) {
 
1960
            ++line;
 
1961
            if(*line == 'i')
 
1962
                ci = true, ++line;
 
1963
        }
 
1964
 
 
1965
        /* second delimiter */
 
1966
        regexpCompile(re, ci);
 
1967
        if(!re_cc)
 
1968
            return false;
 
1969
/* We should probably study the pattern, if the file is large.
 
1970
 * But then again, it's probably not worth it,
 
1971
 * since the expressions are simple, and the lines are short. */
 
1972
        incr = (first == '/' ? 1 : -1);
 
1973
        while(true) {
 
1974
            char *subject;
 
1975
            ln += incr;
 
1976
            if(ln > cw->dol)
 
1977
                ln = 1;
 
1978
            if(ln == 0)
 
1979
                ln = cw->dol;
 
1980
            subject = (char *)fetchLine(ln, 1);
 
1981
            re_count =
 
1982
               pcre_exec(re_cc, 0, subject, pstLength((pst) subject) - 1, 0, 0,
 
1983
               re_vector, 33);
 
1984
            free(subject);
 
1985
            if(re_count < -1) {
 
1986
                pcre_free(re_cc);
 
1987
                setError(MSG_RexpError2, ln);
 
1988
                return (globSub = false);
 
1989
            }
 
1990
            if(re_count >= 0)
 
1991
                break;
 
1992
            if(ln == cw->dot) {
 
1993
                pcre_free(re_cc);
 
1994
                setError(MSG_NotFound);
 
1995
                return false;
 
1996
            }
 
1997
        }                       /* loop over lines */
 
1998
        pcre_free(re_cc);
 
1999
/* and ln is the line that matches */
 
2000
    }
 
2001
 
 
2002
    /* search pattern */
 
2003
    /* Now add or subtract from this number */
 
2004
    while((first = *line) == '+' || first == '-') {
 
2005
        int add = 1;
 
2006
        ++line;
 
2007
        if(isdigitByte(*line))
 
2008
            add = strtol(line, (char **)&line, 10);
 
2009
        ln += (first == '+' ? add : -add);
 
2010
    }
 
2011
 
 
2012
    if(ln > cw->dol) {
 
2013
        setError(MSG_LineHigh);
 
2014
        return false;
 
2015
    }
 
2016
    if(ln < 0) {
 
2017
        setError(MSG_LineLow);
 
2018
        return false;
 
2019
    }
 
2020
 
 
2021
    *lineno = ln;
 
2022
    *split = line;
 
2023
    return true;
 
2024
}                               /* getRangePart */
 
2025
 
 
2026
/* Apply a regular expression to each line, and then execute
 
2027
 * a command for each matching, or nonmatching, line.
 
2028
 * This is the global feature, g/re/p, which gives us the word grep. */
 
2029
static bool
 
2030
doGlobal(const char *line)
 
2031
{
 
2032
    int gcnt = 0;               /* global count */
 
2033
    bool ci = caseInsensitive;
 
2034
    bool change;
 
2035
    char delim = *line;
 
2036
    char *t;
 
2037
    char *re;                   /* regular expression */
 
2038
    int i, origdot, yesdot, nodot;
 
2039
 
 
2040
    if(!delim) {
 
2041
        setError(MSG_RexpMissing, icmd);
 
2042
        return false;
 
2043
    }
 
2044
 
 
2045
    if(!regexpCheck(line, true, true, &re, &line))
 
2046
        return false;
 
2047
    if(*line != delim) {
 
2048
        setError(MSG_NoDelimit);
 
2049
        return false;
 
2050
    }
 
2051
    ++line;
 
2052
    if(*line == 'i')
 
2053
        ++line, ci = true;
 
2054
    skipWhite(&line);
 
2055
 
 
2056
/* clean up any previous stars */
 
2057
    for(t = cw->map + LNWIDTH; *t; t += LNWIDTH)
 
2058
        t[6] = ' ';
 
2059
 
 
2060
/* Find the lines that match the pattern. */
 
2061
    regexpCompile(re, ci);
 
2062
    if(!re_cc)
 
2063
        return false;
 
2064
    for(i = startRange; i <= endRange; ++i) {
 
2065
        char *subject = (char *)fetchLine(i, 1);
 
2066
        re_count =
 
2067
           pcre_exec(re_cc, 0, subject, pstLength((pst) subject), 0, 0,
 
2068
           re_vector, 33);
 
2069
        free(subject);
 
2070
        if(re_count < -1) {
 
2071
            pcre_free(re_cc);
 
2072
            setError(MSG_RexpError2, i);
 
2073
            return false;
 
2074
        }
 
2075
        if(re_count < 0 && cmd == 'v' || re_count >= 0 && cmd == 'g') {
 
2076
            ++gcnt;
 
2077
            cw->map[i * LNWIDTH + 6] = '*';
 
2078
        }
 
2079
    }                           /* loop over line */
 
2080
    pcre_free(re_cc);
 
2081
 
 
2082
    if(!gcnt) {
 
2083
        setError((cmd == 'v') + MSG_NoMatchG);
 
2084
        return false;
 
2085
    }
 
2086
 
 
2087
/* apply the subcommand to every line with a star */
 
2088
    globSub = true;
 
2089
    setError(-1);
 
2090
    if(!*line)
 
2091
        line = "p";
 
2092
    origdot = cw->dot;
 
2093
    yesdot = nodot = 0;
 
2094
    change = true;
 
2095
    while(gcnt && change) {
 
2096
        change = false;         /* kinda like bubble sort */
 
2097
        for(i = 1; i <= cw->dol; ++i) {
 
2098
            t = cw->map + i * LNWIDTH + 6;
 
2099
            if(*t != '*')
 
2100
                continue;
 
2101
            if(intFlag)
 
2102
                goto done;
 
2103
            change = true, --gcnt;
 
2104
            *t = ' ';
 
2105
            cw->dot = i;        /* so we can run the command at this line */
 
2106
            if(runCommand(line)) {
 
2107
                yesdot = cw->dot;
 
2108
/* try this line again, in case we deleted or moved it somewhere else */
 
2109
                if(undoable)
 
2110
                    --i, t -= LNWIDTH;
 
2111
            } else {
 
2112
/* error in subcommand might turn global flag off */
 
2113
                if(!globSub) {
 
2114
                    nodot = i, yesdot = 0;
 
2115
                    goto done;
 
2116
                }               /* serious error */
 
2117
            }                   /* subcommand succeeds or fails */
 
2118
        }                       /* loop over lines */
 
2119
    }                           /* loop making changes */
 
2120
  done:
 
2121
 
 
2122
    globSub = false;
 
2123
/* yesdot could be 0, even on success, if all lines are deleted via g/re/d */
 
2124
    if(yesdot || !cw->dol) {
 
2125
        cw->dot = yesdot;
 
2126
        if((cmd == 's' || cmd == 'i') && subPrint == 1)
 
2127
            printDot();
 
2128
    } else if(nodot) {
 
2129
        cw->dot = nodot;
 
2130
    } else {
 
2131
        cw->dot = origdot;
 
2132
        if(!errorMsg[0])
 
2133
            setError(MSG_NotModifiedG);
 
2134
    }
 
2135
    if(!errorMsg[0] && intFlag)
 
2136
        setError(MSG_Interrupted);
 
2137
    return (errorMsg[0] == 0);
 
2138
}                               /* doGlobal */
 
2139
 
 
2140
static void
 
2141
fieldNumProblem(int desc, char c, int n, int nt, int nrt)
 
2142
{
 
2143
    if(!nrt) {
 
2144
        setError(MSG_NoInputFields + desc);
 
2145
        return;
 
2146
    }
 
2147
    if(!n) {
 
2148
        setError(MSG_ManyInputFields + desc, c, c, nt);
 
2149
        return;
 
2150
    }
 
2151
    if(nt > 1)
 
2152
        setError(MSG_InputRange, n, c, c, nt);
 
2153
    else
 
2154
        setError(MSG_InputRange2, n, c, c);
 
2155
}                               /* fieldNumProblem */
 
2156
 
 
2157
/* Perform a substitution on a given line.
 
2158
 * The lhs has been compiled, and the rhs is passed in for replacement.
 
2159
 * Refer to the static variable re_cc for the compiled lhs.
 
2160
 * The replacement line is static, with a fixed length.
 
2161
 * Return 0 for no match, 1 for a replacement, and -1 for a real problem. */
 
2162
 
 
2163
char replaceLine[REPLACELINELEN];
 
2164
static char *replaceLineEnd;
 
2165
static int replaceLineLen;
 
2166
static int
 
2167
replaceText(const char *line, int len, const char *rhs,
 
2168
   bool ebmuck, int nth, bool global, int ln)
 
2169
{
 
2170
    int offset = 0, lastoffset, instance = 0;
 
2171
    int span;
 
2172
    char *r = replaceLine;
 
2173
    char *r_end = replaceLine + REPLACELINELEN - 8;
 
2174
    const char *s = line, *s_end, *t;
 
2175
    char c, d;
 
2176
 
 
2177
    while(true) {
 
2178
/* find the next match */
 
2179
        re_count = pcre_exec(re_cc, 0, line, len, offset, 0, re_vector, 33);
 
2180
        if(re_count < -1) {
 
2181
            setError(MSG_RexpError2, ln);
 
2182
            return -1;
 
2183
        }
 
2184
        if(re_count < 0)
 
2185
            break;
 
2186
        ++instance;             /* found another match */
 
2187
        lastoffset = offset;
 
2188
        offset = re_vector[1];  /* ready for next iteration */
 
2189
        if(offset == lastoffset && (nth > 1 || global)) {
 
2190
            setError(MSG_ManyEmptyStrings);
 
2191
            return -1;
 
2192
        }
 
2193
        if(!global &&instance != nth)
 
2194
            continue;
 
2195
 
 
2196
/* copy up to the match point */
 
2197
        s_end = line + re_vector[0];
 
2198
        span = s_end - s;
 
2199
        if(r + span >= r_end)
 
2200
            goto longvar;
 
2201
        memcpy(r, s, span);
 
2202
        r += span;
 
2203
        s = line + offset;
 
2204
 
 
2205
/* Now copy over the rhs */
 
2206
/* Special case lc mc uc */
 
2207
        if(ebmuck && (rhs[0] == 'l' || rhs[0] == 'm' || rhs[0] == 'u') &&
 
2208
           rhs[1] == 'c' && rhs[2] == 0) {
 
2209
            span = re_vector[1] - re_vector[0];
 
2210
            if(r + span + 1 > r_end)
 
2211
                goto longvar;
 
2212
            memcpy(r, line + re_vector[0], span);
 
2213
            r[span] = 0;
 
2214
            i_caseShift((unsigned char *)r, rhs[0]);
 
2215
            r += span;
 
2216
            if(!global)
 
2217
                break;
 
2218
            continue;
 
2219
        }
 
2220
 
 
2221
        /* case shift */
 
2222
        /* copy rhs, watching for $n */
 
2223
        t = rhs;
 
2224
        while(c = *t) {
 
2225
            if(r >= r_end)
 
2226
                goto longvar;
 
2227
            d = t[1];
 
2228
            if(c == '\\') {
 
2229
                t += 2;
 
2230
                if(d == '$') {
 
2231
                    *r++ = d;
 
2232
                    continue;
 
2233
                }
 
2234
                if(d == 'n') {
 
2235
                    *r++ = '\n';
 
2236
                    continue;
 
2237
                }
 
2238
                if(d == 't') {
 
2239
                    *r++ = '\t';
 
2240
                    continue;
 
2241
                }
 
2242
                if(d == 'b') {
 
2243
                    *r++ = '\b';
 
2244
                    continue;
 
2245
                }
 
2246
                if(d == 'r') {
 
2247
                    *r++ = '\r';
 
2248
                    continue;
 
2249
                }
 
2250
                if(d == 'f') {
 
2251
                    *r++ = '\f';
 
2252
                    continue;
 
2253
                }
 
2254
                if(d == 'a') {
 
2255
                    *r++ = '\a';
 
2256
                    continue;
 
2257
                }
 
2258
                if(d >= '0' && d <= '7') {
 
2259
                    int octal = d - '0';
 
2260
                    d = *t;
 
2261
                    if(d >= '0' && d <= '7') {
 
2262
                        ++t;
 
2263
                        octal = 8 * octal + d - '0';
 
2264
                        d = *t;
 
2265
                        if(d >= '0' && d <= '7') {
 
2266
                            ++t;
 
2267
                            octal = 8 * octal + d - '0';
 
2268
                        }
 
2269
                    }
 
2270
                    *r++ = octal;
 
2271
                    continue;
 
2272
                }               /* octal */
 
2273
                if(!ebmuck)
 
2274
                    *r++ = '\\';
 
2275
                *r++ = d;
 
2276
                continue;
 
2277
            }                   /* backslash */
 
2278
            if(c == '$' && isdigitByte(d)) {
 
2279
                int y, z;
 
2280
                t += 2;
 
2281
                d -= '0';
 
2282
                if(d > re_count)
 
2283
                    continue;
 
2284
                y = re_vector[2 * d];
 
2285
                z = re_vector[2 * d + 1];
 
2286
                if(y < 0)
 
2287
                    continue;
 
2288
                span = z - y;
 
2289
                if(r + span >= r_end)
 
2290
                    goto longvar;
 
2291
                memcpy(r, line + y, span);
 
2292
                r += span;
 
2293
                continue;
 
2294
            }
 
2295
            *r++ = c;
 
2296
            ++t;
 
2297
        }
 
2298
 
 
2299
        if(!global)
 
2300
            break;
 
2301
    }                           /* loop matching the regular expression */
 
2302
 
 
2303
    if(!instance)
 
2304
        return false;
 
2305
    if(!global &&instance < nth)
 
2306
        return false;
 
2307
 
 
2308
/* We got a match, copy the last span. */
 
2309
    s_end = line + len;
 
2310
    span = s_end - s;
 
2311
    if(r + span >= r_end)
 
2312
        goto longvar;
 
2313
    memcpy(r, s, span);
 
2314
    r += span;
 
2315
    replaceLineEnd = r;
 
2316
    replaceLineLen = r - replaceLine;
 
2317
    return true;
 
2318
 
 
2319
  longvar:
 
2320
    setError(MSG_SubLong, REPLACELINELEN);
 
2321
    return -1;
 
2322
}                               /* replaceText */
 
2323
 
 
2324
/* Substitute text on the lines in startRange through endRange.
 
2325
 * We could be changing the text in an input field.
 
2326
 * If so, we'll call infReplace().
 
2327
 * Also, we might be indirectory mode, whence we must rename the file.
 
2328
 * This is a complicated function!
 
2329
 * The return can be true or false, with the usual meaning,
 
2330
 * but also a return of -1, which is failure,
 
2331
 * and an indication that we need to abort any g// in progress.
 
2332
 * It's a serious problem. */
 
2333
 
 
2334
static int
 
2335
substituteText(const char *line)
 
2336
{
 
2337
    int whichField = 0;
 
2338
    bool bl_mode = false;       /* running the bl command */
 
2339
    bool g_mode = false;        /* s/x/y/g */
 
2340
    bool ci = caseInsensitive;
 
2341
    bool save_nlMode;
 
2342
    char c, *s, *t;
 
2343
    int nth = 0;                /* s/x/y/7 */
 
2344
    int lastSubst = 0;          /* last successful substitution */
 
2345
    char *re;                   /* the parsed regular expression */
 
2346
    int ln;                     /* line number */
 
2347
    int j, linecount, slashcount, nullcount, tagno, total, realtotal;
 
2348
    char lhs[MAXRE], rhs[MAXRE];
 
2349
 
 
2350
    subPrint = 1;               /* default is to print the last line substituted */
 
2351
    re_cc = 0;
 
2352
    if(stringEqual(line, "`bl"))
 
2353
        bl_mode = true, breakLineSetup();
 
2354
 
 
2355
    if(!bl_mode) {
 
2356
/* watch for s2/x/y/ for the second input field */
 
2357
        if(isdigitByte(*line))
 
2358
            whichField = strtol(line, (char **)&line, 10);
 
2359
        if(!*line) {
 
2360
            setError(MSG_RexpMissing2, icmd);
 
2361
            return -1;
 
2362
        }
 
2363
 
 
2364
        if(cw->dirMode && !dirWrite) {
 
2365
            setError(MSG_DirNoWrite);
 
2366
            return -1;
 
2367
        }
 
2368
 
 
2369
        if(!regexpCheck(line, true, true, &re, &line))
 
2370
            return -1;
 
2371
        strcpy(lhs, re);
 
2372
        if(!*line) {
 
2373
            setError(MSG_NoDelimit);
 
2374
            return -1;
 
2375
        }
 
2376
        if(!regexpCheck(line, false, true, &re, &line))
 
2377
            return -1;
 
2378
        strcpy(rhs, re);
 
2379
 
 
2380
        if(*line) {             /* third delimiter */
 
2381
            ++line;
 
2382
            subPrint = 0;
 
2383
            while(c = *line) {
 
2384
                if(c == 'g') {
 
2385
                    g_mode = true;
 
2386
                    ++line;
 
2387
                    continue;
 
2388
                }
 
2389
                if(c == 'i') {
 
2390
                    ci = true;
 
2391
                    ++line;
 
2392
                    continue;
 
2393
                }
 
2394
                if(c == 'p') {
 
2395
                    subPrint = 2;
 
2396
                    ++line;
 
2397
                    continue;
 
2398
                }
 
2399
                if(isdigitByte(c)) {
 
2400
                    if(nth) {
 
2401
                        setError(MSG_SubNumbersMany);
 
2402
                        return -1;
 
2403
                    }
 
2404
                    nth = strtol(line, (char **)&line, 10);
 
2405
                    continue;
 
2406
                }               /* number */
 
2407
                setError(MSG_SubSuffixBad);
 
2408
                return -1;
 
2409
            }                   /* loop gathering suffix flags */
 
2410
            if(g_mode && nth) {
 
2411
                setError(MSG_SubNumberG);
 
2412
                return -1;
 
2413
            }
 
2414
        }                       /* closing delimiter */
 
2415
        if(nth == 0 && !g_mode)
 
2416
            nth = 1;
 
2417
 
 
2418
        regexpCompile(lhs, ci);
 
2419
        if(!re_cc)
 
2420
            return -1;
 
2421
    } else {
 
2422
 
 
2423
        subPrint = 0;
 
2424
    }                           /* bl_mode or not */
 
2425
 
 
2426
    if(!globSub)
 
2427
        setError(-1);
 
2428
 
 
2429
    for(ln = startRange; ln <= endRange && !intFlag; ++ln) {
 
2430
        char *p = (char *)fetchLine(ln, -1);
 
2431
        int len = pstLength((pst) p);
 
2432
 
 
2433
        if(bl_mode) {
 
2434
            int newlen;
 
2435
            if(!breakLine(p, len, &newlen)) {
 
2436
                setError(MSG_BreakLong, REPLACELINELEN);
 
2437
                return -1;
 
2438
            }
 
2439
/* empty line is not allowed */
 
2440
            if(!newlen)
 
2441
                replaceLine[newlen++] = '\n';
 
2442
/* perhaps no changes were made */
 
2443
            if(newlen == len && !memcmp(p, replaceLine, len))
 
2444
                continue;
 
2445
            replaceLineLen = newlen;
 
2446
            replaceLineEnd = replaceLine + newlen;
 
2447
/* But the regular substitute doesn't have the \n on the end.
 
2448
 * We need to make this one conform. */
 
2449
            --replaceLineEnd, --replaceLineLen;
 
2450
        } else {
 
2451
 
 
2452
            if(cw->browseMode) {
 
2453
                char search[20];
 
2454
                char searchend[4];
 
2455
                findInputField(p, 1, whichField, &total, &realtotal, &tagno);
 
2456
                if(!tagno) {
 
2457
                    fieldNumProblem(0, 'i', whichField, total, realtotal);
 
2458
                    continue;
 
2459
                }
 
2460
                sprintf(search, "%c%d<", InternalCodeChar, tagno);
 
2461
                sprintf(searchend, "%c0>", InternalCodeChar);
 
2462
/* Ok, if the line contains a null, this ain't gonna work. */
 
2463
                s = strstr(p, search);
 
2464
                if(!s)
 
2465
                    continue;
 
2466
                s = strchr(s, '<') + 1;
 
2467
                t = strstr(s, searchend);
 
2468
                if(!t)
 
2469
                    continue;
 
2470
                j = replaceText(s, t - s, rhs, true, nth, g_mode, ln);
 
2471
            } else {
 
2472
                j = replaceText(p, len - 1, rhs, true, nth, g_mode, ln);
 
2473
            }
 
2474
            if(j < 0)
 
2475
                goto abort;
 
2476
            if(!j)
 
2477
                continue;
 
2478
        }
 
2479
 
 
2480
/* Did we split this line into many lines? */
 
2481
        linecount = slashcount = nullcount = 0;
 
2482
        for(t = replaceLine; t < replaceLineEnd; ++t) {
 
2483
            c = *t;
 
2484
            if(c == '\n')
 
2485
                ++linecount;
 
2486
            if(c == 0)
 
2487
                ++nullcount;
 
2488
            if(c == '/')
 
2489
                ++slashcount;
 
2490
        }
 
2491
        if(!linesComing(linecount + 1))
 
2492
            goto abort;
 
2493
 
 
2494
        if(cw->sqlMode) {
 
2495
            if(linecount) {
 
2496
                setError(MSG_ReplaceNewline);
 
2497
                goto abort;
 
2498
            }
 
2499
            if(nullcount) {
 
2500
                setError(MSG_ReplaceNull);
 
2501
                goto abort;
 
2502
            }
 
2503
            *replaceLineEnd = '\n';
 
2504
            if(!sqlUpdateRow((pst) p, len - 1, (pst) replaceLine,
 
2505
               replaceLineEnd - replaceLine))
 
2506
                goto abort;
 
2507
        }
 
2508
 
 
2509
        if(cw->dirMode) {
 
2510
/* move the file, then update the text */
 
2511
            char src[ABSPATH], *dest;
 
2512
            if(slashcount + nullcount + linecount) {
 
2513
                setError(MSG_DirNameBad);
 
2514
                goto abort;
 
2515
            }
 
2516
            p[len - 1] = 0;     /* temporary */
 
2517
            t = makeAbsPath(p);
 
2518
            p[len - 1] = '\n';
 
2519
            if(!t)
 
2520
                goto abort;
 
2521
            strcpy(src, t);
 
2522
            *replaceLineEnd = 0;
 
2523
            dest = makeAbsPath(replaceLine);
 
2524
            if(!dest)
 
2525
                goto abort;
 
2526
            if(!stringEqual(src, dest)) {
 
2527
                if(fileTypeByName(dest, true)) {
 
2528
                    setError(MSG_DestFileExists);
 
2529
                    goto abort;
 
2530
                }
 
2531
                if(rename(src, dest)) {
 
2532
                    setError(MSG_NoRename, dest);
 
2533
                    goto abort;
 
2534
                }
 
2535
            }                   /* source and dest are different */
 
2536
        }
 
2537
 
 
2538
        if(cw->browseMode) {
 
2539
            if(nullcount) {
 
2540
                setError(MSG_InputNull2);
 
2541
                goto abort;
 
2542
            }
 
2543
            if(linecount) {
 
2544
                setError(MSG_InputNewline2);
 
2545
                goto abort;
 
2546
            }
 
2547
            replaceLine[replaceLineLen] = 0;
 
2548
/* We're managing our own printing, so leave notify = 0 */
 
2549
            if(!infReplace(tagno, replaceLine, 0))
 
2550
                goto abort;
 
2551
        } else {
 
2552
 
 
2553
            replaceLine[replaceLineLen] = '\n';
 
2554
            if(!linecount) {
 
2555
/* normal substitute */
 
2556
                char newnum[LNWIDTH];
 
2557
                textLines[textLinesCount] = allocMem(replaceLineLen + 1);
 
2558
                memcpy(textLines[textLinesCount], replaceLine,
 
2559
                   replaceLineLen + 1);
 
2560
                sprintf(newnum, "%06d", textLinesCount);
 
2561
                memcpy(cw->map + ln * LNWIDTH, newnum, 6);
 
2562
                ++textLinesCount;
 
2563
            } else {
 
2564
/* Becomes many lines, this is the tricky case. */
 
2565
                save_nlMode = cw->nlMode;
 
2566
                delText(ln, ln);
 
2567
                addTextToBuffer((pst) replaceLine, replaceLineLen + 1, ln - 1);
 
2568
                cw->nlMode = save_nlMode;
 
2569
                endRange += linecount;
 
2570
                ln += linecount;
 
2571
/* There's a quirk when adding newline to the end of a buffer
 
2572
 * that had no newline at the end before. */
 
2573
                if(cw->nlMode &&
 
2574
                   ln == cw->dol && replaceLine[replaceLineLen - 1] == '\n') {
 
2575
                    delText(ln, ln);
 
2576
                    --ln, --endRange;
 
2577
                }
 
2578
            }
 
2579
        }                       /* browse or not */
 
2580
 
 
2581
        if(subPrint == 2)
 
2582
            displayLine(ln);
 
2583
        lastSubst = ln;
 
2584
        cw->firstOpMode = undoable = true;
 
2585
        if(!cw->browseMode)
 
2586
            cw->changeMode = true;
 
2587
    }                           /* loop over lines in the range */
 
2588
    if(re_cc)
 
2589
        pcre_free(re_cc);
 
2590
 
 
2591
    if(intFlag) {
 
2592
        setError(MSG_Interrupted);
 
2593
        return -1;
 
2594
    }
 
2595
 
 
2596
    if(!lastSubst) {
 
2597
        if(!globSub) {
 
2598
            if(!errorMsg[0])
 
2599
                setError(bl_mode + MSG_NoMatch);
 
2600
        }
 
2601
        return false;
 
2602
    }
 
2603
    cw->dot = lastSubst;
 
2604
    if(subPrint == 1 && !globSub)
 
2605
        printDot();
 
2606
    return true;
 
2607
 
 
2608
  abort:
 
2609
    if(re_cc)
 
2610
        pcre_free(re_cc);
 
2611
    return -1;
 
2612
}                               /* substituteText */
 
2613
 
 
2614
/*********************************************************************
 
2615
Implement various two letter commands.
 
2616
Most of these set and clear modes.
 
2617
Return 1 or 0 for success or failure as usual.
 
2618
But return 2 if there is a new command to run.
 
2619
The second parameter is a result parameter, the new command.
 
2620
*********************************************************************/
 
2621
 
 
2622
static int
 
2623
twoLetter(const char *line, const char **runThis)
 
2624
{
 
2625
    static char newline[MAXTTYLINE];
 
2626
    char c;
 
2627
    bool rc, ub;
 
2628
    int i;
 
2629
 
 
2630
    *runThis = newline;
 
2631
 
 
2632
    if(stringEqual(line, "qt"))
 
2633
        ebClose(0);
 
2634
 
 
2635
    if(line[0] == 'd' && line[1] == 'b' && isdigitByte(line[2]) && !line[3]) {
 
2636
        debugLevel = line[2] - '0';
 
2637
        return true;
 
2638
    }
 
2639
 
 
2640
    if(line[0] == 'u' && line[1] == 'a' && isdigitByte(line[2]) && !line[3]) {
 
2641
        char *t = userAgents[line[2] - '0'];
 
2642
        cmd = 'e';
 
2643
        if(!t) {
 
2644
            setError(MSG_NoAgent, line[2]);
 
2645
            return false;
 
2646
        }
 
2647
        currentAgent = t;
 
2648
        if(helpMessagesOn || debugLevel >= 1)
 
2649
            puts(currentAgent);
 
2650
        return true;
 
2651
    }
 
2652
 
 
2653
    if(stringEqual(line, "re") || stringEqual(line, "rea")) {
 
2654
        bool wasbrowse = cw->browseMode;
 
2655
        freeUndoLines(cw->map);
 
2656
        undoWindow.map = 0;
 
2657
        nzFree(preWindow.map);
 
2658
        preWindow.map = 0;
 
2659
        cw->firstOpMode = undoable = false;
 
2660
        cmd = 'e';              /* so error messages are printed */
 
2661
        rc = setupReply(line[2] == 'a');
 
2662
        cw->firstOpMode = undoable = false;
 
2663
        if(wasbrowse && cw->browseMode) {
 
2664
            cw->iplist = 0;
 
2665
            ub = false;
 
2666
            goto et_go;
 
2667
        }
 
2668
        return rc;
 
2669
    }
 
2670
 
 
2671
/* ^^^^ is the same as ^4 */
 
2672
    if(line[0] == '^' && line[1] == '^') {
 
2673
        const char *t = line + 2;
 
2674
        while(*t == '^')
 
2675
            ++t;
 
2676
        if(!*t) {
 
2677
            sprintf(newline, "^%d", t - line);
 
2678
            return 2;
 
2679
        }
 
2680
    }
 
2681
 
 
2682
    if(line[0] == 'm' && (i = stringIsNum(line + 1)) >= 0) {
 
2683
        sprintf(newline, "^M%d", i);
 
2684
        return 2;
 
2685
    }
 
2686
 
 
2687
    if(line[0] == 'c' && line[1] == 'd') {
 
2688
        c = line[2];
 
2689
        if(!c || isspaceByte(c)) {
 
2690
            const char *t = line + 2;
 
2691
            skipWhite(&t);
 
2692
            c = *t;
 
2693
            cmd = 'e';          /* so error messages are printed */
 
2694
            if(!c) {
 
2695
                char cwdbuf[ABSPATH];
 
2696
              pwd:
 
2697
                if(!getcwd(cwdbuf, sizeof (cwdbuf))) {
 
2698
                    setError(c ? MSG_CDGetError : MSG_CDSetError);
 
2699
                    return false;
 
2700
                }
 
2701
                puts(cwdbuf);
 
2702
                return true;
 
2703
            }
 
2704
            if(!envFile(t, &t))
 
2705
                return false;
 
2706
            if(!chdir(t))
 
2707
                goto pwd;
 
2708
            setError(MSG_CDInvalid);
 
2709
            return false;
 
2710
        }
 
2711
    }
 
2712
 
 
2713
    if(line[0] == 'p' && line[1] == 'b') {
 
2714
        c = line[2];
 
2715
        if(!c || c == '.') {
 
2716
            const struct MIMETYPE *mt;
 
2717
            char *cmd;
 
2718
            const char *suffix = 0;
 
2719
            bool trailPercent = false;
 
2720
            if(!cw->dol) {
 
2721
                setError(MSG_AudioEmpty);
 
2722
                return false;
 
2723
            }
 
2724
            if(cw->browseMode) {
 
2725
                setError(MSG_AudioBrowse);
 
2726
                return false;
 
2727
            }
 
2728
            if(cw->dirMode) {
 
2729
                setError(MSG_AudioDir);
 
2730
                return false;
 
2731
            }
 
2732
            if(cw->sqlMode) {
 
2733
                setError(MSG_AudioDB);
 
2734
                return false;
 
2735
            }
 
2736
            if(c == '.') {
 
2737
                suffix = line + 3;
 
2738
            } else {
 
2739
                if(cw->fileName)
 
2740
                    suffix = strrchr(cw->fileName, '.');
 
2741
                if(!suffix) {
 
2742
                    setError(MSG_NoSuffix);
 
2743
                    return false;
 
2744
                }
 
2745
                ++suffix;
 
2746
            }
 
2747
            if(strlen(suffix) > 5) {
 
2748
                setError(MSG_SuffixLong);
 
2749
                return false;
 
2750
            }
 
2751
            mt = findMimeBySuffix(suffix);
 
2752
            if(!mt) {
 
2753
                setError(MSG_SuffixBad, suffix);
 
2754
                return false;
 
2755
            }
 
2756
            if(mt->program[strlen(mt->program) - 1] == '%')
 
2757
                trailPercent = true;
 
2758
            cmd = pluginCommand(mt, 0, suffix);
 
2759
            rc = bufferToProgram(cmd, suffix, trailPercent);
 
2760
            nzFree(cmd);
 
2761
            return rc;
 
2762
        }
 
2763
    }
 
2764
 
 
2765
    if(stringEqual(line, "rf")) {
 
2766
        cmd = 'e';
 
2767
        if(!cw->fileName) {
 
2768
            setError(MSG_NoRefresh);
 
2769
            return false;
 
2770
        }
 
2771
        if(cw->browseMode)
 
2772
            cmd = 'b';
 
2773
        noStack = true;
 
2774
        sprintf(newline, "%c %s", cmd, cw->fileName);
 
2775
        debrowseSuffix(newline);
 
2776
        return 2;
 
2777
    }
 
2778
 
 
2779
    if(stringEqual(line, "sc")) {
 
2780
        if(!cw->sqlMode) {
 
2781
            setError(MSG_NoDB);
 
2782
            return false;
 
2783
        }
 
2784
        showColumns();
 
2785
        return true;
 
2786
    }
 
2787
 
 
2788
    if(stringEqual(line, "ub") || stringEqual(line, "et")) {
 
2789
        ub = (line[0] == 'u');
 
2790
        rc = true;
 
2791
        cmd = 'e';
 
2792
        if(!cw->browseMode) {
 
2793
            setError(MSG_NoBrowse);
 
2794
            return false;
 
2795
        }
 
2796
        freeUndoLines(cw->map);
 
2797
        undoWindow.map = 0;
 
2798
        nzFree(preWindow.map);
 
2799
        preWindow.map = 0;
 
2800
        cw->firstOpMode = undoable = false;
 
2801
        cw->browseMode = false;
 
2802
        cw->iplist = 0;
 
2803
        if(ub) {
 
2804
            debrowseSuffix(cw->fileName);
 
2805
            cw->nlMode = cw->rnlMode;
 
2806
            cw->dot = cw->r_dot, cw->dol = cw->r_dol;
 
2807
            memcpy(cw->labels, cw->r_labels, sizeof (cw->labels));
 
2808
            freeWindowLines(cw->map);
 
2809
            cw->map = cw->r_map;
 
2810
        } else {
 
2811
          et_go:
 
2812
            for(i = 1; i <= cw->dol; ++i) {
 
2813
                int ln = atoi(cw->map + i * LNWIDTH);
 
2814
                removeHiddenNumbers(textLines[ln]);
 
2815
            }
 
2816
            freeWindowLines(cw->r_map);
 
2817
        }
 
2818
        cw->r_map = 0;
 
2819
        freeTags(cw->tags);
 
2820
        cw->tags = 0;
 
2821
        freeJavaContext(cw->jsc);
 
2822
        cw->jsc = 0;
 
2823
        nzFree(cw->dw);
 
2824
        cw->dw = 0;
 
2825
        nzFree(cw->ft);
 
2826
        cw->ft = 0;
 
2827
        nzFree(cw->fd);
 
2828
        cw->fd = 0;
 
2829
        nzFree(cw->fk);
 
2830
        cw->fk = 0;
 
2831
        nzFree(cw->mailInfo);
 
2832
        cw->mailInfo = 0;
 
2833
        if(ub)
 
2834
            fileSize = apparentSize(context, false);
 
2835
        return true;
 
2836
    }
 
2837
 
 
2838
    if(stringEqual(line, "ip")) {
 
2839
        jMyContext();
 
2840
        sethostent(1);
 
2841
        allIPs();
 
2842
        endhostent();
 
2843
        if(!cw->iplist || cw->iplist[0] == -1) {
 
2844
            i_puts(MSG_None);
 
2845
        } else {
 
2846
            IP32bit ip;
 
2847
            for(i = 0; (ip = cw->iplist[i]) != NULL_IP; ++i) {
 
2848
                puts(tcp_ip_dots(ip));
 
2849
            }
 
2850
        }
 
2851
        return true;
 
2852
    }
 
2853
    /* ip */
 
2854
    if(stringEqual(line, "f/") || stringEqual(line, "w/")) {
 
2855
        char *t;
 
2856
        cmd = line[0];
 
2857
        if(!cw->fileName) {
 
2858
            setError(MSG_NoRefresh);
 
2859
            return false;
 
2860
        }
 
2861
        t = strrchr(cw->fileName, '/');
 
2862
        if(!t) {
 
2863
            setError(MSG_NoSlash);
 
2864
            return false;
 
2865
        }
 
2866
        ++t;
 
2867
        if(!*t) {
 
2868
            setError(MSG_YesSlash);
 
2869
            return false;
 
2870
        }
 
2871
        sprintf(newline, "%c `%s", cmd, t);
 
2872
        return 2;
 
2873
    }
 
2874
 
 
2875
    if(line[0] == 'f' && line[2] == 0 &&
 
2876
       (line[1] == 'd' || line[1] == 'k' || line[1] == 't')) {
 
2877
        const char *s;
 
2878
        int t;
 
2879
        cmd = 'e';
 
2880
        if(!cw->browseMode) {
 
2881
            setError(MSG_NoBrowse);
 
2882
            return false;
 
2883
        }
 
2884
        if(line[1] == 't')
 
2885
            s = cw->ft, t = MSG_NoTitle;
 
2886
        if(line[1] == 'd')
 
2887
            s = cw->fd, t = MSG_NoDesc;
 
2888
        if(line[1] == 'k')
 
2889
            s = cw->fk, t = MSG_NoKeywords;
 
2890
        if(s)
 
2891
            puts(s);
 
2892
        else
 
2893
            i_puts(t);
 
2894
        return true;
 
2895
    }
 
2896
 
 
2897
    if(line[0] == 's' && line[1] == 'm') {
 
2898
        const char *t = line + 2;
 
2899
        bool dosig = true;
 
2900
        int account = 0;
 
2901
        cmd = 'e';
 
2902
        if(*t == '-') {
 
2903
            dosig = false;
 
2904
            ++t;
 
2905
        }
 
2906
        if(isdigitByte(*t))
 
2907
            account = strtol(t, (char **)&t, 10);
 
2908
        if(!*t) {
 
2909
/* send mail */
 
2910
            return sendMailCurrent(account, dosig);
 
2911
        } else {
 
2912
            setError(MSG_SMBadChar);
 
2913
            return false;
 
2914
        }
 
2915
    }
 
2916
 
 
2917
    if(stringEqual(line, "sg")) {
 
2918
        searchStringsAll = true;
 
2919
        if(helpMessagesOn)
 
2920
            i_puts(MSG_SubGlobal);
 
2921
        return true;
 
2922
    }
 
2923
 
 
2924
    if(stringEqual(line, "sl")) {
 
2925
        searchStringsAll = false;
 
2926
        if(helpMessagesOn)
 
2927
            i_puts(MSG_SubLocal);
 
2928
        return true;
 
2929
    }
 
2930
 
 
2931
    if(stringEqual(line, "ci")) {
 
2932
        caseInsensitive = true;
 
2933
        if(helpMessagesOn)
 
2934
            i_puts(MSG_CaseIns);
 
2935
        return true;
 
2936
    }
 
2937
 
 
2938
    if(stringEqual(line, "cs")) {
 
2939
        caseInsensitive = false;
 
2940
        if(helpMessagesOn)
 
2941
            i_puts(MSG_CaseSen);
 
2942
        return true;
 
2943
    }
 
2944
 
 
2945
    if(stringEqual(line, "dr")) {
 
2946
        dirWrite = 0;
 
2947
        if(helpMessagesOn)
 
2948
            i_puts(MSG_DirReadonly);
 
2949
        return true;
 
2950
    }
 
2951
 
 
2952
    if(stringEqual(line, "dw")) {
 
2953
        dirWrite = 1;
 
2954
        if(helpMessagesOn)
 
2955
            i_puts(MSG_DirWritable);
 
2956
        return true;
 
2957
    }
 
2958
 
 
2959
    if(stringEqual(line, "dx")) {
 
2960
        dirWrite = 2;
 
2961
        if(helpMessagesOn)
 
2962
            i_puts(MSG_DirX);
 
2963
        return true;
 
2964
    }
 
2965
 
 
2966
    if(stringEqual(line, "hr")) {
 
2967
        allowRedirection ^= 1;
 
2968
        if(helpMessagesOn || debugLevel >= 1)
 
2969
            i_puts(allowRedirection + MSG_RedirectionOff);
 
2970
        return true;
 
2971
    }
 
2972
 
 
2973
    if(stringEqual(line, "iu")) {
 
2974
        iuConvert ^= 1;
 
2975
        if(helpMessagesOn || debugLevel >= 1)
 
2976
            i_puts(iuConvert + MSG_IUConvertOff);
 
2977
        return true;
 
2978
    }
 
2979
 
 
2980
    if(stringEqual(line, "sr")) {
 
2981
        sendReferrer ^= 1;
 
2982
        if(helpMessagesOn || debugLevel >= 1)
 
2983
            i_puts(sendReferrer + MSG_RefererOff);
 
2984
        return true;
 
2985
    }
 
2986
 
 
2987
    if(stringEqual(line, "js")) {
 
2988
        allowJS ^= 1;
 
2989
        if(helpMessagesOn || debugLevel >= 1)
 
2990
            i_puts(allowJS + MSG_JavaOff);
 
2991
        return true;
 
2992
    }
 
2993
 
 
2994
    if(stringEqual(line, "bd")) {
 
2995
        binaryDetect ^= 1;
 
2996
        if(helpMessagesOn || debugLevel >= 1)
 
2997
            i_puts(binaryDetect + MSG_BinaryIgnore);
 
2998
        return true;
 
2999
    }
 
3000
 
 
3001
    if(stringEqual(line, "lna")) {
 
3002
        listNA ^= 1;
 
3003
        if(helpMessagesOn || debugLevel >= 1)
 
3004
            i_puts(listNA + MSG_ListControl);
 
3005
        return true;
 
3006
    }
 
3007
 
 
3008
    if(line[0] == 'f' && line[1] == 'm' &&
 
3009
       line[2] && strchr("pad", line[2]) && !line[3]) {
 
3010
        ftpMode = 0;
 
3011
        if(line[2] == 'p')
 
3012
            ftpMode = 'F';
 
3013
        if(line[2] == 'a')
 
3014
            ftpMode = 'E';
 
3015
        if(helpMessagesOn || debugLevel >= 1) {
 
3016
            if(ftpMode == 'F')
 
3017
                i_puts(MSG_PassiveMode);
 
3018
            if(ftpMode == 'E')
 
3019
                i_puts(MSG_ActiveMode);
 
3020
            if(ftpMode == 0)
 
3021
                i_puts(MSG_PassActMode);
 
3022
        }
 
3023
        return true;
 
3024
    }
 
3025
 
 
3026
    if(stringEqual(line, "vs")) {
 
3027
        verifyCertificates ^= 1;
 
3028
        if(helpMessagesOn || debugLevel >= 1)
 
3029
            i_puts(verifyCertificates + MSG_CertifyOff);
 
3030
        ssl_verify_setting();
 
3031
        return true;
 
3032
    }
 
3033
 
 
3034
    if(stringEqual(line, "hf")) {
 
3035
        showHiddenFiles ^= 1;
 
3036
        if(helpMessagesOn || debugLevel >= 1)
 
3037
            i_puts(showHiddenFiles + MSG_HiddenOff);
 
3038
        return true;
 
3039
    }
 
3040
 
 
3041
    if(stringEqual(line, "tn")) {
 
3042
        textAreaDosNewlines ^= 1;
 
3043
        if(helpMessagesOn || debugLevel >= 1)
 
3044
            i_puts(textAreaDosNewlines + MSG_AreaUnix);
 
3045
        return true;
 
3046
    }
 
3047
 
 
3048
    if(stringEqual(line, "eo")) {
 
3049
        endMarks = 0;
 
3050
        if(helpMessagesOn)
 
3051
            i_puts(MSG_MarkOff);
 
3052
        return true;
 
3053
    }
 
3054
 
 
3055
    if(stringEqual(line, "el")) {
 
3056
        endMarks = 1;
 
3057
        if(helpMessagesOn)
 
3058
            i_puts(MSG_MarkList);
 
3059
        return true;
 
3060
    }
 
3061
 
 
3062
    if(stringEqual(line, "ep")) {
 
3063
        endMarks = 2;
 
3064
        if(helpMessagesOn)
 
3065
            i_puts(MSG_MarkOn);
 
3066
        return true;
 
3067
    }
 
3068
 
 
3069
    *runThis = line;
 
3070
    return 2;                   /* no change */
 
3071
}                               /* twoLetter */
 
3072
 
 
3073
/* Return the number of unbalanced punctuation marks.
 
3074
 * This is used by the next routine. */
 
3075
static void
 
3076
unbalanced(char c, char d, int ln, int *back_p, int *for_p)
 
3077
{                               /* result parameters */
 
3078
    char *t, *open;
 
3079
    char *p = (char *)fetchLine(ln, 1);
 
3080
    bool change;
 
3081
    int backward, forward;
 
3082
 
 
3083
    change = true;
 
3084
    while(change) {
 
3085
        change = false;
 
3086
        open = 0;
 
3087
        for(t = p; *t != '\n'; ++t) {
 
3088
            if(*t == c)
 
3089
                open = t;
 
3090
            if(*t == d && open) {
 
3091
                *open = 0;
 
3092
                *t = 0;
 
3093
                change = true;
 
3094
                open = 0;
 
3095
            }
 
3096
        }
 
3097
    }
 
3098
 
 
3099
    backward = forward = 0;
 
3100
    for(t = p; *t != '\n'; ++t) {
 
3101
        if(*t == c)
 
3102
            ++forward;
 
3103
        if(*t == d)
 
3104
            ++backward;
 
3105
    }
 
3106
 
 
3107
    free(p);
 
3108
    *back_p = backward;
 
3109
    *for_p = forward;
 
3110
}                               /* unbalanced */
 
3111
 
 
3112
/* Find the line that balances the unbalanced punctuation. */
 
3113
static bool
 
3114
balanceLine(const char *line)
 
3115
{
 
3116
    char c, d;                  /* open and close */
 
3117
    char selected;
 
3118
    static char openlist[] = "{([<`";
 
3119
    static char closelist[] = "})]>'";
 
3120
    static const char alllist[] = "{}()[]<>`'";
 
3121
    char *t;
 
3122
    int level = 0;
 
3123
    int i, direction, forward, backward;
 
3124
 
 
3125
    if(c = *line) {
 
3126
        if(!strchr(alllist, c) || line[1]) {
 
3127
            setError(MSG_BalanceChar, alllist);
 
3128
            return false;
 
3129
        }
 
3130
        if(t = strchr(openlist, c)) {
 
3131
            d = closelist[t - openlist];
 
3132
            direction = 1;
 
3133
        } else {
 
3134
            d = c;
 
3135
            t = strchr(closelist, d);
 
3136
            c = openlist[t - closelist];
 
3137
            direction = -1;
 
3138
        }
 
3139
        unbalanced(c, d, endRange, &backward, &forward);
 
3140
        if(direction > 0) {
 
3141
            if((level = forward) == 0) {
 
3142
                setError(MSG_BalanceNoOpen, c);
 
3143
                return false;
 
3144
            }
 
3145
        } else {
 
3146
            if((level = backward) == 0) {
 
3147
                setError(MSG_BalanceNoOpen, d);
 
3148
                return false;
 
3149
            }
 
3150
        }
 
3151
    } else {
 
3152
 
 
3153
/* Look for anything unbalanced, probably a brace. */
 
3154
        for(i = 0; i <= 2; ++i) {
 
3155
            c = openlist[i];
 
3156
            d = closelist[i];
 
3157
            unbalanced(c, d, endRange, &backward, &forward);
 
3158
            if(backward && forward) {
 
3159
                setError(MSG_BalanceAmbig, c, d, c, d);
 
3160
                return false;
 
3161
            }
 
3162
            level = backward + forward;
 
3163
            if(!level)
 
3164
                continue;
 
3165
            direction = 1;
 
3166
            if(backward)
 
3167
                direction = -1;
 
3168
            break;
 
3169
        }
 
3170
        if(!level) {
 
3171
            setError(MSG_BalanceNothing);
 
3172
            return false;
 
3173
        }
 
3174
    }                           /* explicit character passed in, or look for one */
 
3175
 
 
3176
    selected = (direction > 0 ? c : d);
 
3177
 
 
3178
/* search for the balancing line */
 
3179
    i = endRange;
 
3180
    while((i += direction) > 0 && i <= cw->dol) {
 
3181
        unbalanced(c, d, i, &backward, &forward);
 
3182
        if(direction > 0 && backward >= level ||
 
3183
           direction < 0 && forward >= level) {
 
3184
            cw->dot = i;
 
3185
            printDot();
 
3186
            return true;
 
3187
        }
 
3188
        level += (forward - backward) * direction;
 
3189
    }                           /* loop over lines */
 
3190
 
 
3191
    setError(MSG_Unbalanced, selected);
 
3192
    return false;
 
3193
}                               /* balanceLine */
 
3194
 
 
3195
/* Unfold the buffer into one long, allocated string. */
 
3196
bool
 
3197
unfoldBuffer(int cx, bool cr, char **data, int *len)
 
3198
{
 
3199
    char *buf;
 
3200
    int l, ln;
 
3201
    struct ebWindow *w;
 
3202
    int size = apparentSize(cx, false);
 
3203
    if(size < 0)
 
3204
        return false;
 
3205
    w = sessionList[cx].lw;
 
3206
    if(w->browseMode) {
 
3207
        setError(MSG_SessionBrowse, cx);
 
3208
        return false;
 
3209
    }
 
3210
    if(w->dirMode) {
 
3211
        setError(MSG_SessionDir, cx);
 
3212
        return false;
 
3213
    }
 
3214
    if(cr)
 
3215
        size += w->dol;
 
3216
/* a few bytes more, just for safety */
 
3217
    buf = allocMem(size + 4);
 
3218
    *data = buf;
 
3219
    for(ln = 1; ln <= w->dol; ++ln) {
 
3220
        pst line = fetchLineContext(ln, -1, cx);
 
3221
        l = pstLength(line) - 1;
 
3222
        if(l) {
 
3223
            memcpy(buf, line, l);
 
3224
            buf += l;
 
3225
        }
 
3226
        if(cr) {
 
3227
            *buf++ = '\r';
 
3228
            if(l && buf[-2] == '\r')
 
3229
                --buf, --size;
 
3230
        }
 
3231
        *buf++ = '\n';
 
3232
    }                           /* loop over lines */
 
3233
    if(w->dol && w->nlMode) {
 
3234
        if(cr)
 
3235
            --size;
 
3236
    }
 
3237
    *len = size;
 
3238
    (*data)[size] = 0;
 
3239
    return true;
 
3240
}                               /* unfoldBuffer */
 
3241
 
 
3242
static char *
 
3243
showLinks(void)
 
3244
{
 
3245
    int a_l;
 
3246
    char *a = initString(&a_l);
 
3247
    bool click, dclick;
 
3248
    char c, *p, *s, *t, *q, *line, *h;
 
3249
    int j, k = 0, tagno;
 
3250
    void *ev;
 
3251
 
 
3252
    if(cw->browseMode && endRange) {
 
3253
        jMyContext();
 
3254
        line = (char *)fetchLine(endRange, -1);
 
3255
        for(p = line; (c = *p) != '\n'; ++p) {
 
3256
            if(c != InternalCodeChar)
 
3257
                continue;
 
3258
            if(!isdigitByte(p[1]))
 
3259
                continue;
 
3260
            j = strtol(p + 1, &s, 10);
 
3261
            if(*s != '{')
 
3262
                continue;
 
3263
            p = s;
 
3264
            ++k;
 
3265
            findField(line, 0, k, 0, 0, &tagno, &h, &ev);
 
3266
            if(tagno != j)
 
3267
                continue;       /* should never happen */
 
3268
 
 
3269
            click = tagHandler(tagno, "onclick");
 
3270
            dclick = tagHandler(tagno, "ondblclick");
 
3271
 
 
3272
/* find the closing brace */
 
3273
/* It might not be there, could be on the next line. */
 
3274
            for(s = p + 1; (c = *s) != '\n'; ++s)
 
3275
                if(c == InternalCodeChar && s[1] == '0' && s[2] == '}')
 
3276
                    break;
 
3277
/* Ok, everything between p and s exclusive is the description */
 
3278
            if(!h)
 
3279
                h = EMPTYSTRING;
 
3280
            if(stringEqual(h, "#")) {
 
3281
                nzFree(h);
 
3282
                h = EMPTYSTRING;
 
3283
            }
 
3284
 
 
3285
            if(memEqualCI(h, "mailto:", 7)) {
 
3286
                stringAndBytes(&a, &a_l, p + 1, s - p - 1);
 
3287
                stringAndChar(&a, &a_l, ':');
 
3288
                s = h + 7;
 
3289
                t = s + strcspn(s, "?");
 
3290
                stringAndBytes(&a, &a_l, s, t - s);
 
3291
                stringAndChar(&a, &a_l, '\n');
 
3292
                nzFree(h);
 
3293
                continue;
 
3294
            }
 
3295
            /* mail link */
 
3296
            stringAndString(&a, &a_l, "<a href=");
 
3297
 
 
3298
            if(memEqualCI(h, "javascript:", 11)) {
 
3299
                stringAndString(&a, &a_l, "javascript:>\n");
 
3300
            } else if(!*h && (click | dclick)) {
 
3301
                char buf[20];
 
3302
                sprintf(buf, "%s>\n", click ? "onclick" : "ondblclick");
 
3303
                stringAndString(&a, &a_l, buf);
 
3304
            } else {
 
3305
                if(*h)
 
3306
                    stringAndString(&a, &a_l, h);
 
3307
                stringAndString(&a, &a_l, ">\n");
 
3308
            }
 
3309
 
 
3310
            nzFree(h);
 
3311
/* next line is the description of the bookmark */
 
3312
            stringAndBytes(&a, &a_l, p + 1, s - p - 1);
 
3313
            stringAndString(&a, &a_l, "\n</a>\n");
 
3314
        }                       /* loop looking for hyperlinks */
 
3315
    }
 
3316
    /* browse mode */
 
3317
    if(!a_l) {                  /* nothing found yet */
 
3318
        if(!cw->fileName) {
 
3319
            setError(MSG_NoFileName);
 
3320
            return 0;
 
3321
        }
 
3322
        h = cloneString(cw->fileName);
 
3323
        debrowseSuffix(h);
 
3324
        stringAndString(&a, &a_l, "<a href=");
 
3325
        stringAndString(&a, &a_l, h);
 
3326
        stringAndString(&a, &a_l, ">\n");
 
3327
        s = (char *)getDataURL(h);
 
3328
        if(!s || !*s)
 
3329
            s = h;
 
3330
        t = s + strcspn(s, "\1?#");
 
3331
        if(t > s && t[-1] == '/')
 
3332
            --t;
 
3333
        *t = 0;
 
3334
        q = strrchr(s, '/');
 
3335
        if(q && q < t)
 
3336
            s = q + 1;
 
3337
        stringAndBytes(&a, &a_l, s, t - s);
 
3338
        stringAndString(&a, &a_l, "\n</a>\n");
 
3339
        nzFree(h);
 
3340
    }
 
3341
    /* using the filename */
 
3342
    return a;
 
3343
}                               /* showLinks */
 
3344
 
 
3345
static void
 
3346
readyUndo(void)
 
3347
{
 
3348
    struct ebWindow *pw = &preWindow;
 
3349
    if(globSub)
 
3350
        return;
 
3351
    pw->dot = cw->dot;
 
3352
    pw->dol = cw->dol;
 
3353
    memcpy(pw->labels, cw->labels, 26 * sizeof (int));
 
3354
    pw->binMode = cw->binMode;
 
3355
    pw->nlMode = cw->nlMode;
 
3356
    pw->dirMode = cw->dirMode;
 
3357
    if(pw->map) {
 
3358
        if(!cw->map || !stringEqual(pw->map, cw->map)) {
 
3359
            free(pw->map);
 
3360
            pw->map = 0;
 
3361
        }
 
3362
    }
 
3363
    if(cw->map && !pw->map)
 
3364
        pw->map = cloneString(cw->map);
 
3365
}                               /* readyUndo */
 
3366
 
 
3367
/* Run the entered edbrowse command.
 
3368
 * This is indirectly recursive, as in g/x/d
 
3369
 * Pass in the ed command, and return success or failure.
 
3370
 * We assume it has been turned into a C string.
 
3371
 * This means no embeded nulls.
 
3372
 * If you want to use null in a search or substitute, use \0. */
 
3373
bool
 
3374
runCommand(const char *line)
 
3375
{
 
3376
    int i, j, n, writeMode;
 
3377
    struct ebWindow *w;
 
3378
    void *ev;                   /* event variables */
 
3379
    bool nogo, rc;
 
3380
    bool postSpace = false, didRange = false;
 
3381
    char first;
 
3382
    int cx = 0;                 /* numeric suffix as in s/x/y/3 or w2 */
 
3383
    int tagno;
 
3384
    const char *s;
 
3385
    static char newline[MAXTTYLINE];
 
3386
    static char *allocatedLine = 0;
 
3387
 
 
3388
    if(allocatedLine) {
 
3389
        nzFree(allocatedLine);
 
3390
        allocatedLine = 0;
 
3391
    }
 
3392
    nzFree(currentReferrer);
 
3393
    currentReferrer = cloneString(cw->fileName);
 
3394
    js_redirects = false;
 
3395
 
 
3396
    cmd = icmd = 'p';
 
3397
    skipWhite(&line);
 
3398
    first = *line;
 
3399
 
 
3400
    if(!globSub) {
 
3401
/* Allow things like comment, or shell escape, but not if we're
 
3402
 * in the midst of a global substitute, as in g/x/ !echo hello world */
 
3403
        if(first == '#')
 
3404
            return true;
 
3405
        if(first == '!')
 
3406
            return shellEscape(line + 1);
 
3407
 
 
3408
/* Watch for successive q commands. */
 
3409
        lastq = lastqq, lastqq = 0;
 
3410
        noStack = false;
 
3411
 
 
3412
/* special 2 letter commands - most of these change operational modes */
 
3413
        j = twoLetter(line, &line);
 
3414
        if(j != 2)
 
3415
            return j;
 
3416
    }
 
3417
 
 
3418
    startRange = endRange = cw->dot;    /* default range */
 
3419
/* Just hit return to read the next line. */
 
3420
    first = *line;
 
3421
    if(first == 0) {
 
3422
        didRange = true;
 
3423
        ++startRange, ++endRange;
 
3424
        if(endRange > cw->dol) {
 
3425
            setError(MSG_EndBuffer);
 
3426
            return false;
 
3427
        }
 
3428
    }
 
3429
    if(first == ',') {
 
3430
        didRange = true;
 
3431
        ++line;
 
3432
        startRange = 1;
 
3433
        if(cw->dol == 0)
 
3434
            startRange = 0;
 
3435
        endRange = cw->dol;
 
3436
    }
 
3437
    if(first == ';') {
 
3438
        didRange = true;
 
3439
        ++line;
 
3440
        startRange = cw->dot;
 
3441
        endRange = cw->dol;
 
3442
    }
 
3443
    if(first == 'j' || first == 'J') {
 
3444
        didRange = true;
 
3445
        endRange = startRange + 1;
 
3446
        if(endRange > cw->dol) {
 
3447
            setError(MSG_EndJoin);
 
3448
            return false;
 
3449
        }
 
3450
    }
 
3451
    if(first == '=') {
 
3452
        didRange = true;
 
3453
        startRange = endRange = cw->dol;
 
3454
    }
 
3455
    if(first == 'w' || first == 'v' || first == 'g' &&
 
3456
       line[1] && strchr(valid_delim, line[1])) {
 
3457
        didRange = true;
 
3458
        startRange = 1;
 
3459
        if(cw->dol == 0)
 
3460
            startRange = 0;
 
3461
        endRange = cw->dol;
 
3462
    }
 
3463
 
 
3464
    if(!didRange) {
 
3465
        if(!getRangePart(line, &startRange, &line))
 
3466
            return (globSub = false);
 
3467
        endRange = startRange;
 
3468
        if(line[0] == ',') {
 
3469
            ++line;
 
3470
            endRange = cw->dol; /* new default */
 
3471
            first = *line;
 
3472
            if(first && strchr(valid_laddr, first)) {
 
3473
                if(!getRangePart(line, &endRange, &line))
 
3474
                    return (globSub = false);
 
3475
            }
 
3476
        }
 
3477
    }
 
3478
    if(endRange < startRange) {
 
3479
        setError(MSG_BadRange);
 
3480
        return false;
 
3481
    }
 
3482
 
 
3483
/* change uc into a substitute command, converting the whole line */
 
3484
    skipWhite(&line);
 
3485
    first = *line;
 
3486
    if((first == 'u' || first == 'l' || first == 'm') && line[1] == 'c' &&
 
3487
       line[2] == 0) {
 
3488
        sprintf(newline, "s/.*/%cc/", first);
 
3489
        line = newline;
 
3490
    }
 
3491
/* Breakline is actually a substitution of lines. */
 
3492
    if(stringEqual(line, "bl")) {
 
3493
        if(cw->dirMode) {
 
3494
            setError(MSG_BreakDir);
 
3495
            return false;
 
3496
        }
 
3497
        if(cw->sqlMode) {
 
3498
            setError(MSG_BreakDB);
 
3499
            return false;
 
3500
        }
 
3501
        if(cw->browseMode) {
 
3502
            setError(MSG_BreakBrowse);
 
3503
            return false;
 
3504
        }
 
3505
        line = "s`bl";
 
3506
    }
 
3507
 
 
3508
/* get the command */
 
3509
    cmd = *line;
 
3510
    if(cmd)
 
3511
        ++line;
 
3512
    else
 
3513
        cmd = 'p';
 
3514
    icmd = cmd;
 
3515
 
 
3516
    if(!strchr(valid_cmd, cmd)) {
 
3517
        setError(MSG_UnknownCommand, cmd);
 
3518
        return (globSub = false);
 
3519
    }
 
3520
 
 
3521
    first = *line;
 
3522
    writeMode = O_TRUNC;
 
3523
    if(cmd == 'w' && first == '+')
 
3524
        writeMode = O_APPEND, first = *++line;
 
3525
 
 
3526
    if(cw->dirMode && !strchr(dir_cmd, cmd)) {
 
3527
        setError(MSG_DirCommand, icmd);
 
3528
        return (globSub = false);
 
3529
    }
 
3530
    if(cw->sqlMode && !strchr(sql_cmd, cmd)) {
 
3531
        setError(MSG_BadRange, icmd);
 
3532
        return (globSub = false);
 
3533
    }
 
3534
    if(cw->browseMode && !strchr(browse_cmd, cmd)) {
 
3535
        setError(MSG_BrowseCommand, icmd);
 
3536
        return (globSub = false);
 
3537
    }
 
3538
    if(startRange == 0 && !strchr(zero_cmd, cmd)) {
 
3539
        setError(MSG_AtLine0);
 
3540
        return (globSub = false);
 
3541
    }
 
3542
    while(isspaceByte(first))
 
3543
        postSpace = true, first = *++line;
 
3544
    if(strchr(spaceplus_cmd, cmd) && !postSpace && first) {
 
3545
        s = line;
 
3546
        while(isdigitByte(*s))
 
3547
            ++s;
 
3548
        if(*s) {
 
3549
            setError(MSG_NoSpaceAfter);
 
3550
            return (globSub = false);
 
3551
        }
 
3552
    }
 
3553
    if(globSub && !strchr(global_cmd, cmd)) {
 
3554
        setError(MSG_GlobalCommand, icmd);
 
3555
        return (globSub = false);
 
3556
    }
 
3557
 
 
3558
/* move/copy destination, the third address */
 
3559
    if(cmd == 't' || cmd == 'm') {
 
3560
        if(!first) {
 
3561
            destLine = cw->dot;
 
3562
        } else {
 
3563
            if(!strchr(valid_laddr, first)) {
 
3564
                setError(MSG_BadDest);
 
3565
                return (globSub = false);
 
3566
            }
 
3567
            if(!getRangePart(line, &destLine, &line))
 
3568
                return (globSub = false);
 
3569
            first = *line;
 
3570
        }                       /* was there something after m or t */
 
3571
    }
 
3572
 
 
3573
    /* m or t */
 
3574
    /* Any command other than a lone b resets history */
 
3575
    if(cmd != 'b' || first) {
 
3576
        fetchHistory(0, 0);     /* reset history */
 
3577
    }
 
3578
 
 
3579
/* env variable and wild card expansion */
 
3580
    if(strchr("brewf", cmd) && first && !isURL(line) && !isSQL(line)) {
 
3581
        if(cmd != 'r' || !cw->sqlMode) {
 
3582
            if(!envFile(line, &line))
 
3583
                return false;
 
3584
            first = *line;
 
3585
        }
 
3586
    }
 
3587
 
 
3588
    if(cmd == 'z') {
 
3589
        if(isdigitByte(first)) {
 
3590
            last_z = strtol(line, (char **)&line, 10);
 
3591
            if(!last_z)
 
3592
                last_z = 1;
 
3593
            first = *line;
 
3594
        }
 
3595
        startRange = endRange + 1;
 
3596
        endRange = startRange;
 
3597
        if(startRange > cw->dol) {
 
3598
            setError(MSG_LineHigh);
 
3599
            return false;
 
3600
        }
 
3601
        cmd = 'p';
 
3602
        endRange += last_z - 1;
 
3603
        if(endRange > cw->dol)
 
3604
            endRange = cw->dol;
 
3605
    }
 
3606
 
 
3607
    /* the a+ feature, when you thought you were in append mode */
 
3608
    if(cmd == 'a') {
 
3609
        if(stringEqual(line, "+"))
 
3610
            ++line, first = 0;
 
3611
        else
 
3612
            linePending[0] = 0;
 
3613
    } else
 
3614
        linePending[0] = 0;
 
3615
 
 
3616
    if(first && strchr(nofollow_cmd, cmd)) {
 
3617
        setError(MSG_TextAfter, icmd);
 
3618
        return (globSub = false);
 
3619
    }
 
3620
 
 
3621
    if(cmd == 'h') {
 
3622
        showError();
 
3623
        return true;
 
3624
    }
 
3625
    /* h */
 
3626
    if(cmd == 'H') {
 
3627
        if(helpMessagesOn ^= 1)
 
3628
            if(debugLevel >= 1)
 
3629
                i_puts(MSG_HelpOn);
 
3630
        return true;
 
3631
    }
 
3632
 
 
3633
    if(cmd == 'X') {
 
3634
        cw->dot = endRange;
 
3635
        return true;
 
3636
    }
 
3637
 
 
3638
    if(strchr("Llpn", cmd)) {
 
3639
        for(i = startRange; i <= endRange; ++i) {
 
3640
            displayLine(i);
 
3641
            cw->dot = i;
 
3642
            if(intFlag)
 
3643
                break;
 
3644
        }
 
3645
        return true;
 
3646
    }
 
3647
 
 
3648
    if(cmd == '=') {
 
3649
        printf("%d\n", endRange);
 
3650
        return true;
 
3651
    }
 
3652
    /* = */
 
3653
    if(cmd == 'B') {
 
3654
        return balanceLine(line);
 
3655
    }
 
3656
    /* B */
 
3657
    if(cmd == 'u') {
 
3658
        struct ebWindow *uw = &undoWindow;
 
3659
        char *swapmap;
 
3660
        if(!cw->firstOpMode) {
 
3661
            setError(MSG_NoUndo);
 
3662
            return false;
 
3663
        }
 
3664
/* swap, so we can undo our undo, if need be */
 
3665
        i = uw->dot, uw->dot = cw->dot, cw->dot = i;
 
3666
        i = uw->dol, uw->dol = cw->dol, cw->dol = i;
 
3667
        for(j = 0; j < 26; ++j) {
 
3668
            i = uw->labels[j], uw->labels[j] = cw->labels[j], cw->labels[j] = i;
 
3669
        }
 
3670
        swapmap = uw->map, uw->map = cw->map, cw->map = swapmap;
 
3671
        return true;
 
3672
    }
 
3673
    /* u */
 
3674
    if(cmd == 'k') {
 
3675
        if(!islowerByte(first) || line[1]) {
 
3676
            setError(MSG_EnterKAZ);
 
3677
            return false;
 
3678
        }
 
3679
        if(startRange < endRange) {
 
3680
            setError(MSG_RangeLabel);
 
3681
            return false;
 
3682
        }
 
3683
        cw->labels[first - 'a'] = endRange;
 
3684
        return true;
 
3685
    }
 
3686
 
 
3687
    /* k */
 
3688
    /* Find suffix, as in 27,59w2 */
 
3689
    if(!postSpace) {
 
3690
        cx = stringIsNum(line);
 
3691
        if(!cx) {
 
3692
            setError((cmd == '^') ? MSG_Backup0 : MSG_Session0);
 
3693
            return false;
 
3694
        }
 
3695
        if(cx < 0)
 
3696
            cx = 0;
 
3697
    }
 
3698
 
 
3699
    if(cmd == 'q') {
 
3700
        if(cx) {
 
3701
            if(!cxCompare(cx))
 
3702
                return false;
 
3703
            if(!cxActive(cx))
 
3704
                return false;
 
3705
        } else {
 
3706
            cx = context;
 
3707
            if(first) {
 
3708
                setError(MSG_QAfter);
 
3709
                return false;
 
3710
            }
 
3711
        }
 
3712
        saveSubstitutionStrings();
 
3713
        if(!cxQuit(cx, 2))
 
3714
            return false;
 
3715
        if(cx != context)
 
3716
            return true;
 
3717
/* look around for another active session */
 
3718
        while(true) {
 
3719
            if(++cx == MAXSESSION)
 
3720
                cx = 1;
 
3721
            if(cx == context)
 
3722
                ebClose(0);
 
3723
            if(!sessionList[cx].lw)
 
3724
                continue;
 
3725
            cxSwitch(cx, true);
 
3726
            return true;
 
3727
        }                       /* loop over sessions */
 
3728
    }
 
3729
 
 
3730
    if(cmd == 'f') {
 
3731
        if(cx) {
 
3732
            if(!cxCompare(cx))
 
3733
                return false;
 
3734
            if(!cxActive(cx))
 
3735
                return false;
 
3736
            s = sessionList[cx].lw->fileName;
 
3737
            if(s)
 
3738
                printf("%s", s);
 
3739
            else
 
3740
                i_printf(MSG_NoFile);
 
3741
            if(sessionList[cx].lw->binMode)
 
3742
                i_printf(MSG_BinaryBrackets);
 
3743
            nl();
 
3744
            return true;
 
3745
        }                       /* another session */
 
3746
        if(first) {
 
3747
            if(cw->dirMode) {
 
3748
                setError(MSG_DirRename);
 
3749
                return false;
 
3750
            }
 
3751
            if(cw->sqlMode) {
 
3752
                setError(MSG_TableRename);
 
3753
                return false;
 
3754
            }
 
3755
            nzFree(cw->fileName);
 
3756
            cw->fileName = cloneString(line);
 
3757
        }
 
3758
        s = cw->fileName;
 
3759
        if(s)
 
3760
            printf("%s", s);
 
3761
        else
 
3762
            i_printf(MSG_NoFile);
 
3763
        if(cw->binMode)
 
3764
            i_printf(MSG_BinaryBrackets);
 
3765
        nl();
 
3766
        return true;
 
3767
    }
 
3768
 
 
3769
    if(cmd == 'w') {
 
3770
        if(cx) {                /* write to another buffer */
 
3771
            if(writeMode == O_APPEND) {
 
3772
                setError(MSG_BufferAppend);
 
3773
                return false;
 
3774
            }
 
3775
            return writeContext(cx);
 
3776
        }
 
3777
        if(!first)
 
3778
            line = cw->fileName;
 
3779
        if(!line) {
 
3780
            setError(MSG_NoFileSpecified);
 
3781
            return false;
 
3782
        }
 
3783
        if(cw->dirMode && stringEqual(line, cw->fileName)) {
 
3784
            setError(MSG_NoDirWrite);
 
3785
            return false;
 
3786
        }
 
3787
        if(cw->sqlMode && stringEqual(line, cw->fileName)) {
 
3788
            setError(MSG_NoDBWrite);
 
3789
            return false;
 
3790
        }
 
3791
        return writeFile(line, writeMode);
 
3792
    }
 
3793
    /* w */
 
3794
    if(cmd == '^') {            /* back key, pop the stack */
 
3795
        if(first && !cx) {
 
3796
            setError(MSG_ArrowAfter);
 
3797
            return false;
 
3798
        }
 
3799
        if(!cx)
 
3800
            cx = 1;
 
3801
        while(cx) {
 
3802
            struct ebWindow *prev = cw->prev;
 
3803
            if(!prev) {
 
3804
                setError(MSG_NoPrevious);
 
3805
                return false;
 
3806
            }
 
3807
            saveSubstitutionStrings();
 
3808
            if(!cxQuit(context, 1))
 
3809
                return false;
 
3810
            sessionList[context].lw = cw = prev;
 
3811
            restoreSubstitutionStrings(cw);
 
3812
            --cx;
 
3813
        }
 
3814
        printDot();
 
3815
        return true;
 
3816
    }
 
3817
 
 
3818
    if(cmd == 'M') {            /* move this to another session */
 
3819
        if(first && !cx) {
 
3820
            setError(MSG_MAfter);
 
3821
            return false;
 
3822
        }
 
3823
        if(!first) {
 
3824
            setError(MSG_NoDestSession);
 
3825
            return false;
 
3826
        }
 
3827
        if(!cw->prev) {
 
3828
            setError(MSG_NoBackup);
 
3829
            return false;
 
3830
        }
 
3831
        if(!cxCompare(cx))
 
3832
            return false;
 
3833
        if(cxActive(cx) && !cxQuit(cx, 2))
 
3834
            return false;
 
3835
/* Magic with pointers, hang on to your hat. */
 
3836
        sessionList[cx].fw = sessionList[cx].lw = cw;
 
3837
        cs->lw = cw->prev;
 
3838
        cw->prev = 0;
 
3839
        cw = cs->lw;
 
3840
        printDot();
 
3841
        return true;
 
3842
    }
 
3843
    /* M */
 
3844
    if(cmd == 'A') {
 
3845
        char *a;
 
3846
        if(!cxQuit(context, 0))
 
3847
            return false;
 
3848
        if(!(a = showLinks()))
 
3849
            return false;
 
3850
        freeUndoLines(cw->map);
 
3851
        undoWindow.map = 0;
 
3852
        nzFree(preWindow.map);
 
3853
        preWindow.map = 0;
 
3854
        cw->firstOpMode = cw->changeMode = false;
 
3855
        w = createWindow();
 
3856
        w->prev = cw;
 
3857
        cw = w;
 
3858
        cs->lw = w;
 
3859
        rc = addTextToBuffer((pst) a, strlen(a), 0);
 
3860
        nzFree(a);
 
3861
        undoable = cw->changeMode = false;
 
3862
        fileSize = apparentSize(context, false);
 
3863
        return rc;
 
3864
    }
 
3865
    /* A */
 
3866
    if(cmd == '<') {            /* run a function */
 
3867
        return runEbFunction(line);
 
3868
    }
 
3869
 
 
3870
    /* < */
 
3871
    /* go to a file in a directory listing */
 
3872
    if(cmd == 'g' && cw->dirMode && !first) {
 
3873
        char *p, *dirline, *endline;
 
3874
        if(endRange > startRange) {
 
3875
            setError(MSG_RangeG);
 
3876
            return false;
 
3877
        }
 
3878
        p = (char *)fetchLine(endRange, -1);
 
3879
        j = pstLength((pst) p);
 
3880
        --j;
 
3881
        p[j] = 0;               /* temporary */
 
3882
        dirline = makeAbsPath(p);
 
3883
        p[j] = '\n';
 
3884
        cmd = 'e';
 
3885
        if(!dirline)
 
3886
            return false;
 
3887
/* I don't think we need to make a copy here. */
 
3888
        line = dirline;
 
3889
        first = *line;
 
3890
    }
 
3891
    /* g in directory mode */
 
3892
    if(cmd == 'e') {
 
3893
        if(cx) {
 
3894
            if(!cxCompare(cx))
 
3895
                return false;
 
3896
            cxSwitch(cx, true);
 
3897
            return true;
 
3898
        }
 
3899
        if(!first) {
 
3900
            i_printf(MSG_SessionX, context);
 
3901
            return true;
 
3902
        }
 
3903
/* more e to come */
 
3904
    }
 
3905
 
 
3906
    if(cmd == 'g') {            /* see if it's a go command */
 
3907
        char *p, *h;
 
3908
        int tagno;
 
3909
        bool click, dclick, over;
 
3910
        bool jsh, jsgo, jsdead;
 
3911
        s = line;
 
3912
        j = 0;
 
3913
        if(first)
 
3914
            j = strtol(line, (char **)&s, 10);
 
3915
        if(j >= 0 && !*s) {
 
3916
            if(cw->sqlMode) {
 
3917
                setError(MSG_DBG);
 
3918
                return false;
 
3919
            }
 
3920
            jsh = jsgo = nogo = false;
 
3921
            jsdead = cw->jsdead;
 
3922
            if(!cw->jsc)
 
3923
                jsdead = true;
 
3924
            click = dclick = over = false;
 
3925
            cmd = 'b';
 
3926
            if(endRange > startRange) {
 
3927
                setError(MSG_RangeG);
 
3928
                return false;
 
3929
            }
 
3930
            p = (char *)fetchLine(endRange, -1);
 
3931
            jMyContext();
 
3932
            findField(p, 0, j, &n, 0, &tagno, &h, &ev);
 
3933
            debugPrint(5, "findField returns %d, %s", tagno, h);
 
3934
            if(!h) {
 
3935
                fieldNumProblem(1, 'g', j, n, n);
 
3936
                return false;
 
3937
            }
 
3938
            jsh = memEqualCI(h, "javascript:", 11);
 
3939
            if(tagno) {
 
3940
                over = tagHandler(tagno, "onmouseover");
 
3941
                click = tagHandler(tagno, "onclick");
 
3942
                dclick = tagHandler(tagno, "ondblclick");
 
3943
            }
 
3944
            if(click)
 
3945
                jsgo = true;
 
3946
            jsgo |= jsh;
 
3947
            nogo = stringEqual(h, "#");
 
3948
            nogo |= jsh;
 
3949
            debugPrint(5, "go%d nogo%d jsh%d dead%d", jsgo, nogo, jsh, jsdead);
 
3950
            debugPrint(5, "click %d dclick %d over %d", click, dclick, over);
 
3951
            if(jsgo & jsdead) {
 
3952
                if(nogo)
 
3953
                    i_puts(MSG_NJNoAction);
 
3954
                else
 
3955
                    i_puts(MSG_NJGoing);
 
3956
                jsgo = jsh = false;
 
3957
            }
 
3958
            line = allocatedLine = h;
 
3959
            first = *line;
 
3960
            setError(-1);
 
3961
            rc = false;
 
3962
            if(jsgo) {
 
3963
                jSyncup();
 
3964
/* The program might depend on the mouseover code running first */
 
3965
                if(over) {
 
3966
                    rc = handlerGo(ev, "onmouseover");
 
3967
                    jsdw();
 
3968
                    if(newlocation)
 
3969
                        goto redirect;
 
3970
                }
 
3971
            }
 
3972
/* This is the only handler where false tells the browser to do something else. */
 
3973
            if(!rc && !jsdead)
 
3974
                set_property_string(jwin, "status", h);
 
3975
            if(jsgo && click) {
 
3976
                rc = handlerGo(ev, "onclick");
 
3977
                jsdw();
 
3978
                if(newlocation)
 
3979
                    goto redirect;
 
3980
                if(!rc)
 
3981
                    return true;
 
3982
            }
 
3983
            if(jsh) {
 
3984
                rc = javaParseExecute(jwin, h, 0, 0);
 
3985
                jsdw();
 
3986
                if(newlocation)
 
3987
                    goto redirect;
 
3988
                return true;
 
3989
            }
 
3990
            if(nogo)
 
3991
                return true;
 
3992
        }                       /* go command */
 
3993
    }
 
3994
 
 
3995
    if(cmd == 's') {
 
3996
/* Some shorthand, like s,2 to split the line at the second comma */
 
3997
        if(!first) {
 
3998
            strcpy(newline, "//%");
 
3999
            line = newline;
 
4000
        } else if(strchr(",.;:!?)-\"", first) &&
 
4001
           (!line[1] || isdigitByte(line[1]) && !line[2])) {
 
4002
            char esc[2];
 
4003
            esc[0] = esc[1] = 0;
 
4004
            if(first == '.' || first == '?')
 
4005
                esc[0] = '\\';
 
4006
            sprintf(newline, "/%s%c +/%c\\n%s%s",
 
4007
               esc, first, first, (line[1] ? "/" : ""), line + 1);
 
4008
            debugPrint(7, "shorthand regexp %s", newline);
 
4009
            line = newline;
 
4010
        }
 
4011
        first = *line;
 
4012
    }
 
4013
 
 
4014
    scmd = ' ';
 
4015
    if((cmd == 'i' || cmd == 's') && first) {
 
4016
        char c;
 
4017
        s = line;
 
4018
        if(isdigitByte(*s))
 
4019
            cx = strtol(s, (char **)&s, 10);
 
4020
        c = *s;
 
4021
        if(c && (strchr(valid_delim, c) || cmd == 'i' && strchr("*<?=", c))) {
 
4022
            if(!cw->browseMode && (cmd == 'i' || cx)) {
 
4023
                setError(MSG_NoBrowse);
 
4024
                return false;
 
4025
            }
 
4026
            if(endRange > startRange && cmd == 'i') {
 
4027
                setError(MSG_RangeI, c);
 
4028
                return false;
 
4029
            }
 
4030
            if(cmd == 'i' && strchr("?=<*", c)) {
 
4031
                char *p;
 
4032
                int realtotal;
 
4033
                scmd = c;
 
4034
                line = s + 1;
 
4035
                first = *line;
 
4036
                debugPrint(5, "scmd = %c", scmd);
 
4037
                cw->dot = endRange;
 
4038
                p = (char *)fetchLine(cw->dot, -1);
 
4039
                j = 1;
 
4040
                if(scmd == '*')
 
4041
                    j = 2;
 
4042
                if(scmd == '?')
 
4043
                    j = 3;
 
4044
                findInputField(p, j, cx, &n, &realtotal, &tagno);
 
4045
                debugPrint(5, "findField returns %d.%d", n, tagno);
 
4046
                if(!tagno) {
 
4047
                    fieldNumProblem((c == '*' ? 2 : 0), 'i', cx, n, realtotal);
 
4048
                    return false;
 
4049
                }
 
4050
                if(scmd == '?') {
 
4051
                    infShow(tagno, line);
 
4052
                    return true;
 
4053
                }
 
4054
                if(c == '<') {
 
4055
                    bool fromfile = false;
 
4056
                    if(globSub) {
 
4057
                        setError(MSG_IG);
 
4058
                        return (globSub = false);
 
4059
                    }
 
4060
                    skipWhite(&line);
 
4061
                    if(!*line) {
 
4062
                        setError(MSG_NoFileSpecified);
 
4063
                        return false;
 
4064
                    }
 
4065
                    n = stringIsNum(line);
 
4066
                    if(n >= 0) {
 
4067
                        char *p;
 
4068
                        int plen, dol;
 
4069
                        if(!cxCompare(n) || !cxActive(n))
 
4070
                            return false;
 
4071
                        dol = sessionList[n].lw->dol;
 
4072
                        if(!dol) {
 
4073
                            setError(MSG_BufferXEmpty, n);
 
4074
                            return false;
 
4075
                        }
 
4076
                        if(dol > 1) {
 
4077
                            setError(MSG_BufferXLines, n);
 
4078
                            return false;
 
4079
                        }
 
4080
                        p = (char *)fetchLineContext(1, 1, n);
 
4081
                        plen = pstLength((pst) p);
 
4082
                        if(plen > sizeof (newline))
 
4083
                            plen = sizeof (newline);
 
4084
                        memcpy(newline, p, plen);
 
4085
                        n = plen;
 
4086
                        nzFree(p);
 
4087
                    } else {
 
4088
                        int fd;
 
4089
                        fromfile = true;
 
4090
                        if(!envFile(line, &line))
 
4091
                            return false;
 
4092
                        fd = open(line, O_RDONLY | O_TEXT);
 
4093
                        if(fd < 0) {
 
4094
                            setError(MSG_NoOpen, line);
 
4095
                            return false;
 
4096
                        }
 
4097
                        n = read(fd, newline, sizeof (newline));
 
4098
                        close(fd);
 
4099
                        if(n < 0) {
 
4100
                            setError(MSG_NoRead, line);
 
4101
                            return false;
 
4102
                        }
 
4103
                    }
 
4104
                    for(j = 0; j < n; ++j) {
 
4105
                        if(newline[j] == 0) {
 
4106
                            setError(MSG_InputNull, line);
 
4107
                            return false;
 
4108
                        }
 
4109
                        if(newline[j] == '\r' && !fromfile &&
 
4110
                           j < n - 1 && newline[j + 1] != '\n') {
 
4111
                            setError(MSG_InputCR);
 
4112
                            return false;
 
4113
                        }
 
4114
                        if(newline[j] == '\r' || newline[j] == '\n')
 
4115
                            break;
 
4116
                    }
 
4117
                    if(j == sizeof (newline)) {
 
4118
                        setError(MSG_FirstLineLong, line);
 
4119
                        return false;
 
4120
                    }
 
4121
                    newline[j] = 0;
 
4122
                    line = newline;
 
4123
                    scmd = '=';
 
4124
                }
 
4125
                if(c == '*') {
 
4126
                    readyUndo();
 
4127
                    jSyncup();
 
4128
                    if(!infPush(tagno, &allocatedLine))
 
4129
                        return false;
 
4130
                    if(newlocation)
 
4131
                        goto redirect;
 
4132
/* No url means it was a reset button */
 
4133
                    if(!allocatedLine)
 
4134
                        return true;
 
4135
                    line = allocatedLine;
 
4136
                    first = *line;
 
4137
                    cmd = 'b';
 
4138
                }
 
4139
            } else
 
4140
                cmd = 's';
 
4141
        } else {
 
4142
            setError(MSG_TextAfter, icmd);
 
4143
            return false;
 
4144
        }
 
4145
    }
 
4146
 
 
4147
  rebrowse:
 
4148
    if(cmd == 'e' || cmd == 'b' && first && first != '#') {
 
4149
        if(cw->fileName && !noStack && sameURL(line, cw->fileName)) {
 
4150
            if(stringEqual(line, cw->fileName)) {
 
4151
                setError(MSG_AlreadyInBuffer);
 
4152
                return false;
 
4153
            }
 
4154
/* Same url, but a different #section */
 
4155
            s = strchr(line, '#');
 
4156
            if(!s) {            /* no section specified */
 
4157
                cw->dot = 1;
 
4158
                if(!cw->dol)
 
4159
                    cw->dot = 0;
 
4160
                printDot();
 
4161
                return true;
 
4162
            }
 
4163
            line = s;
 
4164
            first = '#';
 
4165
            goto browse;
 
4166
        }
 
4167
 
 
4168
/* Different URL, go get it. */
 
4169
/* did you make changes that you didn't write? */
 
4170
        if(!cxQuit(context, 0))
 
4171
            return false;
 
4172
        freeUndoLines(cw->map);
 
4173
        undoWindow.map = 0;
 
4174
        nzFree(preWindow.map);
 
4175
        preWindow.map = 0;
 
4176
        cw->firstOpMode = cw->changeMode = false;
 
4177
        startRange = endRange = 0;
 
4178
        changeFileName = 0;     /* should already be zero */
 
4179
        w = createWindow();
 
4180
        cw = w;                 /* we might wind up putting this back */
 
4181
/* Check for sendmail link */
 
4182
        if(cmd == 'b' && memEqualCI(line, "mailto:", 7)) {
 
4183
            char *addr, *subj, *body;
 
4184
            char *q;
 
4185
            int ql;
 
4186
            decodeMailURL(line, &addr, &subj, &body);
 
4187
            ql = strlen(addr);
 
4188
            ql += 4;            /* to:\n */
 
4189
            ql += subj ? strlen(subj) : 5;
 
4190
            ql += 9;            /* subject:\n */
 
4191
            if(body)
 
4192
                ql += strlen(body);
 
4193
            q = allocMem(ql + 1);
 
4194
            sprintf(q, "to:%s\nSubject:%s\n%s", addr, subj ? subj : "Hello",
 
4195
               body ? body : "");
 
4196
            j = addTextToBuffer((pst) q, ql, 0);
 
4197
            nzFree(q);
 
4198
            nzFree(addr);
 
4199
            nzFree(subj);
 
4200
            nzFree(body);
 
4201
            if(j)
 
4202
                i_puts(MSG_MailHowto);
 
4203
        } else {
 
4204
            cw->fileName = cloneString(line);
 
4205
            if(isSQL(line))
 
4206
                cw->sqlMode = true;
 
4207
            if(icmd == 'g' && !nogo && isURL(line))
 
4208
                debugPrint(2, "*%s", line);
 
4209
            j = readFile(line, "");
 
4210
        }
 
4211
        w->firstOpMode = w->changeMode = false;
 
4212
        undoable = false;
 
4213
        cw = cs->lw;
 
4214
/* Don't push a new session if we were trying to read a url,
 
4215
 * and didn't get anything.  This is a feature that I'm
 
4216
 * not sure if I really like. */
 
4217
        if(!serverData && (isURL(line) || isSQL(line))) {
 
4218
            fileSize = -1;
 
4219
            freeWindow(w);
 
4220
            if(noStack && cw->prev) {
 
4221
                w = cw;
 
4222
                cw = w->prev;
 
4223
                cs->lw = cw;
 
4224
                freeWindow(w);
 
4225
            }
 
4226
            return j;
 
4227
        }
 
4228
        if(noStack) {
 
4229
            w->prev = cw->prev;
 
4230
            cxQuit(context, 1);
 
4231
        } else {
 
4232
            w->prev = cw;
 
4233
        }
 
4234
        cs->lw = cw = w;
 
4235
        if(!w->prev)
 
4236
            cs->fw = w;
 
4237
        if(!j)
 
4238
            return false;
 
4239
        if(changeFileName) {
 
4240
            nzFree(w->fileName);
 
4241
            w->fileName = changeFileName;
 
4242
            changeFileName = 0;
 
4243
        }
 
4244
/* Some files we just can't browse */
 
4245
        if(!cw->dol || cw->dirMode)
 
4246
            cmd = 'e';
 
4247
        if(cw->binMode && !stringIsPDF(cw->fileName))
 
4248
            cmd = 'e';
 
4249
        if(cmd == 'e')
 
4250
            return true;
 
4251
    }
 
4252
 
 
4253
  browse:
 
4254
    if(cmd == 'b') {
 
4255
        if(!cw->browseMode) {
 
4256
            if(cw->binMode && !stringIsPDF(cw->fileName)) {
 
4257
                setError(MSG_BrowseBinary);
 
4258
                return false;
 
4259
            }
 
4260
            if(!cw->dol) {
 
4261
                setError(MSG_BrowseEmpty);
 
4262
                return false;
 
4263
            }
 
4264
            if(fileSize >= 0) {
 
4265
                debugPrint(1, "%d", fileSize);
 
4266
                fileSize = -1;
 
4267
            }
 
4268
            if(!browseCurrentBuffer()) {
 
4269
                if(icmd == 'b')
 
4270
                    return false;
 
4271
                return true;
 
4272
            }
 
4273
        } else if(!first) {
 
4274
            setError(MSG_BrowseAlready);
 
4275
            return false;
 
4276
        }
 
4277
 
 
4278
        if(newlocation) {
 
4279
            if(!refreshDelay(newloc_d, newlocation)) {
 
4280
                nzFree(newlocation);
 
4281
                newlocation = 0;
 
4282
            } else if(fetchHistory(cw->fileName, newlocation) < 0) {
 
4283
                nzFree(newlocation);
 
4284
                newlocation = 0;
 
4285
                showError();
 
4286
            } else {
 
4287
              redirect:
 
4288
                noStack = newloc_rf;
 
4289
                nzFree(allocatedLine);
 
4290
                line = allocatedLine = newlocation;
 
4291
                debugPrint(2, "redirect %s", line);
 
4292
                newlocation = 0;
 
4293
                icmd = cmd = 'b';
 
4294
                first = *line;
 
4295
                if(intFlag) {
 
4296
                    i_puts(MSG_RedirectionInterrupted);
 
4297
                    return true;
 
4298
                }
 
4299
                goto rebrowse;
 
4300
            }
 
4301
        }
 
4302
 
 
4303
/* Jump to the #section, if specified in the url */
 
4304
        s = strchr(line, '#');
 
4305
        if(!s)
 
4306
            return true;
 
4307
        ++s;
 
4308
/* Sometimes there's a 3 in the midst of a long url,
 
4309
 * probably with post data.  It really screws things up.
 
4310
 * Here is a kludge to avoid this problem.
 
4311
 * Some day I need to figure this out. */
 
4312
        if(strlen(s) > 24)
 
4313
            return true;
 
4314
/* Print the file size before we print the line. */
 
4315
        if(fileSize >= 0) {
 
4316
            debugPrint(1, "%d", fileSize);
 
4317
            fileSize = -1;
 
4318
        }
 
4319
        for(i = 1; i <= cw->dol; ++i) {
 
4320
            char *p = (char *)fetchLine(i, -1);
 
4321
            if(lineHasTag(p, s)) {
 
4322
                cw->dot = i;
 
4323
                printDot();
 
4324
                return true;
 
4325
            }
 
4326
        }
 
4327
        setError(MSG_NoLable2, s);
 
4328
        return false;
 
4329
    }
 
4330
 
 
4331
    readyUndo();
 
4332
 
 
4333
    if(cmd == 'g' || cmd == 'v') {
 
4334
        return doGlobal(line);
 
4335
    }
 
4336
 
 
4337
    if(cmd == 'm' || cmd == 't') {
 
4338
        return moveCopy();
 
4339
    }
 
4340
 
 
4341
    if(cmd == 'i') {
 
4342
        if(scmd == '=') {
 
4343
            rc = infReplace(tagno, line, 1);
 
4344
            if(newlocation)
 
4345
                goto redirect;
 
4346
            return rc;
 
4347
        }
 
4348
        if(cw->browseMode) {
 
4349
            setError(MSG_BrowseI);
 
4350
            return false;
 
4351
        }
 
4352
        cmd = 'a';
 
4353
        --startRange, --endRange;
 
4354
    }
 
4355
 
 
4356
    if(cmd == 'c') {
 
4357
        delText(startRange, endRange);
 
4358
        endRange = --startRange;
 
4359
        cmd = 'a';
 
4360
    }
 
4361
 
 
4362
    if(cmd == 'a') {
 
4363
        if(inscript) {
 
4364
            setError(MSG_InsertFunction);
 
4365
            return false;
 
4366
        }
 
4367
        if(cw->sqlMode) {
 
4368
            j = cw->dol;
 
4369
            rc = sqlAddRows(endRange);
 
4370
/* adjust dot */
 
4371
            j = cw->dol - j;
 
4372
            if(j)
 
4373
                cw->dot = endRange + j;
 
4374
            else if(!endRange && cw->dol)
 
4375
                cw->dot = 1;
 
4376
            else
 
4377
                cw->dot = endRange;
 
4378
            return rc;
 
4379
        }
 
4380
        return inputLinesIntoBuffer();
 
4381
    }
 
4382
 
 
4383
    if(cmd == 'd' || cmd == 'D') {
 
4384
        if(cw->dirMode) {
 
4385
            j = delFiles();
 
4386
            goto afterdelete;
 
4387
        }
 
4388
        if(cw->sqlMode) {
 
4389
            j = sqlDelRows(startRange, endRange);
 
4390
            goto afterdelete;
 
4391
        }
 
4392
        delText(startRange, endRange);
 
4393
        j = 1;
 
4394
      afterdelete:
 
4395
        if(!j)
 
4396
            globSub = false;
 
4397
        else if(cmd == 'D')
 
4398
            printDot();
 
4399
        return j;
 
4400
    }
 
4401
 
 
4402
    if(cmd == 'j' || cmd == 'J') {
 
4403
        return joinText();
 
4404
    }
 
4405
 
 
4406
    if(cmd == 'r') {
 
4407
        if(cx)
 
4408
            return readContext(cx);
 
4409
        if(first) {
 
4410
            if(cw->sqlMode && !isSQL(line)) {
 
4411
                j = 0;          /* count the dashes */
 
4412
                s = strchr(line, '=');
 
4413
                if(s) {
 
4414
                    const char *q;
 
4415
                    for(q = line; q < s; ++q)
 
4416
                        if(!isalnumByte(*q))
 
4417
                            goto notwhere;
 
4418
                    ++s;
 
4419
                } else {
 
4420
                    s = line;
 
4421
                }
 
4422
                if(stringEqual(s, "*"))
 
4423
                    goto sqlwhere;
 
4424
                for(; *s; ++s) {
 
4425
                    if(*s == '-' && s > line && s[1]) {
 
4426
                        ++j;
 
4427
                        continue;
 
4428
                    }
 
4429
                    if(isdigitByte(*s))
 
4430
                        continue;
 
4431
                    if(*s == '/')
 
4432
                        continue;
 
4433
                    goto notwhere;
 
4434
                }
 
4435
                if(j > 1) {
 
4436
                  notwhere:
 
4437
                    setError(MSG_readText);
 
4438
                    return false;
 
4439
                }
 
4440
              sqlwhere:
 
4441
                strcpy(newline, cw->fileName);
 
4442
                strcpy(strchr(newline, ']') + 1, line);
 
4443
                line = newline;
 
4444
            }
 
4445
            j = readFile(line, "");
 
4446
            if(!serverData)
 
4447
                fileSize = -1;
 
4448
            return j;
 
4449
        }
 
4450
        setError(MSG_NoFileSpecified);
 
4451
        return false;
 
4452
    }
 
4453
 
 
4454
    if(cmd == 's') {
 
4455
        j = substituteText(line);
 
4456
        if(j < 0) {
 
4457
            globSub = false;
 
4458
            j = false;
 
4459
        }
 
4460
        if(newlocation)
 
4461
            goto redirect;
 
4462
        return j;
 
4463
    }
 
4464
 
 
4465
    setError(MSG_CNYI, icmd);
 
4466
    return (globSub = false);
 
4467
}                               /* runCommand */
 
4468
 
 
4469
bool
 
4470
edbrowseCommand(const char *line, bool script)
 
4471
{
 
4472
    bool rc;
 
4473
    globSub = intFlag = false;
 
4474
    inscript = script;
 
4475
    fileSize = -1;
 
4476
    skipWhite(&line);
 
4477
    rc = runCommand(line);
 
4478
    if(fileSize >= 0)
 
4479
        debugPrint(1, "%d", fileSize);
 
4480
    fileSize = -1;
 
4481
    if(!rc) {
 
4482
        if(!script)
 
4483
            showErrorConditional(cmd);
 
4484
        eeCheck();
 
4485
    }
 
4486
    if(undoable) {
 
4487
        struct ebWindow *pw = &preWindow;
 
4488
        struct ebWindow *uw = &undoWindow;
 
4489
        debugPrint(6, "undoable");
 
4490
        uw->dot = pw->dot;
 
4491
        uw->dol = pw->dol;
 
4492
        if(uw->map && pw->map && stringEqual(uw->map, pw->map)) {
 
4493
            free(pw->map);
 
4494
        } else {
 
4495
            debugPrint(6, "success freeUndo");
 
4496
            freeUndoLines(pw->map);
 
4497
            uw->map = pw->map;
 
4498
        }
 
4499
        pw->map = 0;
 
4500
        memcpy(uw->labels, pw->labels, 26 * sizeof (int));
 
4501
        uw->binMode = pw->binMode;
 
4502
        uw->nlMode = pw->nlMode;
 
4503
        uw->dirMode = pw->dirMode;
 
4504
        undoable = false;
 
4505
    }
 
4506
    return rc;
 
4507
}                               /* edbrowseCommand */
 
4508
 
 
4509
/* Take some text, usually empty, and put it in a side buffer. */
 
4510
int
 
4511
sideBuffer(int cx, const char *text, int textlen,
 
4512
   const char *bufname, bool autobrowse)
 
4513
{
 
4514
    int svcx = context;
 
4515
    bool rc;
 
4516
    if(cx) {
 
4517
        cxQuit(cx, 2);
 
4518
    } else {
 
4519
        for(cx = 1; cx < MAXSESSION; ++cx)
 
4520
            if(!sessionList[cx].lw)
 
4521
                break;
 
4522
        if(cx == MAXSESSION) {
 
4523
            i_puts(MSG_NoBufferExtraWindow);
 
4524
            return 0;
 
4525
        }
 
4526
    }
 
4527
    cxSwitch(cx, false);
 
4528
    if(bufname) {
 
4529
        cw->fileName = cloneString(bufname);
 
4530
        debrowseSuffix(cw->fileName);
 
4531
    }
 
4532
    if(textlen < 0) {
 
4533
        textlen = strlen(text);
 
4534
    } else {
 
4535
        cw->binMode = looksBinary(text, textlen);
 
4536
    }
 
4537
    if(textlen) {
 
4538
        rc = addTextToBuffer((pst) text, textlen, 0);
 
4539
        if(!rc)
 
4540
            i_printf(MSG_BufferPreload, cx);
 
4541
        if(autobrowse) {
 
4542
/* This is html; we need to render it.
 
4543
 * I'm disabling javascript in this window.
 
4544
 * Why?
 
4545
 * Because this window is being created by javascript,
 
4546
 * and if we call more javascript, well, I don't think
 
4547
 * any of that code is reentrant.
 
4548
 * Smells like a disaster in the making. */
 
4549
            allowJS = false;
 
4550
            browseCurrentBuffer();
 
4551
            allowJS = true;
 
4552
        }                       /* browse the side window */
 
4553
    }
 
4554
    /* back to original context */
 
4555
    cxSwitch(svcx, false);
 
4556
    return cx;
 
4557
}                               /* sideBuffer */
 
4558
 
 
4559
/* Bailing wire and duct tape, here it comes.
 
4560
 * You'd never know I was a professional programmer.  :-)
 
4561
 * Ok, the text buffer, that you edit, is the final arbiter on most
 
4562
 * (but not all) input fields.  And when javascript changes
 
4563
 * one of these values, it is copied back to the text buffer,
 
4564
 * where you can see it, and modify it as you like.
 
4565
 * But what happens if that javascript is running while the html is parsed,
 
4566
 * and before the page even exists?
 
4567
 * I hadn't thought of that.
 
4568
 * The following is a cache of input fields that have been changed,
 
4569
 * before we even had a chance to render the page. */
 
4570
 
 
4571
struct inputChange {
 
4572
    struct inputChange *next, *prev;
 
4573
    int tagno;
 
4574
    char value[4];
 
4575
};
 
4576
static struct listHead inputChangesPending = {
 
4577
    &inputChangesPending, &inputChangesPending
 
4578
};
 
4579
static struct inputChange *ic;
 
4580
 
 
4581
bool
 
4582
browseCurrentBuffer(void)
 
4583
{
 
4584
    char *rawbuf, *newbuf;
 
4585
    int rawsize, j;
 
4586
    bool rc, remote = false, do_ip = false, ispdf = false;
 
4587
    bool save_ch = cw->changeMode;
 
4588
    uchar bmode = 0;
 
4589
 
 
4590
    if(cw->fileName) {
 
4591
        remote = isURL(cw->fileName);
 
4592
        ispdf = stringIsPDF(cw->fileName);
 
4593
    }
 
4594
 
 
4595
/* I'm trusting the pdf suffix, and not looking inside. */
 
4596
    if(ispdf)
 
4597
        bmode = 3;
 
4598
/* A mail message often contains lots of html tags,
 
4599
 * so we need to check for email headers first. */
 
4600
    else if(!remote && emailTest())
 
4601
        bmode = 1;
 
4602
    else if(htmlTest())
 
4603
        bmode = 2;
 
4604
    else {
 
4605
        setError(MSG_Unbrowsable);
 
4606
        return false;
 
4607
    }
 
4608
 
 
4609
    if(!unfoldBuffer(context, false, &rawbuf, &rawsize))
 
4610
        return false;           /* should never happen */
 
4611
 
 
4612
/* expand pdf using pdftohtml */
 
4613
/* http://rpmfind.net/linux/RPM/suse/updates/10.0/i386/rpm/i586/pdftohtml-0.36-130.9.i586.html */
 
4614
    if(bmode == 3) {
 
4615
        char *tbuf;
 
4616
        int tlen;
 
4617
        char *cmd;
 
4618
        if(!memoryOutToFile(edbrowseTempPDF, rawbuf, rawsize,
 
4619
           MSG_TempNoCreate2, MSG_TempNoWrite)) {
 
4620
            nzFree(rawbuf);
 
4621
            return false;
 
4622
        }
 
4623
        nzFree(rawbuf);
 
4624
        unlink(edbrowseTempHTML);
 
4625
        j = strlen(edbrowseTempPDF);
 
4626
        cmd = allocMem(50 + j);
 
4627
        sprintf(cmd, "pdftohtml -i -noframes %s >/dev/null 2>&1",
 
4628
           edbrowseTempPDF);
 
4629
        system(cmd);
 
4630
        nzFree(cmd);
 
4631
        if(fileSizeByName(edbrowseTempHTML) <= 0) {
 
4632
            setError(MSG_NoPDF, edbrowseTempPDF);
 
4633
            return false;
 
4634
        }
 
4635
        rc = fileIntoMemory(edbrowseTempHTML, &rawbuf, &rawsize);
 
4636
        if(!rc)
 
4637
            return false;
 
4638
        iuReformat(rawbuf, rawsize, &tbuf, &tlen);
 
4639
        if(tbuf) {
 
4640
            nzFree(rawbuf);
 
4641
            rawbuf = tbuf;
 
4642
            rawsize = tlen;
 
4643
        }
 
4644
        bmode = 2;
 
4645
    }
 
4646
 
 
4647
    prepareForBrowse(rawbuf, rawsize);
 
4648
 
 
4649
/* No harm in running this code in mail client, but no help either,
 
4650
 * and it begs for bugs, so leave it out. */
 
4651
    if(!ismc) {
 
4652
        freeUndoLines(cw->map);
 
4653
        undoWindow.map = 0;
 
4654
        nzFree(preWindow.map);
 
4655
        preWindow.map = 0;
 
4656
        cw->firstOpMode = false;
 
4657
 
 
4658
/* There shouldn't be anything in the input pending list, but clear
 
4659
 * it out, just to be safe. */
 
4660
        freeList(&inputChangesPending);
 
4661
    }
 
4662
 
 
4663
    if(bmode == 1) {
 
4664
        newbuf = emailParse(rawbuf);
 
4665
        do_ip = true;
 
4666
        if(!ipbFile)
 
4667
            do_ip = false;
 
4668
        if(passMail)
 
4669
            do_ip = false;
 
4670
        if(memEqualCI(newbuf, "<html>\n", 7) && allowRedirection) {
 
4671
/* double browse, mail then html */
 
4672
            bmode = 2;
 
4673
            rawbuf = newbuf;
 
4674
            rawsize = strlen(rawbuf);
 
4675
            prepareForBrowse(rawbuf, rawsize);
 
4676
        }
 
4677
    }
 
4678
 
 
4679
    if(bmode == 2) {
 
4680
        cw->jsdead = !javaOK(cw->fileName);
 
4681
        if(!cw->jsdead)
 
4682
            cw->jsc = createJavaContext();
 
4683
        nzFree(newlocation);    /* should already be 0 */
 
4684
        newlocation = 0;
 
4685
        newbuf = htmlParse(rawbuf, remote);
 
4686
    }
 
4687
 
 
4688
    cw->browseMode = true;
 
4689
    cw->rnlMode = cw->nlMode;
 
4690
    cw->nlMode = false;
 
4691
    cw->r_dot = cw->dot, cw->r_dol = cw->dol;
 
4692
    cw->dot = cw->dol = 0;
 
4693
    cw->r_map = cw->map;
 
4694
    cw->map = 0;
 
4695
    memcpy(cw->r_labels, cw->labels, sizeof (cw->labels));
 
4696
    memset(cw->labels, 0, sizeof (cw->labels));
 
4697
    j = strlen(newbuf);
 
4698
    rc = addTextToBuffer((pst) newbuf, j, 0);
 
4699
    free(newbuf);
 
4700
    cw->firstOpMode = undoable = false;
 
4701
    cw->changeMode = save_ch;
 
4702
    cw->iplist = 0;
 
4703
 
 
4704
    if(cw->fileName) {
 
4705
        j = strlen(cw->fileName);
 
4706
        cw->fileName = reallocMem(cw->fileName, j + 8);
 
4707
        strcat(cw->fileName, ".browse");
 
4708
    }
 
4709
    if(!rc) {
 
4710
        fileSize = -1;
 
4711
        return false;
 
4712
    }                           /* should never happen */
 
4713
    fileSize = apparentSize(context, true);
 
4714
 
 
4715
    if(bmode == 2) {
 
4716
/* apply any input changes pending */
 
4717
        foreach(ic, inputChangesPending)
 
4718
           updateFieldInBuffer(ic->tagno, ic->value, 0, false);
 
4719
        freeList(&inputChangesPending);
 
4720
    }
 
4721
 
 
4722
    if(do_ip & ismc)
 
4723
        allIPs();
 
4724
    return true;
 
4725
}                               /* browseCurrentBuffer */
 
4726
 
 
4727
static bool
 
4728
locateTagInBuffer(int tagno, int *ln_p, char **p_p, char **s_p, char **t_p)
 
4729
{
 
4730
    int ln, n;
 
4731
    char *p, *s, *t, c;
 
4732
    char search[20];
 
4733
    char searchend[4];
 
4734
 
 
4735
    if(parsePage) {
 
4736
        foreachback(ic, inputChangesPending) {
 
4737
            if(ic->tagno != tagno)
 
4738
                continue;
 
4739
            *s_p = ic->value;
 
4740
            *t_p = ic->value + strlen(ic->value);
 
4741
/* we don't need to set the others in this special case */
 
4742
            return true;
 
4743
        }
 
4744
        return false;
 
4745
    }
 
4746
    /* still rendering the page */
 
4747
    sprintf(search, "%c%d<", InternalCodeChar, tagno);
 
4748
    sprintf(searchend, "%c0>", InternalCodeChar);
 
4749
    n = strlen(search);
 
4750
    for(ln = 1; ln <= cw->dol; ++ln) {
 
4751
        p = (char *)fetchLine(ln, -1);
 
4752
        for(s = p; (c = *s) != '\n'; ++s) {
 
4753
            if(c != InternalCodeChar)
 
4754
                continue;
 
4755
            if(!memcmp(s, search, n))
 
4756
                break;
 
4757
        }
 
4758
        if(c == '\n')
 
4759
            continue;           /* not here, try next line */
 
4760
        s = strchr(s, '<') + 1;
 
4761
        t = strstr(s, searchend);
 
4762
        if(!t)
 
4763
            i_printfExit(MSG_NoClosingLine, ln);
 
4764
        *ln_p = ln;
 
4765
        *p_p = p;
 
4766
        *s_p = s;
 
4767
        *t_p = t;
 
4768
        return true;
 
4769
    }
 
4770
 
 
4771
    return false;
 
4772
}                               /* locateTagInBuffer */
 
4773
 
 
4774
/* Update an input field in the current buffer.
 
4775
 * The input field may not be here, if you've deleted some lines. */
 
4776
void
 
4777
updateFieldInBuffer(int tagno, const char *newtext, int notify, bool required)
 
4778
{
 
4779
    int ln, idx, n, plen;
 
4780
    char *p, *s, *t, *new;
 
4781
    char newidx[LNWIDTH + 1];
 
4782
 
 
4783
    if(parsePage) {             /* we don't even have the buffer yet */
 
4784
        ic = allocMem(sizeof (struct inputChange) + strlen(newtext));
 
4785
        ic->tagno = tagno;
 
4786
        strcpy(ic->value, newtext);
 
4787
        addToListBack(&inputChangesPending, ic);
 
4788
        return;
 
4789
    }
 
4790
 
 
4791
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
 
4792
        n = (plen = pstLength((pst) p)) + strlen(newtext) - (t - s);
 
4793
        new = allocMem(n);
 
4794
        memcpy(new, p, s - p);
 
4795
        strcpy(new + (s - p), newtext);
 
4796
        memcpy(new + strlen(new), t, plen - (t - p));
 
4797
        idx = textLinesCount++;
 
4798
        sprintf(newidx, LNFORMAT, idx);
 
4799
        memcpy(cw->map + ln * LNWIDTH, newidx, LNWIDTH);
 
4800
        textLines[idx] = (pst) new;
 
4801
/* In case a javascript routine updates a field that you weren't expecting */
 
4802
        if(notify == 1)
 
4803
            displayLine(ln);
 
4804
        if(notify == 2)
 
4805
            i_printf(MSG_LineUpdated, ln);
 
4806
        cw->firstOpMode = true;
 
4807
        undoable = true;
 
4808
        return;
 
4809
    }
 
4810
 
 
4811
    if(required)
 
4812
        i_printfExit(MSG_NoTagFound, tagno, newtext);
 
4813
}                               /* updateFieldInBuffer */
 
4814
 
 
4815
/* This is the inverse of the above function, fetch instead of update. */
 
4816
char *
 
4817
getFieldFromBuffer(int tagno)
 
4818
{
 
4819
    int ln;
 
4820
    char *p, *s, *t;
 
4821
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t)) {
 
4822
        return pullString1(s, t);
 
4823
    }
 
4824
    /* tag found */
 
4825
    /* line has been deleted, revert to the reset value */
 
4826
    return 0;
 
4827
}                               /* getFieldFromBuffer */
 
4828
 
 
4829
int
 
4830
fieldIsChecked(int tagno)
 
4831
{
 
4832
    int ln;
 
4833
    char *p, *s, *t, *new;
 
4834
    if(locateTagInBuffer(tagno, &ln, &p, &s, &t))
 
4835
        return (*s == '+');
 
4836
    return -1;
 
4837
}                               /* fieldIsChecked */