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