~ubuntu-branches/ubuntu/jaunty/edbrowse/jaunty-security

« back to all changes in this revision

Viewing changes to url.c

  • Committer: Bazaar Package Importer
  • Author(s): Kapil Hari Paranjape
  • Date: 2006-10-20 10:47:30 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061020104730-o7vxbrypwaz932dt
Tags: 3.1.2-1
* New upstream version (3.1.2). Closes: #306486.
  - programs now written in C
  - support for javascript.
* debian/control:
  - added Kapil Hari Paranjape to Uploaders.
  - Build-depends on "libssl-dev", "libmozjs-dev", "libpcre3-dev".
  - Standards-Version to 3.7.2. No changes required.
* debian/rules:
  - add "noopt" feature.
  - set CFLAGS and LIBS.
  - Put $(MAKE) into the build rules.
* debian/copyright: Edited to add the current copyright which
  is GPL with the exception for linking with OpenSSL.
* debian/docs: added "README".
* debian/examples: added "jsrt".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* url.c
 
2
 * (c) 2002 Mikulas Patocka
 
3
 * This file is part of the Links project, released under GPL.
 
4
 *
 
5
 * Modified by Karl Dahlke for integration with edbrowse.
 
6
 */
 
7
 
 
8
#include "eb.h"
 
9
 
 
10
struct {
 
11
    char *prot;
 
12
    int port;
 
13
    bool free_syntax;
 
14
    bool need_slashes;
 
15
    bool need_slash_after_host;
 
16
} protocols[] = {
 
17
    {
 
18
    "file", 0, true, true, false}, {
 
19
    "http", 80, false, true, true}, {
 
20
    "https", 443, false, true, true}, {
 
21
    "proxy", 3128, false, true, true}, {
 
22
    "ftp", 21, false, true, true}, {
 
23
    "rtsp", 554, false, true, true}, {
 
24
    "pnm", 7070, false, true, true}, {
 
25
    "finger", 79, false, true, true}, {
 
26
    "smb", 139, false, true, true}, {
 
27
    "mailto", 0, false, false, false}, {
 
28
    "telnet", 23, false, false, false}, {
 
29
    "tn3270", 0, false, false, false}, {
 
30
    "javascript", 0, true, false, false}, {
 
31
    NULL, 0}
 
32
};
 
33
 
 
34
static bool free_syntax;
 
35
 
 
36
static int
 
37
protocolByName(const char *p, int l)
 
38
{
 
39
    int i;
 
40
    for(i = 0; protocols[i].prot; i++)
 
41
        if(memEqualCI(protocols[i].prot, p, l))
 
42
            return i;
 
43
    return -1;
 
44
}                               /* protocolByName */
 
45
 
 
46
/* Unpercent the host component of a url.  Christ what a pain! */
 
47
void
 
48
unpercentURL(char *url)
 
49
{
 
50
    char c, *u, *w;
 
51
    int n;
 
52
    u = w = url;
 
53
    while(c = *u) {
 
54
        ++u;
 
55
        if(c == '%' && isxdigit(u[0]) && isxdigit(u[1])) {
 
56
            c = fromHex(u[0], u[1]);
 
57
            u += 2;
 
58
        }
 
59
        if(!c)
 
60
            c = ' ';            /* should never happen */
 
61
        *w++ = c;
 
62
        if(strchr("?#\1", c))
 
63
            break;
 
64
        if(c != '/')
 
65
            continue;
 
66
        n = w - url;
 
67
        if(n == 1 || n > 16)
 
68
            break;
 
69
        if(w[-2] != ':' && w[-2] != '/')
 
70
            break;
 
71
    }
 
72
    strcpy(w, u);
 
73
}                               /* unpercentURL */
 
74
 
 
75
/* Decide if it looks like a web url. */
 
76
static bool
 
77
httpDefault(const char *url)
 
