soup-auth-manager: add soup_auth_manager_use_auth()
[platform/upstream/libsoup.git] / libsoup / soup-uri.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* soup-uri.c : utility functions to parse URLs */
3
4 /*
5  * Copyright 1999-2003 Ximian, Inc.
6  */
7
8 #include <string.h>
9 #include <stdlib.h>
10
11 #include "soup-uri.h"
12 #include "soup.h"
13 #include "soup-misc-private.h"
14
15 /**
16  * SECTION:soup-uri
17  * @short_description: URIs
18  *
19  * A #SoupURI represents a (parsed) URI.
20  *
21  * Many applications will not need to use #SoupURI directly at all; on
22  * the client side, soup_message_new() takes a stringified URI, and on
23  * the server side, the path and query components are provided for you
24  * in the server callback.
25  **/
26
27 /**
28  * SoupURI:
29  * @scheme: the URI scheme (eg, "http")
30  * @user: a username, or %NULL
31  * @password: a password, or %NULL
32  * @host: the hostname or IP address
33  * @port: the port number on @host
34  * @path: the path on @host
35  * @query: a query for @path, or %NULL
36  * @fragment: a fragment identifier within @path, or %NULL
37  *
38  * A #SoupURI represents a (parsed) URI. #SoupURI supports RFC 3986
39  * (URI Generic Syntax), and can parse any valid URI. However, libsoup
40  * only uses "http" and "https" URIs internally; You can use
41  * SOUP_URI_VALID_FOR_HTTP() to test if a #SoupURI is a valid HTTP
42  * URI.
43  *
44  * @scheme will always be set in any URI. It is an interned string and
45  * is always all lowercase. (If you parse a URI with a non-lowercase
46  * scheme, it will be converted to lowercase.) The macros
47  * %SOUP_URI_SCHEME_HTTP and %SOUP_URI_SCHEME_HTTPS provide the
48  * interned values for "http" and "https" and can be compared against
49  * URI @scheme values.
50  *
51  * @user and @password are parsed as defined in the older URI specs
52  * (ie, separated by a colon; RFC 3986 only talks about a single
53  * "userinfo" field). Note that @password is not included in the
54  * output of soup_uri_to_string(). libsoup does not normally use these
55  * fields; authentication is handled via #SoupSession signals.
56  *
57  * @host contains the hostname, and @port the port specified in the
58  * URI. If the URI doesn't contain a hostname, @host will be %NULL,
59  * and if it doesn't specify a port, @port may be 0. However, for
60  * "http" and "https" URIs, @host is guaranteed to be non-%NULL
61  * (trying to parse an http URI with no @host will return %NULL), and
62  * @port will always be non-0 (because libsoup knows the default value
63  * to use when it is not specified in the URI).
64  *
65  * @path is always non-%NULL. For http/https URIs, @path will never be
66  * an empty string either; if the input URI has no path, the parsed
67  * #SoupURI will have a @path of "/".
68  *
69  * @query and @fragment are optional for all URI types.
70  * soup_form_decode() may be useful for parsing @query.
71  *
72  * Note that @path, @query, and @fragment may contain
73  * %<!-- -->-encoded characters. soup_uri_new() calls
74  * soup_uri_normalize() on them, but not soup_uri_decode(). This is
75  * necessary to ensure that soup_uri_to_string() will generate a URI
76  * that has exactly the same meaning as the original. (In theory,
77  * #SoupURI should leave @user, @password, and @host partially-encoded
78  * as well, but this would be more annoying than useful.)
79  **/
80
81 /**
82  * SOUP_URI_IS_VALID:
83  * @uri: a #SoupURI
84  *
85  * Tests whether @uri is a valid #SoupURI; that is, that it is non-%NULL
86  * and its @scheme and @path members are also non-%NULL.
87  *
88  * This macro does not check whether http and https URIs have a non-%NULL
89  * @host member.
90  *
91  * Return value: %TRUE if @uri is valid for use.
92  *
93  * Since: 2.38
94  **/
95
96 /**
97  * SOUP_URI_VALID_FOR_HTTP:
98  * @uri: a #SoupURI
99  *
100  * Tests if @uri is a valid #SoupURI for HTTP communication; that is, if
101  * it can be used to construct a #SoupMessage.
102  *
103  * Return value: %TRUE if @uri is a valid "http" or "https" URI.
104  *
105  * Since: 2.24
106  **/
107
108 /**
109  * SOUP_URI_SCHEME_HTTP:
110  *
111  * "http" as an interned string; you can compare this directly to a
112  * #SoupURI's <literal>scheme</literal> field using
113  * <literal>==</literal>.
114  */
115 /**
116  * SOUP_URI_SCHEME_HTTPS:
117  *
118  * "https" as an interned string; you can compare this directly to a
119  * #SoupURI's <literal>scheme</literal> field using
120  * <literal>==</literal>.
121  */
122 /**
123  * SOUP_URI_SCHEME_FTP:
124  *
125  * "ftp" as an interned string; you can compare this directly to a
126  * #SoupURI's <literal>scheme</literal> field using
127  * <literal>==</literal>.
128  *
129  * Since: 2.30
130  */
131 /**
132  * SOUP_URI_SCHEME_FILE:
133  *
134  * "file" as an interned string; you can compare this directly to a
135  * #SoupURI's <literal>scheme</literal> field using
136  * <literal>==</literal>.
137  *
138  * Since: 2.30
139  */
140 /**
141  * SOUP_URI_SCHEME_DATA:
142  *
143  * "data" as an interned string; you can compare this directly to a
144  * #SoupURI's <literal>scheme</literal> field using
145  * <literal>==</literal>.
146  *
147  * Since: 2.30
148  */
149 /**
150  * SOUP_URI_SCHEME_RESOURCE:
151  *
152  * "data" as an interned string; you can compare this directly to a
153  * #SoupURI's <literal>scheme</literal> field using
154  * <literal>==</literal>.
155  *
156  * Since: 2.42
157  */
158
159 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
160 static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra);
161
162 gpointer _SOUP_URI_SCHEME_HTTP, _SOUP_URI_SCHEME_HTTPS;
163 gpointer _SOUP_URI_SCHEME_FTP;
164 gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA, _SOUP_URI_SCHEME_RESOURCE;
165
166 static inline const char *
167 soup_uri_parse_scheme (const char *scheme, int len)
168 {
169         if (len == 4 && !g_ascii_strncasecmp (scheme, "http", len)) {
170                 return SOUP_URI_SCHEME_HTTP;
171         } else if (len == 5 && !g_ascii_strncasecmp (scheme, "https", len)) {
172                 return SOUP_URI_SCHEME_HTTPS;
173         } else if (len == 8 && !g_ascii_strncasecmp (scheme, "resource", len)) {
174                 return SOUP_URI_SCHEME_RESOURCE;
175         } else {
176                 char *lower_scheme;
177
178                 lower_scheme = g_ascii_strdown (scheme, len);
179                 scheme = g_intern_static_string (lower_scheme);
180                 if (scheme != (const char *)lower_scheme)
181                         g_free (lower_scheme);
182                 return scheme;
183         }
184 }
185
186 static inline guint
187 soup_scheme_default_port (const char *scheme)
188 {
189         if (scheme == SOUP_URI_SCHEME_HTTP)
190                 return 80;
191         else if (scheme == SOUP_URI_SCHEME_HTTPS)
192                 return 443;
193         else if (scheme == SOUP_URI_SCHEME_FTP)
194                 return 21;
195         else
196                 return 0;
197 }
198
199 /**
200  * soup_uri_new_with_base:
201  * @base: a base URI
202  * @uri_string: the URI
203  *
204  * Parses @uri_string relative to @base.
205  *
206  * Return value: a parsed #SoupURI.
207  **/
208 SoupURI *
209 soup_uri_new_with_base (SoupURI *base, const char *uri_string)
210 {
211         SoupURI *uri, fixed_base;
212         const char *end, *hash, *colon, *at, *path, *question;
213         const char *p, *hostend;
214         gboolean remove_dot_segments = TRUE;
215         int len;
216
217         g_return_val_if_fail (uri_string != NULL, NULL);
218
219         /* Allow a %NULL path in @base, for compatibility */
220         if (base && base->scheme && !base->path) {
221                 g_warn_if_fail (SOUP_URI_IS_VALID (base));
222
223                 memcpy (&fixed_base, base, sizeof (SoupURI));
224                 fixed_base.path = "";
225                 base = &fixed_base;
226         }
227
228         g_return_val_if_fail (base == NULL || SOUP_URI_IS_VALID (base), NULL);
229
230         /* First some cleanup steps (which are supposed to all be no-ops,
231          * but...). Skip initial whitespace, strip out internal tabs and
232          * line breaks, and ignore trailing whitespace.
233          */
234         while (g_ascii_isspace (*uri_string))
235                 uri_string++;
236
237         len = strcspn (uri_string, "\t\n\r");
238         if (uri_string[len]) {
239                 char *clean = g_malloc (strlen (uri_string) + 1), *d;
240                 const char *s;
241
242                 for (s = uri_string, d = clean; *s; s++) {
243                         if (*s != '\t' && *s != '\n' && *s != '\r')
244                                 *d++ = *s;
245                 }
246                 *d = '\0';
247
248                 uri = soup_uri_new_with_base (base, clean);
249                 g_free (clean);
250                 return uri;
251         }
252         end = uri_string + len;
253         while (end > uri_string && g_ascii_isspace (end[-1]))
254                 end--;
255
256         uri = g_slice_new0 (SoupURI);
257
258         /* Find fragment. */
259         hash = strchr (uri_string, '#');
260         if (hash) {
261                 uri->fragment = uri_normalized_copy (hash + 1, end - hash + 1,
262                                                      NULL);
263                 end = hash;
264         }
265
266         /* Find scheme: initial [a-z+.-]* substring until ":" */
267         p = uri_string;
268         while (p < end && (g_ascii_isalpha (*p) ||
269                            *p == '.' || *p == '+' || *p == '-'))
270                 p++;
271
272         if (p > uri_string && *p == ':') {
273                 uri->scheme = soup_uri_parse_scheme (uri_string, p - uri_string);
274                 uri_string = p + 1;
275         }
276
277         if (uri_string == end && !base && !uri->fragment) {
278                 uri->path = g_strdup ("");
279                 return uri;
280         }
281
282         /* Check for authority */
283         if (strncmp (uri_string, "//", 2) == 0) {
284                 uri_string += 2;
285
286                 path = uri_string + strcspn (uri_string, "/?#");
287                 if (path > end)
288                         path = end;
289                 at = strchr (uri_string, '@');
290                 if (at && at < path) {
291                         colon = strchr (uri_string, ':');
292                         if (colon && colon < at) {
293                                 uri->password = uri_decoded_copy (colon + 1,
294                                                                   at - colon - 1);
295                         } else {
296                                 uri->password = NULL;
297                                 colon = at;
298                         }
299
300                         uri->user = uri_decoded_copy (uri_string,
301                                                       colon - uri_string);
302                         uri_string = at + 1;
303                 } else
304                         uri->user = uri->password = NULL;
305
306                 /* Find host and port. */
307                 if (*uri_string == '[') {
308                         uri_string++;
309                         hostend = strchr (uri_string, ']');
310                         if (!hostend || hostend > path) {
311                                 soup_uri_free (uri);
312                                 return NULL;
313                         }
314                         if (*(hostend + 1) == ':')
315                                 colon = hostend + 1;
316                         else
317                                 colon = NULL;
318                 } else {
319                         colon = memchr (uri_string, ':', path - uri_string);
320                         hostend = colon ? colon : path;
321                 }
322
323                 uri->host = uri_decoded_copy (uri_string, hostend - uri_string);
324
325                 if (colon && colon != path - 1) {
326                         char *portend;
327                         uri->port = strtoul (colon + 1, &portend, 10);
328                         if (portend != (char *)path) {
329                                 soup_uri_free (uri);
330                                 return NULL;
331                         }
332                 }
333
334                 uri_string = path;
335         }
336
337         /* Find query */
338         question = memchr (uri_string, '?', end - uri_string);
339         if (question) {
340                 uri->query = uri_normalized_copy (question + 1,
341                                                   end - (question + 1),
342                                                   NULL);
343                 end = question;
344         }
345
346         if (end != uri_string) {
347                 uri->path = uri_normalized_copy (uri_string, end - uri_string,
348                                                  NULL);
349         }
350
351         /* Apply base URI. This is spelled out in RFC 3986. */
352         if (base && !uri->scheme && uri->host)
353                 uri->scheme = base->scheme;
354         else if (base && !uri->scheme) {
355                 uri->scheme = base->scheme;
356                 uri->user = g_strdup (base->user);
357                 uri->password = g_strdup (base->password);
358                 uri->host = g_strdup (base->host);
359                 uri->port = base->port;
360
361                 if (!uri->path) {
362                         uri->path = g_strdup (base->path);
363                         if (!uri->query)
364                                 uri->query = g_strdup (base->query);
365                         remove_dot_segments = FALSE;
366                 } else if (*uri->path != '/') {
367                         char *newpath, *last;
368
369                         last = strrchr (base->path, '/');
370                         if (last) {
371                                 newpath = g_strdup_printf ("%.*s%s",
372                                                            (int)(last + 1 - base->path),
373                                                            base->path,
374                                                            uri->path);
375                         } else
376                                 newpath = g_strdup_printf ("/%s", uri->path);
377
378                         g_free (uri->path);
379                         uri->path = newpath;
380                 }
381         }
382
383         if (remove_dot_segments && uri->path && *uri->path) {
384                 char *p, *q;
385
386                 /* Remove "./" where "." is a complete segment. */
387                 for (p = uri->path + 1; *p; ) {
388                         if (*(p - 1) == '/' &&
389                             *p == '.' && *(p + 1) == '/')
390                                 memmove (p, p + 2, strlen (p + 2) + 1);
391                         else
392                                 p++;
393                 }
394                 /* Remove "." at end. */
395                 if (p > uri->path + 2 &&
396                     *(p - 1) == '.' && *(p - 2) == '/')
397                         *(p - 1) = '\0';
398
399                 /* Remove "<segment>/../" where <segment> != ".." */
400                 for (p = uri->path + 1; *p; ) {
401                         if (!strncmp (p, "../", 3)) {
402                                 p += 3;
403                                 continue;
404                         }
405                         q = strchr (p + 1, '/');
406                         if (!q)
407                                 break;
408                         if (strncmp (q, "/../", 4) != 0) {
409                                 p = q + 1;
410                                 continue;
411                         }
412                         memmove (p, q + 4, strlen (q + 4) + 1);
413                         p = uri->path + 1;
414                 }
415                 /* Remove "<segment>/.." at end where <segment> != ".." */
416                 q = strrchr (uri->path, '/');
417                 if (q && !strcmp (q, "/..")) {
418                         p = q - 1;
419                         while (p > uri->path && *p != '/')
420                                 p--;
421                         if (strncmp (p, "/../", 4) != 0)
422                                 *(p + 1) = 0;
423                 }
424
425                 /* Remove extraneous initial "/.."s */
426                 while (!strncmp (uri->path, "/../", 4))
427                         memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
428                 if (!strcmp (uri->path, "/.."))
429                         uri->path[1] = '\0';
430         }
431
432         /* HTTP-specific stuff */
433         if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
434             uri->scheme == SOUP_URI_SCHEME_HTTPS) {
435                 if (!uri->path)
436                         uri->path = g_strdup ("/");
437                 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
438                         soup_uri_free (uri);
439                         return NULL;
440                 }
441         }
442
443         if (uri->scheme == SOUP_URI_SCHEME_FTP) {
444                 if (!uri->host) {
445                         soup_uri_free (uri);
446                         return NULL;
447                 }
448         }
449
450         if (!uri->port)
451                 uri->port = soup_scheme_default_port (uri->scheme);
452         if (!uri->path)
453                 uri->path = g_strdup ("");
454
455         return uri;
456 }
457
458 /**
459  * soup_uri_new:
460  * @uri_string: (allow-none): a URI
461  *
462  * Parses an absolute URI.
463  *
464  * You can also pass %NULL for @uri_string if you want to get back an
465  * "empty" #SoupURI that you can fill in by hand. (You will need to
466  * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
467  * those fields are required.)
468  *
469  * Return value: a #SoupURI, or %NULL if the given string was found to be
470  *  invalid.
471  **/
472 SoupURI *
473 soup_uri_new (const char *uri_string)
474 {
475         SoupURI *uri;
476
477         if (!uri_string)
478                 return g_slice_new0 (SoupURI);
479
480         uri = soup_uri_new_with_base (NULL, uri_string);
481         if (!uri)
482                 return NULL;
483         if (!SOUP_URI_IS_VALID (uri)) {
484                 soup_uri_free (uri);
485                 return NULL;
486         }
487
488         return uri;
489 }
490
491
492 /**
493  * soup_uri_to_string:
494  * @uri: a #SoupURI
495  * @just_path_and_query: if %TRUE, output just the path and query portions
496  *
497  * Returns a string representing @uri.
498  *
499  * If @just_path_and_query is %TRUE, this concatenates the path and query
500  * together. That is, it constructs the string that would be needed in
501  * the Request-Line of an HTTP request for @uri.
502  *
503  * Return value: a string representing @uri, which the caller must free.
504  **/
505 char *
506 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
507 {
508         GString *str;
509         char *return_result;
510
511         g_return_val_if_fail (uri != NULL, NULL);
512         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
513
514         str = g_string_sized_new (20);
515
516         if (uri->scheme && !just_path_and_query)
517                 g_string_append_printf (str, "%s:", uri->scheme);
518         if (uri->host && !just_path_and_query) {
519                 g_string_append (str, "//");
520                 if (uri->user) {
521                         append_uri_encoded (str, uri->user, ":;@?/");
522                         g_string_append_c (str, '@');
523                 }
524                 if (strchr (uri->host, ':')) {
525                         g_string_append_c (str, '[');
526                         g_string_append (str, uri->host);
527                         g_string_append_c (str, ']');
528                 } else
529                         append_uri_encoded (str, uri->host, ":/");
530                 if (uri->port && uri->port != soup_scheme_default_port (uri->scheme))
531                         g_string_append_printf (str, ":%u", uri->port);
532                 if (!uri->path && (uri->query || uri->fragment))
533                         g_string_append_c (str, '/');
534                 else if ((!uri->path || !*uri->path) &&
535                          (uri->scheme == SOUP_URI_SCHEME_HTTP ||
536                           uri->scheme == SOUP_URI_SCHEME_HTTPS))
537                         g_string_append_c (str, '/');
538         }
539
540         if (uri->path && *uri->path)
541                 g_string_append (str, uri->path);
542         else if (just_path_and_query)
543                 g_string_append_c (str, '/');
544
545         if (uri->query) {
546                 g_string_append_c (str, '?');
547                 g_string_append (str, uri->query);
548         }
549         if (uri->fragment && !just_path_and_query) {
550                 g_string_append_c (str, '#');
551                 g_string_append (str, uri->fragment);
552         }
553
554         return_result = str->str;
555         g_string_free (str, FALSE);
556
557         return return_result;
558 }
559
560 /**
561  * soup_uri_copy:
562  * @uri: a #SoupURI
563  *
564  * Copies @uri
565  *
566  * Return value: a copy of @uri, which must be freed with soup_uri_free()
567  **/
568 SoupURI *
569 soup_uri_copy (SoupURI *uri)
570 {
571         SoupURI *dup;
572
573         g_return_val_if_fail (uri != NULL, NULL);
574         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
575
576         dup = g_slice_new0 (SoupURI);
577         dup->scheme   = uri->scheme;
578         dup->user     = g_strdup (uri->user);
579         dup->password = g_strdup (uri->password);
580         dup->host     = g_strdup (uri->host);
581         dup->port     = uri->port;
582         dup->path     = g_strdup (uri->path);
583         dup->query    = g_strdup (uri->query);
584         dup->fragment = g_strdup (uri->fragment);
585
586         return dup;
587 }
588
589 static inline gboolean
590 parts_equal (const char *one, const char *two, gboolean insensitive)
591 {
592         if (!one && !two)
593                 return TRUE;
594         if (!one || !two)
595                 return FALSE;
596         return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
597 }
598
599 /**
600  * soup_uri_equal:
601  * @uri1: a #SoupURI
602  * @uri2: another #SoupURI
603  *
604  * Tests whether or not @uri1 and @uri2 are equal in all parts
605  *
606  * Return value: %TRUE or %FALSE
607  **/
608 gboolean 
609 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
610 {
611         g_return_val_if_fail (uri1 != NULL, FALSE);
612         g_return_val_if_fail (uri2 != NULL, FALSE);
613         g_warn_if_fail (SOUP_URI_IS_VALID (uri1));
614         g_warn_if_fail (SOUP_URI_IS_VALID (uri2));
615
616         if (uri1->scheme != uri2->scheme                         ||
617             uri1->port   != uri2->port                           ||
618             !parts_equal (uri1->user, uri2->user, FALSE)         ||
619             !parts_equal (uri1->password, uri2->password, FALSE) ||
620             !parts_equal (uri1->host, uri2->host, TRUE)          ||
621             !parts_equal (uri1->path, uri2->path, FALSE)         ||
622             !parts_equal (uri1->query, uri2->query, FALSE)       ||
623             !parts_equal (uri1->fragment, uri2->fragment, FALSE))
624                 return FALSE;
625
626         return TRUE;
627 }
628
629 /**
630  * soup_uri_free:
631  * @uri: a #SoupURI
632  *
633  * Frees @uri.
634  **/
635 void
636 soup_uri_free (SoupURI *uri)
637 {
638         g_return_if_fail (uri != NULL);
639
640         g_free (uri->user);
641         g_free (uri->password);
642         g_free (uri->host);
643         g_free (uri->path);
644         g_free (uri->query);
645         g_free (uri->fragment);
646
647         g_slice_free (SoupURI, uri);
648 }
649
650 static void
651 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
652 {
653         const unsigned char *s = (const unsigned char *)in;
654
655         while (*s) {
656                 if (soup_char_is_uri_percent_encoded (*s) ||
657                     soup_char_is_uri_gen_delims (*s) ||
658                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
659                         g_string_append_printf (str, "%%%02X", (int)*s++);
660                 else
661                         g_string_append_c (str, *s++);
662         }
663 }
664
665 /**
666  * soup_uri_encode:
667  * @part: a URI part
668  * @escape_extra: (allow-none): additional reserved characters to
669  * escape (or %NULL)
670  *
671  * This %<!-- -->-encodes the given URI part and returns the escaped
672  * version in allocated memory, which the caller must free when it is
673  * done.
674  *
675  * Return value: the encoded URI part
676  **/
677 char *
678 soup_uri_encode (const char *part, const char *escape_extra)
679 {
680         GString *str;
681         char *encoded;
682
683         g_return_val_if_fail (part != NULL, NULL);
684
685         str = g_string_new (NULL);
686         append_uri_encoded (str, part, escape_extra);
687         encoded = str->str;
688         g_string_free (str, FALSE);
689
690         return encoded;
691 }
692
693 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
694 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
695
696 char *
697 uri_decoded_copy (const char *part, int length)
698 {
699         unsigned char *s, *d;
700         char *decoded = g_strndup (part, length);
701
702         g_return_val_if_fail (part != NULL, NULL);
703
704         s = d = (unsigned char *)decoded;
705         do {
706                 if (*s == '%') {
707                         if (!g_ascii_isxdigit (s[1]) ||
708                             !g_ascii_isxdigit (s[2])) {
709                                 *d++ = *s;
710                                 continue;
711                         }
712                         *d++ = HEXCHAR (s);
713                         s += 2;
714                 } else
715                         *d++ = *s;
716         } while (*s++);
717
718         return decoded;
719 }
720
721 /**
722  * soup_uri_decode:
723  * @part: a URI part
724  *
725  * Fully %<!-- -->-decodes @part.
726  *
727  * In the past, this would return %NULL if @part contained invalid
728  * percent-encoding, but now it just ignores the problem (as
729  * soup_uri_new() already did).
730  *
731  * Return value: the decoded URI part.
732  */
733 char *
734 soup_uri_decode (const char *part)
735 {
736         g_return_val_if_fail (part != NULL, NULL);
737
738         return uri_decoded_copy (part, strlen (part));
739 }
740
741 static char *
742 uri_normalized_copy (const char *part, int length,
743                      const char *unescape_extra)
744 {
745         unsigned char *s, *d, c;
746         char *normalized = g_strndup (part, length);
747         gboolean need_fixup = FALSE;
748
749         if (!unescape_extra)
750                 unescape_extra = "";
751
752         s = d = (unsigned char *)normalized;
753         while (*s) {
754                 if (*s == '%') {
755                         if (!g_ascii_isxdigit (s[1]) ||
756                             !g_ascii_isxdigit (s[2])) {
757                                 *d++ = *s++;
758                                 continue;
759                         }
760
761                         c = HEXCHAR (s);
762                         if (soup_char_is_uri_unreserved (c) ||
763                             (c && strchr (unescape_extra, c))) {
764                                 *d++ = c;
765                                 s += 3;
766                         } else {
767                                 /* We leave it unchanged. We used to uppercase percent-encoded
768                                  * triplets but we do not do it any more as RFC3986 Section 6.2.2.1
769                                  * says that they only SHOULD be case normalized.
770                                  */
771                                 *d++ = *s++;
772                                 *d++ = *s++;
773                                 *d++ = *s++;
774                         }
775                 } else {
776                         if (!g_ascii_isgraph (*s) &&
777                             !strchr (unescape_extra, *s))
778                                 need_fixup = TRUE;
779                         *d++ = *s++;
780                 }
781         }
782         *d = '\0';
783
784         if (need_fixup) {
785                 GString *fixed;
786
787                 fixed = g_string_new (NULL);
788                 s = (guchar *)normalized;
789                 while (*s) {
790                         if (g_ascii_isgraph (*s) ||
791                             strchr (unescape_extra, *s))
792                                 g_string_append_c (fixed, *s);
793                         else
794                                 g_string_append_printf (fixed, "%%%02X", (int)*s);
795                         s++;
796                 }
797                 g_free (normalized);
798                 normalized = g_string_free (fixed, FALSE);
799         }
800
801         return normalized;
802 }
803
804 /**
805  * soup_uri_normalize:
806  * @part: a URI part
807  * @unescape_extra: reserved characters to unescape (or %NULL)
808  *
809  * %<!-- -->-decodes any "unreserved" characters (or characters in
810  * @unescape_extra) in @part.
811  *
812  * "Unreserved" characters are those that are not allowed to be used
813  * for punctuation according to the URI spec. For example, letters are
814  * unreserved, so soup_uri_normalize() will turn
815  * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
816  * <literal>http://example.com/foo/bar</literal>, which is guaranteed
817  * to mean the same thing. However, "/" is "reserved", so
818  * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
819  * be changed, because it might mean something different to the
820  * server.
821  *
822  * In the past, this would return %NULL if @part contained invalid
823  * percent-encoding, but now it just ignores the problem (as
824  * soup_uri_new() already did).
825  *
826  * Return value: the normalized URI part
827  */
828 char *
829 soup_uri_normalize (const char *part, const char *unescape_extra)
830 {
831         g_return_val_if_fail (part != NULL, NULL);
832
833         return uri_normalized_copy (part, strlen (part), unescape_extra);
834 }
835
836
837 /**
838  * soup_uri_uses_default_port:
839  * @uri: a #SoupURI
840  *
841  * Tests if @uri uses the default port for its scheme. (Eg, 80 for
842  * http.) (This only works for http, https and ftp; libsoup does not know
843  * the default ports of other protocols.)
844  *
845  * Return value: %TRUE or %FALSE
846  **/
847 gboolean
848 soup_uri_uses_default_port (SoupURI *uri)
849 {
850         g_return_val_if_fail (uri != NULL, FALSE);
851         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
852
853         return uri->port == soup_scheme_default_port (uri->scheme);
854 }
855
856 /**
857  * SOUP_URI_SCHEME_HTTP:
858  *
859  * "http" as an interned string. This can be compared directly against
860  * the value of a #SoupURI's <structfield>scheme</structfield>
861  **/
862
863 /**
864  * SOUP_URI_SCHEME_HTTPS:
865  *
866  * "https" as an interned string. This can be compared directly
867  * against the value of a #SoupURI's <structfield>scheme</structfield>
868  **/
869
870 /**
871  * SOUP_URI_SCHEME_RESOURCE:
872  *
873  * "resource" as an interned string. This can be compared directly
874  * against the value of a #SoupURI's <structfield>scheme</structfield>
875  *
876  * Since: 2.42
877  **/
878
879 /**
880  * soup_uri_get_scheme:
881  * @uri: a #SoupURI
882  *
883  * Gets @uri's scheme.
884  *
885  * Return value: @uri's scheme.
886  *
887  * Since: 2.32
888  **/
889 const char *
890 soup_uri_get_scheme (SoupURI *uri)
891 {
892         g_return_val_if_fail (uri != NULL, NULL);
893
894         return uri->scheme;
895 }
896
897 /**
898  * soup_uri_set_scheme:
899  * @uri: a #SoupURI
900  * @scheme: the URI scheme
901  *
902  * Sets @uri's scheme to @scheme. This will also set @uri's port to
903  * the default port for @scheme, if known.
904  **/
905 void
906 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
907 {
908         g_return_if_fail (uri != NULL);
909         g_return_if_fail (scheme != NULL);
910
911         uri->scheme = soup_uri_parse_scheme (scheme, strlen (scheme));
912         uri->port = soup_scheme_default_port (uri->scheme);
913 }
914
915 /**
916  * soup_uri_get_user:
917  * @uri: a #SoupURI
918  *
919  * Gets @uri's user.
920  *
921  * Return value: @uri's user.
922  *
923  * Since: 2.32
924  **/
925 const char *
926 soup_uri_get_user (SoupURI *uri)
927 {
928         g_return_val_if_fail (uri != NULL, NULL);
929
930         return uri->user;
931 }
932
933 /**
934  * soup_uri_set_user:
935  * @uri: a #SoupURI
936  * @user: (allow-none): the username, or %NULL
937  *
938  * Sets @uri's user to @user.
939  **/
940 void
941 soup_uri_set_user (SoupURI *uri, const char *user)
942 {
943         g_return_if_fail (uri != NULL);
944
945         g_free (uri->user);
946         uri->user = g_strdup (user);
947 }
948
949 /**
950  * soup_uri_get_password:
951  * @uri: a #SoupURI
952  *
953  * Gets @uri's password.
954  *
955  * Return value: @uri's password.
956  *
957  * Since: 2.32
958  **/
959 const char *
960 soup_uri_get_password (SoupURI *uri)
961 {
962         g_return_val_if_fail (uri != NULL, NULL);
963
964         return uri->password;
965 }
966
967 /**
968  * soup_uri_set_password:
969  * @uri: a #SoupURI
970  * @password: (allow-none): the password, or %NULL
971  *
972  * Sets @uri's password to @password.
973  **/
974 void
975 soup_uri_set_password (SoupURI *uri, const char *password)
976 {
977         g_return_if_fail (uri != NULL);
978
979         g_free (uri->password);
980         uri->password = g_strdup (password);
981 }
982
983 /**
984  * soup_uri_get_host:
985  * @uri: a #SoupURI
986  *
987  * Gets @uri's host.
988  *
989  * Return value: @uri's host.
990  *
991  * Since: 2.32
992  **/
993 const char *
994 soup_uri_get_host (SoupURI *uri)
995 {
996         g_return_val_if_fail (uri != NULL, NULL);
997
998         return uri->host;
999 }
1000
1001 /**
1002  * soup_uri_set_host:
1003  * @uri: a #SoupURI
1004  * @host: (allow-none): the hostname or IP address, or %NULL
1005  *
1006  * Sets @uri's host to @host.
1007  *
1008  * If @host is an IPv6 IP address, it should not include the brackets
1009  * required by the URI syntax; they will be added automatically when
1010  * converting @uri to a string.
1011  *
1012  * http and https URIs should not have a %NULL @host.
1013  **/
1014 void
1015 soup_uri_set_host (SoupURI *uri, const char *host)
1016 {
1017         g_return_if_fail (uri != NULL);
1018
1019         g_free (uri->host);
1020         uri->host = g_strdup (host);
1021 }
1022
1023 /**
1024  * soup_uri_get_port:
1025  * @uri: a #SoupURI
1026  *
1027  * Gets @uri's port.
1028  *
1029  * Return value: @uri's port.
1030  *
1031  * Since: 2.32
1032  **/
1033 guint
1034 soup_uri_get_port (SoupURI *uri)
1035 {
1036         g_return_val_if_fail (uri != NULL, 0);
1037
1038         return uri->port;
1039 }
1040
1041 /**
1042  * soup_uri_set_port:
1043  * @uri: a #SoupURI
1044  * @port: the port, or 0
1045  *
1046  * Sets @uri's port to @port. If @port is 0, @uri will not have an
1047  * explicitly-specified port.
1048  **/
1049 void
1050 soup_uri_set_port (SoupURI *uri, guint port)
1051 {
1052         g_return_if_fail (uri != NULL);
1053
1054         uri->port = port;
1055 }
1056
1057 /**
1058  * soup_uri_get_path:
1059  * @uri: a #SoupURI
1060  *
1061  * Gets @uri's path.
1062  *
1063  * Return value: @uri's path.
1064  *
1065  * Since: 2.32
1066  **/
1067 const char *
1068 soup_uri_get_path (SoupURI *uri)
1069 {
1070         g_return_val_if_fail (uri != NULL, NULL);
1071
1072         return uri->path;
1073 }
1074
1075 /**
1076  * soup_uri_set_path:
1077  * @uri: a #SoupURI
1078  * @path: the non-%NULL path
1079  *
1080  * Sets @uri's path to @path.
1081  **/
1082 void
1083 soup_uri_set_path (SoupURI *uri, const char *path)
1084 {
1085         g_return_if_fail (uri != NULL);
1086
1087         /* We allow a NULL path for compatibility, but warn about it. */
1088         if (!path) {
1089                 g_warn_if_fail (path != NULL);
1090                 path = "";
1091         }
1092
1093         g_free (uri->path);
1094         uri->path = g_strdup (path);
1095 }
1096
1097 /**
1098  * soup_uri_get_query:
1099  * @uri: a #SoupURI
1100  *
1101  * Gets @uri's query.
1102  *
1103  * Return value: @uri's query.
1104  *
1105  * Since: 2.32
1106  **/
1107 const char *
1108 soup_uri_get_query (SoupURI *uri)
1109 {
1110         g_return_val_if_fail (uri != NULL, NULL);
1111
1112         return uri->query;
1113 }
1114
1115 /**
1116  * soup_uri_set_query:
1117  * @uri: a #SoupURI
1118  * @query: (allow-none): the query
1119  *
1120  * Sets @uri's query to @query.
1121  **/
1122 void
1123 soup_uri_set_query (SoupURI *uri, const char *query)
1124 {
1125         g_return_if_fail (uri != NULL);
1126
1127         g_free (uri->query);
1128         uri->query = g_strdup (query);
1129 }
1130
1131 /**
1132  * soup_uri_set_query_from_form:
1133  * @uri: a #SoupURI
1134  * @form: (element-type utf8 utf8): a #GHashTable containing HTML form
1135  * information
1136  *
1137  * Sets @uri's query to the result of encoding @form according to the
1138  * HTML form rules. See soup_form_encode_hash() for more information.
1139  **/
1140 void
1141 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
1142 {
1143         g_return_if_fail (uri != NULL);
1144
1145         g_free (uri->query);
1146         uri->query = soup_form_encode_hash (form);
1147 }
1148
1149 /**
1150  * soup_uri_set_query_from_fields:
1151  * @uri: a #SoupURI
1152  * @first_field: name of the first form field to encode into query
1153  * @...: value of @first_field, followed by additional field names
1154  * and values, terminated by %NULL.
1155  *
1156  * Sets @uri's query to the result of encoding the given form fields
1157  * and values according to the * HTML form rules. See
1158  * soup_form_encode() for more information.
1159  **/
1160 void
1161 soup_uri_set_query_from_fields (SoupURI    *uri,
1162                                 const char *first_field,
1163                                 ...)
1164 {
1165         va_list args;
1166
1167         g_return_if_fail (uri != NULL);
1168
1169         g_free (uri->query);
1170         va_start (args, first_field);
1171         uri->query = soup_form_encode_valist (first_field, args);
1172         va_end (args);
1173 }
1174
1175 /**
1176  * soup_uri_get_fragment:
1177  * @uri: a #SoupURI
1178  *
1179  * Gets @uri's fragment.
1180  *
1181  * Return value: @uri's fragment.
1182  *
1183  * Since: 2.32
1184  **/
1185 const char *
1186 soup_uri_get_fragment (SoupURI *uri)
1187 {
1188         g_return_val_if_fail (uri != NULL, NULL);
1189
1190         return uri->fragment;
1191 }
1192
1193 /**
1194  * soup_uri_set_fragment:
1195  * @uri: a #SoupURI
1196  * @fragment: (allow-none): the fragment
1197  *
1198  * Sets @uri's fragment to @fragment.
1199  **/
1200 void
1201 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
1202 {
1203         g_return_if_fail (uri != NULL);
1204
1205         g_free (uri->fragment);
1206         uri->fragment = g_strdup (fragment);
1207 }
1208
1209 /**
1210  * soup_uri_copy_host:
1211  * @uri: a #SoupURI
1212  *
1213  * Makes a copy of @uri, considering only the protocol, host, and port
1214  *
1215  * Return value: the new #SoupURI
1216  *
1217  * Since: 2.28
1218  **/
1219 SoupURI *
1220 soup_uri_copy_host (SoupURI *uri)
1221 {
1222         SoupURI *dup;
1223
1224         g_return_val_if_fail (uri != NULL, NULL);
1225         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1226
1227         dup = soup_uri_new (NULL);
1228         dup->scheme = uri->scheme;
1229         dup->host   = g_strdup (uri->host);
1230         dup->port   = uri->port;
1231         dup->path   = g_strdup ("");
1232
1233         return dup;
1234 }
1235
1236 /**
1237  * soup_uri_host_hash:
1238  * @key: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1239  *
1240  * Hashes @key, considering only the scheme, host, and port.
1241  *
1242  * Return value: a hash
1243  *
1244  * Since: 2.28
1245  **/
1246 guint
1247 soup_uri_host_hash (gconstpointer key)
1248 {
1249         const SoupURI *uri = key;
1250
1251         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
1252         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1253
1254         return GPOINTER_TO_UINT (uri->scheme) + uri->port +
1255                 soup_str_case_hash (uri->host);
1256 }
1257
1258 /**
1259  * soup_uri_host_equal:
1260  * @v1: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1261  * @v2: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1262  *
1263  * Compares @v1 and @v2, considering only the scheme, host, and port.
1264  *
1265  * Return value: whether or not the URIs are equal in scheme, host,
1266  * and port.
1267  *
1268  * Since: 2.28
1269  **/
1270 gboolean
1271 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1272 {
1273         const SoupURI *one = v1;
1274         const SoupURI *two = v2;
1275
1276         g_return_val_if_fail (one != NULL && two != NULL, one == two);
1277         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1278         g_warn_if_fail (SOUP_URI_IS_VALID (one));
1279         g_warn_if_fail (SOUP_URI_IS_VALID (two));
1280
1281         if (one->scheme != two->scheme)
1282                 return FALSE;
1283         if (one->port != two->port)
1284                 return FALSE;
1285
1286         return g_ascii_strcasecmp (one->host, two->host) == 0;
1287 }
1288
1289 G_DEFINE_BOXED_TYPE (SoupURI, soup_uri, soup_uri_copy, soup_uri_free)