]> mj.ucw.cz Git - libucw.git/blob - lib/url.c
Misc.
[libucw.git] / lib / url.c
1 /*
2  *      Sherlock Library -- URL Functions (according to RFC 1738 and 1808)
3  *
4  *      (c) 1997 Martin Mares, <mj@atrey.karlin.mff.cuni.cz>
5  */
6
7 #include <string.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10
11 #include "lib.h"
12 #include "url.h"
13 #include "string.h"
14
15 /* Escaping and de-escaping */
16
17 static uns
18 enhex(uns x)
19 {
20   return (x<10) ? (x + '0') : (x - 10 + 'A');
21 }
22
23 int
24 url_deescape(byte *s, byte *d)
25 {
26   byte *end = d + MAX_URL_SIZE - 10;
27   while (*s)
28     {
29       if (d >= end)
30         return URL_ERR_TOO_LONG;
31       if (*s == '%')
32         {
33           unsigned int val;
34           if (!Cxdigit(s[1]) || !Cxdigit(s[2]))
35             return URL_ERR_INVALID_ESCAPE;
36           val = Cxvalue(s[1])*16 + Cxvalue(s[2]);
37           if (!Cprint(val))
38             return URL_ERR_INVALID_ESCAPED_CHAR;
39           switch (val)
40             {
41             case ';':
42               val = NCC_SEMICOLON; break;
43             case '/':
44               val = NCC_SLASH; break;
45             case '?':
46               val = NCC_QUEST; break;
47             case ':':
48               val = NCC_COLON; break;
49             case '@':
50               val = NCC_AT; break;
51             case '=':
52               val = NCC_EQUAL; break;
53             case '&':
54               val = NCC_AND; break;
55             }
56           *d++ = val;
57           s += 3;
58         }
59       else if (*s >= 0x20 && *s <= 0x7e || *s >= 0xa0)
60         *d++ = *s++;
61       else
62         return URL_ERR_INVALID_CHAR;
63     }
64   *d = 0;
65   return 0;
66 }
67
68 int
69 url_enescape(byte *s, byte *d)
70 {
71   byte *end = d + MAX_URL_SIZE - 10;
72
73   while (*s)
74     {
75       if (d >= end)
76         return URL_ERR_TOO_LONG;
77       if (   *s >= 'A' && *s <= 'Z'
78              || *s >= 'a' && *s <= 'z'
79              || *s >= '0' && *s <= '9'
80              || *s == '$' || *s == '-' || *s == '.' || *s == '+'
81              || *s == '!' || *s == '*' || *s == '\'' || *s == '('
82              || *s == ')' || *s == '_' || *s == ';' || *s == '/'
83              || *s == '?' || *s == ':' || *s == '@' || *s == '='
84              || *s == '&')
85         *d++ = *s++;
86       else
87         {
88           uns val = (*s < NCC_MAX) ? ";/?:@=&"[*s] : *s;
89           *d++ = '%';
90           *d++ = enhex(val >> 4);
91           *d++ = enhex(val & 0x0f);
92           s++;
93         }
94     }
95   *d = 0;
96   return 0;
97 }
98
99 /* Split an URL (several parts may be copied to the destination buffer) */
100
101 uns
102 identify_protocol(byte *p)
103 {
104   if (!strcasecmp(p, "http"))
105     return URL_PROTO_HTTP;
106   if (!strcasecmp(p, "ftp"))
107     return URL_PROTO_FTP;
108   return 0;
109 }
110
111 int
112 url_split(byte *s, struct url *u, byte *d)
113 {
114   bzero(u, sizeof(struct url));
115   u->port = ~0;
116   u->bufend = d + MAX_URL_SIZE - 10;
117
118   if (s[0] != '/')                      /* Seek for "protocol:" */
119     {
120       byte *p = s;
121       while (*p && Calnum(*p))
122         p++;
123       if (p != s && *p == ':')
124         {
125           u->protocol = d;
126           while (s < p)
127             *d++ = *s++;
128           *d++ = 0;
129           u->protoid = identify_protocol(u->protocol);
130           s++;
131         }
132     }
133
134   if (s[0] == '/')                      /* Host spec or absolute path */
135     {
136       if (s[1] == '/')                  /* Host spec */
137         {
138           byte *q, *w, *e;
139           char *ep;
140
141           s += 2;
142           q = d;
143           while (*s && *s != '/')       /* Copy user:passwd@host:port */
144             *d++ = *s++;
145           *d++ = 0;
146           w = strchr(q, '@');
147           if (w)                        /* user:passwd present */
148             {
149               *w++ = 0;
150               u->user = q;
151             }
152           else
153             w = q;
154           e = strchr(w, ':');
155           if (e)                        /* host:port present */
156             {
157               uns p;
158               *e++ = 0;
159               p = strtoul(e, &ep, 10);
160               if (ep && *ep || p > 65535)
161                 return URL_ERR_INVALID_PORT;
162               else if (p)               /* Port 0 (e.g. in :/) is treated as default port */
163                 u->port = p;
164             }
165           u->host = w;
166         }
167     }
168
169   u->rest = s;
170   u->buf = d;
171   return 0;
172 }
173
174 /* Normalization according to given base URL */
175
176 static uns std_ports[] = { ~0, 80, 21 }; /* Default port numbers */
177
178 static int
179 relpath_merge(struct url *u, struct url *b)
180 {
181   byte *a = u->rest;
182   byte *o = b->rest;
183   byte *d = u->buf;
184   byte *e = u->bufend;
185   byte *p;
186
187   if (a[0] == '/')                      /* Absolute path => OK */
188     return 0;
189   if (o[0] != '/')
190     return URL_PATH_UNDERFLOW;
191
192   if (!a[0])                            /* Empty relative URL is a special case */
193     {
194       u->rest = b->rest;
195       return 0;
196     }
197
198   u->rest = d;
199   p = strrchr(o, '/');                  /* Must be found! */
200   while (o <= p)                        /* Copy original path */
201     {
202       if (d >= e)
203         return URL_ERR_TOO_LONG;
204       *d++ = *o++;
205     }
206
207   while (*a)
208     {
209       if (a[0] == '.')
210         {
211           if (a[1] == '/' || !a[1])     /* Skip "./" and ".$" */
212             {
213               a++;
214               if (a[0])
215                 a++;
216               continue;
217             }
218           else if (a[1] == '.' && (a[2] == '/' || !a[2])) /* "../" */
219             {
220               a += 2;
221               if (d <= u->buf + 1)
222                 return URL_PATH_UNDERFLOW;
223               d--;                      /* Discard trailing slash */
224               while (d[-1] != '/')
225                 d--;
226               if (a[0])
227                 a++;
228               continue;
229             }
230         }
231       while (a[0] && a[0] != '/')
232         {
233           if (d >= e)
234             return URL_ERR_TOO_LONG;
235           *d++ = *a++;
236         }
237       if (a[0])
238         *d++ = *a++;
239     }
240
241   *d++ = 0;
242   u->buf = d;
243   return 0;
244 }
245
246 int
247 url_normalize(struct url *u, struct url *b)
248 {
249   byte *k;
250
251   if (u->protocol && !u->protoid)
252     return 0;
253
254   if ((u->protoid == URL_PROTO_HTTP || (!u->protoid && b && b->protoid == URL_PROTO_HTTP))
255       && u->rest && (k = strchr(u->rest, '#')))
256     *k = 0;                             /* Kill fragment reference */
257
258   if (u->port == ~0)
259     u->port = std_ports[u->protoid];
260
261   if (   u->protocol && !u->host
262          || u->host && !*u->host
263          || !u->host && u->user
264          || !u->rest)
265     return URL_SYNTAX_ERROR;
266
267   if (u->protocol)                      /* Absolute URL */
268     return 0;
269
270   if (!b)                               /* Relative to something? */
271     return URL_ERR_REL_NOTHING;
272   if (!b->protoid)
273     return URL_ERR_UNKNOWN_PROTOCOL;
274
275   if (!u->protocol)
276     {
277       u->protocol = b->protocol;
278       u->protoid = b->protoid;
279     }
280
281   if (!u->host)
282     {
283       u->host = b->host;
284       u->user = b->user;
285       u->port = b->port;
286       return relpath_merge(u, b); 
287     }
288
289   return 0;
290 }
291
292 /* Name canonicalization */
293
294 static void
295 lowercase(byte *b)
296 {
297   if (b)
298     while (*b)
299       {
300         if (*b >= 'A' && *b <= 'Z')
301           *b = *b + 0x20;
302         b++;
303       }
304 }
305
306 static void
307 kill_end_dot(byte *b)
308 {
309   byte *k;
310
311   if (b)
312     {
313       k = b + strlen(b) - 1;
314       if (k > b && *k == '.')
315         *k = 0;
316     }
317 }
318
319 int
320 url_canonicalize(struct url *u)
321 {
322   lowercase(u->protocol);
323   lowercase(u->host);
324   kill_end_dot(u->host);
325   if ((!u->rest || !*u->rest) && (u->protoid == URL_PROTO_HTTP || u->protoid == URL_PROTO_FTP))
326     u->rest = "/";
327   return 0;
328 }
329
330 /* Pack a broken-down URL */
331
332 byte *
333 append(byte *d, byte *s, byte *e)
334 {
335   if (d)
336     while (*s)
337       {
338         if (d >= e)
339           return NULL;
340         *d++ = *s++;
341       }
342   return d;
343 }
344
345 int
346 url_pack(struct url *u, byte *d)
347 {
348   byte *e = d + MAX_URL_SIZE - 10;
349
350   if (u->protocol)
351     {
352       d = append(d, u->protocol, e);
353       d = append(d, ":", e);
354       u->protoid = identify_protocol(u->protocol);
355     }
356   if (u->host)
357     {
358       d = append(d, "//", e);
359       if (u->user)
360         {
361           d = append(d, u->user, e);
362           d = append(d, "@", e);
363         }
364       d = append(d, u->host, e);
365       if (u->port != std_ports[u->protoid] && u->port != ~0)
366         {
367           char z[10];
368           sprintf(z, "%d", u->port);
369           d = append(d, ":", e);
370           d = append(d, z, e);
371         }
372     }
373   if (u->rest)
374     d = append(d, u->rest, e);
375   if (!d)
376     return URL_ERR_TOO_LONG;
377   *d = 0;
378   return 0;
379 }
380
381 /* Error messages */
382
383 static char *errmsg[] = {
384   "Something is wrong",
385   "Too long",
386   "Invalid character",
387   "Invalid escape",
388   "Invalid escaped character",
389   "Invalid port number",
390   "Relative URL not allowed",
391   "Unknown protocol",
392   "Syntax error",
393   "Path underflow"
394 };
395
396 char *
397 url_error(uns err)
398 {
399   if (err >= sizeof(errmsg) / sizeof(char *))
400     err = 0;
401   return errmsg[err];
402 }
403
404 /* A "macro" for canonical split */
405
406 int
407 url_canon_split(byte *u, byte *buf1, byte *buf2, struct url *url)
408 {
409   int err;
410
411   if (err = url_deescape(u, buf1))
412     return err;
413   if (err = url_split(buf1, url, buf2))
414     return err;
415   if (err = url_normalize(url, NULL))
416     return err;
417   return url_canonicalize(url);
418 }
419
420 /* Testing */
421
422 #ifdef TEST
423
424 int main(int argc, char **argv)
425 {
426   char buf1[MAX_URL_SIZE], buf2[MAX_URL_SIZE], buf3[MAX_URL_SIZE], buf4[MAX_URL_SIZE];
427   int err;
428   struct url url, url0;
429
430   if (argc != 2)
431     return 1;
432   if (err = url_deescape(argv[1], buf1))
433     {
434       printf("deesc: error %d\n", err);
435       return 1;
436     }
437   printf("deesc: %s\n", buf1);
438   if (err = url_split(buf1, &url, buf2))
439     {
440       printf("split: error %d\n", err);
441       return 1;
442     }
443   printf("split: @%s@%s@%s@%d@%s\n", url.protocol, url.user, url.host, url.port, url.rest);
444   if (err = url_split("http://mj@www.hell.org/123/sub_dir/index.html", &url0, buf3))
445     {
446       printf("split base: error %d\n", err);
447       return 1;
448     }
449   if (err = url_normalize(&url0, NULL))
450     {
451       printf("normalize base: error %d\n", err);
452       return 1;
453     }
454   printf("base: @%s@%s@%s@%d@%s\n", url0.protocol, url0.user, url0.host, url0.port, url0.rest);
455   if (err = url_normalize(&url, &url0))
456     {
457       printf("normalize: error %d\n", err);
458       return 1;
459     }
460   printf("normalize: @%s@%s@%s@%d@%s\n", url.protocol, url.user, url.host, url.port, url.rest);
461   if (err = url_canonicalize(&url))
462     {
463       printf("canonicalize: error %d\n", err);
464       return 1;
465     }
466   printf("canonicalize: @%s@%s@%s@%d@%s\n", url.protocol, url.user, url.host, url.port, url.rest);
467   if (err = url_pack(&url, buf4))
468     {
469       printf("pack: error %d\n", err);
470       return 1;
471     }
472   printf("pack: %s\n", buf1);
473   if (err = url_enescape(buf4, buf2))
474     {
475       printf("enesc: error %d\n", err);
476       return 1;
477     }
478   printf("enesc: %s\n", buf2);
479   return 0;
480 }
481
482 #endif