~ubuntu-branches/ubuntu/warty/lynx/warty-security

« back to all changes in this revision

Viewing changes to WWW/Library/Implementation/HTParse.c

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2004-09-16 12:14:10 UTC
  • Revision ID: james.westby@ubuntu.com-20040916121410-cz1gu92c4nqfeyrg
Tags: upstream-2.8.5
ImportĀ upstreamĀ versionĀ 2.8.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*              Parse HyperText Document Address                HTParse.c
 
2
**              ================================
 
3
*/
 
4
 
 
5
#include <HTUtils.h>
 
6
#include <HTParse.h>
 
7
 
 
8
#include <LYUtils.h>
 
9
#include <LYLeaks.h>
 
10
#include <LYStrings.h>
 
11
#include <LYCharUtils.h>
 
12
 
 
13
#ifdef HAVE_ALLOCA_H
 
14
#include <alloca.h>
 
15
#else
 
16
#ifdef __MINGW32__
 
17
#include <malloc.h>
 
18
#endif /* __MINGW32__ */
 
19
#endif
 
20
 
 
21
#define HEX_ESCAPE '%'
 
22
 
 
23
struct struct_parts {
 
24
        char * access;
 
25
        char * host;
 
26
        char * absolute;
 
27
        char * relative;
 
28
        char * search;          /* treated normally as part of path */
 
29
        char * anchor;
 
30
};
 
31
 
 
32
 
 
33
/*      Strip white space off a string.                         HTStrip()
 
34
**      -------------------------------
 
35
**
 
36
** On exit,
 
37
**      Return value points to first non-white character, or to 0 if none.
 
38
**      All trailing white space is OVERWRITTEN with zero.
 
39
*/
 
40
PUBLIC char * HTStrip ARGS1(
 
41
        char *,         s)
 
42
{
 
43
#define SPACE(c) ((c == ' ') || (c == '\t') || (c == '\n'))
 
44
    char * p = s;
 
45
    for (p = s; *p; p++)
 
46
        ;                       /* Find end of string */
 
47
    for (p--; p >= s; p--) {
 
48
        if (SPACE(*p))
 
49
            *p = '\0';          /* Zap trailing blanks */
 
50
        else
 
51
            break;
 
52
    }
 
53
    while (SPACE(*s))
 
54
        s++;                    /* Strip leading blanks */
 
55
    return s;
 
56
}
 
57
 
 
58
/*      Scan a filename for its constituents.                   scan()
 
59
**      -------------------------------------
 
60
**
 
61
** On entry,
 
62
**      name    points to a document name which may be incomplete.
 
63
** On exit,
 
64
**      absolute or relative may be nonzero (but not both).
 
65
**      host, anchor and access may be nonzero if they were specified.
 
66
**      Any which are nonzero point to zero terminated strings.
 
67
*/
 
68
PRIVATE void scan ARGS2(
 
69
        char *,                 name,
 
70
        struct struct_parts *,  parts)
 
71
{
 
72
    char * after_access;
 
73
    char * p;
 
74
 
 
75
    parts->access = NULL;
 
76
    parts->host = NULL;
 
77
    parts->absolute = NULL;
 
78
    parts->relative = NULL;
 
79
    parts->search = NULL;       /* normally not used - kw */
 
80
    parts->anchor = NULL;
 
81
 
 
82
    /*
 
83
    **  Scan left-to-right for a scheme (access).
 
84
    */
 
85
    after_access = name;
 
86
    for (p = name; *p; p++) {
 
87
        if (*p==':') {
 
88
            *p = '\0';
 
89
            parts->access = name;       /* Access name has been specified */
 
90
            after_access = (p + 1);
 
91
            break;
 
92
        }
 
93
        if (*p == '/' || *p == '#' || *p == ';' || *p == '?')
 
94
            break;
 
95
    }
 
96
 
 
97
    /*
 
98
    **  Scan left-to-right for a fragment (anchor).
 
99
    */
 
100
    for (p = after_access; *p; p++) {
 
101
        if (*p =='#') {
 
102
            parts->anchor = (p + 1);
 
103
            *p = '\0';                  /* terminate the rest */
 
104
            break;              /* leave things after first # alone - kw */
 
105
        }
 
106
    }
 
107
 
 
108
    /*
 
109
    **  Scan left-to-right for a host or absolute path.
 
110
    */
 
111
    p = after_access;
 
112
    if (*p == '/') {
 
113
        if (p[1] == '/') {
 
114
            parts->host = (p + 2);        /* host has been specified    */
 
115
            *p = '\0';                    /* Terminate access           */
 
116
            p = strchr(parts->host, '/'); /* look for end of host name if any */
 
117
            if (p != NULL) {
 
118
                *p = '\0';                      /* Terminate host */
 
119
                parts->absolute = (p + 1);      /* Root has been found */
 
120
            } else {
 
121
                p = strchr(parts->host, '?');
 
122
                if (p != NULL) {
 
123
                    *p = '\0';                  /* Terminate host */
 
124
                    parts->search = (p + 1);
 
125
                }
 
126
            }
 
127
        } else {
 
128
            parts->absolute = (p + 1);          /* Root found but no host */
 
129
        }
 
130
    } else {
 
131
        parts->relative = (*after_access) ?
 
132
                             after_access : NULL; /* NULL for "" */
 
133
    }
 
134
 
 
135
    /*
 
136
    **  Check schemes that commonly have unescaped hashes.
 
137
    */
 
138
    if (parts->access && parts->anchor &&
 
139
                /* optimize */ strchr("lnsdLNSD", *parts->access) != NULL) {
 
140
        if ((!parts->host && strcasecomp(parts->access, "lynxcgi")) ||
 
141
            !strcasecomp(parts->access, "nntp") ||
 
142
            !strcasecomp(parts->access, "snews") ||
 
143
            !strcasecomp(parts->access, "news") ||
 
144
            !strcasecomp(parts->access, "data")) {
 
145
            /*
 
146
             *  Access specified but no host and not a lynxcgi URL, so the
 
147
             *  anchor may not really be one, e.g., news:j462#36487@foo.bar,
 
148
             *  or it's an nntp or snews URL, or news URL with a host.
 
149
             *  Restore the '#' in the address.
 
150
             */
 
151
            /* but only if we have found a path component of which this will
 
152
             * become part. - kw  */
 
153
            if (parts->relative || parts->absolute) {
 
154
                *(parts->anchor - 1) = '#';
 
155
                parts->anchor = NULL;
 
156
            }
 
157
        }
 
158
    }
 
159
} /*scan */
 