78
{
 
79
    static const char *const domainSuffix[] = {
 
80
        "com", "biz", "info", "net", "org", "gov", "edu", "us", "uk", "au",
 
81
        "ca", "de", "jp", "nz", 0
 
82
    };
 
83
    int n, len;
 
84
    const char *s, *lastdot, *end = url + strcspn(url, "/?#\1");
 
85
    if(end - url > 7 && stringEqual(end - 7, ".browse"))
 
86
        end -= 7;
 
87
    s = strrchr(url, ':');
 
88
    if(s && s < end) {
 
89
        const char *colon = s;
 
90
        ++s;
 
91
        while(isdigitByte(*s))
 
92
            ++s;
 
93
        if(s == end)
 
94
            end = colon;
 
95
    }
 
96
/* need at least two embedded dots */
 
97
    n = 0;
 
98
    for(s = url + 1; s < end - 1; ++s)
 
99
        if(*s == '.' && s[-1] != '.' && s[1] != '.')
 
100
            ++n, lastdot = s;
 
101
    if(n < 2)
 
102
        return false;
 
103
/* All digits, like an ip address, is ok. */
 
104
    if(n == 3) {
 
105
        for(s = url; s < end; ++s)
 
106
            if(!isdigitByte(*s) && *s != '.')
 
107
                break;
 
108
        if(s == end)
 
109
            return true;
 
110
    }
 
111
/* Look for standard domain suffix */
 
112
    ++lastdot;
 
113
    len = end - lastdot;
 
114
    for(n = 0; domainSuffix[n]; ++n)
 
115
        if(memEqualCI(lastdot, domainSuffix[n], len) && !domainSuffix[n][len])
 
116
            return true;
 
117
/* www.anything.xx is ok */
 
118
    if(len == 2 && memEqualCI(url, "www.", 4))
 
119
        return true;
 
120
    return false;
 
121
}                               /* httpDefault */
 
122
 
 
123
static int
 
124
parseURL(const char *url, const char **proto, int *prlen, const char **user, int *uslen, const char **pass, int *palen, /* ftp protocol */
 
125
   const char **host, int *holen,
 
126
   const char **portloc, int *port,
 
127
   const char **data, int *dalen, const char **post)
 
128
{
 
129
    const char *p, *q;
 
130
    int a;
 
131
 
 
132
    if(proto)
 
133
        *proto = NULL;
 
134
    if(prlen)
 
135
        *prlen = 0;
 
136
    if(user)
 
137
        *user = NULL;
 
138
    if(uslen)
 
139
        *uslen = 0;
 
140
    if(pass)
 
141
        *pass = NULL;
 
142
    if(palen)
 
143
        *palen = 0;
 
144
    if(host)
 
145
        *host = NULL;
 
146
    if(holen)
 
147
        *holen = 0;
 
148
    if(portloc)
 
149
        *portloc = 0;
 
150
    if(port)
 
151
        *port = 0;
 
152
    if(data)
 
153
        *data = NULL;
 
154
    if(dalen)
 
155
        *dalen = 0;
 
156
    if(post)
 
157
        *post = NULL;
 
158
    free_syntax = false;
 
159
 
 
160
    if(!url)
 
161
        return -1;
 
162
 
 
163
/* Find the leading protocol:// */
 
164
    a = -1;
 
165
    p = strchr(url, ':');
 
166
    if(p) {
 
167
/* You have to have something after the colon */
 
168
        q = p + 1;
 
169
        if(*q == '/')
 
170
            ++q;
 
171
        if(*q == '/')
 
172
            ++q;
 
173
        while(isspaceByte(*q))
 
174
            ++q;
 
175
        if(!*q)
 
176
            return false;
 
177
        a = protocolByName(url, p - url);
 
178
    }
 
179
    if(a >= 0) {
 
180
        if(proto)
 
181
            *proto = url;
 
182
        if(prlen)
 
183
            *prlen = p - url;
 
184
        if(p[1] != '/' || p[2] != '/') {
 
185
            if(protocols[a].need_slashes) {
 
186
                if(p[1] != '/') {
 
187
                    setError("%s:// expected", protocols[a].prot);
 
188
                    return -1;
 
189
                }
 
190
/* We got one out of two slashes, I'm going to call it good */
 
191
                ++p;
 
192
            }
 
193
            p -= 2;
 
194
        }
 
195
        p += 3;
 
196
    } else {                    /* nothing yet */
 
197
        if(p && p - url < 12 && p[1] == '/') {
 
198
            for(q = url; q < p; ++q)
 
199
                if(!isalphaByte(*q))
 
200
                    break;
 
201
            if(q == p) {        /* some protocol we don't know */
 
202
                char qprot[12];
 
203
                memcpy(qprot, url, p - url);
 
204
                qprot[p - url] = 0;
 
205
                setError("unrecognized protocol %s", qprot);
 
206
                return -1;
 
207
            }
 
208
        }
 
209
        if(httpDefault(url)) {
 
210
            static const char http[] = "http://";
 
211
            if(proto)
 
212
                *proto = http;
 
213
            if(prlen)
 
214
                *prlen = 4;
 
215
            a = 1;
 
216
            p = url;
 
217
        }
 
218
    }
 
219
 
 
220
    if(a < 0)
 
221
        return false;
 
222
 
 
223
    if(free_syntax = protocols[a].free_syntax) {
 
224
        if(data)
 
225
            *data = p;
 
226
        if(dalen)
 
227
            *dalen = strlen(p);
 
228
        return true;
 
229
    }
 
230
 
 
231
    q = p + strcspn(p, "@?#/\1");
 
232
    if(*q == '@') {             /* user:password@host */
 
233
        const char *pp = strchr(p, ':');
 
234
        if(!pp || pp > q) {     /* no password */
 
235
            if(user)
 
236
                *user = p;
 
237
            if(uslen)
 
238
                *uslen = q - p;
 
239
        } else {
 
240
            if(user)
 
241
                *user = p;
 
242
            if(uslen)
 
243
                *uslen = pp - p;
 
244
            if(pass)
 
245
                *pass = pp + 1;
 
246
            if(palen)
 
247
                *palen = q - pp - 1;
 
248
        }
 
249
        p = q + 1;
 
250
    }
 
251
    /* @ */
 
252
    q = p + strcspn(p, ":?#/\1");
 
253
    if(host)
 
254
        *host = p;
 
255
    if(holen)
 
256
        *holen = q - p;
 
257
    if(*q == ':') {             /* port specified */
 
258
        int n;
 
259
        const char *cc, *pp = q + strcspn(q, "/?#\1");
 
260
        n = strtol(q + 1, (char **)&cc, 10);
 
261
        if(cc != pp || !isdigitByte(q[1])) {
 
262
            setError("invalid :port specifier at the end of the domain");
 
263
            return -1;
 
264
        }
 
265
        if(port)
 
266
            *port = n;
 
267
        if(portloc)
 
268
            *portloc = q;
 
269
        q = pp;                 /* up to the slash */
 
270
    } else {
 
271
        if(port)
 
272
            *port = protocols[a].port;
 
273
    }                           /* colon or not */
 
274
 
 
275
/* Skip past /, but not ? or # */
 
276
    if(*q == '/')
 
277
        q++;
 
278
    p = q;
 
279
 
 
280
/* post data is handled separately */
 
281
    q = p + strcspn(p, "\1");
 
282
    if(data)
 
283
        *data = p;
 
284
    if(dalen)
 
285
        *dalen = q - p;
 
286
    if(post)
 
287
        *post = *q ? q + 1 : NULL;
 
288
    return true;
 
289
}                               /* parseURL */
 
