[SoupURI] add a bit of ftp URI support
[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 gpointer _SOUP_URI_SCHEME_FTP;
100 gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA;
101
102 static inline const char *
103 soup_uri_get_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
150         uri = g_slice_new0 (SoupURI);
151
152         /* See RFC 3986 for details. IF YOU CHANGE ANYTHING IN THIS
153          * FUNCTION, RUN tests/uri-parsing AFTERWARDS.
154          */
155
156         /* Find fragment. */
157         end = hash = strchr (uri_string, '#');
158         if (hash && hash[1]) {
159                 uri->fragment = uri_normalized_copy (hash + 1, strlen (hash + 1),
160                                                      NULL, FALSE);
161                 if (!uri->fragment) {
162                         soup_uri_free (uri);
163                         return NULL;
164                 }
165         } else
166                 end = uri_string + strlen (uri_string);
167
168         /* Find scheme: initial [a-z+.-]* substring until ":" */
169         p = uri_string;
170         while (p < end && (g_ascii_isalnum (*p) ||
171                            *p == '.' || *p == '+' || *p == '-'))
172                 p++;
173
174         if (p > uri_string && *p == ':') {
175                 uri->scheme = soup_uri_get_scheme (uri_string, p - uri_string);
176                 if (!uri->scheme) {
177                         soup_uri_free (uri);
178                         return NULL;
179                 }
180                 uri_string = p + 1;
181         }
182
183         if (!*uri_string && !base)
184                 return uri;
185
186         /* Check for authority */
187         if (strncmp (uri_string, "//", 2) == 0) {
188                 uri_string += 2;
189
190                 path = uri_string + strcspn (uri_string, "/?#");
191                 at = strchr (uri_string, '@');
192                 if (at && at < path) {
193                         colon = strchr (uri_string, ':');
194                         if (colon && colon < at) {
195                                 uri->password = uri_decoded_copy (colon + 1,
196                                                                   at - colon - 1);
197                                 if (!uri->password) {
198                                         soup_uri_free (uri);
199                                         return NULL;
200                                 }
201                         } else {
202                                 uri->password = NULL;
203                                 colon = at;
204                         }
205
206                         uri->user = uri_decoded_copy (uri_string,
207                                                       colon - uri_string);
208                         if (!uri->user) {
209                                 soup_uri_free (uri);
210                                 return NULL;
211                         }
212                         uri_string = at + 1;
213                 } else
214                         uri->user = uri->password = NULL;
215
216                 /* Find host and port. */
217                 if (*uri_string == '[') {
218                         uri_string++;
219                         hostend = strchr (uri_string, ']');
220                         if (!hostend || hostend > path) {
221                                 soup_uri_free (uri);
222                                 return NULL;
223                         }
224                         if (*(hostend + 1) == ':')
225                                 colon = hostend + 1;
226                         else
227                                 colon = NULL;
228                 } else {
229                         colon = memchr (uri_string, ':', path - uri_string);
230                         hostend = colon ? colon : path;
231                 }
232
233                 uri->host = uri_decoded_copy (uri_string, hostend - uri_string);
234                 if (!uri->host) {
235                         soup_uri_free (uri);
236                         return NULL;
237                 }
238
239                 if (colon && colon != path - 1) {
240                         char *portend;
241                         uri->port = strtoul (colon + 1, &portend, 10);
242                         if (portend != (char *)path) {
243                                 soup_uri_free (uri);
244                                 return NULL;
245                         }
246                 }
247
248                 uri_string = path;
249         }
250
251         /* Find query */
252         question = memchr (uri_string, '?', end - uri_string);
253         if (question) {
254                 uri->query = uri_normalized_copy (question + 1,
255                                                   end - (question + 1),
256                                                   NULL, TRUE);
257                 if (!uri->query) {
258                         soup_uri_free (uri);
259                         return NULL;
260                 }
261                 end = question;
262         }
263
264         if (end != uri_string) {
265                 uri->path = uri_normalized_copy (uri_string, end - uri_string,
266                                                  NULL, TRUE);
267                 if (!uri->path) {
268                         soup_uri_free (uri);
269                         return NULL;
270                 }
271         }
272
273         /* Apply base URI. Again, 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 - 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 = uri->path, *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 /* From RFC 3986 */
563 #define SOUP_URI_UNRESERVED  0
564 #define SOUP_URI_PCT_ENCODED 1
565 #define SOUP_URI_GEN_DELIMS  2
566 #define SOUP_URI_SUB_DELIMS  4
567 static const char uri_encoded_char[] = {
568         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x00 - 0x0f */
569         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 0x10 - 0x1f */
570         1, 4, 1, 2, 4, 1, 4, 4, 4, 4, 4, 4, 4, 0, 0, 2,  /*  !"#$%&'()*+,-./ */
571         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 4, 1, 2,  /* 0123456789:;<=>? */
572         2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /* @ABCDEFGHIJKLMNO */
573         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 0,  /* PQRSTUVWXYZ[\]^_ */
574         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /* `abcdefghijklmno */
575         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /* pqrstuvwxyz{|}~  */
576         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
577         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
578         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
579         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
580         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
581         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
582         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
583         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
584 };
585
586 static void
587 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
588 {
589         const unsigned char *s = (const unsigned char *)in;
590
591         while (*s) {
592                 if ((uri_encoded_char[*s] & (SOUP_URI_PCT_ENCODED | SOUP_URI_GEN_DELIMS)) ||
593                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
594                         g_string_append_printf (str, "%%%02X", (int)*s++);
595                 else
596                         g_string_append_c (str, *s++);
597         }
598 }
599
600 /**
601  * soup_uri_encode:
602  * @part: a URI part
603  * @escape_extra: additional reserved characters to escape (or %NULL)
604  *
605  * This %<!-- -->-encodes the given URI part and returns the escaped
606  * version in allocated memory, which the caller must free when it is
607  * done.
608  *
609  * Return value: the encoded URI part
610  **/
611 char *
612 soup_uri_encode (const char *part, const char *escape_extra)
613 {
614         GString *str;
615         char *encoded;
616
617         str = g_string_new (NULL);
618         append_uri_encoded (str, part, escape_extra);
619         encoded = str->str;
620         g_string_free (str, FALSE);
621
622         return encoded;
623 }
624
625 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
626 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
627
628 static char *
629 uri_decoded_copy (const char *part, int length)
630 {
631         unsigned char *s, *d;
632         char *decoded = g_strndup (part, length);
633
634         s = d = (unsigned char *)decoded;
635         do {
636                 if (*s == '%') {
637                         if (!g_ascii_isxdigit (s[1]) ||
638                             !g_ascii_isxdigit (s[2])) {
639                                 g_free (decoded);
640                                 return NULL;
641                         }
642                         *d++ = HEXCHAR (s);
643                         s += 2;
644                 } else
645                         *d++ = *s;
646         } while (*s++);
647
648         return decoded;
649 }
650
651 /**
652  * soup_uri_decode:
653  * @part: a URI part
654  *
655  * Fully %<!-- -->-decodes @part.
656  *
657  * Return value: the decoded URI part, or %NULL if an invalid percent
658  * code was encountered.
659  */
660 char *
661 soup_uri_decode (const char *part)
662 {
663         return uri_decoded_copy (part, strlen (part));
664 }
665
666 static char *
667 uri_normalized_copy (const char *part, int length,
668                      const char *unescape_extra, gboolean fixup)
669 {
670         unsigned char *s, *d, c;
671         char *normalized = g_strndup (part, length);
672         gboolean need_fixup = FALSE;
673
674         s = d = (unsigned char *)normalized;
675         do {
676                 if (*s == '%') {
677                         if (!g_ascii_isxdigit (s[1]) ||
678                             !g_ascii_isxdigit (s[2])) {
679                                 g_free (normalized);
680                                 return NULL;
681                         }
682
683                         c = HEXCHAR (s);
684                         if (uri_encoded_char[c] == SOUP_URI_UNRESERVED ||
685                             (unescape_extra && strchr (unescape_extra, c))) {
686                                 *d++ = c;
687                                 s += 2;
688                         } else {
689                                 *d++ = *s++;
690                                 *d++ = g_ascii_toupper (*s++);
691                                 *d++ = g_ascii_toupper (*s);
692                         }
693                 } else {
694                         if (*s == ' ')
695                                 need_fixup = TRUE;
696                         *d++ = *s;
697                 }
698         } while (*s++);
699
700         if (fixup && need_fixup) {
701                 char *tmp, *sp;
702                 /* This code is lame, but so are people who put
703                  * unencoded spaces in URLs!
704                  */
705                 while ((sp = strchr (normalized, ' '))) {
706                         tmp = g_strdup_printf ("%.*s%%20%s",
707                                                (int)(sp - normalized),
708                                                normalized, sp + 1);
709                         g_free (normalized);
710                         normalized = tmp;
711                 };
712         }
713
714         return normalized;
715 }
716
717 /**
718  * soup_uri_normalize:
719  * @part: a URI part
720  * @unescape_extra: reserved characters to unescape (or %NULL)
721  *
722  * %<!-- -->-decodes any "unreserved" characters (or characters in
723  * @unescape_extra) in @part.
724  *
725  * "Unreserved" characters are those that are not allowed to be used
726  * for punctuation according to the URI spec. For example, letters are
727  * unreserved, so soup_uri_normalize() will turn
728  * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
729  * <literal>http://example.com/foo/bar</literal>, which is guaranteed
730  * to mean the same thing. However, "/" is "reserved", so
731  * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
732  * be changed, because it might mean something different to the
733  * server.
734  *
735  * Return value: the normalized URI part, or %NULL if an invalid percent
736  * code was encountered.
737  */
738 char *
739 soup_uri_normalize (const char *part, const char *unescape_extra)
740 {
741         return uri_normalized_copy (part, strlen (part), unescape_extra, FALSE);
742 }
743
744
745 /**
746  * soup_uri_uses_default_port:
747  * @uri: a #SoupURI
748  *
749  * Tests if @uri uses the default port for its scheme. (Eg, 80 for
750  * http.) (This only works for http and https; libsoup does not know
751  * the default ports of other protocols.)
752  *
753  * Return value: %TRUE or %FALSE
754  **/
755 gboolean
756 soup_uri_uses_default_port (SoupURI *uri)
757 {
758         g_return_val_if_fail (uri->scheme == SOUP_URI_SCHEME_HTTP ||
759                               uri->scheme == SOUP_URI_SCHEME_HTTPS ||
760                               uri->scheme == SOUP_URI_SCHEME_FTP, FALSE);
761
762         return uri->port == soup_scheme_default_port (uri->scheme);
763 }
764
765 /**
766  * SOUP_URI_SCHEME_HTTP:
767  *
768  * "http" as an interned string. This can be compared directly against
769  * the value of a #SoupURI's <structfield>scheme</structfield>
770  **/
771
772 /**
773  * SOUP_URI_SCHEME_HTTPS:
774  *
775  * "https" as an interned string. This can be compared directly
776  * against the value of a #SoupURI's <structfield>scheme</structfield>
777  **/
778
779 /**
780  * soup_uri_set_scheme:
781  * @uri: a #SoupURI
782  * @scheme: the URI scheme
783  *
784  * Sets @uri's scheme to @scheme. This will also set @uri's port to
785  * the default port for @scheme, if known.
786  **/
787 void
788 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
789 {
790         uri->scheme = soup_uri_get_scheme (scheme, strlen (scheme));
791         uri->port = soup_scheme_default_port (uri->scheme);
792 }
793
794 /**
795  * soup_uri_set_user:
796  * @uri: a #SoupURI
797  * @user: the username, or %NULL
798  *
799  * Sets @uri's user to @user.
800  **/
801 void
802 soup_uri_set_user (SoupURI *uri, const char *user)
803 {
804         g_free (uri->user);
805         uri->user = g_strdup (user);
806 }
807
808 /**
809  * soup_uri_set_password:
810  * @uri: a #SoupURI
811  * @password: the password, or %NULL
812  *
813  * Sets @uri's password to @password.
814  **/
815 void
816 soup_uri_set_password (SoupURI *uri, const char *password)
817 {
818         g_free (uri->password);
819         uri->password = g_strdup (password);
820 }
821
822 /**
823  * soup_uri_set_host:
824  * @uri: a #SoupURI
825  * @host: the hostname or IP address, or %NULL
826  *
827  * Sets @uri's host to @host.
828  *
829  * If @host is an IPv6 IP address, it should not include the brackets
830  * required by the URI syntax; they will be added automatically when
831  * converting @uri to a string.
832  **/
833 void
834 soup_uri_set_host (SoupURI *uri, const char *host)
835 {
836         g_free (uri->host);
837         uri->host = g_strdup (host);
838 }
839
840 /**
841  * soup_uri_set_port:
842  * @uri: a #SoupURI
843  * @port: the port, or 0
844  *
845  * Sets @uri's port to @port. If @port is 0, @uri will not have an
846  * explicitly-specified port.
847  **/
848 void
849 soup_uri_set_port (SoupURI *uri, guint port)
850 {
851         uri->port = port;
852 }
853
854 /**
855  * soup_uri_set_path:
856  * @uri: a #SoupURI
857  * @path: the path
858  *
859  * Sets @uri's path to @path.
860  **/
861 void
862 soup_uri_set_path (SoupURI *uri, const char *path)
863 {
864         g_free (uri->path);
865         uri->path = g_strdup (path);
866 }
867
868 /**
869  * soup_uri_set_query:
870  * @uri: a #SoupURI
871  * @query: the query
872  *
873  * Sets @uri's query to @query.
874  **/
875 void
876 soup_uri_set_query (SoupURI *uri, const char *query)
877 {
878         g_free (uri->query);
879         uri->query = g_strdup (query);
880 }
881
882 /**
883  * soup_uri_set_query_from_form:
884  * @uri: a #SoupURI
885  * @form: a #GHashTable containing HTML form information
886  *
887  * Sets @uri's query to the result of encoding @form according to the
888  * HTML form rules. See soup_form_encode_hash() for more information.
889  **/
890 void
891 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
892 {
893         g_free (uri->query);
894         uri->query = soup_form_encode_urlencoded (form);
895 }
896
897 /**
898  * soup_uri_set_query_from_fields:
899  * @uri: a #SoupURI
900  * @first_field: name of the first form field to encode into query
901  * @...: value of @first_field, followed by additional field names
902  * and values, terminated by %NULL.
903  *
904  * Sets @uri's query to the result of encoding the given form fields
905  * and values according to the * HTML form rules. See
906  * soup_form_encode() for more information.
907  **/
908 void
909 soup_uri_set_query_from_fields (SoupURI    *uri,
910                                 const char *first_field,
911                                 ...)
912 {
913         va_list args;
914
915         g_free (uri->query);
916         va_start (args, first_field);
917         uri->query = soup_form_encode_valist (first_field, args);
918         va_end (args);
919 }
920
921 /**
922  * soup_uri_set_fragment:
923  * @uri: a #SoupURI
924  * @fragment: the fragment
925  *
926  * Sets @uri's fragment to @fragment.
927  **/
928 void
929 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
930 {
931         g_free (uri->fragment);
932         uri->fragment = g_strdup (fragment);
933 }
934
935 /**
936  * soup_uri_copy_host:
937  * @uri: a #SoupUri
938  *
939  * Makes a copy of @uri, considering only the protocol, host, and port
940  *
941  * Return value: the new #SoupUri
942  *
943  * Since: 2.26.3
944  **/
945 SoupURI *
946 soup_uri_copy_host (SoupURI *uri)
947 {
948         SoupURI *dup;
949
950         g_return_val_if_fail (uri != NULL, NULL);
951
952         dup = soup_uri_new (NULL);
953         dup->scheme = uri->scheme;
954         dup->host   = g_strdup (uri->host);
955         dup->port   = uri->port;
956         if (dup->scheme == SOUP_URI_SCHEME_HTTP ||
957             dup->scheme == SOUP_URI_SCHEME_HTTPS)
958                 dup->path = g_strdup ("");
959
960         return dup;
961 }
962
963 /**
964  * soup_uri_host_hash:
965  * @key: a #SoupURI
966  *
967  * Hashes @key, considering only the scheme, host, and port.
968  *
969  * Return value: a hash
970  *
971  * Since: 2.26.3
972  **/
973 guint
974 soup_uri_host_hash (gconstpointer key)
975 {
976         const SoupURI *uri = key;
977
978         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
979
980         return GPOINTER_TO_UINT (uri->scheme) + uri->port +
981                 soup_str_case_hash (uri->host);
982 }
983
984 /**
985  * soup_uri_host_equal:
986  * @v1: a #SoupURI
987  * @v2: a #SoupURI
988  *
989  * Compares @v1 and @v2, considering only the scheme, host, and port.
990  *
991  * Return value: whether or not the URIs are equal in scheme, host,
992  * and port.
993  *
994  * Since: 2.26.3
995  **/
996 gboolean
997 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
998 {
999         const SoupURI *one = v1;
1000         const SoupURI *two = v2;
1001
1002         g_return_val_if_fail (one != NULL && two != NULL, one == two);
1003         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1004
1005         if (one->scheme != two->scheme)
1006                 return FALSE;
1007         if (one->port != two->port)
1008                 return FALSE;
1009
1010         return g_ascii_strcasecmp (one->host, two->host) == 0;
1011 }
1012
1013
1014 GType
1015 soup_uri_get_type (void)
1016 {
1017         static volatile gsize type_volatile = 0;
1018
1019         if (g_once_init_enter (&type_volatile)) {
1020                 GType type = g_boxed_type_register_static (
1021                         g_intern_static_string ("SoupURI"),
1022                         (GBoxedCopyFunc) soup_uri_copy,
1023                         (GBoxedFreeFunc) soup_uri_free);
1024                 g_once_init_leave (&type_volatile, type);
1025         }
1026         return type_volatile;
1027 }