160
 
 
161
#if defined(HAVE_ALLOCA) && !defined(LY_FIND_LEAKS)
 
162
#define LYalloca(x)        alloca(x)
 
163
#define LYalloca_free(x)   {}
 
164
#else
 
165
#define LYalloca(x)        malloc(x)
 
166
#define LYalloca_free(x)   free(x)
 
167
#endif
 
168
 
 
169
/*      Parse a Name relative to another name.                  HTParse()
 
170
**      --------------------------------------
 
171
**
 
172
**      This returns those parts of a name which are given (and requested)
 
173
**      substituting bits from the related name where necessary.
 
174
**
 
175
** On entry,
 
176
**      aName           A filename given
 
177
**      relatedName     A name relative to which aName is to be parsed
 
178
**      wanted          A mask for the bits which are wanted.
 
179
**
 
180
** On exit,
 
181
**     returns         A pointer to a malloc'd string which MUST BE FREED
 
182
*/
 
183
PUBLIC char * HTParse ARGS3(
 
184
        CONST char *,   aName,
 
185
        CONST char *,   relatedName,
 
186
        int,            wanted)
 
187
{
 
188
    char * result = NULL;
 
189
    char * tail = NULL;  /* a pointer to the end of the 'result' string */
 
190
    char * return_value = NULL;
 
191
    int len, len1, len2;
 
192
    char * name = NULL;
 
193
    char * rel = NULL;
 
194
    char * p;
 
195
    char * acc_method;
 
196
    struct struct_parts given, related;
 
197
 
 
198
    CTRACE((tfp, "HTParse: aName:`%s'\n", aName));
 
199
    CTRACE((tfp, "   relatedName:`%s'\n", relatedName));
 
200
 
 
201
    if (wanted & (PARSE_STRICTPATH | PARSE_QUERY)) { /* if detail wanted... */
 
202
        if ((wanted & (PARSE_STRICTPATH | PARSE_QUERY))
 
203
            == (PARSE_STRICTPATH | PARSE_QUERY)) /* if strictpath AND query */
 
204
            wanted |= PARSE_PATH; /* then treat as if PARSE_PATH wanted */
 
205
        if (wanted & PARSE_PATH) /* if PARSE_PATH wanted */
 
206
            wanted &= ~(PARSE_STRICTPATH | PARSE_QUERY); /* ignore details */
 
207
    }
 
208
    CTRACE((tfp, "   want:%s%s%s%s%s%s%s\n",
 
209
            wanted & PARSE_PUNCTUATION ? " punc"   : "",
 
210
            wanted & PARSE_ANCHOR      ? " anchor" : "",
 
211
            wanted & PARSE_PATH        ? " path"   : "",
 
212
            wanted & PARSE_HOST        ? " host"   : "",
 
213
            wanted & PARSE_ACCESS      ? " access" : "",
 
214
            wanted & PARSE_STRICTPATH  ? " PATH"   : "",
 
215
            wanted & PARSE_QUERY       ? " QUERY"  : ""));
 
216
 
 
217
    /*
 
218
    ** Allocate the temporary string. Optimized.
 
219
    */
 
220
    len1 = strlen(aName) + 1;
 
221
    len2 = strlen(relatedName) + 1;
 
222
    len = len1 + len2 + 8;     /* Lots of space: more than enough */
 
223
 
 
224
    result = tail = (char*)LYalloca(len * 2 + len1 + len2);
 
225
    if (result == NULL) {
 
226
        outofmem(__FILE__, "HTParse");
 
227
    }
 
228
    *result = '\0';
 
229
    name = result + len;
 
230
    rel = name + len1;
 
231
 
 
232
    /*
 
233
    **  Make working copy of the input string to cut up.
 
234
    */
 
235
    memcpy(name, aName, len1);
 
236
 
 
237
    /*
 
238
    **  Cut up the string into URL fields.
 
239
    */
 
240
    scan(name, &given);
 
241
 
 
242
    /*
 
243
    **  Now related string.
 
244
    */
 
245
    if ((given.access && given.host && given.absolute) || !*relatedName) {
 
246
        /*
 
247
        **  Inherit nothing!
 
248
        */
 
249
        related.access = NULL;
 
250
        related.host = NULL;
 
251
        related.absolute = NULL;
 
252
        related.relative = NULL;
 
253
        related.search = NULL;
 
254
        related.anchor = NULL;
 
255
    } else {
 
256
        memcpy(rel, relatedName, len2);
 
257
        scan(rel,  &related);
 
258
    }
 
259
 
 
260
 
 
261
    /*
 
262
    **  Handle the scheme (access) field.
 
263
    */
 
264
    if (given.access && given.host && !given.relative && !given.absolute) {
 
265
        if (!strcmp(given.access, "http") ||
 
266
            !strcmp(given.access, "https") ||
 
267
            !strcmp(given.access, "ftp"))
 
268
            /*
 
269
            **  Assume root.
 
270
            */
 
271
            given.absolute = "";
 
272
    }
 
273
    acc_method = given.access ? given.access : related.access;
 
274
    if (wanted & PARSE_ACCESS) {
 
275
        if (acc_method) {
 
276
            strcpy(tail, acc_method);
 
277
            tail += strlen(tail);
 
278
            if (wanted & PARSE_PUNCTUATION) {
 
279
                *tail++ = ':';
 
280
                *tail = '\0';
 
281
            }
 
282
        }
 
283
    }
 
284
 
 
285
    /*
 
286
    **  If different schemes, inherit nothing.
 
287
    **
 
288
    **  We'll try complying with RFC 1808 and
 
289
    **  the Fielding draft, and inherit nothing
 
290
    **  if both schemes are given, rather than
 
291
    **  only when they differ, except for
 
292
    **  file URLs - FM
 
293
    **
 
294
    **  After trying it for a while, it's still
 
295
    **  premature, IHMO, to go along with it, so
 
296
    **  this is back to inheriting for identical
 
297
    **  schemes whether or not they are "file".
 
298
    **  If you want to try it again yourself,
 
299
    **  uncomment the strcasecomp() below. - FM
 
300
    */
 
301
    if ((given.access && related.access) &&
 
302
        (/* strcasecomp(given.access, "file") || */
 
303
         strcmp(given.access, related.access))) {
 
304
        related.host = NULL;
 
305
        related.absolute = NULL;
 
306
        related.relative = NULL;
 
307
        related.search = NULL;
 
308
        related.anchor = NULL;
 
309
    }
 
310
 
 
311
    /*
 
312
    **  Handle the host field.
 
313
    */
 
314
    if (wanted & PARSE_HOST) {
 
315
        if (given.host || related.host) {
 
316
            if (wanted & PARSE_PUNCTUATION) {
 
317
                *tail++ = '/';
 
318
                *tail++ = '/';
 
319
            }
 
320
            strcpy(tail, given.host ? given.host : related.host);
 
321
#define CLEAN_URLS
 
322
#ifdef CLEAN_URLS
 
323
            /*
 
324
            **  Ignore default port numbers, and trailing dots on FQDNs,
 
325
            **  which will only cause identical addresses to look different.
 
326
            **  (related is already a clean url).
 
327
            */
 
328
            {
 
329
                char *p2, *h;
 
330
                if ((p2 = strchr(result, '@')) != NULL)
 
331
                   tail = (p2 + 1);
 
332
                p2 = strchr(tail, ':');
 
333
                if (p2 != NULL && !isdigit(UCH(p2[1])))
 
334
                    /*
 
335
                    **  Colon not followed by a port number.
 
336
                    */
 
337
                    *p2 = '\0';
 
338
                if (p2 != NULL && *p2 != '\0' && acc_method != NULL) {
 
339
                    /*
 
340
                    **  Port specified.
 
341
                    */
 
342
                    if ((!strcmp(acc_method, "http"      ) && !strcmp(p2, ":80" )) ||
 
343
                        (!strcmp(acc_method, "https"     ) && !strcmp(p2, ":443")) ||
 
344
                        (!strcmp(acc_method, "gopher"    ) && !strcmp(p2, ":70" )) ||
 
345
                        (!strcmp(acc_method, "ftp"       ) && !strcmp(p2, ":21" )) ||
 
346
                        (!strcmp(acc_method, "wais"      ) && !strcmp(p2, ":210")) ||
 
347
                        (!strcmp(acc_method, "nntp"      ) && !strcmp(p2, ":119")) ||
 
348
                        (!strcmp(acc_method, "news"      ) && !strcmp(p2, ":119")) ||
 
349
                        (!strcmp(acc_method, "newspost"  ) && !strcmp(p2, ":119")) ||
 
350
                        (!strcmp(acc_method, "newsreply" ) && !strcmp(p2, ":119")) ||
 
351
                        (!strcmp(acc_method, "snews"     ) && !strcmp(p2, ":563")) ||
 
352
                        (!strcmp(acc_method, "snewspost" ) && !strcmp(p2, ":563")) ||
 
353
                        (!strcmp(acc_method, "snewsreply") && !strcmp(p2, ":563")) ||
 
354
                        (!strcmp(acc_method, "finger"    ) && !strcmp(p2, ":79" )) ||
 
355
                        (!strcmp(acc_method, "telnet"    ) && !strcmp(p2, ":23" )) ||
 
356
                        (!strcmp(acc_method, "tn3270"    ) && !strcmp(p2, ":23" )) ||
 
357
                        (!strcmp(acc_method, "rlogin"    ) && !strcmp(p2, ":513")) ||
 
358
                        (!strcmp(acc_method, "cso"       ) && !strcmp(p2, ":105")))
 
359
                    *p2 = '\0'; /* It is the default: ignore it */
 
360
                }
 
361
                if (p2 == NULL) {
 
362
                    int len3 = strlen(tail);
 
363
 
 
364
                    if (len3 > 0) {
 
365
                        h = tail + len3 - 1;    /* last char of hostname */
 
366
                        if (*h == '.')
 
367
                            *h = '\0';          /* chop final . */
 
368
                    }
 
369
                } else if (p2 != result) {
 
370
                    h = p2;
 
371
                    h--;                /* End of hostname */
 
372
                    if (*h == '.') {
 
373
                        /*
 
374
                        **  Slide p2 over h.
 
375
                        */
 
376
                        while (*p2 != '\0')
 
377
                            *h++ = *p2++;
 
378
                        *h = '\0';      /* terminate */
 
379
                    }
 
380
                }
 
381
            }
 
382
#endif /* CLEAN_URLS */
 
383
        }
 
384
    }
 
385
 
 
386
    /*
 
387
     * Trim any blanks from the result so far - there's no excuse for blanks
 
388
     * in a hostname.  Also update the tail here.
 
389
     */
 
390
    tail = LYRemoveBlanks(result);
 
391
 
 
392
    /*
 
393
    **  If host in given or related was ended directly with a '?' (no
 
394
    **  slash), fake the search part into absolute.  This is the only
 
395
    **  case search is returned from scan.  A host must have been present.
 
396
    **  this restores the '?' at which the host part had been truncated in
 
397
    **  scan, we have to do this after host part handling is done. - kw
 
398
    */
 
399
    if (given.search && *(given.search - 1) == '\0') {
 
400
        given.absolute = given.search - 1;
 
401
        given.absolute[0] = '?';
 
402
    } else if (related.search && !related.absolute &&
 
403
               *(related.search - 1) == '\0') {
 
404
        related.absolute = related.search - 1;
 
405
        related.absolute[0] = '?';
 
406
    }
 
407
 
 
408
    /*
 
409
    **  If different hosts, inherit no path.
 
410
    */
 
411
    if (given.host && related.host)
 
412
        if (strcmp(given.host, related.host) != 0) {
 
413
            related.absolute = NULL;
 
414
            related.relative = NULL;
 
415
            related.anchor = NULL;
 
416
        }
 
417
 
 
418
    /*
 
419
    **  Handle the path.
 
420
    */
 
421
    if (wanted & (PARSE_PATH | PARSE_STRICTPATH | PARSE_QUERY)) {
 
422
        int want_detail = (wanted & (PARSE_STRICTPATH | PARSE_QUERY));
 
423
 
 
424
        if (acc_method && !given.absolute && given.relative) {
 
425
            /*
 
426
             * Treat all given nntp or snews paths, or given paths for news
 
427
             * URLs with a host, as absolute.
 
428
             */
 
429
            switch (*acc_method) {
 
430
            case 'N':
 
431
            case 'n':
 
432
                if (!strcasecomp(acc_method, "nntp") ||
 
433
                    (!strcasecomp(acc_method, "news") &&
 
434
                     !strncasecomp(result, "news://", 7))) {
 
435
                    given.absolute = given.relative;
 
436
                    given.relative = NULL;
 
437
                }
 
438
                break;
 
439
            case 'S':
 
440
            case 's':
 
441
                if (!strcasecomp(acc_method, "snews")) {
 
442
                    given.absolute = given.relative;
 
443
                    given.relative = NULL;
 
444
                }
 
445
                break;
 
446
            }
 
447
        }
 
448
 
 
449
        if (given.absolute) {                   /* All is given */
 
450
            if (wanted & PARSE_PUNCTUATION)
 
451
                *tail++ = '/';
 
452
            strcpy(tail, given.absolute);
 
453
            CTRACE((tfp, "HTParse: (ABS)\n"));
 
454
        } else if (related.absolute) {          /* Adopt path not name */
 
455
            *tail++ = '/';
 
456
            strcpy(tail, related.absolute);
 
457
            if (given.relative) {
 
458
                p = strchr(tail, '?');  /* Search part? */
 
459
                if (p == NULL)
 
460
                    p = (tail + strlen(tail) - 1);
 
461
                for (; *p != '/'; p--)
 
462
                    ;                           /* last / */
 
463
                p[1] = '\0';                    /* Remove filename */
 
464
                strcat(p, given.relative); /* Add given one */
 
465
                HTSimplify (result);
 
466
            }
 
467
            CTRACE((tfp, "HTParse: (Related-ABS)\n"));
 
468
        } else if (given.relative) {
 
469
            strcpy(tail, given.relative);               /* what we've got */
 
470
            CTRACE((tfp, "HTParse: (REL)\n"));
 
471
        } else if (related.relative) {
 
472
            strcpy(tail, related.relative);
 
473
            CTRACE((tfp, "HTParse: (Related-REL)\n"));
 
474
        } else {  /* No inheritance */
 
475
            if (!isLYNXCGI(aName) &&
 
476
                !isLYNXEXEC(aName) &&
 
477
                !isLYNXPROG(aName)) {
 
478
                *tail++ = '/';
 
479
                *tail = '\0';
 
480
            }
 
481
            if (!strcmp(result, "news:/"))
 
482
                result[5] = '*';
 
483
            CTRACE((tfp, "HTParse: (No inheritance)\n"));
 
484
        }
 
485
        if (want_detail) {
 
486
            p = strchr(tail, '?');      /* Search part? */
 
487
            if (p) {
 
488
                if (PARSE_STRICTPATH) {
 
489
                    *p = '\0';
 
490
                } else {
 
491
                    if (!(wanted & PARSE_PUNCTUATION))
 
492
                        p++;
 
493
                    do {
 
494
                        *tail++ = *p;
 
495
                    } while (*p++);
 
496
                }
 
497
            } else {
 
498
                if (wanted & PARSE_QUERY)
 
499
                    *tail = '\0';
 
500
            }
 
501
        }
 
502
    }
 
503
 
 
504
    /*
 
505
    **  Handle the fragment (anchor). Never inherit.
 
506
    */
 
507
    if (wanted & PARSE_ANCHOR) {
 
508
        if (given.anchor && *given.anchor) {
 
509
            tail += strlen(tail);
 
510
            if (wanted & PARSE_PUNCTUATION)
 
511
                *tail++ = '#';
 
512
            strcpy(tail, given.anchor);
 
513
        }
 
514
    }
 
515
 
 
516
    /*
 
517
     * If there are any blanks remaining in the string, escape them as needed.
 
518
     * See the discussion in LYLegitimizeHREF() for example.
 
519
     */
 
520
    if ((p = strchr(result, ' ')) != 0) {
 
521
        switch (is_url(result)) {
 
522
        case UNKNOWN_URL_TYPE:
 
523
            CTRACE((tfp, "HTParse:      ignore:`%s'\n", result));
 
524
            break;
 
525
        case LYNXEXEC_URL_TYPE:
 
526
        case LYNXPROG_URL_TYPE:
 
527
        case LYNXCGI_URL_TYPE:
 
528
        case LYNXPRINT_URL_TYPE:
 
529
        case LYNXHIST_URL_TYPE:
 
530
        case LYNXDOWNLOAD_URL_TYPE:
 
531
        case LYNXKEYMAP_URL_TYPE:
 
532
        case LYNXIMGMAP_URL_TYPE:
 
533
        case LYNXCOOKIE_URL_TYPE:
 
534
        case LYNXDIRED_URL_TYPE:
 
535
        case LYNXOPTIONS_URL_TYPE:
 
536
        case LYNXCFG_URL_TYPE:
 
537
        case LYNXCOMPILE_OPTS_URL_TYPE:
 
538
        case LYNXMESSAGES_URL_TYPE:
 
539
            CTRACE((tfp, "HTParse:      spaces:`%s'\n", result));
 
540
            break;
 
541
        case NOT_A_URL_TYPE:
 
542
        default:
 
543
            CTRACE((tfp, "HTParse:      encode:`%s'\n", result));
 
544
            do {
 
545
                char *q = p + strlen(p) + 2;
 
546
                while (q != p + 1) {
 
547
                    q[0] = q[-2];
 
548
                    --q;
 
549
                }
 
550
                p[0] = '%';
 
551
                p[1] = '2';
 
552
                p[2] = '0';
 
553
            } while ((p = strchr(result, ' ')) != 0);
 
554
            break;
 
555
        }
 
556
    }
 
557
    CTRACE((tfp, "HTParse:      result:`%s'\n", result));
 
558
 
 
559
    StrAllocCopy(return_value, result);
 
560
    LYalloca_free(result);
 
561
 
 
562
    /* FIXME: could be optimized using HTParse() internals */
 
563
    if (*relatedName &&
 
564
        ((wanted & PARSE_ALL_WITHOUT_ANCHOR) == PARSE_ALL_WITHOUT_ANCHOR)) {
 
565
        /*
 
566
         *  Check whether to fill in localhost. - FM
 
567
         */
 
568
        LYFillLocalFileURL(&return_value, relatedName);
 
569
        CTRACE((tfp, "pass LYFillLocalFile:`%s'\n", return_value));
 
570
    }
 
571
 
 
572
    return return_value;                /* exactly the right length */
 
573
}
 