290
 
 
291
bool
 
292
isURL(const char *url)
 
293
{
 
294
    int j = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
 
295
    if(j < 0)
 
296
        return false;
 
297
    return j;
 
298
}                               /* isURL */
 
299
 
 
300
/* Helper functions to return pieces of the URL.
 
301
 * Makes a copy, so you can have your 0 on the end.
 
302
 * Return 0 for an error, and "" if that piece is missing. */
 
303
 
 
304
const char *
 
305
getProtURL(const char *url)
 
306
{
 
307
    static char buf[12];
 
308
    int l;
 
309
    const char *s;
 
310
    int rc = parseURL(url, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
 
311
    if(rc <= 0)
 
312
        return 0;
 
313
    memcpy(buf, s, l);
 
314
    buf[l] = 0;
 
315
    return buf;
 
316
}                               /* getProtURL */
 
317
 
 
318
static char hostbuf[400];
 
319
const char *
 
320
getHostURL(const char *url)
 
321
{
 
322
    int l;
 
323
    const char *s;
 
324
    char *t;
 
325
    char c, d;
 
326
    int rc = parseURL(url, 0, 0, 0, 0, 0, 0, &s, &l, 0, 0, 0, 0, 0);
 
327
    if(rc <= 0)
 
328
        return 0;
 
329
    if(free_syntax)
 
330
        return 0;
 
331
    if(!s)
 
332
        return EMPTYSTRING;
 
333
    if(l >= sizeof (hostbuf)) {
 
334
        setError("domain name too long");
 
335
        return 0;
 
336
    }
 
337
    memcpy(hostbuf, s, l);
 
338
    if(l && hostbuf[l - 1] == '.')
 
339
        --l;
 
340
    hostbuf[l] = 0;
 
341
/* domain names must be ascii, with no spaces */
 
342
    d = 0;
 
343
    for(s = t = hostbuf; (c = *s); ++s) {
 
344
        c &= 0x7f;
 
345
        if(c == ' ')
 
346
            continue;
 
347
        if(c == '.' && d == '.')
 
348
            continue;
 
349
        *t++ = d = c;
 
350
    }
 
351
    *t = 0;
 
352
    return hostbuf;
 
353
}                               /* getHostURL */
 
354
 
 
355
const char *
 
356
getHostPassURL(const char *url)
 
357
{
 
358
    int hl;
 
359
    const char *h, *z, *u;
 
360
    char *t;
 
361
    int rc = parseURL(url, 0, 0, &u, 0, 0, 0, &h, &hl, 0, 0, 0, 0, 0);
 
362
    if(rc <= 0 || !h)
 
363
        return 0;
 
364
    if(free_syntax)
 
365
        return 0;
 
366
    z = h;
 
367
    t = hostbuf;
 
368
    if(u)
 
369
        z = u, hl += h - u, t += h - u;
 
370
    if(hl >= sizeof (hostbuf)) {
 
371
        setError("domain name too long");
 
372
        return 0;
 
373
    }
 
374
    memcpy(hostbuf, z, hl);
 
375
    hostbuf[hl] = 0;
 
376
/* domain names must be ascii */
 
377
    for(; *t; ++t)
 
378
        *t &= 0x7f;
 
379
    return hostbuf;
 
380
}                               /* getHostPassURL */
 
381
 
 
382
const char *
 
383
getUserURL(const char *url)
 
384
{
 
385
    static char buf[MAXUSERPASS];
 
386
    int l;
 
387
    const char *s;
 
388
    int rc = parseURL(url, 0, 0, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0);
 
389
    if(rc <= 0)
 
390
        return 0;
 
391
    if(free_syntax)
 
392
        return EMPTYSTRING;
 
393
    if(!s)
 
394
        return EMPTYSTRING;
 
395
    if(l >= sizeof (buf)) {
 
396
        setError("user name too long");
 
397
        return 0;
 
398
    }
 
399
    memcpy(buf, s, l);
 
400
    buf[l] = 0;
 
401
    return buf;
 
402
}                               /* getUserURL */
 
403
 
 
404
const char *
 
405
getPassURL(const char *url)
 
406
{
 
407
    static char buf[MAXUSERPASS];
 
408
    int l;
 
409
    const char *s;
 
410
    int rc = parseURL(url, 0, 0, 0, 0, &s, &l, 0, 0, 0, 0, 0, 0, 0);
 
411
    if(rc <= 0)
 
412
        return 0;
 
413
    if(free_syntax)
 
414
        return EMPTYSTRING;
 
415
    if(!s)
 
416
        return EMPTYSTRING;
 
417
    if(l >= sizeof (buf)) {
 
418
        setError("password too long");
 
419
        return 0;
 
420
    }
 
421
    memcpy(buf, s, l);
 
422
    buf[l] = 0;
 
423
    return buf;
 
424
}                               /* getPassURL */
 
425
 
 
426
const char *
 
427
getDataURL(const char *url)
 
428
{
 
429
    const char *s;
 
430
    int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &s, 0, 0);
 
431
    if(rc <= 0)
 
432
        return 0;
 
433
    return s;
 
434
}                               /* getDataURL */
 
