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