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