435
 
 
436
void
 
437
getDirURL(const char *url, const char **start_p, const char **end_p)
 
438
{
 
439
    const char *dir = getDataURL(url);
 
440
    const char *end;
 
441
    static const char myslash[] = "/";
 
442
    if(!dir || dir == url)
 
443
        goto slash;
 
444
    if(free_syntax)
 
445
        goto slash;
 
446
    if(!strchr("#?\1", *dir)) {
 
447
        if(*--dir != '/')
 
448
            errorPrint("1misplaced / in getDirURL(%s)", url);
 
449
    }
 
450
    end = strpbrk(dir, "#?\1");
 
451
    if(!end)
 
452
        end = dir + strlen(dir);
 
453
    while(end > dir && end[-1] != '/')
 
454
        --end;
 
455
    if(end > dir) {
 
456
        *start_p = dir;
 
457
        *end_p = end;
 
458
        return;
 
459
    }
 
460
  slash:
 
461
    *start_p = myslash;
 
462
    *end_p = myslash + 1;
 
463
}                               /* getDirURL */
 
464
 
 
465
bool
 
466
getPortLocURL(const char *url, const char **portloc, int *port)
 
467
{
 
468
    int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, portloc, port, 0, 0, 0);
 
469
    if(rc <= 0)
 