574
 
 
575
/*      HTParseAnchor(), fast HTParse() specialization
 
576
**      ----------------------------------------------
 
577
**
 
578
** On exit,
 
579
**      returns         A pointer within input string (probably to its end '\0')
 
580
*/
 
581
PUBLIC CONST char * HTParseAnchor ARGS1(
 
582
        CONST char *,   aName)
 
583
{
 
584
    CONST char* p = aName;
 
585
    for ( ; *p && *p != '#'; p++)
 
586
        ;
 
587
    if (*p == '#') {
 
588
        /* the safe way based on HTParse() -
 
589
         * keeping in mind scan() peculiarities on schemes:
 
590
         */
 
591
        struct struct_parts given;
 
592
 
 
593
        char* name = (char*)LYalloca((p - aName) + strlen(p) + 1);
 
594
        if (name == NULL) {
 
595
            outofmem(__FILE__, "HTParseAnchor");
 
596
        }
 
597
        strcpy(name, aName);
 
598
        scan(name, &given);
 
599
        LYalloca_free(name);
 
600
 
 
601
        p++; /*next to '#'*/
 
602
        if (given.anchor == NULL) {
 
603
            for ( ; *p; p++)  /*scroll to end '\0'*/
 
604
                ;
 
605
        }
 
606
    }
 
607
    return p;
 
608
}
 
