SoupURI: never return NULL from soup_uri_decode/soup_uri_normalize
[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-form.h"
14 #include "soup-misc.h"
15
16 /**
17  * SECTION:soup-uri
18  * @short_description: URIs
19  *
20  * A #SoupURI represents a (parsed) URI.
21  *
22  * Many applications will not need to use #SoupURI directly at all; on
23  * the client side, soup_message_new() takes a stringified URI, and on
24  * the server side, the path and query components are provided for you
25  * in the server callback.
26  **/
27
28 /**
29  * SoupURI:
30  * @scheme: the URI scheme (eg, "http")
31  * @user: a username, or %NULL
32  * @password: a password, or %NULL
33  * @host: the hostname or IP address
34  * @port: the port number on @host
35  * @path: the path on @host
36  * @query: a query for @path, or %NULL
37  * @fragment: a fragment identifier within @path, or %NULL
38  *
39  * A #SoupURI represents a (parsed) URI. #SoupURI supports RFC 3986
40  * (URI Generic Syntax), and can parse any valid URI. However, libsoup
41  * only uses "http" and "https" URIs internally; You can use
42  * SOUP_URI_VALID_FOR_HTTP() to test if a #SoupURI is a valid HTTP
43  * URI.
44  *
45  * @scheme will always be set in any URI. It is an interned string and
46  * is always all lowercase. (If you parse a URI with a non-lowercase
47  * scheme, it will be converted to lowercase.) The macros
48  * %SOUP_URI_SCHEME_HTTP and %SOUP_URI_SCHEME_HTTPS provide the
49  * interned values for "http" and "https" and can be compared against
50  * URI @scheme values.
51  *
52  * @user and @password are parsed as defined in the older URI specs
53  * (ie, separated by a colon; RFC 3986 only talks about a single
54  * "userinfo" field). Note that @password is not included in the
55  * output of soup_uri_to_string(). libsoup does not normally use these
56  * fields; authentication is handled via #SoupSession signals.
57  *
58  * @host contains the hostname, and @port the port specified in the
59  * URI. If the URI doesn't contain a hostname, @host will be %NULL,
60  * and if it doesn't specify a port, @port may be 0. However, for
61  * "http" and "https" URIs, @host is guaranteed to be non-%NULL
62  * (trying to parse an http URI with no @host will return %NULL), and
63  * @port will always be non-0 (because libsoup knows the default value
64  * to use when it is not specified in the URI).
65  *
66  * @path is always non-%NULL. For http/https URIs, @path will never be
67  * an empty string either; if the input URI has no path, the parsed
68  * #SoupURI will have a @path of "/".
69  *
70  * @query and @fragment are optional for all URI types.
71  * soup_form_decode_urlencoded() may be useful for parsing @query.
72  *
73  * Note that @path, @query, and @fragment may contain
74  * %<!-- -->-encoded characters. soup_uri_new() calls
75  * soup_uri_normalize() on them, but not soup_uri_decode(). This is
76  * necessary to ensure that soup_uri_to_string() will generate a URI
77  * that has exactly the same meaning as the original. (In theory,
78  * #SoupURI should leave @user, @password, and @host partially-encoded
79  * as well, but this would be more annoying than useful.)
80  **/
81
82 /**
83  * SOUP_URI_VALID_FOR_HTTP:
84  * @uri: a #SoupURI
85  *
86  * Tests if @uri is a valid #SoupURI for HTTP communication; that is, if
87  * it can be used to construct a #SoupMessage.
88  *
89  * Return value: %TRUE if @uri is a valid "http" or "https" URI.
90  *
91  * Since: 2.24
92  **/
93
94 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
95 static char *uri_decoded_copy (const char *str, int length);
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                 return uri;
200
201         /* Check for authority */
202         if (strncmp (uri_string, "//", 2) == 0) {
203                 uri_string += 2;
204
205                 path = uri_string + strcspn (uri_string, "/?#");
206                 if (path > end)
207                         path = end;
208                 at = strchr (uri_string, '@');
209                 if (at && at < path) {
210                         colon = strchr (uri_string, ':');
211                         if (colon && colon < at) {
212                                 uri->password = uri_decoded_copy (colon + 1,
213                                                                   at - colon - 1);
214                         } else {
215                                 uri->password = NULL;
216                                 colon = at;
217                         }
218
219                         uri->user = uri_decoded_copy (uri_string,
220                                                       colon - uri_string);
221                         uri_string = at + 1;
222                 } else
223                         uri->user = uri->password = NULL;
224
225                 /* Find host and port. */
226                 if (*uri_string == '[') {
227                         uri_string++;
228                         hostend = strchr (uri_string, ']');
229                         if (!hostend || hostend > path) {
230                                 soup_uri_free (uri);
231                                 return NULL;
232                         }
233                         if (*(hostend + 1) == ':')
234                                 colon = hostend + 1;
235                         else
236                                 colon = NULL;
237                 } else {
238                         colon = memchr (uri_string, ':', path - uri_string);
239                         hostend = colon ? colon : path;
240                 }
241
242                 uri->host = uri_decoded_copy (uri_string, hostend - uri_string);
243
244                 if (colon && colon != path - 1) {
245                         char *portend;
246                         uri->port = strtoul (colon + 1, &portend, 10);
247                         if (portend != (char *)path) {
248                                 soup_uri_free (uri);
249                                 return NULL;
250                         }
251                 }
252
253                 uri_string = path;
254         }
255
256         /* Find query */
257         question = memchr (uri_string, '?', end - uri_string);
258         if (question) {
259                 uri->query = uri_normalized_copy (question + 1,
260                                                   end - (question + 1),
261                                                   NULL);
262                 end = question;
263         }
264
265         if (end != uri_string) {
266                 uri->path = uri_normalized_copy (uri_string, end - uri_string,
267                                                  NULL);
268         }
269
270         /* Apply base URI. This is spelled out in RFC 3986. */
271         if (base && !uri->scheme && uri->host)
272                 uri->scheme = base->scheme;
273         else if (base && !uri->scheme) {
274                 uri->scheme = base->scheme;
275                 uri->user = g_strdup (base->user);
276                 uri->password = g_strdup (base->password);
277                 uri->host = g_strdup (base->host);
278                 uri->port = base->port;
279
280                 if (!uri->path) {
281                         uri->path = g_strdup (base->path);
282                         if (!uri->query)
283                                 uri->query = g_strdup (base->query);
284                         remove_dot_segments = FALSE;
285                 } else if (*uri->path != '/') {
286                         char *newpath, *last;
287
288                         last = strrchr (base->path, '/');
289                         if (last) {
290                                 newpath = g_strdup_printf ("%.*s%s",
291                                                            (int)(last + 1 - base->path),
292                                                            base->path,
293                                                            uri->path);
294                         } else
295                                 newpath = g_strdup_printf ("/%s", uri->path);
296
297                         g_free (uri->path);
298                         uri->path = newpath;
299                 }
300         }
301
302         if (remove_dot_segments && uri->path && *uri->path) {
303                 char *p, *q;
304
305                 /* Remove "./" where "." is a complete segment. */
306                 for (p = uri->path + 1; *p; ) {
307                         if (*(p - 1) == '/' &&
308                             *p == '.' && *(p + 1) == '/')
309                                 memmove (p, p + 2, strlen (p + 2) + 1);
310                         else
311                                 p++;
312                 }
313                 /* Remove "." at end. */
314                 if (p > uri->path + 2 &&
315                     *(p - 1) == '.' && *(p - 2) == '/')
316                         *(p - 1) = '\0';
317
318                 /* Remove "<segment>/../" where <segment> != ".." */
319                 for (p = uri->path + 1; *p; ) {
320                         if (!strncmp (p, "../", 3)) {
321                                 p += 3;
322                                 continue;
323                         }
324                         q = strchr (p + 1, '/');
325                         if (!q)
326                                 break;
327                         if (strncmp (q, "/../", 4) != 0) {
328                                 p = q + 1;
329                                 continue;
330                         }
331                         memmove (p, q + 4, strlen (q + 4) + 1);
332                         p = uri->path + 1;
333                 }
334                 /* Remove "<segment>/.." at end where <segment> != ".." */
335                 q = strrchr (uri->path, '/');
336                 if (q && !strcmp (q, "/..")) {
337                         p = q - 1;
338                         while (p > uri->path && *p != '/')
339                                 p--;
340                         if (strncmp (p, "/../", 4) != 0)
341                                 *(p + 1) = 0;
342                 }
343
344                 /* Remove extraneous initial "/.."s */
345                 while (!strncmp (uri->path, "/../", 4))
346                         memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
347                 if (!strcmp (uri->path, "/.."))
348                         uri->path[1] = '\0';
349         }
350
351         /* HTTP-specific stuff */
352         if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
353             uri->scheme == SOUP_URI_SCHEME_HTTPS) {
354                 if (!uri->path)
355                         uri->path = g_strdup ("/");
356                 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
357                         soup_uri_free (uri);
358                         return NULL;
359                 }
360         }
361
362         if (uri->scheme == SOUP_URI_SCHEME_FTP) {
363                 if (!uri->host) {
364                         soup_uri_free (uri);
365                         return NULL;
366                 }
367         }
368
369         if (!uri->port)
370                 uri->port = soup_scheme_default_port (uri->scheme);
371         if (!uri->path)
372                 uri->path = g_strdup ("");
373
374         return uri;
375 }
376
377 /**
378  * soup_uri_new:
379  * @uri_string: a URI
380  *
381  * Parses an absolute URI.
382  *
383  * You can also pass %NULL for @uri_string if you want to get back an
384  * "empty" #SoupURI that you can fill in by hand. (You will need to
385  * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
386  * those fields are required.)
387  *
388  * Return value: a #SoupURI, or %NULL.
389  **/
390 SoupURI *
391 soup_uri_new (const char *uri_string)
392 {
393         SoupURI *uri;
394
395         if (!uri_string)
396                 return g_slice_new0 (SoupURI);
397
398         uri = soup_uri_new_with_base (NULL, uri_string);
399         if (!uri)
400                 return NULL;
401         if (!uri->scheme) {
402                 soup_uri_free (uri);
403                 return NULL;
404         }
405
406         return uri;
407 }
408
409
410 /**
411  * soup_uri_to_string:
412  * @uri: a #SoupURI
413  * @just_path_and_query: if %TRUE, output just the path and query portions
414  *
415  * Returns a string representing @uri.
416  *
417  * If @just_path_and_query is %TRUE, this concatenates the path and query
418  * together. That is, it constructs the string that would be needed in
419  * the Request-Line of an HTTP request for @uri.
420  *
421  * Return value: a string representing @uri, which the caller must free.
422  **/
423 char *
424 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
425 {
426         GString *str;
427         char *return_result;
428
429         g_return_val_if_fail (uri != NULL, NULL);
430
431         /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
432          * tests/uri-parsing AFTERWARD.
433          */
434
435         str = g_string_sized_new (20);
436
437         if (uri->scheme && !just_path_and_query)
438                 g_string_append_printf (str, "%s:", uri->scheme);
439         if (uri->host && !just_path_and_query) {
440                 g_string_append (str, "//");
441                 if (uri->user) {
442                         append_uri_encoded (str, uri->user, ":;@?/");
443                         g_string_append_c (str, '@');
444                 }
445                 if (strchr (uri->host, ':')) {
446                         g_string_append_c (str, '[');
447                         g_string_append (str, uri->host);
448                         g_string_append_c (str, ']');
449                 } else
450                         append_uri_encoded (str, uri->host, ":/");
451                 if (uri->port && uri->port != soup_scheme_default_port (uri->scheme))
452                         g_string_append_printf (str, ":%d", uri->port);
453                 if (!uri->path && (uri->query || uri->fragment))
454                         g_string_append_c (str, '/');
455         }
456
457         if (uri->path && *uri->path)
458                 g_string_append (str, uri->path);
459
460         if (uri->query) {
461                 g_string_append_c (str, '?');
462                 g_string_append (str, uri->query);
463         }
464         if (uri->fragment && !just_path_and_query) {
465                 g_string_append_c (str, '#');
466                 g_string_append (str, uri->fragment);
467         }
468
469         return_result = str->str;
470         g_string_free (str, FALSE);
471
472         return return_result;
473 }
474
475 /**
476  * soup_uri_copy:
477  * @uri: a #SoupURI
478  *
479  * Copies @uri
480  *
481  * Return value: a copy of @uri, which must be freed with soup_uri_free()
482  **/
483 SoupURI *
484 soup_uri_copy (SoupURI *uri)
485 {
486         SoupURI *dup;
487
488         g_return_val_if_fail (uri != NULL, NULL);
489
490         dup = g_slice_new0 (SoupURI);
491         dup->scheme   = uri->scheme;
492         dup->user     = g_strdup (uri->user);
493         dup->password = g_strdup (uri->password);
494         dup->host     = g_strdup (uri->host);
495         dup->port     = uri->port;
496         dup->path     = g_strdup (uri->path);
497         dup->query    = g_strdup (uri->query);
498         dup->fragment = g_strdup (uri->fragment);
499
500         return dup;
501 }
502
503 static inline gboolean
504 parts_equal (const char *one, const char *two, gboolean insensitive)
505 {
506         if (!one && !two)
507                 return TRUE;
508         if (!one || !two)
509                 return FALSE;
510         return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
511 }
512
513 /**
514  * soup_uri_equal:
515  * @uri1: a #SoupURI
516  * @uri2: another #SoupURI
517  *
518  * Tests whether or not @uri1 and @uri2 are equal in all parts
519  *
520  * Return value: %TRUE or %FALSE
521  **/
522 gboolean 
523 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
524 {
525         if (uri1->scheme != uri2->scheme                         ||
526             uri1->port   != uri2->port                           ||
527             !parts_equal (uri1->user, uri2->user, FALSE)         ||
528             !parts_equal (uri1->password, uri2->password, FALSE) ||
529             !parts_equal (uri1->host, uri2->host, TRUE)          ||
530             !parts_equal (uri1->path, uri2->path, FALSE)         ||
531             !parts_equal (uri1->query, uri2->query, FALSE)       ||
532             !parts_equal (uri1->fragment, uri2->fragment, FALSE))
533                 return FALSE;
534
535         return TRUE;
536 }
537
538 /**
539  * soup_uri_free:
540  * @uri: a #SoupURI
541  *
542  * Frees @uri.
543  **/
544 void
545 soup_uri_free (SoupURI *uri)
546 {
547         g_return_if_fail (uri != NULL);
548
549         g_free (uri->user);
550         g_free (uri->password);
551         g_free (uri->host);
552         g_free (uri->path);
553         g_free (uri->query);
554         g_free (uri->fragment);
555
556         g_slice_free (SoupURI, uri);
557 }
558
559 static void
560 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
561 {
562         const unsigned char *s = (const unsigned char *)in;
563
564         while (*s) {
565                 if (soup_char_is_uri_percent_encoded (*s) ||
566                     soup_char_is_uri_gen_delims (*s) ||
567                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
568                         g_string_append_printf (str, "%%%02X", (int)*s++);
569                 else
570                         g_string_append_c (str, *s++);
571         }
572 }
573
574 /**
575  * soup_uri_encode:
576  * @part: a URI part
577  * @escape_extra: additional reserved characters to escape (or %NULL)
578  *
579  * This %<!-- -->-encodes the given URI part and returns the escaped
580  * version in allocated memory, which the caller must free when it is
581  * done.
582  *
583  * Return value: the encoded URI part
584  **/
585 char *
586 soup_uri_encode (const char *part, const char *escape_extra)
587 {
588         GString *str;
589         char *encoded;
590
591         str = g_string_new (NULL);
592         append_uri_encoded (str, part, escape_extra);
593         encoded = str->str;
594         g_string_free (str, FALSE);
595
596         return encoded;
597 }
598
599 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
600 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
601
602 static char *
603 uri_decoded_copy (const char *part, int length)
604 {
605         unsigned char *s, *d;
606         char *decoded = g_strndup (part, length);
607
608         s = d = (unsigned char *)decoded;
609         do {
610                 if (*s == '%') {
611                         if (!g_ascii_isxdigit (s[1]) ||
612                             !g_ascii_isxdigit (s[2])) {
613                                 *d++ = *s;
614                                 continue;
615                         }
616                         *d++ = HEXCHAR (s);
617                         s += 2;
618                 } else
619                         *d++ = *s;
620         } while (*s++);
621
622         return decoded;
623 }
624
625 /**
626  * soup_uri_decode:
627  * @part: a URI part
628  *
629  * Fully %<!-- -->-decodes @part.
630  *
631  * In the past, this would return %NULL if @part contained invalid
632  * percent-encoding, but now it just ignores the problem (as
633  * soup_uri_new() already did).
634  *
635  * Return value: the decoded URI part.
636  */
637 char *
638 soup_uri_decode (const char *part)
639 {
640         return uri_decoded_copy (part, strlen (part));
641 }
642
643 static char *
644 uri_normalized_copy (const char *part, int length,
645                      const char *unescape_extra)
646 {
647         unsigned char *s, *d, c;
648         char *normalized = g_strndup (part, length);
649         gboolean need_fixup = FALSE;
650
651         s = d = (unsigned char *)normalized;
652         do {
653                 if (*s == '%') {
654                         if (!g_ascii_isxdigit (s[1]) ||
655                             !g_ascii_isxdigit (s[2])) {
656                                 *d++ = *s;
657                                 continue;
658                         }
659
660                         c = HEXCHAR (s);
661                         if (soup_char_is_uri_unreserved (c) ||
662                             (unescape_extra && strchr (unescape_extra, c))) {
663                                 *d++ = c;
664                                 s += 2;
665                         } else {
666                                 /* We leave it unchanged. We used to uppercase percent-encoded
667                                  * triplets but we do not do it any more as RFC3986 Section 6.2.2.1
668                                  * says that they only SHOULD be case normalized.
669                                  */
670                                 *d++ = *s++;
671                                 *d++ = *s++;
672                                 *d++ = *s;
673                         }
674                 } else {
675                         if (*s == ' ')
676                                 need_fixup = TRUE;
677                         *d++ = *s;
678                 }
679         } while (*s++);
680
681         if (need_fixup) {
682                 GString *fixed;
683                 char *sp, *p;
684
685                 fixed = g_string_new (NULL);
686                 p = normalized;
687                 while ((sp = strchr (p, ' '))) {
688                         g_string_append_len (fixed, p, sp - p);
689                         g_string_append (fixed, "%20");
690                         p = sp + 1;
691                 }
692                 g_string_append (fixed, p);
693                 g_free (normalized);
694                 normalized = g_string_free (fixed, FALSE);
695         }
696
697         return normalized;
698 }
699
700 /**
701  * soup_uri_normalize:
702  * @part: a URI part
703  * @unescape_extra: reserved characters to unescape (or %NULL)
704  *
705  * %<!-- -->-decodes any "unreserved" characters (or characters in
706  * @unescape_extra) in @part.
707  *
708  * "Unreserved" characters are those that are not allowed to be used
709  * for punctuation according to the URI spec. For example, letters are
710  * unreserved, so soup_uri_normalize() will turn
711  * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
712  * <literal>http://example.com/foo/bar</literal>, which is guaranteed
713  * to mean the same thing. However, "/" is "reserved", so
714  * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
715  * be changed, because it might mean something different to the
716  * server.
717  *
718  * In the past, this would return %NULL if @part contained invalid
719  * percent-encoding, but now it just ignores the problem (as
720  * soup_uri_new() already did).
721  *
722  * Return value: the normalized URI part
723  */
724 char *
725 soup_uri_normalize (const char *part, const char *unescape_extra)
726 {
727         return uri_normalized_copy (part, strlen (part), unescape_extra);
728 }
729
730
731 /**
732  * soup_uri_uses_default_port:
733  * @uri: a #SoupURI
734  *
735  * Tests if @uri uses the default port for its scheme. (Eg, 80 for
736  * http.) (This only works for http and https; libsoup does not know
737  * the default ports of other protocols.)
738  *
739  * Return value: %TRUE or %FALSE
740  **/
741 gboolean
742 soup_uri_uses_default_port (SoupURI *uri)
743 {
744         g_return_val_if_fail (uri->scheme == SOUP_URI_SCHEME_HTTP ||
745                               uri->scheme == SOUP_URI_SCHEME_HTTPS ||
746                               uri->scheme == SOUP_URI_SCHEME_FTP, FALSE);
747
748         return uri->port == soup_scheme_default_port (uri->scheme);
749 }
750
751 /**
752  * SOUP_URI_SCHEME_HTTP:
753  *
754  * "http" as an interned string. This can be compared directly against
755  * the value of a #SoupURI's <structfield>scheme</structfield>
756  **/
757
758 /**
759  * SOUP_URI_SCHEME_HTTPS:
760  *
761  * "https" as an interned string. This can be compared directly
762  * against the value of a #SoupURI's <structfield>scheme</structfield>
763  **/
764
765 /**
766  * soup_uri_get_scheme:
767  * @uri: a #SoupURI
768  *
769  * Gets @uri's scheme.
770  *
771  * Return value: @uri's scheme.
772  *
773  * Since: 2.32
774  **/
775 const char *
776 soup_uri_get_scheme (SoupURI *uri)
777 {
778         return uri->scheme;
779 }
780
781 /**
782  * soup_uri_set_scheme:
783  * @uri: a #SoupURI
784  * @scheme: the URI scheme
785  *
786  * Sets @uri's scheme to @scheme. This will also set @uri's port to
787  * the default port for @scheme, if known.
788  **/
789 void
790 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
791 {
792         uri->scheme = soup_uri_parse_scheme (scheme, strlen (scheme));
793         uri->port = soup_scheme_default_port (uri->scheme);
794 }
795
796 /**
797  * soup_uri_get_user:
798  * @uri: a #SoupURI
799  *
800  * Gets @uri's user.
801  *
802  * Return value: @uri's user.
803  *
804  * Since: 2.32
805  **/
806 const char *
807 soup_uri_get_user (SoupURI *uri)
808 {
809         return uri->user;
810 }
811
812 /**
813  * soup_uri_set_user:
814  * @uri: a #SoupURI
815  * @user: the username, or %NULL
816  *
817  * Sets @uri's user to @user.
818  **/
819 void
820 soup_uri_set_user (SoupURI *uri, const char *user)
821 {
822         g_free (uri->user);
823         uri->user = g_strdup (user);
824 }
825
826 /**
827  * soup_uri_get_password:
828  * @uri: a #SoupURI
829  *
830  * Gets @uri's password.
831  *
832  * Return value: @uri's password.
833  *
834  * Since: 2.32
835  **/
836 const char *
837 soup_uri_get_password (SoupURI *uri)
838 {
839         return uri->password;
840 }
841
842 /**
843  * soup_uri_set_password:
844  * @uri: a #SoupURI
845  * @password: the password, or %NULL
846  *
847  * Sets @uri's password to @password.
848  **/
849 void
850 soup_uri_set_password (SoupURI *uri, const char *password)
851 {
852         g_free (uri->password);
853         uri->password = g_strdup (password);
854 }
855
856 /**
857  * soup_uri_get_host:
858  * @uri: a #SoupURI
859  *
860  * Gets @uri's host.
861  *
862  * Return value: @uri's host.
863  *
864  * Since: 2.32
865  **/
866 const char *
867 soup_uri_get_host (SoupURI *uri)
868 {
869         return uri->host;
870 }
871
872 /**
873  * soup_uri_set_host:
874  * @uri: a #SoupURI
875  * @host: the hostname or IP address, or %NULL
876  *
877  * Sets @uri's host to @host.
878  *
879  * If @host is an IPv6 IP address, it should not include the brackets
880  * required by the URI syntax; they will be added automatically when
881  * converting @uri to a string.
882  **/
883 void
884 soup_uri_set_host (SoupURI *uri, const char *host)
885 {
886         g_free (uri->host);
887         uri->host = g_strdup (host);
888 }
889
890 /**
891  * soup_uri_get_port:
892  * @uri: a #SoupURI
893  *
894  * Gets @uri's port.
895  *
896  * Return value: @uri's port.
897  *
898  * Since: 2.32
899  **/
900 guint
901 soup_uri_get_port (SoupURI *uri)
902 {
903         return uri->port;
904 }
905
906 /**
907  * soup_uri_set_port:
908  * @uri: a #SoupURI
909  * @port: the port, or 0
910  *
911  * Sets @uri's port to @port. If @port is 0, @uri will not have an
912  * explicitly-specified port.
913  **/
914 void
915 soup_uri_set_port (SoupURI *uri, guint port)
916 {
917         uri->port = port;
918 }
919
920 /**
921  * soup_uri_get_path:
922  * @uri: a #SoupURI
923  *
924  * Gets @uri's path.
925  *
926  * Return value: @uri's path.
927  *
928  * Since: 2.32
929  **/
930 const char *
931 soup_uri_get_path (SoupURI *uri)
932 {
933         return uri->path;
934 }
935
936 /**
937  * soup_uri_set_path:
938  * @uri: a #SoupURI
939  * @path: the path
940  *
941  * Sets @uri's path to @path.
942  **/
943 void
944 soup_uri_set_path (SoupURI *uri, const char *path)
945 {
946         g_free (uri->path);
947         uri->path = g_strdup (path);
948 }
949
950 /**
951  * soup_uri_get_query:
952  * @uri: a #SoupURI
953  *
954  * Gets @uri's query.
955  *
956  * Return value: @uri's query.
957  *
958  * Since: 2.32
959  **/
960 const char *
961 soup_uri_get_query (SoupURI *uri)
962 {
963         return uri->query;
964 }
965
966 /**
967  * soup_uri_set_query:
968  * @uri: a #SoupURI
969  * @query: the query
970  *
971  * Sets @uri's query to @query.
972  **/
973 void
974 soup_uri_set_query (SoupURI *uri, const char *query)
975 {
976         g_free (uri->query);
977         uri->query = g_strdup (query);
978 }
979
980 /**
981  * soup_uri_set_query_from_form:
982  * @uri: a #SoupURI
983  * @form: (element-type utf8 utf8): a #GHashTable containing HTML form 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_urlencoded (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         if (dup->scheme == SOUP_URI_SCHEME_HTTP ||
1071             dup->scheme == SOUP_URI_SCHEME_HTTPS)
1072                 dup->path = g_strdup ("");
1073
1074         return dup;
1075 }
1076
1077 /**
1078  * soup_uri_host_hash:
1079  * @key: a #SoupURI
1080  *
1081  * Hashes @key, considering only the scheme, host, and port.
1082  *
1083  * Return value: a hash
1084  *
1085  * Since: 2.26.3
1086  **/
1087 guint
1088 soup_uri_host_hash (gconstpointer key)
1089 {
1090         const SoupURI *uri = key;
1091
1092         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
1093
1094         return GPOINTER_TO_UINT (uri->scheme) + uri->port +
1095                 soup_str_case_hash (uri->host);
1096 }
1097
1098 /**
1099  * soup_uri_host_equal:
1100  * @v1: a #SoupURI
1101  * @v2: a #SoupURI
1102  *
1103  * Compares @v1 and @v2, considering only the scheme, host, and port.
1104  *
1105  * Return value: whether or not the URIs are equal in scheme, host,
1106  * and port.
1107  *
1108  * Since: 2.26.3
1109  **/
1110 gboolean
1111 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1112 {
1113         const SoupURI *one = v1;
1114         const SoupURI *two = v2;
1115
1116         g_return_val_if_fail (one != NULL && two != NULL, one == two);
1117         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1118
1119         if (one->scheme != two->scheme)
1120                 return FALSE;
1121         if (one->port != two->port)
1122                 return FALSE;
1123
1124         return g_ascii_strcasecmp (one->host, two->host) == 0;
1125 }
1126
1127
1128 GType
1129 soup_uri_get_type (void)
1130 {
1131         static volatile gsize type_volatile = 0;
1132
1133         if (g_once_init_enter (&type_volatile)) {
1134                 GType type = g_boxed_type_register_static (
1135                         g_intern_static_string ("SoupURI"),
1136                         (GBoxedCopyFunc) soup_uri_copy,
1137                         (GBoxedFreeFunc) soup_uri_free);
1138                 g_once_init_leave (&type_volatile, type);
1139         }
1140         return type_volatile;
1141 }