470
        return false;
 
471
    if(free_syntax)
 
472
        return false;
 
473
    return true;
 
474
}                               /* getPortLocURL */
 
475
 
 
476
int
 
477
getPortURL(const char *url)
 
478
{
 
479
    int port;
 
480
    int rc = parseURL(url, 0, 0, 0, 0, 0, 0, 0, 0, 0, &port, 0, 0, 0);
 
481
    if(rc <= 0)
 
482
        return 0;
 
483
    if(free_syntax)
 
484
        return 0;
 
485
    return port;
 
486
}                               /* getPortURL */
 
487
 
 
488
bool
 
489
isProxyURL(const char *url)
 
490
{
 
491
    return ((url[0] | 0x20) == 'p');
 
492
}
 
493
 
 
494
/* Don't let a web page fetch itself. */
 
495
static char *histURL[MAXFETCH];
 
496
static int histFrom[MAXFETCH];
 
497
static int n_fetch;
 
498
int
 
499
fetchHistory(const char *prev, const char *next)
 
500
{
 
501
    int i;
 
502
    int from = -1;
 
503
/* zero is a reset */
 
504
    debugPrint(4, "fetch hist %s : %s", prev, next);
 
505
    if(prev == 0) {
 
506
        for(i = 0; i < n_fetch; ++i)
 
507
            free(histURL[i]);
 
508
        n_fetch = 0;
 
509
        if(!next)
 
510
            return false;
 
511
    } else {
 
512
        if(memEqualCI(prev, "http://", 7))
 
513
            prev += 7;
 
514
        for(i = 0; i < n_fetch; ++i)
 
515
            if(stringEqual(prev, histURL[i])) {
 
516
                from = i;
 
517
                break;
 
518
            }
 
519
    }
 
520
    if(n_fetch >= MAXFETCH) {
 
521
        setError
 
522
           ("too many fetches from the internet, you may want to disable `redirect html'");
 
523
        return -1;
 
524
    }
 
525
/* Have we seen this one before? */
 
526
    if(memEqualCI(next, "http://", 7))
 
527
        next += 7;
 
528
    for(i = 0; i < n_fetch; ++i)
 
529
        if(stringEqual(next, histURL[i]))
 
530
            break;
 
531
    if(i == n_fetch) {          /* new */
 
532
        histURL[i] = cloneString(next);
 
533
        histFrom[i] = from;
 
534
        ++n_fetch;
 
535
        return true;
 
536
    }
 
537
/* Oops, we've already fetched this page. */
 
538
    while(from >= 0 && from != i)
 
539
        from = histFrom[from];
 
540
    if(from < 0)
 
541
        return false;
 
542
    setError("web page indirectly fetches itself, an infinite loop");
 
543
    return -1;
 
544
}                               /* FetchHistory */
 
545
 
 
546
const char *
 
547
firstURL(void)
 
548
{
 
549
    if(!n_fetch)
 
550
        return 0;
 
551
    return histURL[0];
 
552
}                               /* firstURL */
 
553
 
 
554
static void
 
555
squashDirectories(char *url)
 
556
{
 
557
    char *dd = (char *)getDataURL(url);
 
558
    char *s, *t, *end;
 
559
    if(memEqualCI(url, "javascript:", 11))
 
560
        return;
 
561
    if(!dd || dd == url)
 
562
        return;
 
563
    if(!*dd)
 
564
        return;
 
565
    if(strchr("#?\1", *dd))
 
566
        return;
 
567
    --dd;
 
568
    if(*dd != '/')
 
569
        errorPrint("@misplaced / in %s", url);
 
570
    end = dd + strcspn(dd, "?#\1");
 
571
    while(true) {
 
572
        s = strstr(dd, "/./");
 
573
        if(s && s < end) {
 
574
            strcpy(s, s + 2);
 
575
            continue;
 
576
        }
 
577
        s = strstr(dd, "/../");
 
578
        if(!s)
 
579
            break;
 
580
        if(s > end)
 
581
            break;
 
582
        if(s == dd) {
 
583
            strcpy(s, s + 3);
 
584
            continue;
 
585
        }
 
586
        for(t = s - 1; *t != '/'; --t) ;
 
587
        s += 3;
 
588
        strcpy(t, s);
 
589
    }
 
590
}                               /* squashDirectories */
 
