2009-08-26 Chris Toshok <toshok@ximian.com>
[moon.git] / src / uri.cpp
blob05eb20a210ae8a9bf9ac8ff5951071bb1e176afc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * uri.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <stdio.h>
16 #include <string.h>
17 #include <ctype.h>
19 #include "uri.h"
22 /* see rfc1738, section 2.2 */
23 static bool
24 is_unsafe (unsigned char c)
26 if (c <= 0x1f || c >= 0x7f)
27 return true;
29 switch (c) {
30 case '<':
31 case '>':
32 case '"':
33 case '#':
34 case '%':
35 case '{':
36 case '}':
37 case '|':
38 case '\\':
39 case '^':
40 case '~':
41 case '[':
42 case ']':
43 case '`':
44 return true;
46 return false;
49 static void
50 uri_params_clear (Uri::Param **params)
52 Uri::Param *next, *param = *params;
54 while (param) {
55 next = param->next;
56 g_free (param->value);
57 g_free (param->name);
58 g_free (param);
59 param = next;
62 *params = NULL;
65 static Uri::Param *
66 uri_params_copy (Uri::Param *params)
68 Uri::Param *list, *param, *cur, *tail;
70 list = NULL;
71 tail = (Uri::Param *) &list;
73 cur = params;
74 while (cur) {
75 param = g_new (Uri::Param, 1);
76 param->value = g_strdup (cur->value);
77 param->name = g_strdup (cur->name);
78 param->next = NULL;
80 tail->next = param;
81 tail = param;
83 cur = cur->next;
86 return list;
89 static bool
90 uri_params_equal (Uri::Param *params0, Uri::Param *params1)
92 // Note: this might need to be changed to allow params out of order
93 Uri::Param *p0 = params0;
94 Uri::Param *p1 = params1;
96 while (p0) {
97 if (!p1 || strcmp (p0->name, p1->name) != 0
98 || strcmp (p0->value, p1->value) != 0)
99 return false;
101 p0 = p0->next;
102 p1 = p1->next;
105 if (p1 != NULL)
106 return false;
108 return true;
111 Uri::Uri ()
113 scheme = NULL;
114 user = NULL;
115 auth = NULL;
116 passwd = NULL;
117 host = NULL;
118 port = 0;
119 path = NULL;
120 params = NULL;
121 query = NULL;
122 fragment = NULL;
123 originalString = g_strdup("");
124 isAbsolute = false;
127 Uri::Uri (const Uri& uri)
129 scheme = NULL;
130 user = NULL;
131 auth = NULL;
132 passwd = NULL;
133 host = NULL;
134 port = 0;
135 path = NULL;
136 params = NULL;
137 query = NULL;
138 fragment = NULL;
139 originalString = NULL;
140 isAbsolute = false;
142 Uri::Copy (&uri, this);
145 Uri::~Uri ()
147 Free ();
150 void
151 Uri::Free ()
153 g_free (scheme); scheme = NULL;
154 g_free (user); user = NULL;
155 g_free (auth); auth = NULL;
156 g_free (passwd); passwd = NULL;
157 g_free (host); host = NULL;
158 g_free (path); path = NULL;
159 uri_params_clear (&params);
160 g_free (query); query = NULL;
161 g_free (fragment); fragment = NULL;
162 g_free (originalString); originalString = NULL;
163 isAbsolute = false;
166 void
167 Uri::Copy (const Uri *from, Uri *to)
169 if (from != NULL) {
170 to->scheme = g_strdup (from->scheme);
171 to->user = g_strdup (from->user);
172 to->auth = g_strdup (from->auth);
173 to->passwd = g_strdup (from->passwd);
174 to->host = g_strdup (from->host);
175 to->port = from->port;
176 to->path = g_strdup (from->path);
177 to->params = uri_params_copy (from->params);
178 to->query = g_strdup (from->query);
179 to->fragment = g_strdup (from->fragment);
180 to->originalString = g_strdup (from->originalString);
181 to->isAbsolute = from->isAbsolute;
182 } else {
183 to->scheme = NULL;
184 to->user = NULL;
185 to->auth = NULL;
186 to->passwd = NULL;
187 to->host = NULL;
188 to->port = -1;
189 to->path = NULL;
190 to->query = NULL;
191 to->fragment = NULL;
192 to->originalString = NULL;
193 to->isAbsolute = false;
197 /* canonicalise a path */
198 static char *
199 canon_path (char *path, bool allow_root, bool allow_trailing_sep)
201 register char *d, *inptr;
203 d = inptr = path;
205 while (*inptr) {
206 if (inptr[0] == '/' && (inptr[1] == '/' || (inptr[1] == '\0' && !allow_trailing_sep)))
207 inptr++;
208 else
209 *d++ = *inptr++;
212 if (!allow_root && (d == path + 1) && d[-1] == '/')
213 d--;
214 else if (allow_root && d == path && path[0] == '/')
215 *d++ = '/';
217 *d = '\0';
219 return path[0] ? path : NULL;
222 #define HEXVAL(c) (isdigit ((int) ((unsigned char) c)) ? (c) - '0' : tolower ((unsigned char) c) - 'a' + 10)
224 #define is_xdigit(c) isxdigit ((int) ((unsigned char) c))
226 static void
227 url_decode (char *in, const char *url)
229 register char *inptr, *outptr;
231 inptr = outptr = in;
232 while (*inptr) {
233 if (*inptr == '%') {
234 if (is_xdigit (inptr[1]) && is_xdigit (inptr[2])) {
235 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
236 inptr += 3;
237 } else {
238 g_warning ("Invalid encoding in url: %s at %s", url, inptr);
239 *outptr++ = *inptr++;
241 } else
242 *outptr++ = *inptr++;
245 *outptr = '\0';
248 bool
249 Uri::Parse (const char *uri, bool allow_trailing_sep)
251 char *name, *value, *scheme = NULL, *user = NULL, *auth = NULL, *passwd = NULL, *host = NULL, *path = NULL, *query = NULL, *fragment = NULL;
252 Uri::Param *param, *tail, *params = NULL;
253 register const char *start, *inptr;
254 bool isAbsolute;
255 bool parse_path = false;
256 int port = -1;
257 size_t n;
259 tail = (Uri::Param *) &params;
260 start = uri;
262 isAbsolute = true;
264 if (!*start) {
265 isAbsolute = false;
266 goto done;
269 inptr = start;
270 while (*inptr && *inptr != ':' && *inptr != '/' && *inptr != '?' && *inptr != '#' && *inptr != '\\')
271 inptr++;
273 if (inptr > start && *inptr == ':') {
274 scheme = g_ascii_strdown (start, inptr - start);
276 inptr++;
277 if (!*inptr) {
278 isAbsolute = false;
279 goto done;
282 if (!strncmp (inptr, "//", 2))
283 inptr += 2;
285 start = inptr;
286 while (*inptr && *inptr != ';' && *inptr != ':' && *inptr != '@' && *inptr != '/')
287 inptr++;
288 } else {
289 isAbsolute = !strncmp (inptr, "\\\\", 2);
290 parse_path = true;
291 scheme = NULL;
292 inptr = uri;
295 switch (*inptr) {
296 case ';': /* <user>;auth= */
297 case ':': /* <user>:passwd or <host>:port */
298 case '@': /* <user>@host */
299 if (inptr - start) {
300 user = g_strndup (start, inptr - start);
301 url_decode (user, uri);
304 switch (*inptr) {
305 case ';': /* ;auth= */
306 if (!g_ascii_strncasecmp (inptr, ";auth=", 6)) {
307 inptr += 6;
308 start = inptr;
309 while (*inptr && *inptr != ':' && *inptr != '@')
310 inptr++;
312 if (inptr - start) {
313 auth = g_strndup (start, inptr - start);
314 url_decode (auth, uri);
317 if (*inptr == ':') {
318 inptr++;
319 start = inptr;
320 goto decode_passwd;
321 } else if (*inptr == '@') {
322 inptr++;
323 start = inptr;
324 goto decode_host;
327 break;
328 case ':': /* <user>:passwd@ or <host>:port */
329 inptr++;
330 start = inptr;
331 decode_passwd:
332 while (*inptr && *inptr != '@' && *inptr != '/')
333 inptr++;
335 if (*inptr == '@') {
336 /* <user>:passwd@ */
337 if (inptr - start) {
338 passwd = g_strndup (start, inptr - start);
339 url_decode (passwd, uri);
342 inptr++;
343 start = inptr;
344 goto decode_host;
345 } else {
346 /* <host>:port */
347 host = user;
348 user = NULL;
349 inptr = start;
350 goto decode_port;
353 break;
354 case '@': /* <user>@host */
355 inptr++;
356 start = inptr;
357 decode_host:
358 while (*inptr && *inptr != ':' && *inptr != '/')
359 inptr++;
361 if (inptr > start) {
362 n = inptr - start;
363 while (n > 0 && start[n - 1] == '.')
364 n--;
366 if (n > 0)
367 host = g_strndup (start, n);
370 if (*inptr == ':') {
371 inptr++;
372 decode_port:
373 port = 0;
375 while (*inptr >= '0' && *inptr <= '9' && port < 6554)
376 port = (port * 10) + ((*inptr++) - '0');
378 if (port > 65535) {
379 /* chop off the last digit */
380 port /= 10;
383 while (*inptr && *inptr != '/')
384 inptr++;
387 break;
388 case '/': /* <host>/path or simply <host> */
389 case '\0':
390 if (inptr > start) {
391 n = inptr - start;
392 while (n > 0 && start[n - 1] == '.')
393 n--;
395 if (n > 0)
396 host = g_strndup (start, n);
398 break;
399 default:
400 break;
403 if (parse_path || *inptr == '/') {
404 /* look for params, query, or fragment */
405 start = inptr;
406 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
407 inptr++;
409 /* canonicalise and save the path component */
410 if ((n = (inptr - start))) {
411 value = g_strndup (start, n);
412 url_decode (value, uri);
414 if (!(path = canon_path (value, !host, allow_trailing_sep)))
415 g_free (value);
418 switch (*inptr) {
419 case ';':
420 while (*inptr == ';') {
421 while (*inptr == ';')
422 inptr++;
424 start = inptr;
425 while (*inptr && *inptr != '=' && *inptr != ';' && *inptr != '?' && *inptr != '#')
426 inptr++;
428 name = g_strndup (start, inptr - start);
429 url_decode (name, uri);
431 if (*inptr == '=') {
432 inptr++;
433 start = inptr;
434 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
435 inptr++;
437 value = g_strndup (start, inptr - start);
438 url_decode (value, uri);
439 } else {
440 value = g_strdup ("");
443 param = g_new (Uri::Param, 1);
444 param->value = value;
445 param->name = name;
446 param->next = NULL;
448 tail->next = param;
449 tail = param;
452 if (*inptr == '#')
453 goto decode_fragment;
454 else if (*inptr != '?')
455 break;
457 /* fall thru */
458 case '?':
459 inptr++;
460 start = inptr;
461 while (*inptr && *inptr != '#')
462 inptr++;
464 query = g_strndup (start, inptr - start);
465 url_decode (query, uri);
467 if (*inptr != '#')
468 break;
470 /* fall thru */
471 case '#':
472 decode_fragment:
473 fragment = g_strdup (inptr + 1);
474 url_decode (fragment, uri);
475 break;
479 done:
481 Free ();
483 // update the values
485 this->scheme = scheme;
486 this->user = user;
487 this->auth = auth;
488 this->passwd = passwd;
489 this->host = host;
490 this->port = port;
491 this->path = path;
492 this->params = params;
493 this->query = query;
494 this->fragment = fragment;
495 this->originalString = g_strdup (uri);
496 this->isAbsolute = isAbsolute;
498 return true;
501 void
502 Uri::Combine (const char *relative_path)
504 const char *filename;
505 char *new_path;
507 if (path && relative_path[0] != '/') {
508 if (!(filename = strrchr (path, '/')))
509 new_path = g_strdup (relative_path);
510 else
511 new_path = g_strdup_printf ("%.*s/%s", filename - path, path, g_str_has_prefix (relative_path, "../") ? relative_path+3 : relative_path);
512 g_free (path);
514 path = new_path;
515 } else {
516 g_free (path);
518 path = g_strdup (relative_path);
522 void
523 Uri::Combine (const Uri *relative_uri)
525 if (relative_uri->isAbsolute)
526 g_warning ("Uri::Combine (): Not a relative Uri");
527 if (relative_uri->path)
528 Combine (relative_uri->path);
531 static void
532 append_url_encoded (GString *string, const char *in, const char *extra)
534 register const char *inptr = in;
535 const char *start;
537 while (*inptr) {
538 start = inptr;
539 while (*inptr && !is_unsafe (*inptr) && !strchr (extra, *inptr))
540 inptr++;
542 g_string_append_len (string, start, inptr - start);
544 while (*inptr && (is_unsafe (*inptr) || strchr (extra, *inptr)))
545 g_string_append_printf (string, "%%%.02hhx", *inptr++);
549 static void
550 append_param (GString *string, Uri::Param *param)
552 g_string_append_c (string, ';');
554 append_url_encoded (string, param->name, "?=#");
556 if (param->value && *param->value) {
557 g_string_append_c (string, '=');
558 append_url_encoded (string, param->value, "?;#");
562 char *
563 Uri::ToString (UriToStringFlags flags) const
565 Uri::Param *param;
566 GString *string;
567 char *uri;
569 string = g_string_new ("");
571 if (this->host) {
572 g_string_append (string, this->scheme);
573 g_string_append (string, "://");
575 if (this->user) {
576 append_url_encoded (string, this->user, ":;@/");
578 if (this->auth) {
579 g_string_append (string, ";auth=");
580 append_url_encoded (string, this->auth, ":@/");
583 if (this->passwd && !(flags & UriHidePasswd)) {
584 g_string_append_c (string, ':');
585 append_url_encoded (string, this->passwd, "@/");
588 g_string_append_c (string, '@');
591 g_string_append (string, this->host);
593 if (this->port > 0)
594 g_string_append_printf (string, ":%d", this->port);
597 if (this->path) {
598 if (this->host && *this->path != '/')
599 g_string_append_c (string, '/');
601 append_url_encoded (string, this->path, ";?#");
602 } else if (this->host && (this->params || this->query || this->fragment)) {
603 g_string_append_c (string, '/');
606 param = this->params;
607 while (param) {
608 append_param (string, param);
609 param = param->next;
612 if (this->query && !(flags & UriHideQuery)) {
613 g_string_append_c (string, '?');
614 append_url_encoded (string, this->query, "#");
617 if (this->fragment && !(flags & UriHideFragment)) {
618 g_string_append_c (string, '#');
619 append_url_encoded (string, this->fragment, "");
622 uri = string->str;
623 g_string_free (string, false);
625 return uri;
628 bool
629 Uri::operator== (const Uri &v) const
631 if (isAbsolute != v.isAbsolute)
632 return false;
633 if (port != v.port)
634 return false;
635 if (!!scheme != !!v.scheme
636 || (scheme && strcmp (scheme, v.scheme)))
637 return false;
638 if (!!user != !!v.user
639 || (user && strcmp (user, v.user)))
640 return false;
641 if (!!auth != !!v.auth
642 || (auth && strcmp (auth, v.auth)))
643 return false;
644 if (!!passwd != !!v.passwd
645 || (passwd && strcmp (passwd, v.passwd)))
646 return false;
647 if (!!host != !!v.host
648 || (host && strcmp (host, v.host)))
649 return false;
650 if (!!path != !!v.path
651 || (path && strcmp (path, v.path)))
652 return false;
653 if (!!query != !!v.query
654 || (query && strcmp (query, v.query)))
655 return false;
656 if (!!fragment != !!v.fragment
657 || (fragment && strcmp (fragment, v.fragment)))
658 return false;
659 if (!uri_params_equal (params, v.params))
660 return false;
662 // we intentionally don't compare original strings
663 return true;
666 bool
667 Uri::Equals (const Uri *left, const Uri *right)
669 if (!left && !right)
670 return true;
671 if (!left || !right)
672 return false;
673 return left->operator==(*right);
676 bool
677 Uri::IsNullOrEmpty (const Uri *uri)
679 if (!uri || (uri->scheme == NULL && uri->user == NULL && uri->auth == NULL &&
680 uri->passwd == NULL && uri->host == NULL && uri->port == 0 && uri->path == NULL
681 && uri->params == NULL && uri->query == NULL && uri->fragment == NULL &&
682 strcmp (uri->originalString, "") == 0 && !uri->isAbsolute))
683 return true;
685 return false;
688 guint
689 Uri::GetHashCode ()
691 char* str = ToString();
692 guint hash = g_str_hash (str);
693 g_free (str);
694 return hash;
697 bool
698 Uri::IsScheme (const char *scheme) const
700 if (!!this->scheme != !!scheme)
701 return false;
703 if (this->scheme)
704 return !g_ascii_strcasecmp (this->scheme, scheme);
706 return true;
709 bool
710 Uri::SameSiteOfOrigin (const Uri *left, const Uri *right)
712 // works only on absolute URI
713 if (!left || !left->isAbsolute || !right || !right->isAbsolute)
714 return false;
716 if (left->port != right->port)
717 return false;
719 if (!left->scheme || !right->scheme || (strcmp (left->scheme, right->scheme) != 0))
720 return false;
722 // comparing 2 file:/// URI where no hosts is present
723 if (!left->host && !right->host && (strcmp (left->scheme, "file") == 0))
724 return true;
726 if (!left->host || !right->host || (strcmp (left->host, right->host) != 0))
727 return false;
729 return true;