609
 
 
610
/*      Simplify a filename.                            HTSimplify()
 
611
**      --------------------
 
612
**
 
613
**  A unix-style file is allowed to contain the sequence xxx/../ which may
 
614
**  be replaced by "" , and the sequence "/./" which may be replaced by "/".
 
615
**  Simplification helps us recognize duplicate filenames.
 
616
**
 
617
**      Thus,   /etc/junk/../fred       becomes /etc/fred
 
618
**              /etc/junk/./fred        becomes /etc/junk/fred
 
619
**
 
620
**      but we should NOT change
 
621
**              http://fred.xxx.edu/../..
 
622
**
 
623
**      or      ../../albert.html
 
624
*/
 
625
PUBLIC void HTSimplify ARGS1(
 
626
        char *,         filename)
 
627
{
 
628
    char *p;
 
629
    char *q, *q1;
 
630
 
 
631
    if (filename == NULL)
 
632
        return;
 
633
 
 
634
    if (!(filename[0] && filename[1]) ||
 
635
        filename[0] == '?' || filename[1] == '?' || filename[2] == '?')
 
636
        return;
 
637
 
 
638
    if (strchr(filename, '/') != NULL) {
 
639
        for (p = (filename + 2); *p; p++) {
 
640
            if (*p == '?') {
 
641
                /*
 
642
                **  We're still treating a ?searchpart as part of
 
643
                **  the path in HTParse() and scan(), but if we
 
644
                **  encounter a '?' here, assume it's the delimiter
 
645
                **  and break.  We also could check for a parameter
 
646
                **  delimiter (';') here, but the current Fielding
 
647
                **  draft (wisely or ill-advisedly :) says that it
 
648
                **  should be ignored and collapsing be allowed in
 
649
                **  it's value).  The only defined parameter at
 
650
                **  present is ;type=[A, I, or D] for ftp URLs, so
 
651
                **  if there's a "/..", "/../", "/./", or terminal
 
652
                **  '.' following the ';', it must be due to the
 
653
                **  ';' being an unescaped path character and not
 
654
                **  actually a parameter delimiter. - FM
 
655
                */
 
656
                break;
 
657
            }
 
658
            if (*p == '/') {
 
659
                if ((p[1] == '.') && (p[2] == '.') &&
 
660
                    (p[3] == '/' || p[3] == '?' || p[3] == '\0')) {
 
661
                    /*
 
662
                    **  Handle "../", "..?" or "..".
 
663
                    */
 
664
                    for (q = (p - 1); (q >= filename) && (*q != '/'); q--)
 
665
                        /*
 
666
                        **  Back up to previous slash or beginning of string.
 
667
                        */
 
668
                        ;
 
669
                    if ((q[0] == '/') &&
 
670
                        (strncmp(q, "/../", 4) &&
 
671
                         strncmp(q, "/..?", 4)) &&
 
672
                        !((q - 1) > filename && q[-1] == '/')) {
 
673
                        /*
 
674
                        **  Not at beginning of string or in a
 
675
                        **  host field, so remove the "/xxx/..".
 
676
                        */
 
677
                        q1 = (p + 3);
 
678
                        p = q;
 
679
                        while (*q1 != '\0')
 
680
                            *p++ = *q1++;
 
681
                        *p = '\0';              /* terminate */
 
682
                        /*
 
683
                        **  Start again with previous slash.
 
684
                        */
 
685
                        p = (q - 1);
 
686
                    }
 
687
                } else if (p[1] == '.' && p[2] == '/') {
 
688
                    /*
 
689
                    **  Handle "./" by removing both characters.
 
690
                    */
 
691
                    q = p;
 
692
                    q1 = (p + 2);
 
693
                    while (*q1 != '\0')
 
694
                        *q++ = *q1++;
 
695
                    *q = '\0';          /* terminate */
 
696
                    p--;
 
697
                } else if (p[1] == '.' && p[2] == '?') {
 
698
                    /*
 
699
                    **  Handle ".?" by removing the dot.
 
700
                    */
 
701
                    q = (p + 1);
 
702
                    q1 = (p + 2);
 
703
                    while (*q1 != '\0')
 
704
                        *q++ = *q1++;
 
705
                    *q = '\0';          /* terminate */
 
706
                    p--;
 
707
                } else if (p[1] == '.' && p[2] == '\0') {
 
708
                    /*
 
709
                    **  Handle terminal "." by removing the character.
 
710
                    */
 
711
                    p[1] = '\0';
 
712
                }
 
713
            }
 
714
        }
 
715
        if (p >= filename + 2 && *p == '?' && *(p-1)  == '.') {
 
716
            if (*(p-2) == '/') {
 
717
                /*
 
718
                **  Handle "/.?" by removing the dot.
 
719
                */
 
720
                q = p - 1;
 
721
                q1 = p;
 
722
                while (*q1 != '\0')
 
723
                    *q++ = *q1++;
 
724
                *q = '\0';
 
725
            } else if (*(p-2) == '.' &&
 
726
                       p >= filename + 4 && *(p-3) == '/' &&
 
727
                       (*(p-4) != '/' ||
 
728
                        (p > filename + 4 && *(p-5) != ':'))) {
 
729
                    /*
 
730
                    **  Handle "xxx/..?"
 
731
                    */
 
732
                for (q = (p - 4); (q > filename) && (*q != '/'); q--)
 
733
                        /*
 
734
                        **  Back up to previous slash or beginning of string.
 
735
                        */
 
736
                    ;
 
737
                if (*q == '/') {
 
738
                    if (q > filename && *(q-1) == '/' &&
 
739
                        !(q > filename + 1 && *(q-1) != ':'))
 
740
                        return;
 
741
                    q++;
 
742
                }
 
743
                if (strncmp(q, "../", 3) && strncmp(q, "./", 2)) {
 
744
                        /*
 
745
                        **  Not after "//" at beginning of string or
 
746
                        **  after "://", and xxx is not ".." or ".",
 
747
                        **  so remove the "xxx/..".
 
748
                        */
 
749
                    q1 = p;
 
750
                    p = q;
 
751
                    while (*q1 != '\0')
 
752
                        *p++ = *q1++;
 
753
                    *p = '\0';          /* terminate */
 
754
                }
 
755
            }
 
756
        }
 
757
    }
 
758
}
 