591
 
 
592
char *
 
593
resolveURL(const char *base, const char *rel)
 
594
{
 
595
    char *n;                    /* new url */
 
596
    const char *s, *p;
 
597
    char *q;
 
598
    int l;
 
599
    if(!base)
 
600
        base = EMPTYSTRING;
 
601
    n = allocMem(strlen(base) + strlen(rel) + 12);
 
602
    debugPrint(5, "resolve(%s|%s)", base, rel);
 
603
    if(rel[0] == '#') {
 
604
/* # alone means do nothing. */
 
605
        if(!rel[1]) {
 
606
            strcpy(n, rel);
 
607
          out_n:
 
608
            debugPrint(5, "= %s", n);
 
609
            return n;
 
610
        }
 
611
/* We could have changed the base url via the <base> tag,
 
612
 * so this #ref could actually refer to some other web page.
 
613
 * Best to run through standard procedure. */
 
614
        strcpy(n, base);
 
615
        for(q = n; *q && *q != '\1' && *q != '#'; q++) ;
 
616
        strcpy(q, rel);
 
617
        goto out_n;
 
618
    }
 
619
    if(rel[0] == '?' || rel[0] == '\1') {
 
620
        strcpy(n, base);
 
621
        for(q = n; *q && *q != '\1' && *q != '#' && *q != '?'; q++) ;
 
622
        strcpy(q, rel);
 
623
        goto out_n;
 
624
    }
 
625
    if(rel[0] == '/' && rel[1] == '/') {
 
626
        if(s = strstr(base, "//")) {
 
627
            strncpy(n, base, s - base);
 
628
            n[s - base] = 0;
 
629
        } else
 
630
            strcpy(n, "http:");
 
631
        strcat(n, rel);
 
632
        goto squash;
 
633
    }
 
634
    if(parseURL(rel, &s, &l, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) > 0) {
 
635
        n[0] = 0;
 
636
        if(s != rel) {
 
637
/* It didn't have http in front of it before, put it on now. */
 
638
            strncpy(n, s, l);
 
639
            strcpy(n + l, "://");
 
640
        }
 
641
        strcat(n, rel);
 
642
        goto squash;
 
643
    }                           /* relative is already a url */
 
644
    s = base;
 
645
    if(rel[0] == '/') {
 
646
        s = getDataURL(base);
 
647
        if(!s) {
 
648
            strcpy(n, rel);
 
649
            goto squash;
 
650
        }
 
651
        if(!*s) {
 
652
            if(s - base >= 7 && stringEqual(s - 7, ".browse"))
 
653
                s -= 7;
 
654
            if(s > base && s[-1] == '/')
 
655
                --s;
 
656
        } else if(!strchr("#?\1", *s))
 
657
            --s;
 
658
        l = s - base;
 
659
        strncpy(n, base, l);
 
660
        strcpy(n + l, rel);
 
661
        goto squash;
 
662
    }
 
663
/* This is a relative change, paste it on after the last slash */
 
664
    s = base;
 
665
    if(parseURL(base, 0, 0, 0, 0, 0, 0, &p, 0, 0, 0, 0, 0, 0) > 0 && p)
 
666
        s = p;
 
667
    for(p = 0; *s; ++s) {
 
668
        if(*s == '/')
 
669
            p = s;
 
670
        if(strchr("#?\1", *s))
 
671
            break;
 
672
    }
 
673
    if(!p) {
 
674
        if(isURL(base))
 
675
            p = s;
 
676
        else
 
677
            p = base;
 
678
    }
 
679
    l = p - base;
 
680
    if(l) {
 
681
        strncpy(n, base, l);
 
682
        n[l++] = '/';
 
683
    }
 
684
    strcpy(n + l, rel);
 
685
  squash:
 
686
    squashDirectories(n);
 
687
    goto out_n;
 
688
}                               /* resolveURL */
 
689
 
 
690
/* This routine could be, should be, more sophisticated */
 
691
bool
 
692
sameURL(const char *s, const char *t)
 
