2 * Sherlock Library -- URL Functions (according to RFC 1738 and 1808)
4 * (c) 1997--2002 Martin Mares <mj@ucw.cz>
5 * (c) 2001 Robert Spalek <robert@ucw.cz>
7 * This software may be freely distributed and used according to the terms
8 * of the GNU Lesser General Public License.
13 #include "lib/chartype.h"
23 static uns url_ignore_spaces;
24 static uns url_ignore_underflow;
25 static byte *url_component_separators = "";
26 static uns url_min_repeat_count = 0x7fffffff;
27 static uns url_max_repeat_length = 0;
29 static struct cfitem url_config[] = {
30 { "URL", CT_SECTION, NULL },
31 { "IgnoreSpaces", CT_INT, &url_ignore_spaces },
32 { "IgnoreUnderflow", CT_INT, &url_ignore_underflow },
33 { "ComponentSeparators", CT_STRING, &url_component_separators },
34 { "MinRepeatCount", CT_INT, &url_min_repeat_count },
35 { "MaxRepeatLength", CT_INT, &url_max_repeat_length },
36 { NULL, CT_STOP, NULL }
39 static void CONSTRUCTOR url_init_config(void)
41 cf_register(url_config);
44 /* Escaping and de-escaping */
49 return (x<10) ? (x + '0') : (x - 10 + 'A');
53 url_deescape(byte *s, byte *d)
56 byte *end = d + MAX_URL_SIZE - 10;
60 return URL_ERR_TOO_LONG;
64 if (!Cxdigit(s[1]) || !Cxdigit(s[2]))
65 return URL_ERR_INVALID_ESCAPE;
66 val = Cxvalue(s[1])*16 + Cxvalue(s[2]);
68 return URL_ERR_INVALID_ESCAPED_CHAR;
72 val = NCC_SEMICOLON; break;
74 val = NCC_SLASH; break;
76 val = NCC_QUEST; break;
78 val = NCC_COLON; break;
82 val = NCC_EQUAL; break;
86 val = NCC_HASH; break;
98 if (!url_ignore_spaces || !(!*s || d == dstart))
103 return URL_ERR_TOO_LONG;
109 return URL_ERR_INVALID_CHAR;
116 url_enescape(byte *s, byte *d)
118 byte *end = d + MAX_URL_SIZE - 10;
124 return URL_ERR_TOO_LONG;
125 if (Calnum(c) || /* RFC 1738(2.2): Only alphanumerics ... */
126 c == '$' || c == '-' || c == '_' || c == '.' || c == '+' || /* ... and several other exceptions ... */
127 c == '!' || c == '*' || c == '\'' || c == '(' || c == ')' ||
129 c == '/' || c == '?' || c == ':' || c == '@' || /* ... and reserved chars used for reserved purpose */
130 c == '=' || c == '&' || c == '#' || c == ';')
134 uns val = (*s < NCC_MAX) ? NCC_CHARS[*s] : *s;
136 *d++ = enhex(val >> 4);
137 *d++ = enhex(val & 0x0f);
145 /* Split an URL (several parts may be copied to the destination buffer) */
147 byte *url_proto_names[URL_PROTO_MAX] = URL_PNAMES;
148 static int url_proto_path_flags[URL_PROTO_MAX] = URL_PATH_FLAGS;
151 identify_protocol(byte *p)
155 for(i=1; i<URL_PROTO_MAX; i++)
156 if (!strcasecmp(p, url_proto_names[i]))
158 return URL_PROTO_UNKNOWN;
162 url_split(byte *s, struct url *u, byte *d)
164 bzero(u, sizeof(struct url));
166 u->bufend = d + MAX_URL_SIZE - 10;
168 if (s[0] != '/') /* Seek for "protocol:" */
171 while (*p && Calnum(*p))
173 if (p != s && *p == ':')
179 u->protoid = identify_protocol(u->protocol);
181 if (url_proto_path_flags[u->protoid] && (s[0] != '/' || s[1] != '/'))
183 /* The protocol requires complete host spec, but it's missing -> treat as a relative path instead */
184 int len = d - u->protocol;
193 if (s[0] == '/') /* Host spec or absolute path */
195 if (s[1] == '/') /* Host spec */
202 while (*s && *s != '/') /* Copy user:passwd@host:port */
206 if (w) /* user:passwd present */
210 if (e = strchr(q, ':'))
219 if (e) /* host:port present */
223 p = strtoul(e, &ep, 10);
224 if (ep && *ep || p > 65535)
225 return URL_ERR_INVALID_PORT;
226 else if (p) /* Port 0 (e.g. in :/) is treated as default port */
238 /* Normalization according to given base URL */
240 static uns std_ports[] = URL_DEFPORTS; /* Default port numbers */
243 relpath_merge(struct url *u, struct url *b)
251 if (a[0] == '/') /* Absolute path => OK */
254 return URL_PATH_UNDERFLOW;
256 if (!a[0]) /* Empty URL -> inherit everything */
262 u->rest = d; /* We know we'll need to copy the path somewhere else */
264 if (a[0] == '#') /* Another fragment */
266 for(p=o; *p && *p != '#'; p++)
270 if (a[0] == '?') /* New query */
272 for(p=o; *p && *p != '#' && *p != '?'; p++)
276 if (a[0] == ';') /* Change parameters */
278 for(p=o; *p && *p != ';' && *p != '?' && *p != '#'; p++)
283 p = NULL; /* Copy original path and find the last slash */
284 while (*o && *o != ';' && *o != '?' && *o != '#')
287 return URL_ERR_TOO_LONG;
288 if ((*d++ = *o++) == '/')
292 return URL_ERR_REL_NOTHING;
299 if (a[1] == '/' || !a[1]) /* Skip "./" and ".$" */
306 else if (a[1] == '.' && (a[2] == '/' || !a[2])) /* "../" */
314 * RFC 1808 says we should leave ".." as a path segment, but
315 * we intentionally break the rule and refuse the URL.
317 if (!url_ignore_underflow)
318 return URL_PATH_UNDERFLOW;
322 d--; /* Discard trailing slash */
329 while (a[0] && a[0] != '/')
332 return URL_ERR_TOO_LONG;
344 copy: /* Combine part of old URL with the new one */
349 return URL_ERR_TOO_LONG;
354 return URL_ERR_TOO_LONG;
359 url_normalize(struct url *u, struct url *b)
364 if (url_proto_path_flags[u->protoid] && !u->host ||
365 u->host && !*u->host ||
366 !u->host && u->user ||
367 !u->user && u->pass ||
369 return URL_SYNTAX_ERROR;
373 /* Now we know it's a relative URL. Do we have any base? */
374 if (!b || !url_proto_path_flags[b->protoid])
375 return URL_ERR_REL_NOTHING;
376 u->protocol = b->protocol;
377 u->protoid = b->protoid;
379 /* Reference to the same host */
386 if (err = relpath_merge(u, b))
391 /* Fill in missing info */
393 u->port = std_ports[u->protoid];
398 /* Name canonicalization */
406 if (*b >= 'A' && *b <= 'Z')
413 kill_end_dot(byte *b)
419 k = b + strlen(b) - 1;
420 while (k > b && *k == '.')
426 url_canonicalize(struct url *u)
430 lowercase(u->protocol);
432 kill_end_dot(u->host);
433 if ((!u->rest || !*u->rest) && url_proto_path_flags[u->protoid])
435 if (u->rest && (c = strchr(u->rest, '#'))) /* Kill fragment reference */
440 /* Pack a broken-down URL */
443 append(byte *d, byte *s, byte *e)
456 url_pack(struct url *u, byte *d)
458 byte *e = d + MAX_URL_SIZE - 10;
462 d = append(d, u->protocol, e);
463 d = append(d, ":", e);
464 u->protoid = identify_protocol(u->protocol);
468 d = append(d, "//", e);
471 d = append(d, u->user, e);
474 d = append(d, ":", e);
475 d = append(d, u->pass, e);
477 d = append(d, "@", e);
479 d = append(d, u->host, e);
480 if (u->port != std_ports[u->protoid] && u->port != ~0U)
483 sprintf(z, "%d", u->port);
484 d = append(d, ":", e);
489 d = append(d, u->rest, e);
491 return URL_ERR_TOO_LONG;
498 static char *errmsg[] = {
499 "Something is wrong",
503 "Invalid escaped character",
504 "Invalid port number",
505 "Relative URL not allowed",
514 if (err >= sizeof(errmsg) / sizeof(char *))
519 /* Standard cookbook recipes */
522 url_canon_split(byte *u, byte *buf1, byte *buf2, struct url *url)
526 if (err = url_deescape(u, buf1))
528 if (err = url_split(buf1, url, buf2))
530 if (err = url_normalize(url, NULL))
532 return url_canonicalize(url);
536 url_auto_canonicalize(byte *src, byte *dst)
538 byte buf1[MAX_URL_SIZE], buf2[MAX_URL_SIZE], buf3[MAX_URL_SIZE];
542 (void)((err = url_canon_split(src, buf1, buf2, &ur)) ||
543 (err = url_pack(&ur, buf3)) ||
544 (err = url_enescape(buf3, dst)));
552 int main(int argc, char **argv)
554 char buf1[MAX_URL_SIZE], buf2[MAX_URL_SIZE], buf3[MAX_URL_SIZE], buf4[MAX_URL_SIZE];
556 struct url url, url0;
560 if (err = url_deescape(argv[1], buf1))
562 printf("deesc: error %d\n", err);
565 printf("deesc: %s\n", buf1);
566 if (err = url_split(buf1, &url, buf2))
568 printf("split: error %d\n", err);
571 printf("split: @%s@%s@%s@%s@%d@%s\n", url.protocol, url.user, url.pass, url.host, url.port, url.rest);
572 if (err = url_split("http://mj@www.hell.org/123/sub_dir/index.html;param?query&zzz/subquery#fragment", &url0, buf3))
574 printf("split base: error %d\n", err);
577 if (err = url_normalize(&url0, NULL))
579 printf("normalize base: error %d\n", err);
582 printf("base: @%s@%s@%s@%s@%d@%s\n", url0.protocol, url0.user, url0.pass, url0.host, url0.port, url0.rest);
583 if (err = url_normalize(&url, &url0))
585 printf("normalize: error %d\n", err);
588 printf("normalize: @%s@%s@%s@%s@%d@%s\n", url.protocol, url.user, url.pass, url.host, url.port, url.rest);
589 if (err = url_canonicalize(&url))
591 printf("canonicalize: error %d\n", err);
594 printf("canonicalize: @%s@%s@%s@%s@%d@%s\n", url.protocol, url.user, url.pass, url.host, url.port, url.rest);
595 if (err = url_pack(&url, buf4))
597 printf("pack: error %d\n", err);
600 printf("pack: %s\n", buf4);
601 if (err = url_enescape(buf4, buf2))
603 printf("enesc: error %d\n", err);
606 printf("enesc: %s\n", buf2);
619 hashf(byte *start, int length)
623 hf = (hf << 8 | hf >> 24) ^ *start++;
628 repeat_count(struct component *comp, uns count, uns len)
630 struct component *orig_comp = comp;
640 for (i=0; i<len; i++)
641 if (comp[i].hash != orig_comp[i].hash
642 || comp[i].length != orig_comp[i].length
643 || memcmp(comp[i].start, orig_comp[i].start, comp[i].length))
649 url_has_repeated_component(byte *url)
651 struct component *comp;
652 uns comps, comp_len, rep_prefix;
656 for (comps=0, c=url; c; comps++)
658 c = strpbrk(c, url_component_separators);
662 if (comps < url_min_repeat_count)
664 comp = alloca(comps * sizeof(struct component));
665 for (i=0, c=url; c; i++)
668 c = strpbrk(c, url_component_separators);
671 comp[i].length = c - comp[i].start;
675 comp[i].length = strlen(comp[i].start);
678 for (i=0; i<comps; i++)
679 comp[i].hash = hashf(comp[i].start, comp[i].length);
680 for (comp_len = 1; comp_len <= url_max_repeat_length && comp_len <= comps; comp_len++)
681 for (rep_prefix = 0; rep_prefix <= comps - comp_len; rep_prefix++)
682 if (repeat_count(comp + rep_prefix, comps - rep_prefix, comp_len) >= url_min_repeat_count)