2009-06-17 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / uri.cpp
blob6588330ac14c1983aa761ce8ff2b63bcbe7b6685
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 is_unsafe (unsigned char c)
25 if (c <= 0x1f || c >= 0x7f)
26 return true;
28 switch (c) {
29 case '<':
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 return true;
45 return false;
48 Uri::Uri ()
50 scheme = NULL;
51 user = NULL;
52 auth = NULL;
53 passwd = NULL;
54 host = NULL;
55 port = 0;
56 path = NULL;
57 params = NULL;
58 query = NULL;
59 fragment = NULL;
60 originalString = g_strdup("");
61 isAbsolute = false;
64 Uri::Uri (const Uri& uri)
66 scheme = NULL;
67 user = NULL;
68 auth = NULL;
69 passwd = NULL;
70 host = NULL;
71 port = 0;
72 path = NULL;
73 g_datalist_init (&params);
74 query = NULL;
75 fragment = NULL;
76 originalString = NULL;
77 isAbsolute = false;
79 Uri::Copy (&uri, this);
82 Uri::~Uri ()
84 Free ();
87 void
88 Uri::Free ()
90 g_free (scheme); scheme = NULL;
91 g_free (user); user = NULL;
92 g_free (auth); auth = NULL;
93 g_free (passwd); passwd = NULL;
94 g_free (host); host = NULL;
95 g_free (path); path = NULL;
96 g_datalist_clear (&params);
97 g_datalist_init (&params);
98 g_free (query); query = NULL;
99 g_free (fragment); fragment = NULL;
100 g_free (originalString); originalString = NULL;
101 isAbsolute = false;
104 static void
105 clone_params (GQuark quark, gpointer data, gpointer user_data)
107 gchar *str = (gchar *) data;
108 GData **params = (GData **) user_data;
109 g_datalist_id_set_data_full (params, quark, g_strdup (str), g_free);
112 void
113 Uri::Copy (const Uri *from, Uri *to)
115 g_datalist_init (&to->params);
117 if (from != NULL) {
118 to->scheme = g_strdup (from->scheme);
119 to->user = g_strdup (from->user);
120 to->auth = g_strdup (from->auth);
121 to->passwd = g_strdup (from->passwd);
122 to->host = g_strdup (from->host);
123 to->port = from->port;
124 to->path = g_strdup (from->path);
125 g_datalist_foreach ((GData**)&from->params, clone_params, &to->params);
126 to->query = g_strdup (from->query);
127 to->fragment = g_strdup (from->fragment);
128 to->originalString = g_strdup (from->originalString);
129 to->isAbsolute = from->isAbsolute;
130 } else {
131 to->scheme = NULL;
132 to->user = NULL;
133 to->auth = NULL;
134 to->passwd = NULL;
135 to->host = NULL;
136 to->port = -1;
137 to->path = NULL;
138 to->query = NULL;
139 to->fragment = NULL;
140 to->originalString = NULL;
141 to->isAbsolute = false;
145 /* canonicalise a path */
146 static char *
147 canon_path (char *path, bool allow_root, bool allow_trailing_sep)
149 register char *d, *inptr;
151 d = inptr = path;
153 while (*inptr) {
154 if (inptr[0] == '/' && (inptr[1] == '/' || (inptr[1] == '\0' && !allow_trailing_sep)))
155 inptr++;
156 else
157 *d++ = *inptr++;
160 if (!allow_root && (d == path + 1) && d[-1] == '/')
161 d--;
162 else if (allow_root && d == path && path[0] == '/')
163 *d++ = '/';
165 *d = '\0';
167 return path[0] ? path : NULL;
170 #define HEXVAL(c) (isdigit ((int) ((unsigned char) c)) ? (c) - '0' : tolower ((unsigned char) c) - 'a' + 10)
172 #define is_xdigit(c) isxdigit ((int) ((unsigned char) c))
174 static void
175 url_decode (char *in, const char *url)
177 register char *inptr, *outptr;
179 inptr = outptr = in;
180 while (*inptr) {
181 if (*inptr == '%') {
182 if (is_xdigit (inptr[1]) && is_xdigit (inptr[2])) {
183 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
184 inptr += 3;
185 } else {
186 g_warning ("Invalid encoding in url: %s at %s", url, inptr);
187 *outptr++ = *inptr++;
189 } else
190 *outptr++ = *inptr++;
193 *outptr = '\0';
196 bool
197 Uri::Parse (const char *uri, bool allow_trailing_sep)
199 char *name, *value, *scheme = NULL, *user = NULL, *auth = NULL, *passwd = NULL, *host = NULL, *path = NULL, *query = NULL, *fragment = NULL;
200 register const char *start, *inptr;
201 bool isAbsolute;
202 bool parse_path = false;
203 GData *params = NULL;
204 int port = -1;
205 size_t n;
207 start = uri;
209 isAbsolute = true;
211 if (!*start) {
212 isAbsolute = false;
213 goto done;
216 inptr = start;
217 while (*inptr && *inptr != ':' && *inptr != '/' && *inptr != '?' && *inptr != '#' && *inptr != '\\')
218 inptr++;
220 if (inptr > start && *inptr == ':') {
221 scheme = g_ascii_strdown (start, inptr - start);
223 inptr++;
224 if (!*inptr) {
225 isAbsolute = false;
226 goto done;
229 if (!strncmp (inptr, "//", 2))
230 inptr += 2;
232 start = inptr;
233 while (*inptr && *inptr != ';' && *inptr != ':' && *inptr != '@' && *inptr != '/')
234 inptr++;
235 } else {
236 isAbsolute = !strncmp (inptr, "\\\\", 2);
237 parse_path = true;
238 scheme = NULL;
239 inptr = uri;
242 switch (*inptr) {
243 case ';': /* <user>;auth= */
244 case ':': /* <user>:passwd or <host>:port */
245 case '@': /* <user>@host */
246 if (inptr - start) {
247 user = g_strndup (start, inptr - start);
248 url_decode (user, uri);
251 switch (*inptr) {
252 case ';': /* ;auth= */
253 if (!g_ascii_strncasecmp (inptr, ";auth=", 6)) {
254 inptr += 6;
255 start = inptr;
256 while (*inptr && *inptr != ':' && *inptr != '@')
257 inptr++;
259 if (inptr - start) {
260 auth = g_strndup (start, inptr - start);
261 url_decode (auth, uri);
264 if (*inptr == ':') {
265 inptr++;
266 start = inptr;
267 goto decode_passwd;
268 } else if (*inptr == '@') {
269 inptr++;
270 start = inptr;
271 goto decode_host;
274 break;
275 case ':': /* <user>:passwd@ or <host>:port */
276 inptr++;
277 start = inptr;
278 decode_passwd:
279 while (*inptr && *inptr != '@' && *inptr != '/')
280 inptr++;
282 if (*inptr == '@') {
283 /* <user>:passwd@ */
284 if (inptr - start) {
285 passwd = g_strndup (start, inptr - start);
286 url_decode (passwd, uri);
289 inptr++;
290 start = inptr;
291 goto decode_host;
292 } else {
293 /* <host>:port */
294 host = user;
295 user = NULL;
296 inptr = start;
297 goto decode_port;
300 break;
301 case '@': /* <user>@host */
302 inptr++;
303 start = inptr;
304 decode_host:
305 while (*inptr && *inptr != ':' && *inptr != '/')
306 inptr++;
308 if (inptr > start) {
309 n = inptr - start;
310 while (n > 0 && start[n - 1] == '.')
311 n--;
313 if (n > 0)
314 host = g_strndup (start, n);
317 if (*inptr == ':') {
318 inptr++;
319 decode_port:
320 port = 0;
322 while (*inptr >= '0' && *inptr <= '9' && port < 6554)
323 port = (port * 10) + ((*inptr++) - '0');
325 if (port > 65535) {
326 /* chop off the last digit */
327 port /= 10;
330 while (*inptr && *inptr != '/')
331 inptr++;
334 break;
335 case '/': /* <host>/path or simply <host> */
336 case '\0':
337 if (inptr > start) {
338 n = inptr - start;
339 while (n > 0 && start[n - 1] == '.')
340 n--;
342 if (n > 0)
343 host = g_strndup (start, n);
345 break;
346 default:
347 break;
350 if (parse_path || *inptr == '/') {
351 /* look for params, query, or fragment */
352 start = inptr;
353 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
354 inptr++;
356 /* canonicalise and save the path component */
357 if ((n = (inptr - start))) {
358 value = g_strndup (start, n);
359 url_decode (value, uri);
361 if (!(path = canon_path (value, !host, allow_trailing_sep)))
362 g_free (value);
365 switch (*inptr) {
366 case ';':
367 while (*inptr == ';') {
368 while (*inptr == ';')
369 inptr++;
371 start = inptr;
372 while (*inptr && *inptr != '=' && *inptr != ';' && *inptr != '?' && *inptr != '#')
373 inptr++;
375 name = g_strndup (start, inptr - start);
376 url_decode (name, uri);
378 if (*inptr == '=') {
379 inptr++;
380 start = inptr;
381 while (*inptr && *inptr != ';' && *inptr != '?' && *inptr != '#')
382 inptr++;
384 value = g_strndup (start, inptr - start);
385 url_decode (value, uri);
386 } else {
387 value = g_strdup ("");
390 g_datalist_set_data_full (&params, name, value, g_free);
391 g_free (name);
394 if (*inptr == '#')
395 goto decode_fragment;
396 else if (*inptr != '?')
397 break;
399 /* fall thru */
400 case '?':
401 inptr++;
402 start = inptr;
403 while (*inptr && *inptr != '#')
404 inptr++;
406 query = g_strndup (start, inptr - start);
407 url_decode (query, uri);
409 if (*inptr != '#')
410 break;
412 /* fall thru */
413 case '#':
414 decode_fragment:
415 fragment = g_strdup (inptr + 1);
416 url_decode (fragment, uri);
417 break;
421 done:
423 Free ();
425 // update the values
427 this->scheme = scheme;
428 this->user = user;
429 this->auth = auth;
430 this->passwd = passwd;
431 this->host = host;
432 this->port = port;
433 this->path = path;
434 this->params = params;
435 this->query = query;
436 this->fragment = fragment;
437 this->originalString = g_strdup (uri);
438 this->isAbsolute = isAbsolute;
440 return true;
443 void
444 Uri::Combine (const char *relative_path)
446 const char *filename;
447 char *new_path;
449 if (path && relative_path[0] != '/') {
450 if (!(filename = strrchr (path, '/')))
451 new_path = g_strdup (relative_path);
452 else
453 new_path = g_strdup_printf ("%.*s/%s", filename - path, path, g_str_has_prefix (relative_path, "../") ? relative_path+3 : relative_path);
454 g_free (path);
456 path = new_path;
457 } else {
458 g_free (path);
460 path = g_strdup (relative_path);
464 void
465 Uri::Combine (const Uri *relative_uri)
467 if (relative_uri->isAbsolute)
468 g_warning ("Uri::Combine (): Not a relative Uri");
469 if (relative_uri->path)
470 Combine (relative_uri->path);
473 static void
474 append_url_encoded (GString *string, const char *in, const char *extra)
476 register const char *inptr = in;
477 const char *start;
479 while (*inptr) {
480 start = inptr;
481 while (*inptr && !is_unsafe (*inptr) && !strchr (extra, *inptr))
482 inptr++;
484 g_string_append_len (string, start, inptr - start);
486 while (*inptr && (is_unsafe (*inptr) || strchr (extra, *inptr)))
487 g_string_append_printf (string, "%%%.02hhx", *inptr++);
491 static void
492 append_param (GQuark key_id, gpointer value, gpointer user_data)
494 GString *string = (GString *) user_data;
496 g_string_append_c (string, ';');
497 append_url_encoded (string, g_quark_to_string (key_id), "?=#");
498 if (*((char *) value)) {
499 g_string_append_c (string, '=');
500 append_url_encoded (string, (const char *) value, "?;#");
504 char *
505 Uri::ToString (UriToStringFlags flags) const
507 GString *string;
508 char *uri;
510 string = g_string_new ("");
512 if (this->host) {
513 g_string_append (string, this->scheme);
514 g_string_append (string, "://");
516 if (this->user) {
517 append_url_encoded (string, this->user, ":;@/");
519 if (this->auth) {
520 g_string_append (string, ";auth=");
521 append_url_encoded (string, this->auth, ":@/");
524 if (this->passwd && !(flags & UriHidePasswd)) {
525 g_string_append_c (string, ':');
526 append_url_encoded (string, this->passwd, "@/");
529 g_string_append_c (string, '@');
532 g_string_append (string, this->host);
534 if (this->port > 0)
535 g_string_append_printf (string, ":%d", this->port);
538 if (this->path) {
539 if (this->host && *this->path != '/')
540 g_string_append_c (string, '/');
542 append_url_encoded (string, this->path, ";?#");
543 } else if (this->host && (this->params || this->query || this->fragment)) {
544 g_string_append_c (string, '/');
547 if (this->params)
548 g_datalist_foreach ((GData **) &this->params, append_param, string);
550 if (this->query) {
551 g_string_append_c (string, '?');
552 append_url_encoded (string, this->query, "#");
555 if (this->fragment && !(flags & UriHideFragment)) {
556 g_string_append_c (string, '#');
557 append_url_encoded (string, this->fragment, "");
560 uri = string->str;
561 g_string_free (string, false);
563 return uri;
566 bool
567 Uri::operator== (const Uri &v) const
569 if (isAbsolute != v.isAbsolute)
570 return false;
571 if (port != v.port)
572 return false;
573 if (!!scheme != !!v.scheme
574 || (scheme && strcmp (scheme, v.scheme)))
575 return false;
576 if (!!user != !!v.user
577 || (user && strcmp (user, v.user)))
578 return false;
579 if (!!auth != !!v.auth
580 || (auth && strcmp (auth, v.auth)))
581 return false;
582 if (!!passwd != !!v.passwd
583 || (passwd && strcmp (passwd, v.passwd)))
584 return false;
585 if (!!host != !!v.host
586 || (host && strcmp (host, v.host)))
587 return false;
588 if (!!path != !!v.path
589 || (path && strcmp (path, v.path)))
590 return false;
591 if (!!query != !!v.query
592 || (query && strcmp (query, v.query)))
593 return false;
594 if (!!fragment != !!v.fragment
595 || (fragment && strcmp (fragment, v.fragment)))
596 return false;
598 // XXX we don't compare this->data vs v.data yet.
600 // we intentionally don't compare original strings
601 return true;
604 bool
605 Uri::Equals (const Uri *left, const Uri *right)
607 if (!left && !right)
608 return true;
609 if (!left || !right)
610 return false;
611 return left->operator==(*right);
614 bool
615 Uri::IsNullOrEmpty (const Uri *uri)
617 if (!uri || (uri->scheme == NULL && uri->user == NULL && uri->auth == NULL &&
618 uri->passwd == NULL && uri->host == NULL && uri->port == 0 && uri->path == NULL
619 && uri->params == NULL && uri->query == NULL && uri->fragment == NULL &&
620 strcmp (uri->originalString, "") == 0 && !uri->isAbsolute))
621 return true;
623 return false;
626 guint
627 Uri::GetHashCode ()
629 char* str = ToString();
630 guint hash = g_str_hash (str);
631 g_free (str);
632 return hash;
635 bool
636 Uri::IsScheme (const char *scheme)
638 if (!!this->scheme != !!scheme)
639 return false;
641 if (this->scheme)
642 return !g_ascii_strcasecmp (this->scheme, scheme);
644 return true;
647 bool
648 Uri::SameSiteOfOrigin (const Uri *left, const Uri *right)
650 // works only on absolute URI
651 if (!left || !left->isAbsolute || !right || !right->isAbsolute)
652 return false;
654 if (left->port != right->port)
655 return false;
657 if (!left->scheme || !right->scheme || (strcmp (left->scheme, right->scheme) != 0))
658 return false;
660 // comparing 2 file:/// URI where no hosts is present
661 if (!left->host && !right->host && (strcmp (left->scheme, "file") == 0))
662 return true;
664 if (!left->host || !right->host || (strcmp (left->host, right->host) != 0))
665 return false;
667 return true;