2009-11-08 Chris Toshok <toshok@ximian.com>
[moon.git] / src / uri.cpp
blobfd0dc27839b7fc1afe7f93376313b98e54e45534
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 struct path_component_t {
223 const char *start;
224 size_t len;
227 static char *
228 flatten_path (const char *path)
230 path_component_t part;
231 const char *inptr;
232 char *result, *p;
233 GArray *parts;
234 size_t n;
235 guint i;
236 bool keep = false;
238 if (path == NULL)
239 return NULL;
241 parts = g_array_new (false, false, sizeof (path_component_t));
242 n = 0;
244 inptr = path;
245 while (*inptr) {
246 while (*inptr == '/')
247 inptr++;
249 if (*inptr == '\0')
250 break;
252 part.start = inptr;
253 while (*inptr && *inptr != '/')
254 inptr++;
256 part.len = (size_t) (inptr - part.start);
257 keep = false;
258 if (part.len == 2 && !strncmp (part.start, "..", 2)) {
259 // drop the most recent parent (if not ..)
260 if (parts->len > 0) {
261 path_component_t prev_part = g_array_index (parts, path_component_t, parts->len - 1);
262 if (prev_part.len == 2 && !strncmp (prev_part.start, "..", 2)) {
263 keep = true;
264 } else {
265 part = prev_part;
266 n -= part.len;
267 parts->len--;
269 } else {
270 keep = true;
272 } else if (part.len == 1 && !strncmp (part.start, ".", 1)) {
273 // drop this path component
275 } else if (part.len > 0) {
276 // keep track of this component
277 keep = true;
280 if (keep) {
281 g_array_append_val (parts, part);
282 n += part.len;
286 // at this point, n is the char count of all path components (minus separators)
287 n += parts->len;
288 p = result = (char *) g_malloc (n + 2);
290 if (path[0] == '/')
291 *p++ = '/';
293 for (i = 0; i < parts->len; i++) {
294 part = g_array_index (parts, path_component_t, i);
295 memcpy (p, part.start, part.len);
296 p += part.len;
297 *p++ = '/';
300 *p = '\0';
301 if (p > result && inptr > path && inptr[-1] != '/')
302 p[-1] = '\0';
304 g_array_free (parts, true);
306 return result;
309 #define HEXVAL(c) (isdigit ((int) ((unsigned char) c)) ? (c) - '0' : tolower ((unsigned char) c) - 'a' + 10)
311 #define is_xdigit(c) isxdigit ((int) ((unsigned char) c))
313 static void
314 url_decode (char *in, const char *url)
316 register char *inptr, *outptr;
318 inptr = outptr = in;
319 while (*inptr) {
320 if (*inptr == '%') {
321 if (is_xdigit (inptr[1]) && is_xdigit (inptr[2])) {
322 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
323 inptr += 3;
324 } else {
325 g_warning ("Invalid encoding in url: %s at %s", url, inptr);
326 *outptr++ = *inptr++;
328 } else
329 *outptr++ = *inptr++;
332 *outptr = '\0';
335 bool
336 Uri::Parse (const char *uri, bool allow_trailing_sep)
338 char *name, *value, *scheme = NULL, *user = NULL, *auth = NULL, *passwd = NULL, *host = NULL, *path = NULL, *query = NULL, *fragment = NULL;
339 Uri::Param *param, *tail, *params = NULL;
340 register const char *start, *inptr;
341 bool isAbsolute;
342 bool parse_path = false;
343 int port = -1;
344 size_t n;
346 tail = (Uri::Param *) &params;
347 start = uri;
349 isAbsolute = true;
351 if (!*start) {
352 isAbsolute = false;
353 goto done;
356 inptr = start;
357 while (*inptr && *inptr != ':' && *inptr != '/' && *inptr != '?' && *inptr != '#' && *inptr != '\\')
358 inptr++;
360 if (inptr > start && *inptr == ':') {
361 scheme = g_ascii_strdown (start, inptr - start);
363 inptr++;
364 if (!*inptr) {
365 isAbsolute = false;
366 goto done;
369 if (!strncmp (inptr, "//", 2))
370 inptr += 2;
372 start = inptr;
373 while (*inptr && *inptr != ';' && *inptr != ':' && *inptr != '@' && *inptr != '/')
374 inptr++;
375 } else {
376 isAbsolute = !strncmp (inptr, "\\\\", 2);
377 parse_path = true;
378 scheme = NULL;
379 inptr = uri;
382 switch (*inptr) {
383 case ';': /* <user>;auth= */
384 case ':': /* <user>:passwd or <host>:port */
385 case '@': /* <user>@host */
386 if (inptr - start) {
387 user = g_strndup (start, inptr - start);
388 url_decode (user, uri);
391 switch (*inptr) {
392 case ';': /* ;auth= */
393 if (!g_ascii_strncasecmp (inptr, ";auth=", 6)) {
394 inptr += 6;
395 start = inptr;
396 while (*inptr && *inptr != ':' && *inptr != '@')
397 inptr++;
399 if (inptr - start) {
400 auth = g_strndup (start, inptr - start);
401 url_decode (auth, uri);
404 if (*inptr == ':') {
405 inptr++;
406 start = inptr;
407 goto decode_passwd;
408 } else if (*inptr == '@') {
409 inptr++;
410 start = inptr;
411 goto decode_host;
414 break;
415 case ':': /* <user>:passwd@ or <host>:port */
416 inptr++;
417 start = inptr;
418 decode_passwd:
419 while (*inptr && *inptr != '@' && *inptr != '/')
420 inptr++;
422 if (*inptr == '@') {
423 /* <user>:passwd@ */
424 if (inptr - start) {
425 passwd = g_strndup (start, inptr - start);
426 url_decode (passwd, uri);
429 inptr++;
430 start = inptr;
431 goto decode_host;
432 } else {
433 /* <host>:port */
434 host = user;
435 user = NULL;
436 inptr = start;
437 goto decode_port;
440 break;
441 case '@': /* <user>@host */
442 inptr++;
443 start = inptr;
444 decode_host:
445 while (*inptr && *inptr != ':' && *inptr != '/')
446 inptr++;
448 if (inptr > start) {
449 n = inptr - start;
450 while (n > 0 && start[n - 1] == '.')
451 n--;
453 if (n > 0)
454 host = g_strndup (start, n);
457 if (*inptr == ':') {
458 inptr++;
459 decode_port:
460 port = 0;
462 while (*inptr >= '0' && *inptr <= '9' && port < 6554)
463 port = (port * 10) + ((*inptr++) - '0');
465 if (port > 65535) {
466 /* chop off the last digit */
467 port /= 10;
470 while (*inptr && *inptr != '/')
471 inptr++;
474 break;
475 case '/': /* <host>/path or simply <host> */
476 case '\0':
477 if (inptr > start) {
478 n = inptr - start;
479 while (n > 0 && start[n - 1] == '.')
480 n--;
482 if (n > 0)
483 host = g_strndup (start, n);
485 break;
486 default:
487 break;
490 if (parse_path || *inptr == '/') {
491 /* look for params, query, or fragment */
492 start = inptr;
493 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
494 inptr++;
496 /* canonicalise and save the path component */
497 if ((n = (inptr - start))) {
498 value = g_strndup (start, n);
499 url_decode (value, uri);
501 if (!(path = canon_path (value, !host, allow_trailing_sep)))
502 g_free (value);
504 if (isAbsolute) {
505 value = flatten_path (path);
506 g_free (path);
507 path = value;
511 switch (*inptr) {
512 case ';':
513 while (*inptr == ';') {
514 while (*inptr == ';')
515 inptr++;
517 start = inptr;
518 while (*inptr && *inptr != '=' && *inptr != ';' && *inptr != '?' && *inptr != '#')
519 inptr++;
521 name = g_strndup (start, inptr - start);
522 url_decode (name, uri);
524 if (*inptr == '=') {
525 inptr++;
526 start = inptr;
527 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
528 inptr++;
530 value = g_strndup (start, inptr - start);
531 url_decode (value, uri);
532 } else {
533 value = g_strdup ("");
536 param = g_new (Uri::Param, 1);
537 param->value = value;
538 param->name = name;
539 param->next = NULL;
541 tail->next = param;
542 tail = param;
545 if (*inptr == '#')
546 goto decode_fragment;
547 else if (*inptr != '?')
548 break;
550 /* fall thru */
551 case '?':
552 inptr++;
553 start = inptr;
554 while (*inptr && *inptr != '#')
555 inptr++;
557 query = g_strndup (start, inptr - start);
558 url_decode (query, uri);
560 if (*inptr != '#')
561 break;
563 /* fall thru */
564 case '#':
565 decode_fragment:
566 fragment = g_strdup (inptr + 1);
567 url_decode (fragment, uri);
568 break;
572 done:
574 Free ();
576 // update the values
578 this->scheme = scheme;
579 this->user = user;
580 this->auth = auth;
581 this->passwd = passwd;
582 this->host = host;
583 this->port = port;
584 this->path = path;
585 this->params = params;
586 this->query = query;
587 this->fragment = fragment;
588 this->originalString = g_strdup (uri);
589 this->isAbsolute = isAbsolute;
591 return true;
594 void
595 Uri::Combine (const char *relative_path)
597 char *combined, *p;
598 if (path) {
599 // strip off the 'filename' component
600 if (!(p = strrchr (path, '/')))
601 p = path;
602 *p = '\0';
604 // combine with the relative path
605 combined = g_strdup_printf ("%s/%s", path, relative_path);
606 g_free (path);
608 // flatten the resulting combined path
609 path = flatten_path (combined);
610 g_free (combined);
611 } else {
612 path = flatten_path (relative_path);
616 void
617 Uri::Combine (const Uri *relative_uri)
619 if (relative_uri->isAbsolute)
620 g_warning ("Uri::Combine (): Not a relative Uri");
621 if (relative_uri->path)
622 Combine (relative_uri->path);
625 static void
626 append_url_encoded (GString *string, const char *in, const char *extra)
628 register const char *inptr = in;
629 const char *start;
631 while (*inptr) {
632 start = inptr;
633 while (*inptr && !is_unsafe (*inptr) && !strchr (extra, *inptr))
634 inptr++;
636 g_string_append_len (string, start, inptr - start);
638 while (*inptr && (is_unsafe (*inptr) || strchr (extra, *inptr)))
639 g_string_append_printf (string, "%%%.02hhx", *inptr++);
643 static void
644 append_param (GString *string, Uri::Param *param)
646 g_string_append_c (string, ';');
648 append_url_encoded (string, param->name, "?=#");
650 if (param->value && *param->value) {
651 g_string_append_c (string, '=');
652 append_url_encoded (string, param->value, "?;#");
656 char *
657 Uri::ToString (UriToStringFlags flags) const
659 Uri::Param *param;
660 GString *string;
661 char *uri;
663 string = g_string_new ("");
665 if (this->host) {
666 g_string_append (string, this->scheme);
667 g_string_append (string, "://");
669 if (this->user) {
670 append_url_encoded (string, this->user, ":;@/");
672 if (this->auth) {
673 g_string_append (string, ";auth=");
674 append_url_encoded (string, this->auth, ":@/");
677 if (this->passwd && !(flags & UriHidePasswd)) {
678 g_string_append_c (string, ':');
679 append_url_encoded (string, this->passwd, "@/");
682 g_string_append_c (string, '@');
685 g_string_append (string, this->host);
687 if (this->port > 0)
688 g_string_append_printf (string, ":%d", this->port);
691 if (this->path) {
692 if (this->host && *this->path != '/')
693 g_string_append_c (string, '/');
695 append_url_encoded (string, this->path, " ;?#");
696 } else if (this->host && (this->params || this->query || this->fragment)) {
697 g_string_append_c (string, '/');
700 param = this->params;
701 while (param) {
702 append_param (string, param);
703 param = param->next;
706 if (this->query && !(flags & UriHideQuery)) {
707 g_string_append_c (string, '?');
708 append_url_encoded (string, this->query, "#");
711 if (this->fragment && !(flags & UriHideFragment)) {
712 g_string_append_c (string, '#');
713 append_url_encoded (string, this->fragment, "");
716 uri = string->str;
717 g_string_free (string, false);
719 return uri;
722 bool
723 Uri::operator== (const Uri &v) const
725 if (isAbsolute != v.isAbsolute)
726 return false;
727 if (port != v.port)
728 return false;
729 if (!!scheme != !!v.scheme
730 || (scheme && strcmp (scheme, v.scheme)))
731 return false;
732 if (!!user != !!v.user
733 || (user && strcmp (user, v.user)))
734 return false;
735 if (!!auth != !!v.auth
736 || (auth && strcmp (auth, v.auth)))
737 return false;
738 if (!!passwd != !!v.passwd
739 || (passwd && strcmp (passwd, v.passwd)))
740 return false;
741 if (!!host != !!v.host
742 || (host && strcmp (host, v.host)))
743 return false;
744 if (!!path != !!v.path
745 || (path && strcmp (path, v.path)))
746 return false;
747 if (!!query != !!v.query
748 || (query && strcmp (query, v.query)))
749 return false;
750 if (!!fragment != !!v.fragment
751 || (fragment && strcmp (fragment, v.fragment)))
752 return false;
753 if (!uri_params_equal (params, v.params))
754 return false;
756 // we intentionally don't compare original strings
757 return true;
760 bool
761 Uri::Equals (const Uri *left, const Uri *right)
763 if (!left && !right)
764 return true;
765 if (!left || !right)
766 return false;
767 return left->operator==(*right);
770 bool
771 Uri::IsNullOrEmpty (const Uri *uri)
773 if (!uri || (uri->scheme == NULL && uri->user == NULL && uri->auth == NULL &&
774 uri->passwd == NULL && uri->host == NULL && uri->port == 0 && uri->path == NULL
775 && uri->params == NULL && uri->query == NULL && uri->fragment == NULL &&
776 strcmp (uri->originalString, "") == 0 && !uri->isAbsolute))
777 return true;
779 return false;
782 guint
783 Uri::GetHashCode ()
785 char* str = ToString();
786 guint hash = g_str_hash (str);
787 g_free (str);
788 return hash;
791 bool
792 Uri::IsScheme (const char *scheme) const
794 if (!!this->scheme != !!scheme)
795 return false;
797 if (this->scheme)
798 return !g_ascii_strcasecmp (this->scheme, scheme);
800 return true;
803 bool
804 Uri::SameSiteOfOrigin (const Uri *left, const Uri *right)
806 // works only on absolute URI
807 if (!left || !left->isAbsolute || !right || !right->isAbsolute)
808 return false;
810 if (left->port != right->port)
811 return false;
813 if (!left->scheme || !right->scheme || (strcmp (left->scheme, right->scheme) != 0))
814 return false;
816 // comparing 2 file:/// URI where no hosts is present
817 if (!left->host && !right->host && (strcmp (left->scheme, "file") == 0))
818 return true;
820 if (!left->host || !right->host || (strcmp (left->host, right->host) != 0))
821 return false;
823 return true;