revert jeff's last commit since it breaks the build
[moon.git] / src / uri.cpp
blob1d1282df30988b43caea151a8b641f95e2dd7508
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 "deployment.h"
20 #include "uri.h"
23 /* see rfc1738, section 2.2 */
24 static bool
25 is_unsafe (unsigned char c)
27 if (c <= 0x1f || c >= 0x7f)
28 return true;
30 switch (c) {
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 case '`':
45 return true;
47 return false;
50 static void
51 uri_params_clear (Uri::Param **params)
53 Uri::Param *next, *param = *params;
55 while (param) {
56 next = param->next;
57 g_free (param->value);
58 g_free (param->name);
59 g_free (param);
60 param = next;
63 *params = NULL;
66 static Uri::Param *
67 uri_params_copy (Uri::Param *params)
69 Uri::Param *list, *param, *cur, *tail;
71 list = NULL;
72 tail = (Uri::Param *) &list;
74 cur = params;
75 while (cur) {
76 param = g_new (Uri::Param, 1);
77 param->value = g_strdup (cur->value);
78 param->name = g_strdup (cur->name);
79 param->next = NULL;
81 tail->next = param;
82 tail = param;
84 cur = cur->next;
87 return list;
90 static bool
91 uri_params_equal (Uri::Param *params0, Uri::Param *params1)
93 // Note: this might need to be changed to allow params out of order
94 Uri::Param *p0 = params0;
95 Uri::Param *p1 = params1;
97 while (p0) {
98 if (!p1 || strcmp (p0->name, p1->name) != 0
99 || strcmp (p0->value, p1->value) != 0)
100 return false;
102 p0 = p0->next;
103 p1 = p1->next;
106 if (p1 != NULL)
107 return false;
109 return true;
112 Uri::Uri ()
114 scheme = NULL;
115 user = NULL;
116 auth = NULL;
117 passwd = NULL;
118 host = NULL;
119 port = 0;
120 path = NULL;
121 params = NULL;
122 query = NULL;
123 fragment = NULL;
124 originalString = g_strdup("");
125 isAbsolute = false;
128 Uri::Uri (const Uri& uri)
130 scheme = NULL;
131 user = NULL;
132 auth = NULL;
133 passwd = NULL;
134 host = NULL;
135 port = 0;
136 path = NULL;
137 params = NULL;
138 query = NULL;
139 fragment = NULL;
140 originalString = NULL;
141 isAbsolute = false;
143 Uri::Copy (&uri, this);
146 Uri::~Uri ()
148 Free ();
151 void
152 Uri::Free ()
154 g_free (scheme); scheme = NULL;
155 g_free (user); user = NULL;
156 g_free (auth); auth = NULL;
157 g_free (passwd); passwd = NULL;
158 g_free (host); host = NULL;
159 g_free (path); path = NULL;
160 uri_params_clear (&params);
161 g_free (query); query = NULL;
162 g_free (fragment); fragment = NULL;
163 g_free (originalString); originalString = NULL;
164 isAbsolute = false;
167 void
168 Uri::Copy (const Uri *from, Uri *to)
170 if (from != NULL) {
171 to->scheme = g_strdup (from->scheme);
172 to->user = g_strdup (from->user);
173 to->auth = g_strdup (from->auth);
174 to->passwd = g_strdup (from->passwd);
175 to->host = g_strdup (from->host);
176 to->port = from->port;
177 to->path = g_strdup (from->path);
178 to->params = uri_params_copy (from->params);
179 to->query = g_strdup (from->query);
180 to->fragment = g_strdup (from->fragment);
181 to->originalString = g_strdup (from->originalString);
182 to->isAbsolute = from->isAbsolute;
183 } else {
184 to->scheme = NULL;
185 to->user = NULL;
186 to->auth = NULL;
187 to->passwd = NULL;
188 to->host = NULL;
189 to->port = -1;
190 to->path = NULL;
191 to->query = NULL;
192 to->fragment = NULL;
193 to->originalString = NULL;
194 to->isAbsolute = false;
198 /* canonicalise a path */
199 static char *
200 canon_path (char *path, bool allow_root, bool allow_trailing_sep)
202 register char *d, *inptr;
204 d = inptr = path;
206 while (*inptr) {
207 if (inptr[0] == '/' && (inptr[1] == '/' || (inptr[1] == '\0' && !allow_trailing_sep)))
208 inptr++;
209 else
210 *d++ = *inptr++;
213 if (!allow_root && (d == path + 1) && d[-1] == '/')
214 d--;
215 else if (allow_root && d == path && path[0] == '/')
216 *d++ = '/';
218 *d = '\0';
220 return path[0] ? path : NULL;
223 struct path_component_t {
224 const char *start;
225 size_t len;
228 static char *
229 flatten_path (const char *path)
231 path_component_t part;
232 const char *inptr;
233 char *result, *p;
234 GArray *parts;
235 size_t n;
236 guint i;
237 bool keep = false;
239 if (path == NULL)
240 return NULL;
242 parts = g_array_new (false, false, sizeof (path_component_t));
243 n = 0;
245 inptr = path;
246 while (*inptr) {
247 while (*inptr == '/')
248 inptr++;
250 if (*inptr == '\0')
251 break;
253 part.start = inptr;
254 while (*inptr && *inptr != '/')
255 inptr++;
257 part.len = (size_t) (inptr - part.start);
258 keep = false;
259 if (part.len == 2 && !strncmp (part.start, "..", 2)) {
260 // drop the most recent parent (if not ..)
261 if (parts->len > 0) {
262 path_component_t prev_part = g_array_index (parts, path_component_t, parts->len - 1);
263 if (prev_part.len == 2 && !strncmp (prev_part.start, "..", 2)) {
264 keep = true;
265 } else {
266 part = prev_part;
267 n -= part.len;
268 parts->len--;
270 } else {
271 keep = true;
273 } else if (part.len == 1 && !strncmp (part.start, ".", 1)) {
274 // drop this path component
276 } else if (part.len > 0) {
277 // keep track of this component
278 keep = true;
281 if (keep) {
282 g_array_append_val (parts, part);
283 n += part.len;
287 // at this point, n is the char count of all path components (minus separators)
288 n += parts->len;
289 p = result = (char *) g_malloc (n + 2);
291 if (path[0] == '/')
292 *p++ = '/';
294 for (i = 0; i < parts->len; i++) {
295 part = g_array_index (parts, path_component_t, i);
296 memcpy (p, part.start, part.len);
297 p += part.len;
298 *p++ = '/';
301 *p = '\0';
302 if (p > result && inptr > path && inptr[-1] != '/')
303 p[-1] = '\0';
305 g_array_free (parts, true);
307 return result;
310 #define HEXVAL(c) (isdigit ((int) ((unsigned char) c)) ? (c) - '0' : tolower ((unsigned char) c) - 'a' + 10)
312 #define is_xdigit(c) isxdigit ((int) ((unsigned char) c))
314 static void
315 url_decode (char *in, const char *url)
317 register char *inptr, *outptr;
319 inptr = outptr = in;
320 while (*inptr) {
321 if (*inptr == '%') {
322 if (is_xdigit (inptr[1]) && is_xdigit (inptr[2])) {
323 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
324 inptr += 3;
325 } else {
326 g_warning ("Invalid encoding in url: %s at %s", url, inptr);
327 *outptr++ = *inptr++;
329 } else
330 *outptr++ = *inptr++;
333 *outptr = '\0';
336 static struct {
337 const char *name;
338 int port;
339 } services[] = {
340 { "http", 80 },
341 { "https", 443 },
342 { "mms", 80 }, /* 1755 */
343 { "rtsp", 80 }, /* 554 */
344 { "rtsps", 80 }, /* 332 */
347 static int
348 get_port_by_name (const char *name)
350 guint i;
352 for (i = 0; i < G_N_ELEMENTS (services); i++) {
353 if (!strcmp (services[i].name, name))
354 return services[i].port;
357 return 0;
360 bool
361 Uri::Parse (const char *uri, bool allow_trailing_sep)
363 char *name, *value, *scheme = NULL, *user = NULL, *auth = NULL, *passwd = NULL, *host = NULL, *path = NULL, *query = NULL, *fragment = NULL;
364 Uri::Param *param, *tail, *params = NULL;
365 register const char *start, *inptr;
366 bool isAbsolute;
367 bool parse_path = false;
368 int port = -1;
369 size_t n;
371 tail = (Uri::Param *) &params;
372 start = uri;
374 isAbsolute = true;
376 if (!*start) {
377 isAbsolute = false;
378 goto done;
381 inptr = start;
382 while (*inptr && *inptr != ':' && *inptr != '/' && *inptr != '?' && *inptr != '#' && *inptr != '\\')
383 inptr++;
385 if (inptr > start && *inptr == ':') {
386 scheme = g_ascii_strdown (start, inptr - start);
388 inptr++;
389 if (!*inptr) {
390 isAbsolute = false;
391 goto done;
394 if (!strncmp (inptr, "//", 2))
395 inptr += 2;
397 start = inptr;
398 while (*inptr && *inptr != ';' && *inptr != ':' && *inptr != '@' && *inptr != '/')
399 inptr++;
400 } else {
401 isAbsolute = !strncmp (inptr, "\\\\", 2);
402 parse_path = true;
403 scheme = NULL;
404 inptr = uri;
407 switch (*inptr) {
408 case ';': /* <user>;auth= */
409 case ':': /* <user>:passwd or <host>:port */
410 case '@': /* <user>@host */
411 if (inptr - start) {
412 user = g_strndup (start, inptr - start);
413 url_decode (user, uri);
416 switch (*inptr) {
417 case ';': /* ;auth= */
418 if (!g_ascii_strncasecmp (inptr, ";auth=", 6)) {
419 inptr += 6;
420 start = inptr;
421 while (*inptr && *inptr != ':' && *inptr != '@')
422 inptr++;
424 if (inptr - start) {
425 auth = g_strndup (start, inptr - start);
426 url_decode (auth, uri);
429 if (*inptr == ':') {
430 inptr++;
431 start = inptr;
432 goto decode_passwd;
433 } else if (*inptr == '@') {
434 inptr++;
435 start = inptr;
436 goto decode_host;
439 break;
440 case ':': /* <user>:passwd@ or <host>:port */
441 inptr++;
442 start = inptr;
443 decode_passwd:
444 while (*inptr && *inptr != '@' && *inptr != '/')
445 inptr++;
447 if (*inptr == '@') {
448 /* <user>:passwd@ */
449 if (inptr - start) {
450 passwd = g_strndup (start, inptr - start);
451 url_decode (passwd, uri);
454 inptr++;
455 start = inptr;
456 goto decode_host;
457 } else {
458 /* <host>:port */
459 host = user;
460 user = NULL;
461 inptr = start;
462 goto decode_port;
465 break;
466 case '@': /* <user>@host */
467 inptr++;
468 start = inptr;
469 decode_host:
470 while (*inptr && *inptr != ':' && *inptr != '/')
471 inptr++;
473 if (inptr > start) {
474 n = inptr - start;
475 while (n > 0 && start[n - 1] == '.')
476 n--;
478 if (n > 0)
479 host = g_ascii_strdown (start, n);
482 if (*inptr == ':') {
483 inptr++;
484 decode_port:
485 port = 0;
487 while (*inptr >= '0' && *inptr <= '9' && port < 6554)
488 port = (port * 10) + ((*inptr++) - '0');
490 if (port > 65535) {
491 /* chop off the last digit */
492 port /= 10;
495 /* remove default port numbers */
496 if (scheme && port == get_port_by_name (scheme))
497 port = 0;
499 while (*inptr && *inptr != '/')
500 inptr++;
503 break;
504 case '/': /* <host>/path or simply <host> */
505 case '\0':
506 if (inptr > start) {
507 n = inptr - start;
508 while (n > 0 && start[n - 1] == '.')
509 n--;
511 if (n > 0)
512 host = g_ascii_strdown (start, n);
514 break;
515 default:
516 break;
519 if (parse_path || *inptr == '/') {
520 /* look for params, query, or fragment */
521 start = inptr;
522 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
523 inptr++;
525 /* canonicalise and save the path component */
526 if ((n = (inptr - start))) {
527 value = g_strndup (start, n);
528 url_decode (value, uri);
530 if (!(path = canon_path (value, !host, allow_trailing_sep)))
531 g_free (value);
533 if (isAbsolute) {
534 value = flatten_path (path);
535 g_free (path);
536 path = value;
540 switch (*inptr) {
541 case ';':
542 while (*inptr == ';') {
543 while (*inptr == ';')
544 inptr++;
546 start = inptr;
547 while (*inptr && *inptr != '=' && *inptr != ';' && *inptr != '?' && *inptr != '#')
548 inptr++;
550 name = g_strndup (start, inptr - start);
551 url_decode (name, uri);
553 if (*inptr == '=') {
554 inptr++;
555 start = inptr;
556 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
557 inptr++;
559 value = g_strndup (start, inptr - start);
560 url_decode (value, uri);
561 } else {
562 value = g_strdup ("");
565 param = g_new (Uri::Param, 1);
566 param->value = value;
567 param->name = name;
568 param->next = NULL;
570 tail->next = param;
571 tail = param;
574 if (*inptr == '#')
575 goto decode_fragment;
576 else if (*inptr != '?')
577 break;
579 /* fall thru */
580 case '?':
581 inptr++;
582 start = inptr;
583 while (*inptr && *inptr != '#')
584 inptr++;
586 query = g_strndup (start, inptr - start);
587 url_decode (query, uri);
589 if (*inptr != '#')
590 break;
592 /* fall thru */
593 case '#':
594 decode_fragment:
595 fragment = g_strdup (inptr + 1);
596 url_decode (fragment, uri);
597 break;
601 done:
603 Free ();
605 // update the values
607 this->scheme = scheme;
608 this->user = user;
609 this->auth = auth;
610 this->passwd = passwd;
611 this->host = host;
612 this->port = port;
613 this->path = path;
614 this->params = params;
615 this->query = query;
616 this->fragment = fragment;
617 this->originalString = g_strdup (uri);
618 this->isAbsolute = isAbsolute;
620 return true;
623 void
624 Uri::Combine (const char *relative_path)
626 Deployment *deployment = Deployment::GetCurrent ();
627 char *combined, *p;
629 if (path) {
630 if (deployment->IsLoadedFromXap () || relative_path[0] != '/') {
631 // strip off the 'filename' component
632 if (!(p = strrchr (path, '/')))
633 p = path;
634 *p = '\0';
636 // combine with the relative path
637 combined = g_strdup_printf ("%s/%s", path, relative_path);
638 g_free (path);
640 // flatten the resulting combined path
641 path = flatten_path (combined);
642 g_free (combined);
643 } else {
644 // replace the path
645 g_free (path);
646 path = flatten_path (relative_path);
648 } else {
649 path = flatten_path (relative_path);
653 void
654 Uri::Combine (const Uri *relative_uri)
656 if (relative_uri->isAbsolute)
657 g_warning ("Uri::Combine (): Not a relative Uri");
658 if (relative_uri->path)
659 Combine (relative_uri->path);
662 static void
663 append_url_encoded (GString *string, const char *in, const char *extra)
665 register const char *inptr = in;
666 const char *start;
668 while (*inptr) {
669 start = inptr;
670 while (*inptr && !is_unsafe (*inptr) && !strchr (extra, *inptr))
671 inptr++;
673 g_string_append_len (string, start, inptr - start);
675 while (*inptr && (is_unsafe (*inptr) || strchr (extra, *inptr)))
676 g_string_append_printf (string, "%%%.02hhx", *inptr++);
680 static void
681 append_param (GString *string, Uri::Param *param)
683 g_string_append_c (string, ';');
685 append_url_encoded (string, param->name, "?=#");
687 if (param->value && *param->value) {
688 g_string_append_c (string, '=');
689 append_url_encoded (string, param->value, "?;#");
693 char *
694 Uri::ToString (UriToStringFlags flags) const
696 Uri::Param *param;
697 GString *string;
698 char *uri;
700 string = g_string_new ("");
702 if (this->host) {
703 g_string_append (string, this->scheme);
704 g_string_append (string, "://");
706 if (this->user) {
707 append_url_encoded (string, this->user, ":;@/");
709 if (this->auth) {
710 g_string_append (string, ";auth=");
711 append_url_encoded (string, this->auth, ":@/");
714 if (this->passwd && !(flags & UriHidePasswd)) {
715 g_string_append_c (string, ':');
716 append_url_encoded (string, this->passwd, "@/");
719 g_string_append_c (string, '@');
722 g_string_append (string, this->host);
724 if (this->port > 0)
725 g_string_append_printf (string, ":%d", this->port);
728 if (this->path) {
729 if (this->host && *this->path != '/')
730 g_string_append_c (string, '/');
732 append_url_encoded (string, this->path, " ;?#");
733 } else if (this->host && (this->params || this->query || this->fragment)) {
734 g_string_append_c (string, '/');
737 param = this->params;
738 while (param) {
739 append_param (string, param);
740 param = param->next;
743 if (this->query && !(flags & UriHideQuery)) {
744 g_string_append_c (string, '?');
745 append_url_encoded (string, this->query, "#");
748 if (this->fragment && !(flags & UriHideFragment)) {
749 g_string_append_c (string, '#');
750 append_url_encoded (string, this->fragment, "");
753 uri = string->str;
754 g_string_free (string, false);
756 return uri;
759 bool
760 Uri::operator== (const Uri &v) const
762 if (isAbsolute != v.isAbsolute)
763 return false;
764 if (port != v.port)
765 return false;
766 if (!!scheme != !!v.scheme
767 || (scheme && strcmp (scheme, v.scheme)))
768 return false;
769 if (!!user != !!v.user
770 || (user && strcmp (user, v.user)))
771 return false;
772 if (!!auth != !!v.auth
773 || (auth && strcmp (auth, v.auth)))
774 return false;
775 if (!!passwd != !!v.passwd
776 || (passwd && strcmp (passwd, v.passwd)))
777 return false;
778 if (!!host != !!v.host
779 || (host && strcmp (host, v.host)))
780 return false;
781 if (!!path != !!v.path
782 || (path && strcmp (path, v.path)))
783 return false;
784 if (!!query != !!v.query
785 || (query && strcmp (query, v.query)))
786 return false;
787 if (!!fragment != !!v.fragment
788 || (fragment && strcmp (fragment, v.fragment)))
789 return false;
790 if (!uri_params_equal (params, v.params))
791 return false;
793 // we intentionally don't compare original strings
794 return true;
797 bool
798 Uri::Equals (const Uri *left, const Uri *right)
800 if (!left && !right)
801 return true;
802 if (!left || !right)
803 return false;
804 return left->operator==(*right);
807 bool
808 Uri::IsNullOrEmpty (const Uri *uri)
810 if (!uri || (uri->scheme == NULL && uri->user == NULL && uri->auth == NULL &&
811 uri->passwd == NULL && uri->host == NULL && uri->port == 0 && uri->path == NULL
812 && uri->params == NULL && uri->query == NULL && uri->fragment == NULL &&
813 strcmp (uri->originalString, "") == 0 && !uri->isAbsolute))
814 return true;
816 return false;
819 guint
820 Uri::GetHashCode ()
822 char* str = ToString();
823 guint hash = g_str_hash (str);
824 g_free (str);
825 return hash;
828 bool
829 Uri::IsScheme (const char *scheme) const
831 if (!!this->scheme != !!scheme)
832 return false;
834 if (this->scheme)
835 return !g_ascii_strcasecmp (this->scheme, scheme);
837 return true;
840 bool
841 Uri::SameSiteOfOrigin (const Uri *left, const Uri *right)
843 // works only on absolute URI
844 if (!left || !left->isAbsolute || !right || !right->isAbsolute)
845 return false;
847 if (left->port != right->port)
848 return false;
850 if (!left->scheme || !right->scheme || (strcmp (left->scheme, right->scheme) != 0))
851 return false;
853 // comparing 2 file:/// URI where no hosts is present
854 if (!left->host && !right->host && (strcmp (left->scheme, "file") == 0))
855 return true;
857 if (!left->host || !right->host || (strcmp (left->host, right->host) != 0))
858 return false;
860 return true;