759
 
 
760
/*      Make Relative Name.                                     HTRelative()
 
761
**      -------------------
 
762
**
 
763
** This function creates and returns a string which gives an expression of
 
764
** one address as related to another.  Where there is no relation, an absolute
 
765
** address is returned.
 
766
**
 
767
**  On entry,
 
768
**      Both names must be absolute, fully qualified names of nodes
 
769
**      (no anchor bits)
 
770
**
 
771
**  On exit,
 
772
**      The return result points to a newly allocated name which, if
 
773
**      parsed by HTParse relative to relatedName, will yield aName.
 
774
**      The caller is responsible for freeing the resulting name later.
 
775
**
 
776
*/
 
777
PUBLIC char * HTRelative ARGS2(
 
778
        CONST char *,   aName,
 
779
        CONST char *,   relatedName)
 
780
{
 
781
    char * result = NULL;
 
782
    CONST char *p = aName;
 
783
    CONST char *q = relatedName;
 
784
    CONST char * after_access = NULL;
 
785
    CONST char * path = NULL;
 
786
    CONST char * last_slash = NULL;
 
787
    int slashes = 0;
 
788
 
 
789
    for (; *p; p++, q++) {      /* Find extent of match */
 
790
        if (*p != *q)
 
791
            break;
 
792
        if (*p == ':')
 
793
            after_access = p+1;
 
794
        if (*p == '/') {
 
795
            last_slash = p;
 
796
            slashes++;
 
797
            if (slashes == 3)
 
798
                path=p;
 
799
        }
 
800
    }
 
801
 
 
802
    /* q, p point to the first non-matching character or zero */
 
803
 
 
804
    if (!after_access) {                        /* Different access */
 
805
        StrAllocCopy(result, aName);
 
806
    } else if (slashes < 3){                    /* Different nodes */
 
807
        StrAllocCopy(result, after_access);
 
808
    } else if (slashes == 3){                   /* Same node, different path */
 
809
        StrAllocCopy(result, path);
 
810
    } else {                                    /* Some path in common */
 
811
        int levels = 0;
 
812
        for (; *q && (*q != '#'); q++)
 
813
            if (*q == '/')
 
814
                levels++;
 
815
        result = typecallocn(char, 3*levels + strlen(last_slash) + 1);
 
816
        if (result == NULL)
 
817
            outofmem(__FILE__, "HTRelative");
 
818
        result[0] = '\0';
 
819
        for (; levels; levels--)
 
820
            strcat(result, "../");
 
821
        strcat(result, last_slash+1);
 
822
    }
 
823
    CTRACE((tfp,
 
824
            "HTparse: `%s' expressed relative to\n   `%s' is\n   `%s'.\n",
 
825
            aName, relatedName, result));
 
826
    return result;
 
827
}
 
