Reapplying patch to disable attempts to use gtk-doc
[profile/ivi/libsoup2.4.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                 else if ((!uri->path || !*uri->path) &&
484                          (uri->scheme == SOUP_URI_SCHEME_HTTP ||
485                           uri->scheme == SOUP_URI_SCHEME_HTTPS))
486                         g_string_append_c (str, '/');
487         }
488
489         if (uri->path && *uri->path)
490                 g_string_append (str, uri->path);
491         else if (just_path_and_query)
492                 g_string_append_c (str, '/');
493
494         if (uri->query) {
495                 g_string_append_c (str, '?');
496                 g_string_append (str, uri->query);
497         }
498         if (uri->fragment && !just_path_and_query) {
499                 g_string_append_c (str, '#');
500                 g_string_append (str, uri->fragment);
501         }
502
503         return_result = str->str;
504         g_string_free (str, FALSE);
505
506         return return_result;
507 }
508
509 /**
510  * soup_uri_copy:
511  * @uri: a #SoupURI
512  *
513  * Copies @uri
514  *
515  * Return value: a copy of @uri, which must be freed with soup_uri_free()
516  **/
517 SoupURI *
518 soup_uri_copy (SoupURI *uri)
519 {
520         SoupURI *dup;
521
522         g_return_val_if_fail (uri != NULL, NULL);
523         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
524
525         dup = g_slice_new0 (SoupURI);
526         dup->scheme   = uri->scheme;
527         dup->user     = g_strdup (uri->user);
528         dup->password = g_strdup (uri->password);
529         dup->host     = g_strdup (uri->host);
530         dup->port     = uri->port;
531         dup->path     = g_strdup (uri->path);
532         dup->query    = g_strdup (uri->query);
533         dup->fragment = g_strdup (uri->fragment);
534
535         return dup;
536 }
537
538 static inline gboolean
539 parts_equal (const char *one, const char *two, gboolean insensitive)
540 {
541         if (!one && !two)
542                 return TRUE;
543         if (!one || !two)
544                 return FALSE;
545         return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
546 }
547
548 /**
549  * soup_uri_equal:
550  * @uri1: a #SoupURI
551  * @uri2: another #SoupURI
552  *
553  * Tests whether or not @uri1 and @uri2 are equal in all parts
554  *
555  * Return value: %TRUE or %FALSE
556  **/
557 gboolean 
558 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
559 {
560         g_return_val_if_fail (uri1 != NULL, FALSE);
561         g_return_val_if_fail (uri2 != NULL, FALSE);
562         g_warn_if_fail (SOUP_URI_IS_VALID (uri1));
563         g_warn_if_fail (SOUP_URI_IS_VALID (uri2));
564
565         if (uri1->scheme != uri2->scheme                         ||
566             uri1->port   != uri2->port                           ||
567             !parts_equal (uri1->user, uri2->user, FALSE)         ||
568             !parts_equal (uri1->password, uri2->password, FALSE) ||
569             !parts_equal (uri1->host, uri2->host, TRUE)          ||
570             !parts_equal (uri1->path, uri2->path, FALSE)         ||
571             !parts_equal (uri1->query, uri2->query, FALSE)       ||
572             !parts_equal (uri1->fragment, uri2->fragment, FALSE))
573                 return FALSE;
574
575         return TRUE;
576 }
577
578 /**
579  * soup_uri_free:
580  * @uri: a #SoupURI
581  *
582  * Frees @uri.
583  **/
584 void
585 soup_uri_free (SoupURI *uri)
586 {
587         g_return_if_fail (uri != NULL);
588
589         g_free (uri->user);
590         g_free (uri->password);
591         g_free (uri->host);
592         g_free (uri->path);
593         g_free (uri->query);
594         g_free (uri->fragment);
595
596         g_slice_free (SoupURI, uri);
597 }
598
599 static void
600 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
601 {
602         const unsigned char *s = (const unsigned char *)in;
603
604         while (*s) {
605                 if (soup_char_is_uri_percent_encoded (*s) ||
606                     soup_char_is_uri_gen_delims (*s) ||
607                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
608                         g_string_append_printf (str, "%%%02X", (int)*s++);
609                 else
610                         g_string_append_c (str, *s++);
611         }
612 }
613
614 /**
615  * soup_uri_encode:
616  * @part: a URI part
617  * @escape_extra: (allow-none): additional reserved characters to
618  * escape (or %NULL)
619  *
620  * This %<!-- -->-encodes the given URI part and returns the escaped
621  * version in allocated memory, which the caller must free when it is
622  * done.
623  *
624  * Return value: the encoded URI part
625  **/
626 char *
627 soup_uri_encode (const char *part, const char *escape_extra)
628 {
629         GString *str;
630         char *encoded;
631
632         g_return_val_if_fail (part != NULL, NULL);
633
634         str = g_string_new (NULL);
635         append_uri_encoded (str, part, escape_extra);
636         encoded = str->str;
637         g_string_free (str, FALSE);
638
639         return encoded;
640 }
641
642 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
643 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
644
645 char *
646 uri_decoded_copy (const char *part, int length)
647 {
648         unsigned char *s, *d;
649         char *decoded = g_strndup (part, length);
650
651         g_return_val_if_fail (part != NULL, NULL);
652
653         s = d = (unsigned char *)decoded;
654         do {
655                 if (*s == '%') {
656                         if (!g_ascii_isxdigit (s[1]) ||
657                             !g_ascii_isxdigit (s[2])) {
658                                 *d++ = *s;
659                                 continue;
660                         }
661                         *d++ = HEXCHAR (s);
662                         s += 2;
663                 } else
664                         *d++ = *s;
665         } while (*s++);
666
667         return decoded;
668 }
669
670 /**
671  * soup_uri_decode:
672  * @part: a URI part
673  *
674  * Fully %<!-- -->-decodes @part.
675  *
676  * In the past, this would return %NULL if @part contained invalid
677  * percent-encoding, but now it just ignores the problem (as
678  * soup_uri_new() already did).
679  *
680  * Return value: the decoded URI part.
681  */
682 char *
683 soup_uri_decode (const char *part)
684 {
685         g_return_val_if_fail (part != NULL, NULL);
686
687         return uri_decoded_copy (part, strlen (part));
688 }
689
690 static char *
691 uri_normalized_copy (const char *part, int length,
692                      const char *unescape_extra)
693 {
694         unsigned char *s, *d, c;
695         char *normalized = g_strndup (part, length);
696         gboolean need_fixup = FALSE;
697
698         s = d = (unsigned char *)normalized;
699         do {
700                 if (*s == '%') {
701                         if (!g_ascii_isxdigit (s[1]) ||
702                             !g_ascii_isxdigit (s[2])) {
703                                 *d++ = *s;
704                                 continue;
705                         }
706
707                         c = HEXCHAR (s);
708                         if (soup_char_is_uri_unreserved (c) ||
709                             (unescape_extra && strchr (unescape_extra, c))) {
710                                 *d++ = c;
711                                 s += 2;
712                         } else {
713                                 /* We leave it unchanged. We used to uppercase percent-encoded
714                                  * triplets but we do not do it any more as RFC3986 Section 6.2.2.1
715                                  * says that they only SHOULD be case normalized.
716                                  */
717                                 *d++ = *s++;
718                                 *d++ = *s++;
719                                 *d++ = *s;
720                         }
721                 } else {
722                         if (!g_ascii_isgraph (*s))
723                                 need_fixup = TRUE;
724                         *d++ = *s;
725                 }
726         } while (*s++);
727
728         if (need_fixup) {
729                 GString *fixed;
730
731                 fixed = g_string_new (NULL);
732                 s = (guchar *)normalized;
733                 while (*s) {
734                         if (g_ascii_isgraph (*s))
735                                 g_string_append_c (fixed, *s);
736                         else
737                                 g_string_append_printf (fixed, "%%%02X", (int)*s);
738                         s++;
739                 }
740                 g_free (normalized);
741                 normalized = g_string_free (fixed, FALSE);
742         }
743
744         return normalized;
745 }
746
747 /**
748  * soup_uri_normalize:
749  * @part: a URI part
750  * @unescape_extra: reserved characters to unescape (or %NULL)
751  *
752  * %<!-- -->-decodes any "unreserved" characters (or characters in
753  * @unescape_extra) in @part.
754  *
755  * "Unreserved" characters are those that are not allowed to be used
756  * for punctuation according to the URI spec. For example, letters are
757  * unreserved, so soup_uri_normalize() will turn
758  * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
759  * <literal>http://example.com/foo/bar</literal>, which is guaranteed
760  * to mean the same thing. However, "/" is "reserved", so
761  * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
762  * be changed, because it might mean something different to the
763  * server.
764  *
765  * In the past, this would return %NULL if @part contained invalid
766  * percent-encoding, but now it just ignores the problem (as
767  * soup_uri_new() already did).
768  *
769  * Return value: the normalized URI part
770  */
771 char *
772 soup_uri_normalize (const char *part, const char *unescape_extra)
773 {
774         g_return_val_if_fail (part != NULL, NULL);
775
776         return uri_normalized_copy (part, strlen (part), unescape_extra);
777 }
778
779
780 /**
781  * soup_uri_uses_default_port:
782  * @uri: a #SoupURI
783  *
784  * Tests if @uri uses the default port for its scheme. (Eg, 80 for
785  * http.) (This only works for http, https and ftp; libsoup does not know
786  * the default ports of other protocols.)
787  *
788  * Return value: %TRUE or %FALSE
789  **/
790 gboolean
791 soup_uri_uses_default_port (SoupURI *uri)
792 {
793         g_return_val_if_fail (uri != NULL, FALSE);
794         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
795
796         return uri->port == soup_scheme_default_port (uri->scheme);
797 }
798
799 /**
800  * SOUP_URI_SCHEME_HTTP:
801  *
802  * "http" as an interned string. This can be compared directly against
803  * the value of a #SoupURI's <structfield>scheme</structfield>
804  **/
805
806 /**
807  * SOUP_URI_SCHEME_HTTPS:
808  *
809  * "https" as an interned string. This can be compared directly
810  * against the value of a #SoupURI's <structfield>scheme</structfield>
811  **/
812
813 /**
814  * soup_uri_get_scheme:
815  * @uri: a #SoupURI
816  *
817  * Gets @uri's scheme.
818  *
819  * Return value: @uri's scheme.
820  *
821  * Since: 2.32
822  **/
823 const char *
824 soup_uri_get_scheme (SoupURI *uri)
825 {
826         g_return_val_if_fail (uri != NULL, NULL);
827
828         return uri->scheme;
829 }
830
831 /**
832  * soup_uri_set_scheme:
833  * @uri: a #SoupURI
834  * @scheme: the URI scheme
835  *
836  * Sets @uri's scheme to @scheme. This will also set @uri's port to
837  * the default port for @scheme, if known.
838  **/
839 void
840 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
841 {
842         g_return_if_fail (uri != NULL);
843         g_return_if_fail (scheme != NULL);
844
845         uri->scheme = soup_uri_parse_scheme (scheme, strlen (scheme));
846         uri->port = soup_scheme_default_port (uri->scheme);
847 }
848
849 /**
850  * soup_uri_get_user:
851  * @uri: a #SoupURI
852  *
853  * Gets @uri's user.
854  *
855  * Return value: @uri's user.
856  *
857  * Since: 2.32
858  **/
859 const char *
860 soup_uri_get_user (SoupURI *uri)
861 {
862         g_return_val_if_fail (uri != NULL, NULL);
863
864         return uri->user;
865 }
866
867 /**
868  * soup_uri_set_user:
869  * @uri: a #SoupURI
870  * @user: (allow-none): the username, or %NULL
871  *
872  * Sets @uri's user to @user.
873  **/
874 void
875 soup_uri_set_user (SoupURI *uri, const char *user)
876 {
877         g_return_if_fail (uri != NULL);
878
879         g_free (uri->user);
880         uri->user = g_strdup (user);
881 }
882
883 /**
884  * soup_uri_get_password:
885  * @uri: a #SoupURI
886  *
887  * Gets @uri's password.
888  *
889  * Return value: @uri's password.
890  *
891  * Since: 2.32
892  **/
893 const char *
894 soup_uri_get_password (SoupURI *uri)
895 {
896         g_return_val_if_fail (uri != NULL, NULL);
897
898         return uri->password;
899 }
900
901 /**
902  * soup_uri_set_password:
903  * @uri: a #SoupURI
904  * @password: (allow-none): the password, or %NULL
905  *
906  * Sets @uri's password to @password.
907  **/
908 void
909 soup_uri_set_password (SoupURI *uri, const char *password)
910 {
911         g_return_if_fail (uri != NULL);
912
913         g_free (uri->password);
914         uri->password = g_strdup (password);
915 }
916
917 /**
918  * soup_uri_get_host:
919  * @uri: a #SoupURI
920  *
921  * Gets @uri's host.
922  *
923  * Return value: @uri's host.
924  *
925  * Since: 2.32
926  **/
927 const char *
928 soup_uri_get_host (SoupURI *uri)
929 {
930         g_return_val_if_fail (uri != NULL, NULL);
931
932         return uri->host;
933 }
934
935 /**
936  * soup_uri_set_host:
937  * @uri: a #SoupURI
938  * @host: (allow-none): the hostname or IP address, or %NULL
939  *
940  * Sets @uri's host to @host.
941  *
942  * If @host is an IPv6 IP address, it should not include the brackets
943  * required by the URI syntax; they will be added automatically when
944  * converting @uri to a string.
945  *
946  * http and https URIs should not have a %NULL @host.
947  **/
948 void
949 soup_uri_set_host (SoupURI *uri, const char *host)
950 {
951         g_return_if_fail (uri != NULL);
952
953         g_free (uri->host);
954         uri->host = g_strdup (host);
955 }
956
957 /**
958  * soup_uri_get_port:
959  * @uri: a #SoupURI
960  *
961  * Gets @uri's port.
962  *
963  * Return value: @uri's port.
964  *
965  * Since: 2.32
966  **/
967 guint
968 soup_uri_get_port (SoupURI *uri)
969 {
970         g_return_val_if_fail (uri != NULL, 0);
971
972         return uri->port;
973 }
974
975 /**
976  * soup_uri_set_port:
977  * @uri: a #SoupURI
978  * @port: the port, or 0
979  *
980  * Sets @uri's port to @port. If @port is 0, @uri will not have an
981  * explicitly-specified port.
982  **/
983 void
984 soup_uri_set_port (SoupURI *uri, guint port)
985 {
986         g_return_if_fail (uri != NULL);
987
988         uri->port = port;
989 }
990
991 /**
992  * soup_uri_get_path:
993  * @uri: a #SoupURI
994  *
995  * Gets @uri's path.
996  *
997  * Return value: @uri's path.
998  *
999  * Since: 2.32
1000  **/
1001 const char *
1002 soup_uri_get_path (SoupURI *uri)
1003 {
1004         g_return_val_if_fail (uri != NULL, NULL);
1005
1006         return uri->path;
1007 }
1008
1009 /**
1010  * soup_uri_set_path:
1011  * @uri: a #SoupURI
1012  * @path: the non-%NULL path
1013  *
1014  * Sets @uri's path to @path.
1015  **/
1016 void
1017 soup_uri_set_path (SoupURI *uri, const char *path)
1018 {
1019         g_return_if_fail (uri != NULL);
1020
1021         /* We allow a NULL path for compatibility, but warn about it. */
1022         if (!path) {
1023                 g_warn_if_fail (path != NULL);
1024                 path = "";
1025         }
1026
1027         g_free (uri->path);
1028         uri->path = g_strdup (path);
1029 }
1030
1031 /**
1032  * soup_uri_get_query:
1033  * @uri: a #SoupURI
1034  *
1035  * Gets @uri's query.
1036  *
1037  * Return value: @uri's query.
1038  *
1039  * Since: 2.32
1040  **/
1041 const char *
1042 soup_uri_get_query (SoupURI *uri)
1043 {
1044         g_return_val_if_fail (uri != NULL, NULL);
1045
1046         return uri->query;
1047 }
1048
1049 /**
1050  * soup_uri_set_query:
1051  * @uri: a #SoupURI
1052  * @query: (allow-none): the query
1053  *
1054  * Sets @uri's query to @query.
1055  **/
1056 void
1057 soup_uri_set_query (SoupURI *uri, const char *query)
1058 {
1059         g_return_if_fail (uri != NULL);
1060
1061         g_free (uri->query);
1062         uri->query = g_strdup (query);
1063 }
1064
1065 /**
1066  * soup_uri_set_query_from_form:
1067  * @uri: a #SoupURI
1068  * @form: (element-type utf8 utf8): a #GHashTable containing HTML form
1069  * information
1070  *
1071  * Sets @uri's query to the result of encoding @form according to the
1072  * HTML form rules. See soup_form_encode_hash() for more information.
1073  **/
1074 void
1075 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
1076 {
1077         g_return_if_fail (uri != NULL);
1078
1079         g_free (uri->query);
1080         uri->query = soup_form_encode_hash (form);
1081 }
1082
1083 /**
1084  * soup_uri_set_query_from_fields:
1085  * @uri: a #SoupURI
1086  * @first_field: name of the first form field to encode into query
1087  * @...: value of @first_field, followed by additional field names
1088  * and values, terminated by %NULL.
1089  *
1090  * Sets @uri's query to the result of encoding the given form fields
1091  * and values according to the * HTML form rules. See
1092  * soup_form_encode() for more information.
1093  **/
1094 void
1095 soup_uri_set_query_from_fields (SoupURI    *uri,
1096                                 const char *first_field,
1097                                 ...)
1098 {
1099         va_list args;
1100
1101         g_return_if_fail (uri != NULL);
1102
1103         g_free (uri->query);
1104         va_start (args, first_field);
1105         uri->query = soup_form_encode_valist (first_field, args);
1106         va_end (args);
1107 }
1108
1109 /**
1110  * soup_uri_get_fragment:
1111  * @uri: a #SoupURI
1112  *
1113  * Gets @uri's fragment.
1114  *
1115  * Return value: @uri's fragment.
1116  *
1117  * Since: 2.32
1118  **/
1119 const char *
1120 soup_uri_get_fragment (SoupURI *uri)
1121 {
1122         g_return_val_if_fail (uri != NULL, NULL);
1123
1124         return uri->fragment;
1125 }
1126
1127 /**
1128  * soup_uri_set_fragment:
1129  * @uri: a #SoupURI
1130  * @fragment: (allow-none): the fragment
1131  *
1132  * Sets @uri's fragment to @fragment.
1133  **/
1134 void
1135 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
1136 {
1137         g_return_if_fail (uri != NULL);
1138
1139         g_free (uri->fragment);
1140         uri->fragment = g_strdup (fragment);
1141 }
1142
1143 /**
1144  * soup_uri_copy_host:
1145  * @uri: a #SoupURI
1146  *
1147  * Makes a copy of @uri, considering only the protocol, host, and port
1148  *
1149  * Return value: the new #SoupURI
1150  *
1151  * Since: 2.26.3
1152  **/
1153 SoupURI *
1154 soup_uri_copy_host (SoupURI *uri)
1155 {
1156         SoupURI *dup;
1157
1158         g_return_val_if_fail (uri != NULL, NULL);
1159         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1160
1161         dup = soup_uri_new (NULL);
1162         dup->scheme = uri->scheme;
1163         dup->host   = g_strdup (uri->host);
1164         dup->port   = uri->port;
1165         dup->path   = g_strdup ("");
1166
1167         return dup;
1168 }
1169
1170 /**
1171  * soup_uri_host_hash:
1172  * @key: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1173  *
1174  * Hashes @key, considering only the scheme, host, and port.
1175  *
1176  * Return value: a hash
1177  *
1178  * Since: 2.26.3
1179  **/
1180 guint
1181 soup_uri_host_hash (gconstpointer key)
1182 {
1183         const SoupURI *uri = key;
1184
1185         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
1186         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1187
1188         return GPOINTER_TO_UINT (uri->scheme) + uri->port +
1189                 soup_str_case_hash (uri->host);
1190 }
1191
1192 /**
1193  * soup_uri_host_equal:
1194  * @v1: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1195  * @v2: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1196  *
1197  * Compares @v1 and @v2, considering only the scheme, host, and port.
1198  *
1199  * Return value: whether or not the URIs are equal in scheme, host,
1200  * and port.
1201  *
1202  * Since: 2.26.3
1203  **/
1204 gboolean
1205 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1206 {
1207         const SoupURI *one = v1;
1208         const SoupURI *two = v2;
1209
1210         g_return_val_if_fail (one != NULL && two != NULL, one == two);
1211         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1212         g_warn_if_fail (SOUP_URI_IS_VALID (one));
1213         g_warn_if_fail (SOUP_URI_IS_VALID (two));
1214
1215         if (one->scheme != two->scheme)
1216                 return FALSE;
1217         if (one->port != two->port)
1218                 return FALSE;
1219
1220         return g_ascii_strcasecmp (one->host, two->host) == 0;
1221 }
1222
1223
1224 GType
1225 soup_uri_get_type (void)
1226 {
1227         static volatile gsize type_volatile = 0;
1228
1229         if (g_once_init_enter (&type_volatile)) {
1230                 GType type = g_boxed_type_register_static (
1231                         g_intern_static_string ("SoupURI"),
1232                         (GBoxedCopyFunc) soup_uri_copy,
1233                         (GBoxedFreeFunc) soup_uri_free);
1234                 g_once_init_leave (&type_volatile, type);
1235         }
1236         return type_volatile;
1237 }