693
{
 
694
    const char *u, *v;
 
695
    int l;
 
696
/* It's ok if one says http and the other implies it. */
 
697
    if(memEqualCI(s, "http://", 7))
 
698
        s += 7;
 
699
    if(memEqualCI(t, "http://", 7))
 
700
        t += 7;
 
701
    u = s + strcspn(s, "#");
 
702
    v = t + strcspn(t, "#?\1");
 
703
    if(u - s >= 7 && stringEqual(u - 7, ".browse"))
 
704
        u -= 7;
 
705
    if(v - t >= 7 && stringEqual(v - 7, ".browse"))
 
706
        v -= 7;
 
707
    l = u - s;
 
708
    if(l != v - t)
 
709
        return false;
 
710
    return !memcmp(s, t, l);
 
711
}                               /* sameURL */
 
712
 
 
713
/* Find some helpful text to print, in place of an image. */
 
714
/* Text longer than 80 chars isn't helpful, so we return a static buffer. */
 
715
char *
 
716
altText(const char *base)
 
717
{
 
718
    static char buf[80];
 
719
    int len, n;
 
720
    int recount = 0;
 
721
    char *s;
 
722
    debugPrint(5, "altText(%s)", base);
 
723
    if(!base)
 
724
        return 0;
 
725
    if(stringEqual(base, "#"))
 
726
        return 0;
 
727
    if(memEqualCI(base, "javascript", 10))
 
728
        return 0;
 
729
  retry:
 
730
    if(recount >= 2)
 
731
        return 0;
 
732
    strncpy(buf, base, sizeof (buf) - 1);
 
733
    spaceCrunch(buf, true, true);
 
734
    len = strlen(buf);
 
735
    if(len && !isalnumByte(buf[len - 1]))
 
736
        buf[--len] = 0;
 
737
    while(len && !isalnumByte(buf[0]))
 
738
        strcpy(buf, buf + 1), --len;
 
739
    if(len > 10) {
 
740
/* see if it's a phrase/sentence or a pathname/url */
 
741
/* Do this by counting spaces */
 
742
        for(n = 0, s = buf; *s; ++s)
 
743
            if(*s == ' ')
 
744
                ++n;
 
745
        if(n * 8 >= len)
 
746
            return buf;         /* looks like words */
 
747
/* Ok, now we believe it's a pathname or url */
 
748
/* Get rid of everything after ? or # */
 
749
        s = strpbrk(buf, "#?\1");
 
750
        if(s)
 
751
            *s = 0;
 
752
/* get rid of common suffix */
 
753
        s = strrchr(buf, '.');
 
754
        if(s) {
 
755
/* get rid of trailing .html */
 
756
            static const char *const suffix[] = {
 
757
                "html", "htm", "shtml", "shtm", "php", "asp", "cgi", "rm",
 
758
                "ram",
 
759
                "gif", "jpg", "bmp",
 
760
                0
 
761
            };
 
762
            n = stringInListCI(suffix, s + 1);
 
763
            if(n >= 0 || s[1] == 0)
 
764
                *s = 0;
 
765
        }
 
766
/* Get rid of everything up to the last slash, leaving the file name */
 
767
        s = strrchr(buf, '/');
 
768
        if(s && recount) {
 
769
            char *ss;
 
770
            *s = 0;
 
771
            ss = strrchr(buf, '/');
 
772
            if(!ss)
 
773
                return 0;
 
774
            if(ss > buf && ss[-1] == '/')
 
775
                return 0;
 
776
            *s = '/';
 
777
            s = ss;
 
778
        }
 
779
        if(s)
 
780
            strcpy(buf, s + 1);
 
781
    }                           /* more than ten characters */
 
782
    ++recount;
 
783
/* If we don't have enough letters, forget it */
 
784
    len = strlen(buf);
 
785
    if(len < 3)
 
786
        goto retry;
 
787
    for(n = 0, s = buf; *s; ++s)
 
788
        if(isalphaByte(*s))
 
789
            ++n;
 
790
    if(n * 2 <= len)
 
791
        return 0;               /* not enough letters */
 
792
    return buf;
 
793
}                               /* altText */
 
794
 
 
795
/* get post data ready for a url. */
 
796
char *
 
797
encodePostData(const char *s)
 
798
{
 
799
    char *post, c;
 
800
    int l;
 
801
    char buf[4];
 
802
 
 
803
    if(!s)
 
804
        return 0;
 
805
    if(s == EMPTYSTRING)
 
806
        return EMPTYSTRING;
 
807
    post = initString(&l);
 
808
    while(c = *s++) {
 
809
        if(isalnumByte(c))
 
810
            goto putc;
 
811
        if(c == ' ') {
 
812
            c = '+';
 
813
            goto putc;
 
814
        }
 
815
        if(strchr("-._~*()!", c))
 
816
            goto putc;
 
817
        sprintf(buf, "%%%02X", (uchar) c);
 
818
        stringAndString(&post, &l, buf);
 
819
        continue;
 
820
      putc:
 
821
        stringAndChar(&post, &l, c);
 
822
    }
 
823
    return post;
 
824
}                               /* encodePostData */
 