828
 
 
829
/*      Escape undesirable characters using %                   HTEscape()
 
830
**      -------------------------------------
 
831
**
 
832
**      This function takes a pointer to a string in which
 
833
**      some characters may be unacceptable unescaped.
 
834
**      It returns a string which has these characters
 
835
**      represented by a '%' character followed by two hex digits.
 
836
**
 
837
**      Unlike HTUnEscape(), this routine returns a calloc'd string.
 
838
*/
 
839
PRIVATE CONST unsigned char isAcceptable[96] =
 
840
 
 
841
/*      Bit 0           xalpha          -- see HTFile.h
 
842
**      Bit 1           xpalpha         -- as xalpha but with plus.
 
843
**      Bit 2 ...       path            -- as xpalphas but with /
 
844
*/
 
845
    /*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
 
846
    {    0,0,0,0,0,0,0,0,0,0,7,6,0,7,7,4,       /* 2x   !"#$%&'()*+,-./  */
 
847
         7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,       /* 3x  0123456789:;<=>?  */
 
848
         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 4x  @ABCDEFGHIJKLMNO  */
 
849
         7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,       /* 5X  PQRSTUVWXYZ[\]^_  */
 
850
         0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 6x  `abcdefghijklmno  */
 
851
         7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0 };     /* 7X  pqrstuvwxyz{|}~  DEL */
 
852
 
 
853
PRIVATE char *hex = "0123456789ABCDEF";
 
854
#define ACCEPTABLE(a)   ( a>=32 && a<128 && ((isAcceptable[a-32]) & mask))
 
855
 
 
856
PUBLIC char * HTEscape ARGS2(
 
857
        CONST char *,   str,
 
858
        unsigned char,  mask)
 
859
{
 
860
    CONST char * p;
 
861
    char * q;
 
862
    char * result;
 
863
    int unacceptable = 0;
 
864
    for (p = str; *p; p++)
 
865
        if (!ACCEPTABLE(UCH(TOASCII(*p))))
 
866
            unacceptable++;
 
867
    result = typecallocn(char, p-str + unacceptable + unacceptable + 1);
 
868
    if (result == NULL)
 
869
        outofmem(__FILE__, "HTEscape");
 
870
    for (q = result, p = str; *p; p++) {
 
871
        unsigned char a = TOASCII(*p);
 
872
        if (!ACCEPTABLE(a)) {
 
873
            *q++ = HEX_ESCAPE;  /* Means hex coming */
 
874
            *q++ = hex[a >> 4];
 
875
            *q++ = hex[a & 15];
 
876
        }
 
877
        else *q++ = *p;
 
878
    }
 
879
    *q++ = '\0';                /* Terminate */
 
880
    return result;
 
881
}
 
882
 
 
883
/*      Escape unsafe characters using %                        HTEscapeUnsafe()
 
884
**      --------------------------------
 
885
**
 
886
**      This function takes a pointer to a string in which
 
887
**      some characters may be that may be unsafe are unescaped.
 
888
**      It returns a string which has these characters
 
889
**      represented by a '%' character followed by two hex digits.
 
890
**
 
891
**      Unlike HTUnEscape(), this routine returns a malloc'd string.
 
892
*/
 
893
#define UNSAFE(ch) (((ch) <= 32) || ((ch) >= 127))
 
894
 
 
895
PUBLIC char *HTEscapeUnsafe ARGS1(
 
896
        CONST char *,   str)
 
897
{
 
898
    CONST char * p;
 
899
    char * q;
 
900
    char * result;
 
901
    int unacceptable = 0;
 
902
    for (p = str; *p; p++)
 
903
        if (UNSAFE(UCH(TOASCII(*p))))
 
904
            unacceptable++;
 
905
    result = typecallocn(char, p-str + unacceptable + unacceptable + 1);
 
906
    if (result == NULL)
 
907
        outofmem(__FILE__, "HTEscapeUnsafe");
 
908
    for (q = result, p = str; *p; p++) {
 
909
        unsigned char a = TOASCII(*p);
 
910
        if (UNSAFE(a)) {
 
911
            *q++ = HEX_ESCAPE;  /* Means hex coming */
 
912
            *q++ = hex[a >> 4];
 
913
            *q++ = hex[a & 15];
 
914
        }
 
915
        else *q++ = *p;
 
916
    }
 
917
    *q++ = '\0';                /* Terminate */
 
918
    return result;
 
919
}
 
920
 
 
921
/*      Escape undesirable characters using % but space to +.   HTEscapeSP()
 
922
**      -----------------------------------------------------
 
923
**
 
924
**      This function takes a pointer to a string in which
 
925
**      some characters may be unacceptable unescaped.
 
926
**      It returns a string which has these characters
 
927
**      represented by a '%' character followed by two hex digits,
 
928
**      except that spaces are converted to '+' instead of %2B.
 
929
**
 
930
**      Unlike HTUnEscape(), this routine returns a calloced string.
 
931
*/
 
932
PUBLIC char * HTEscapeSP ARGS2(
 
933
        CONST char *,   str,
 
934
        unsigned char,  mask)
 
935
{
 
936
    CONST char * p;
 
937
    char * q;
 
938
    char * result;
 
939
    int unacceptable = 0;
 
940
    for (p = str; *p; p++)
 
941
        if (!(*p == ' ' || ACCEPTABLE(UCH(TOASCII(*p)))))
 
942
            unacceptable++;
 
943
    result = typecallocn(char, p-str + unacceptable + unacceptable + 1);
 
944
    if (result == NULL)
 
945
        outofmem(__FILE__, "HTEscape");
 
946
    for (q = result, p = str; *p; p++) {
 
947
        unsigned char a = TOASCII(*p);
 
948
        if (a == 32) {
 
949
            *q++ = '+';
 
950
        } else if (!ACCEPTABLE(a)) {
 
951
            *q++ = HEX_ESCAPE;  /* Means hex coming */
 
952
            *q++ = hex[a >> 4];
 
953
            *q++ = hex[a & 15];
 
954
        } else {
 
955
            *q++ = *p;
 
956
        }
 
957
    }
 
958
    *q++ = '\0';                        /* Terminate */
 
959
    return result;
 
960
}
 
961
 
 
962
/*      Decode %xx escaped characters.                          HTUnEscape()
 
963
**      ------------------------------
 
964
**
 
965
**      This function takes a pointer to a string in which some
 
966
**      characters may have been encoded in %xy form, where xy is
 
967
**      the ASCII hex code for character 16x+y.
 
968
**      The string is converted in place, as it will never grow.
 
969
*/
 
970
PRIVATE char from_hex ARGS1(
 
971
        char,           c)
 
972
{
 
973
    return (char) ( c >= '0' && c <= '9' ?  c - '0'
 
974
            : c >= 'A' && c <= 'F'? c - 'A' + 10
 
975
            : c - 'a' + 10);     /* accept small letters just in case */
 
976
}
 
977
 
 
978
PUBLIC char * HTUnEscape ARGS1(
 
979
        char *,         str)
 
980
{
 
981
    char * p = str;
 
982
    char * q = str;
 
983
 
 
984
    if (!(p && *p))
 
985
        return str;
 
986
 
 
987
    while (*p != '\0') {
 
988
        if (*p == HEX_ESCAPE &&
 
989
            /*
 
990
             *  Tests shouldn't be needed, but better safe than sorry.
 
991
             */
 
992
            p[1] && p[2] &&
 
993
            isxdigit(UCH(p[1])) &&
 
994
            isxdigit(UCH(p[2]))) {
 
995
            p++;
 
996
            if (*p)
 
997
                *q = (char) (from_hex(*p++) * 16);
 
998
            if (*p) {
 
999
                /*
 
1000
                ** Careful! FROMASCII() may evaluate its arg more than once!
 
1001
                */  /* S/390 -- gil -- 0221 */
 
1002
                *q = (char) (*q + from_hex(*p++));
 
1003
            }
 
1004
            *q = FROMASCII(*q);
 
1005
            q++;
 
1006
        } else {
 
1007
            *q++ = *p++;
 
1008
        }
 
1009
    }
 
1010
 
 
1011
    *q++ = '\0';
 
1012
    return str;
 
1013
 
 
1014
} /* HTUnEscape */
 
1015
 
 
1016
/*      Decode some %xx escaped characters.                   HTUnEscapeSome()
 
1017
**      -----------------------------------                     Klaus Weide
 
1018
**                                                          (kweide@tezcat.com)
 
1019
**      This function takes a pointer to a string in which some
 
1020
**      characters may have been encoded in %xy form, where xy is
 
1021
**      the ASCII hex code for character 16x+y, and a pointer to
 
1022
**      a second string containing one or more characters which
 
1023
**      should be unescaped if escaped in the first string.
 
1024
**      The first string is converted in place, as it will never grow.
 
1025
*/
 
1026
PUBLIC char * HTUnEscapeSome ARGS2(
 
1027
        char *,         str,
 
1028
        CONST char *,   do_trans)
 
1029
{
 
1030
    char * p = str;
 
1031
    char * q = str;
 
1032
    char testcode;
 
1033
 
 
1034
    if (p == NULL || *p == '\0' || do_trans == NULL || *do_trans == '\0')
 
1035
        return str;
 
1036
 
 
1037
    while (*p != '\0') {
 
1038
        if (*p == HEX_ESCAPE &&
 
1039
            p[1] && p[2] &&     /* tests shouldn't be needed, but.. */
 
1040
            isxdigit(UCH(p[1])) &&
 
1041
            isxdigit(UCH(p[2])) &&
 
1042
            (testcode = (char) FROMASCII(from_hex(p[1])*16 +
 
1043
                from_hex(p[2]))) && /* %00 no good*/
 
1044
            strchr(do_trans, testcode)) { /* it's one of the ones we want */
 
1045
            *q++ = testcode;
 
1046
            p += 3;
 
1047
        } else {
 
1048
            *q++ = *p++;
 
1049
        }
 
1050
    }
 
1051
 
 
1052
    *q++ = '\0';
 
1053
    return str;
 
1054
 
 
1055
} /* HTUnEscapeSome */
 
1056
 
 
1057
PRIVATE CONST unsigned char crfc[96] =
 
1058
 
 
1059
/*      Bit 0           xalpha          -- need "quoting"
 
1060
**      Bit 1           xpalpha         -- need \escape if quoted
 
1061
*/
 
1062
    /*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
 
1063
    {    1,0,3,0,0,0,0,0,1,1,0,0,1,0,1,0,       /* 2x   !"#$%&'()*+,-./  */
 
1064
         0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,       /* 3x  0123456789:;<=>?  */
 
1065
         1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,       /* 4x  @ABCDEFGHIJKLMNO  */
 
1066
         0,0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,       /* 5X  PQRSTUVWXYZ[\]^_  */
 
1067
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,       /* 6x  `abcdefghijklmno  */
 
