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