825
 
 
826
static char
 
827
dohex(char c, const char **sp)
 
828
{
 
829
    const char *s = *sp;
 
830
    char d, e;
 
831
    if(c == '+')
 
832
        return ' ';
 
833
    if(c != '%')
 
834
        return c;
 
835
    d = *s++;
 
836
    e = *s++;
 
837
    if(!isxdigit(d) || !isxdigit(e))
 
838
        return c;               /* should never happen */
 
839
    d = fromHex(d, e);
 
840
    if(!d)
 
841
        d = ' ';                /* don't allow nulls */
 
842
    *sp = s;
 
843
    return d;
 
844
}                               /* dohex */
 
845
 
 
846
char *
 
847
decodePostData(const char *data, const char *name, int seqno)
 
848
{
 
849
    const char *s, *n, *t;
 
850
    char *new = 0, *w = 0;
 
851
    int j = 0;
 
852
    char c;
 
853
 
 
854
    if(!seqno && !name)
 
855
        errorPrint("@decodePostData(0,0)");
 
856
 
 
857
    for(s = data; *s; s = (*t ? t + 1 : t)) {
 
858
        n = 0;
 
859
        t = strchr(s, '&');
 
860
        if(!t)
 
861
            t = s + strlen(s);
 
862
/* select attribute by number */
 
863
        ++j;
 
864
        if(j == seqno)
 
865
            w = new = allocMem(t - s + 1);
 
866
        if(seqno && !w)
 
867
            continue;
 
868
        if(name)
 
869
            n = name;
 
870
        while(s < t && (c = *s) != '=') {
 
871
            ++s;
 
872
            c = dohex(c, &s);
 
873
            if(n) {
 
874
/* I don't know if this is suppose to be case insensitive all the time,
 
875
 * though there are situations when it must be, as in
 
876
 * mailto:address?Subject=blah-blah */
 
877
                if(isalphaByte(c)) {
 
878
                    if(!((c ^ *n) & 0xdf))
 
879
                        ++n;
 
880
                    else
 
881
                        n = 0;
 
882
                } else if(c == *n)
 
883
                    ++n;
 
884
                else
 
885
                    n = 0;
 
886
            }
 
887
            if(w)
 
888
                *w++ = c;
 
889
        }
 
890
 
 
891
        if(s == t) {            /* no equals, just a string */
 
892
            if(name)
 
893
                continue;
 
894
            *w = 0;
 
895
            return new;
 
896
        }
 
897
        if(w)
 
898
            *w++ = c;
 
899
        ++s;                    /* skip past equals */
 
900
        if(name) {
 
901
            if(!n)
 
902
                continue;
 
903
            if(*n)
 
904
                continue;
 
905
            w = new = allocMem(t - s + 1);
 
906
        }
 
907
 
 
908
/* At this point we have a match */
 
909
        while(s < t) {
 
910
            c = *s++;
 
911
            c = dohex(c, &s);
 
912
            *w++ = c;
 
913
        }
 
914
        *w = 0;
 
915
        return new;
 
916
    }
 
917
 
 
918
    return 0;
 
919
}                               /* decodePostData */
 
920
 
 
921
void
 
922
decodeMailURL(const char *url, char **addr_p, char **subj_p, char **body_p)
 
923
{
 
924
    const char *s;
 
925
    char *new;
 
926
    if(memEqualCI(url, "mailto:", 7))
 
927
        url += 7;
 
928
    s = url + strcspn(url, "/?");
 
929
    if(addr_p)
 
930
        *addr_p = pullString1(url, s);
 
931
    if(subj_p)
 
932
        *subj_p = 0;
 
933
    if(body_p)
 
934
        *body_p = 0;
 
935
    s = strchr(url, '?');
 
936
    if(!s)
 
937
        return;
 
938
    url = s + 1;
 
939
    if(subj_p)
 
940
        *subj_p = decodePostData(url, "subject", 0);
 
941
    if(body_p)
 
942
        *body_p = decodePostData(url, "body", 0);
 
943
}                               /* decodeMailURL */