1068
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3 };     /* 7X  pqrstuvwxyz{|}~  DEL */
 
1069
 
 
1070
/*
 
1071
**  Turn a string which is not a RFC 822 token into a quoted-string. - KW
 
1072
**  The "quoted" parameter tells whether we need the beginning/ending quote
 
1073
**  marks.  If not, the caller will provide them -TD
 
1074
*/
 
1075
PUBLIC void HTMake822Word ARGS2(
 
1076
        char **,        str,
 
1077
        int,            quoted)
 
1078
{
 
1079
    CONST char * p;
 
1080
    char * q;
 
1081
    char * result;
 
1082
    unsigned char a;
 
1083
    int added = 0;
 
1084
 
 
1085
    if (isEmpty(*str)) {
 
1086
        StrAllocCopy(*str, quoted ? "\"\"" : "");
 
1087
        return;
 
1088
    }
 
1089
    for (p = *str; *p; p++) {
 
1090
        a = TOASCII(*p);  /* S/390 -- gil -- 0240 */
 
1091
        if (a < 32 || a >= 128 ||
 
1092
            ((crfc[a-32]) & 1)) {
 
1093
            if (!added)
 
1094
                added = 2;
 
1095
            if (a >= 160 || a == '\t')
 
1096
                continue;
 
1097
            if (a == '\r' || a == '\n')
 
1098
                added += 2;
 
1099
            else if ((a & 127) < 32 || ((crfc[a-32]) & 2))
 
1100
                added++;
 
1101
        }
 
1102
    }
 
1103
    if (!added)
 
1104
        return;
 
1105
    result = typecallocn(char, p-(*str) + added + 1);
 
1106
    if (result == NULL)
 
1107
        outofmem(__FILE__, "HTMake822Word");
 
1108
 
 
1109
    q = result;
 
1110
    if (quoted)
 
1111
        *q++ = '"';
 
1112
    /*
 
1113
    ** Having converted the character to ASCII, we can't use symbolic
 
1114
    ** escape codes, since they're in the host character set, which
 
1115
    ** is not necessarily ASCII.  Thus we use octal escape codes instead.
 
1116
    ** -- gil (Paul Gilmartin) <pg@sweng.stortek.com>
 
1117
    */  /* S/390 -- gil -- 0268 */
 
1118
    for (p = *str; *p; p++) {
 
1119
        a = TOASCII(*p);
 
1120
        if ((a != '\011') && ((a & 127) < 32 ||
 
1121
                            ( a < 128 && ((crfc[a-32]) & 2))))
 
1122
            *q++ = '\033';
 
1123
        *q++ = *p;
 
1124
        if (a == '\012' || (a == '\015' && (TOASCII(*(p+1)) != '\012')))
 
1125
            *q++ = ' ';
 
1126
    }
 
1127
    if (quoted)
 
1128
        *q++ = '"';
 
1129
    *q++ = '\0';                        /* Terminate */
 
1130
    FREE(*str);
 
1131
    *str = result;
 
1132
}