Remove build warning
[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 <string.h>
9 #include <stdlib.h>
10
11 #include "soup-uri.h"
12 #include "soup.h"
13 #include "soup-misc-private.h"
14
15 /**
16  * SECTION:soup-uri
17  * @short_description: URIs
18  *
19  * A #SoupURI represents a (parsed) URI.
20  *
21  * Many applications will not need to use #SoupURI directly at all; on
22  * the client side, soup_message_new() takes a stringified URI, and on
23  * the server side, the path and query components are provided for you
24  * in the server callback.
25  **/
26
27 /**
28  * SoupURI:
29  * @scheme: the URI scheme (eg, "http")
30  * @user: a username, or %NULL
31  * @password: a password, or %NULL
32  * @host: the hostname or IP address
33  * @port: the port number on @host
34  * @path: the path on @host
35  * @query: a query for @path, or %NULL
36  * @fragment: a fragment identifier within @path, or %NULL
37  *
38  * A #SoupURI represents a (parsed) URI. #SoupURI supports RFC 3986
39  * (URI Generic Syntax), and can parse any valid URI. However, libsoup
40  * only uses "http" and "https" URIs internally; You can use
41  * SOUP_URI_VALID_FOR_HTTP() to test if a #SoupURI is a valid HTTP
42  * URI.
43  *
44  * @scheme will always be set in any URI. It is an interned string and
45  * is always all lowercase. (If you parse a URI with a non-lowercase
46  * scheme, it will be converted to lowercase.) The macros
47  * %SOUP_URI_SCHEME_HTTP and %SOUP_URI_SCHEME_HTTPS provide the
48  * interned values for "http" and "https" and can be compared against
49  * URI @scheme values.
50  *
51  * @user and @password are parsed as defined in the older URI specs
52  * (ie, separated by a colon; RFC 3986 only talks about a single
53  * "userinfo" field). Note that @password is not included in the
54  * output of soup_uri_to_string(). libsoup does not normally use these
55  * fields; authentication is handled via #SoupSession signals.
56  *
57  * @host contains the hostname, and @port the port specified in the
58  * URI. If the URI doesn't contain a hostname, @host will be %NULL,
59  * and if it doesn't specify a port, @port may be 0. However, for
60  * "http" and "https" URIs, @host is guaranteed to be non-%NULL
61  * (trying to parse an http URI with no @host will return %NULL), and
62  * @port will always be non-0 (because libsoup knows the default value
63  * to use when it is not specified in the URI).
64  *
65  * @path is always non-%NULL. For http/https URIs, @path will never be
66  * an empty string either; if the input URI has no path, the parsed
67  * #SoupURI will have a @path of "/".
68  *
69  * @query and @fragment are optional for all URI types.
70  * soup_form_decode() may be useful for parsing @query.
71  *
72  * Note that @path, @query, and @fragment may contain
73  * %<!-- -->-encoded characters. soup_uri_new() calls
74  * soup_uri_normalize() on them, but not soup_uri_decode(). This is
75  * necessary to ensure that soup_uri_to_string() will generate a URI
76  * that has exactly the same meaning as the original. (In theory,
77  * #SoupURI should leave @user, @password, and @host partially-encoded
78  * as well, but this would be more annoying than useful.)
79  **/
80
81 /**
82  * SOUP_URI_IS_VALID:
83  * @uri: a #SoupURI
84  *
85  * Tests whether @uri is a valid #SoupURI; that is, that it is non-%NULL
86  * and its @scheme and @path members are also non-%NULL.
87  *
88  * This macro does not check whether http and https URIs have a non-%NULL
89  * @host member.
90  *
91  * Return value: %TRUE if @uri is valid for use.
92  *
93  * Since: 2.38
94  **/
95
96 /**
97  * SOUP_URI_VALID_FOR_HTTP:
98  * @uri: a #SoupURI
99  *
100  * Tests if @uri is a valid #SoupURI for HTTP communication; that is, if
101  * it can be used to construct a #SoupMessage.
102  *
103  * Return value: %TRUE if @uri is a valid "http" or "https" URI.
104  *
105  * Since: 2.24
106  **/
107
108 /**
109  * SOUP_URI_SCHEME_HTTP:
110  *
111  * "http" as an interned string; you can compare this directly to a
112  * #SoupURI's <literal>scheme</literal> field using
113  * <literal>==</literal>.
114  */
115 /**
116  * SOUP_URI_SCHEME_HTTPS:
117  *
118  * "https" as an interned string; you can compare this directly to a
119  * #SoupURI's <literal>scheme</literal> field using
120  * <literal>==</literal>.
121  */
122 /**
123  * SOUP_URI_SCHEME_FTP:
124  *
125  * "ftp" as an interned string; you can compare this directly to a
126  * #SoupURI's <literal>scheme</literal> field using
127  * <literal>==</literal>.
128  *
129  * Since: 2.30
130  */
131 /**
132  * SOUP_URI_SCHEME_FILE:
133  *
134  * "file" as an interned string; you can compare this directly to a
135  * #SoupURI's <literal>scheme</literal> field using
136  * <literal>==</literal>.
137  *
138  * Since: 2.30
139  */
140 /**
141  * SOUP_URI_SCHEME_DATA:
142  *
143  * "data" as an interned string; you can compare this directly to a
144  * #SoupURI's <literal>scheme</literal> field using
145  * <literal>==</literal>.
146  *
147  * Since: 2.30
148  */
149 /**
150  * SOUP_URI_SCHEME_RESOURCE:
151  *
152  * "data" as an interned string; you can compare this directly to a
153  * #SoupURI's <literal>scheme</literal> field using
154  * <literal>==</literal>.
155  *
156  * Since: 2.42
157  */
158
159 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
160 static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra);
161
162 gpointer _SOUP_URI_SCHEME_HTTP, _SOUP_URI_SCHEME_HTTPS;
163 gpointer _SOUP_URI_SCHEME_FTP;
164 gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA, _SOUP_URI_SCHEME_RESOURCE;
165
166 static inline const char *
167 soup_uri_parse_scheme (const char *scheme, int len)
168 {
169         if (len == 4 && !g_ascii_strncasecmp (scheme, "http", len)) {
170                 return SOUP_URI_SCHEME_HTTP;
171         } else if (len == 5 && !g_ascii_strncasecmp (scheme, "https", len)) {
172                 return SOUP_URI_SCHEME_HTTPS;
173         } else if (len == 8 && !g_ascii_strncasecmp (scheme, "resource", len)) {
174                 return SOUP_URI_SCHEME_RESOURCE;
175         } else {
176                 char *lower_scheme;
177
178                 lower_scheme = g_ascii_strdown (scheme, len);
179                 scheme = g_intern_static_string (lower_scheme);
180                 if (scheme != (const char *)lower_scheme)
181                         g_free (lower_scheme);
182                 return scheme;
183         }
184 }
185
186 static inline guint
187 soup_scheme_default_port (const char *scheme)
188 {
189         if (scheme == SOUP_URI_SCHEME_HTTP)
190                 return 80;
191         else if (scheme == SOUP_URI_SCHEME_HTTPS)
192                 return 443;
193         else if (scheme == SOUP_URI_SCHEME_FTP)
194                 return 21;
195         else
196                 return 0;
197 }
198
199 /**
200  * soup_uri_new_with_base:
201  * @base: a base URI
202  * @uri_string: the URI
203  *
204  * Parses @uri_string relative to @base.
205  *
206  * Return value: a parsed #SoupURI.
207  **/
208 SoupURI *
209 soup_uri_new_with_base (SoupURI *base, const char *uri_string)
210 {
211         SoupURI *uri, fixed_base;
212         const char *end, *hash, *colon, *at, *path, *question;
213         const char *p, *hostend;
214         gboolean remove_dot_segments = TRUE;
215         int len;
216
217         g_return_val_if_fail (uri_string != NULL, NULL);
218
219         /* Allow a %NULL path in @base, for compatibility */
220         if (base && base->scheme && !base->path) {
221                 g_warn_if_fail (SOUP_URI_IS_VALID (base));
222
223                 memcpy (&fixed_base, base, sizeof (SoupURI));
224                 fixed_base.path = "";
225                 base = &fixed_base;
226         }
227
228         g_return_val_if_fail (base == NULL || SOUP_URI_IS_VALID (base), NULL);
229
230         /* First some cleanup steps (which are supposed to all be no-ops,
231          * but...). Skip initial whitespace, strip out internal tabs and
232          * line breaks, and ignore trailing whitespace.
233          */
234         while (g_ascii_isspace (*uri_string))
235                 uri_string++;
236
237         len = strcspn (uri_string, "\t\n\r");
238         if (uri_string[len]) {
239                 char *clean = g_malloc (strlen (uri_string) + 1), *d;
240                 const char *s;
241
242                 for (s = uri_string, d = clean; *s; s++) {
243                         if (*s != '\t' && *s != '\n' && *s != '\r')
244                                 *d++ = *s;
245                 }
246                 *d = '\0';
247
248                 uri = soup_uri_new_with_base (base, clean);
249                 g_free (clean);
250                 return uri;
251         }
252         end = uri_string + len;
253         while (end > uri_string && g_ascii_isspace (end[-1]))
254                 end--;
255
256         uri = g_slice_new0 (SoupURI);
257
258         /* Find fragment. */
259         hash = strchr (uri_string, '#');
260         if (hash) {
261                 uri->fragment = uri_normalized_copy (hash + 1, end - hash + 1,
262                                                      NULL);
263                 end = hash;
264         }
265
266         /* Find scheme */
267         p = uri_string;
268         while (p < end && (g_ascii_isalpha (*p) ||
269                            (p > uri_string && (g_ascii_isdigit (*p) ||
270                                                *p == '.' ||
271                                                *p == '+' ||
272                                                *p == '-'))))
273                 p++;
274
275         if (p > uri_string && *p == ':') {
276                 uri->scheme = soup_uri_parse_scheme (uri_string, p - uri_string);
277                 uri_string = p + 1;
278         }
279
280         if (uri_string == end && !base && !uri->fragment) {
281                 uri->path = g_strdup ("");
282                 return uri;
283         }
284
285         /* Check for authority */
286         if (strncmp (uri_string, "//", 2) == 0) {
287                 uri_string += 2;
288
289                 path = uri_string + strcspn (uri_string, "/?#");
290                 if (path > end)
291                         path = end;
292                 at = strchr (uri_string, '@');
293                 if (at && at < path) {
294                         colon = strchr (uri_string, ':');
295                         if (colon && colon < at) {
296                                 uri->password = soup_uri_decoded_copy (colon + 1,
297                                                                        at - colon - 1, NULL);
298                         } else {
299                                 uri->password = NULL;
300                                 colon = at;
301                         }
302
303                         uri->user = soup_uri_decoded_copy (uri_string,
304                                                            colon - uri_string, NULL);
305                         uri_string = at + 1;
306                 } else
307                         uri->user = uri->password = NULL;
308
309                 /* Find host and port. */
310                 if (*uri_string == '[') {
311                         const char *pct;
312
313                         uri_string++;
314                         hostend = strchr (uri_string, ']');
315                         if (!hostend || hostend > path) {
316                                 soup_uri_free (uri);
317                                 return NULL;
318                         }
319                         if (*(hostend + 1) == ':')
320                                 colon = hostend + 1;
321                         else
322                                 colon = NULL;
323
324                         pct = memchr (uri_string, '%', hostend - uri_string);
325                         if (!pct || (pct[1] == '2' && pct[2] == '5')) {
326                                 uri->host = soup_uri_decoded_copy (uri_string,
327                                                                    hostend - uri_string, NULL);
328                         } else
329                                 uri->host = g_strndup (uri_string, hostend - uri_string);
330                 } else {
331                         colon = memchr (uri_string, ':', path - uri_string);
332                         hostend = colon ? colon : path;
333                         uri->host = soup_uri_decoded_copy (uri_string,
334                                                            hostend - uri_string, NULL);
335                 }
336
337                 if (colon && colon != path - 1) {
338                         char *portend;
339                         uri->port = strtoul (colon + 1, &portend, 10);
340                         if (portend != (char *)path) {
341                                 soup_uri_free (uri);
342                                 return NULL;
343                         }
344                 }
345
346                 uri_string = path;
347         }
348
349         /* Find query */
350         question = memchr (uri_string, '?', end - uri_string);
351         if (question) {
352                 uri->query = uri_normalized_copy (question + 1,
353                                                   end - (question + 1),
354                                                   NULL);
355                 end = question;
356         }
357
358         if (end != uri_string) {
359                 uri->path = uri_normalized_copy (uri_string, end - uri_string,
360                                                  NULL);
361         }
362
363         /* Apply base URI. This is spelled out in RFC 3986. */
364         if (base && !uri->scheme && uri->host)
365                 uri->scheme = base->scheme;
366         else if (base && !uri->scheme) {
367                 uri->scheme = base->scheme;
368                 uri->user = g_strdup (base->user);
369                 uri->password = g_strdup (base->password);
370                 uri->host = g_strdup (base->host);
371                 uri->port = base->port;
372
373                 if (!uri->path) {
374                         uri->path = g_strdup (base->path);
375                         if (!uri->query)
376                                 uri->query = g_strdup (base->query);
377                         remove_dot_segments = FALSE;
378                 } else if (*uri->path != '/') {
379                         char *newpath, *last;
380
381                         last = strrchr (base->path, '/');
382                         if (last) {
383                                 newpath = g_strdup_printf ("%.*s%s",
384                                                            (int)(last + 1 - base->path),
385                                                            base->path,
386                                                            uri->path);
387                         } else
388                                 newpath = g_strdup_printf ("/%s", uri->path);
389
390                         g_free (uri->path);
391                         uri->path = newpath;
392                 }
393         }
394
395         if (remove_dot_segments && uri->path && *uri->path) {
396                 char *p, *q;
397
398                 /* Remove "./" where "." is a complete segment. */
399                 for (p = uri->path + 1; *p; ) {
400                         if (*(p - 1) == '/' &&
401                             *p == '.' && *(p + 1) == '/')
402                                 memmove (p, p + 2, strlen (p + 2) + 1);
403                         else
404                                 p++;
405                 }
406                 /* Remove "." at end. */
407                 if (p > uri->path + 2 &&
408                     *(p - 1) == '.' && *(p - 2) == '/')
409                         *(p - 1) = '\0';
410
411                 /* Remove "<segment>/../" where <segment> != ".." */
412                 for (p = uri->path + 1; *p; ) {
413                         if (!strncmp (p, "../", 3)) {
414                                 p += 3;
415                                 continue;
416                         }
417                         q = strchr (p + 1, '/');
418                         if (!q)
419                                 break;
420                         if (strncmp (q, "/../", 4) != 0) {
421                                 p = q + 1;
422                                 continue;
423                         }
424                         memmove (p, q + 4, strlen (q + 4) + 1);
425                         p = uri->path + 1;
426                 }
427                 /* Remove "<segment>/.." at end where <segment> != ".." */
428                 q = strrchr (uri->path, '/');
429                 if (q && !strcmp (q, "/..")) {
430                         p = q - 1;
431                         while (p > uri->path && *p != '/')
432                                 p--;
433                         if (strncmp (p, "/../", 4) != 0)
434                                 *(p + 1) = 0;
435                 }
436
437                 /* Remove extraneous initial "/.."s */
438                 while (!strncmp (uri->path, "/../", 4))
439                         memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
440                 if (!strcmp (uri->path, "/.."))
441                         uri->path[1] = '\0';
442         }
443
444         /* HTTP-specific stuff */
445         if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
446             uri->scheme == SOUP_URI_SCHEME_HTTPS) {
447                 if (!uri->path)
448                         uri->path = g_strdup ("/");
449                 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
450                         soup_uri_free (uri);
451                         return NULL;
452                 }
453         }
454
455         if (uri->scheme == SOUP_URI_SCHEME_FTP) {
456                 if (!uri->host) {
457                         soup_uri_free (uri);
458                         return NULL;
459                 }
460         }
461
462         if (!uri->port)
463                 uri->port = soup_scheme_default_port (uri->scheme);
464         if (!uri->path)
465                 uri->path = g_strdup ("");
466
467         return uri;
468 }
469
470 /**
471  * soup_uri_new:
472  * @uri_string: (allow-none): a URI
473  *
474  * Parses an absolute URI.
475  *
476  * You can also pass %NULL for @uri_string if you want to get back an
477  * "empty" #SoupURI that you can fill in by hand. (You will need to
478  * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
479  * those fields are required.)
480  *
481  * Return value: a #SoupURI, or %NULL if the given string was found to be
482  *  invalid.
483  **/
484 SoupURI *
485 soup_uri_new (const char *uri_string)
486 {
487         SoupURI *uri;
488
489         if (!uri_string)
490                 return g_slice_new0 (SoupURI);
491
492         uri = soup_uri_new_with_base (NULL, uri_string);
493         if (!uri)
494                 return NULL;
495         if (!SOUP_URI_IS_VALID (uri)) {
496                 soup_uri_free (uri);
497                 return NULL;
498         }
499
500         return uri;
501 }
502
503
504 char *
505 soup_uri_to_string_internal (SoupURI *uri, gboolean just_path_and_query,
506                              gboolean force_port)
507 {
508         GString *str;
509         char *return_result;
510
511         g_return_val_if_fail (uri != NULL, NULL);
512         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
513
514         str = g_string_sized_new (40);
515
516         if (uri->scheme && !just_path_and_query)
517                 g_string_append_printf (str, "%s:", uri->scheme);
518         if (uri->host && !just_path_and_query) {
519                 g_string_append (str, "//");
520                 if (uri->user) {
521                         append_uri_encoded (str, uri->user, ":;@?/");
522                         g_string_append_c (str, '@');
523                 }
524                 if (strchr (uri->host, ':')) {
525                         const char *pct;
526
527                         g_string_append_c (str, '[');
528                         pct = strchr (uri->host, '%');
529                         if (pct) {
530                                 g_string_append_printf (str, "%.*s%%25%s",
531                                                         (int) (pct - uri->host),
532                                                         uri->host, pct + 1);
533                         } else
534                                 g_string_append (str, uri->host);
535                         g_string_append_c (str, ']');
536                 } else
537                         append_uri_encoded (str, uri->host, ":/");
538                 if (uri->port && (force_port || uri->port != soup_scheme_default_port (uri->scheme)))
539                         g_string_append_printf (str, ":%u", uri->port);
540                 if (!uri->path && (uri->query || uri->fragment))
541                         g_string_append_c (str, '/');
542                 else if ((!uri->path || !*uri->path) &&
543                          (uri->scheme == SOUP_URI_SCHEME_HTTP ||
544                           uri->scheme == SOUP_URI_SCHEME_HTTPS))
545                         g_string_append_c (str, '/');
546         }
547
548         if (uri->path && *uri->path)
549                 g_string_append (str, uri->path);
550         else if (just_path_and_query)
551                 g_string_append_c (str, '/');
552
553         if (uri->query) {
554                 g_string_append_c (str, '?');
555                 g_string_append (str, uri->query);
556         }
557         if (uri->fragment && !just_path_and_query) {
558                 g_string_append_c (str, '#');
559                 g_string_append (str, uri->fragment);
560         }
561
562         return_result = str->str;
563         g_string_free (str, FALSE);
564
565         return return_result;
566 }
567
568 /**
569  * soup_uri_to_string:
570  * @uri: a #SoupURI
571  * @just_path_and_query: if %TRUE, output just the path and query portions
572  *
573  * Returns a string representing @uri.
574  *
575  * If @just_path_and_query is %TRUE, this concatenates the path and query
576  * together. That is, it constructs the string that would be needed in
577  * the Request-Line of an HTTP request for @uri.
578  *
579  * Note that the output will never contain a password, even if @uri
580  * does.
581  *
582  * Return value: a string representing @uri, which the caller must free.
583  **/
584 char *
585 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
586 {
587         return soup_uri_to_string_internal (uri, just_path_and_query, FALSE);
588 }
589
590 /**
591  * soup_uri_copy:
592  * @uri: a #SoupURI
593  *
594  * Copies @uri
595  *
596  * Return value: a copy of @uri, which must be freed with soup_uri_free()
597  **/
598 SoupURI *
599 soup_uri_copy (SoupURI *uri)
600 {
601         SoupURI *dup;
602
603         g_return_val_if_fail (uri != NULL, NULL);
604         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
605
606         dup = g_slice_new0 (SoupURI);
607         dup->scheme   = uri->scheme;
608         dup->user     = g_strdup (uri->user);
609         dup->password = g_strdup (uri->password);
610         dup->host     = g_strdup (uri->host);
611         dup->port     = uri->port;
612         dup->path     = g_strdup (uri->path);
613         dup->query    = g_strdup (uri->query);
614         dup->fragment = g_strdup (uri->fragment);
615
616         return dup;
617 }
618
619 static inline gboolean
620 parts_equal (const char *one, const char *two, gboolean insensitive)
621 {
622         if (!one && !two)
623                 return TRUE;
624         if (!one || !two)
625                 return FALSE;
626         return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
627 }
628
629 /**
630  * soup_uri_equal:
631  * @uri1: a #SoupURI
632  * @uri2: another #SoupURI
633  *
634  * Tests whether or not @uri1 and @uri2 are equal in all parts
635  *
636  * Return value: %TRUE or %FALSE
637  **/
638 gboolean 
639 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
640 {
641         g_return_val_if_fail (uri1 != NULL, FALSE);
642         g_return_val_if_fail (uri2 != NULL, FALSE);
643         g_warn_if_fail (SOUP_URI_IS_VALID (uri1));
644         g_warn_if_fail (SOUP_URI_IS_VALID (uri2));
645
646         if (uri1->scheme != uri2->scheme                         ||
647             uri1->port   != uri2->port                           ||
648             !parts_equal (uri1->user, uri2->user, FALSE)         ||
649             !parts_equal (uri1->password, uri2->password, FALSE) ||
650             !parts_equal (uri1->host, uri2->host, TRUE)          ||
651             !parts_equal (uri1->path, uri2->path, FALSE)         ||
652             !parts_equal (uri1->query, uri2->query, FALSE)       ||
653             !parts_equal (uri1->fragment, uri2->fragment, FALSE))
654                 return FALSE;
655
656         return TRUE;
657 }
658
659 /**
660  * soup_uri_free:
661  * @uri: a #SoupURI
662  *
663  * Frees @uri.
664  **/
665 void
666 soup_uri_free (SoupURI *uri)
667 {
668         g_return_if_fail (uri != NULL);
669
670         g_free (uri->user);
671         g_free (uri->password);
672         g_free (uri->host);
673         g_free (uri->path);
674         g_free (uri->query);
675         g_free (uri->fragment);
676
677         g_slice_free (SoupURI, uri);
678 }
679
680 static void
681 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
682 {
683         const unsigned char *s = (const unsigned char *)in;
684
685         while (*s) {
686                 if (soup_char_is_uri_percent_encoded (*s) ||
687                     soup_char_is_uri_gen_delims (*s) ||
688                     (extra_enc_chars && strchr (extra_enc_chars, *s)))
689                         g_string_append_printf (str, "%%%02X", (int)*s++);
690                 else
691                         g_string_append_c (str, *s++);
692         }
693 }
694
695 /**
696  * soup_uri_encode:
697  * @part: a URI part
698  * @escape_extra: (allow-none): additional reserved characters to
699  * escape (or %NULL)
700  *
701  * This %<!-- -->-encodes the given URI part and returns the escaped
702  * version in allocated memory, which the caller must free when it is
703  * done.
704  *
705  * Return value: the encoded URI part
706  **/
707 char *
708 soup_uri_encode (const char *part, const char *escape_extra)
709 {
710         GString *str;
711         char *encoded;
712
713         g_return_val_if_fail (part != NULL, NULL);
714
715         str = g_string_new (NULL);
716         append_uri_encoded (str, part, escape_extra);
717         encoded = str->str;
718         g_string_free (str, FALSE);
719
720         return encoded;
721 }
722
723 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
724 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
725
726 char *
727 soup_uri_decoded_copy (const char *part, int length, int *decoded_length)
728 {
729         unsigned char *s, *d;
730         char *decoded;
731
732         g_return_val_if_fail (part != NULL, NULL);
733
734         decoded = g_strndup (part, length);
735         s = d = (unsigned char *)decoded;
736         do {
737                 if (*s == '%') {
738                         if (!g_ascii_isxdigit (s[1]) ||
739                             !g_ascii_isxdigit (s[2])) {
740                                 *d++ = *s;
741                                 continue;
742                         }
743                         *d++ = HEXCHAR (s);
744                         s += 2;
745                 } else
746                         *d++ = *s;
747         } while (*s++);
748
749         if (decoded_length)
750                 *decoded_length = d - (unsigned char *)decoded - 1;
751
752         return decoded;
753 }
754
755 /**
756  * soup_uri_decode:
757  * @part: a URI part
758  *
759  * Fully %<!-- -->-decodes @part.
760  *
761  * In the past, this would return %NULL if @part contained invalid
762  * percent-encoding, but now it just ignores the problem (as
763  * soup_uri_new() already did).
764  *
765  * Return value: the decoded URI part.
766  */
767 char *
768 soup_uri_decode (const char *part)
769 {
770         g_return_val_if_fail (part != NULL, NULL);
771
772         return soup_uri_decoded_copy (part, strlen (part), NULL);
773 }
774
775 static char *
776 uri_normalized_copy (const char *part, int length,
777                      const char *unescape_extra)
778 {
779         unsigned char *s, *d, c;
780         char *normalized = g_strndup (part, length);
781         gboolean need_fixup = FALSE;
782
783         if (!unescape_extra)
784                 unescape_extra = "";
785
786         s = d = (unsigned char *)normalized;
787         while (*s) {
788                 if (*s == '%') {
789                         if (!g_ascii_isxdigit (s[1]) ||
790                             !g_ascii_isxdigit (s[2])) {
791                                 *d++ = *s++;
792                                 continue;
793                         }
794
795                         c = HEXCHAR (s);
796                         if (soup_char_is_uri_unreserved (c) ||
797                             (c && strchr (unescape_extra, c))) {
798                                 *d++ = c;
799                                 s += 3;
800                         } else {
801                                 /* We leave it unchanged. We used to uppercase percent-encoded
802                                  * triplets but we do not do it any more as RFC3986 Section 6.2.2.1
803                                  * says that they only SHOULD be case normalized.
804                                  */
805                                 *d++ = *s++;
806                                 *d++ = *s++;
807                                 *d++ = *s++;
808                         }
809                 } else {
810                         if (!g_ascii_isgraph (*s) &&
811                             !strchr (unescape_extra, *s))
812                                 need_fixup = TRUE;
813                         *d++ = *s++;
814                 }
815         }
816         *d = '\0';
817
818         if (need_fixup) {
819                 GString *fixed;
820
821                 fixed = g_string_new (NULL);
822                 s = (guchar *)normalized;
823                 while (*s) {
824                         if (g_ascii_isgraph (*s) ||
825                             strchr (unescape_extra, *s))
826                                 g_string_append_c (fixed, *s);
827                         else
828                                 g_string_append_printf (fixed, "%%%02X", (int)*s);
829                         s++;
830                 }
831                 g_free (normalized);
832                 normalized = g_string_free (fixed, FALSE);
833         }
834
835         return normalized;
836 }
837
838 /**
839  * soup_uri_normalize:
840  * @part: a URI part
841  * @unescape_extra: reserved characters to unescape (or %NULL)
842  *
843  * %<!-- -->-decodes any "unreserved" characters (or characters in
844  * @unescape_extra) in @part.
845  *
846  * "Unreserved" characters are those that are not allowed to be used
847  * for punctuation according to the URI spec. For example, letters are
848  * unreserved, so soup_uri_normalize() will turn
849  * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
850  * <literal>http://example.com/foo/bar</literal>, which is guaranteed
851  * to mean the same thing. However, "/" is "reserved", so
852  * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
853  * be changed, because it might mean something different to the
854  * server.
855  *
856  * In the past, this would return %NULL if @part contained invalid
857  * percent-encoding, but now it just ignores the problem (as
858  * soup_uri_new() already did).
859  *
860  * Return value: the normalized URI part
861  */
862 char *
863 soup_uri_normalize (const char *part, const char *unescape_extra)
864 {
865         g_return_val_if_fail (part != NULL, NULL);
866
867         return uri_normalized_copy (part, strlen (part), unescape_extra);
868 }
869
870
871 /**
872  * soup_uri_uses_default_port:
873  * @uri: a #SoupURI
874  *
875  * Tests if @uri uses the default port for its scheme. (Eg, 80 for
876  * http.) (This only works for http, https and ftp; libsoup does not know
877  * the default ports of other protocols.)
878  *
879  * Return value: %TRUE or %FALSE
880  **/
881 gboolean
882 soup_uri_uses_default_port (SoupURI *uri)
883 {
884         g_return_val_if_fail (uri != NULL, FALSE);
885         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
886
887         return uri->port == soup_scheme_default_port (uri->scheme);
888 }
889
890 /**
891  * soup_uri_get_scheme:
892  * @uri: a #SoupURI
893  *
894  * Gets @uri's scheme.
895  *
896  * Return value: @uri's scheme.
897  *
898  * Since: 2.32
899  **/
900 const char *
901 soup_uri_get_scheme (SoupURI *uri)
902 {
903         g_return_val_if_fail (uri != NULL, NULL);
904
905         return uri->scheme;
906 }
907
908 /**
909  * soup_uri_set_scheme:
910  * @uri: a #SoupURI
911  * @scheme: the URI scheme
912  *
913  * Sets @uri's scheme to @scheme. This will also set @uri's port to
914  * the default port for @scheme, if known.
915  **/
916 void
917 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
918 {
919         g_return_if_fail (uri != NULL);
920         g_return_if_fail (scheme != NULL);
921
922         uri->scheme = soup_uri_parse_scheme (scheme, strlen (scheme));
923         uri->port = soup_scheme_default_port (uri->scheme);
924 }
925
926 /**
927  * soup_uri_get_user:
928  * @uri: a #SoupURI
929  *
930  * Gets @uri's user.
931  *
932  * Return value: @uri's user.
933  *
934  * Since: 2.32
935  **/
936 const char *
937 soup_uri_get_user (SoupURI *uri)
938 {
939         g_return_val_if_fail (uri != NULL, NULL);
940
941         return uri->user;
942 }
943
944 /**
945  * soup_uri_set_user:
946  * @uri: a #SoupURI
947  * @user: (allow-none): the username, or %NULL
948  *
949  * Sets @uri's user to @user.
950  **/
951 void
952 soup_uri_set_user (SoupURI *uri, const char *user)
953 {
954         g_return_if_fail (uri != NULL);
955
956         g_free (uri->user);
957         uri->user = g_strdup (user);
958 }
959
960 /**
961  * soup_uri_get_password:
962  * @uri: a #SoupURI
963  *
964  * Gets @uri's password.
965  *
966  * Return value: @uri's password.
967  *
968  * Since: 2.32
969  **/
970 const char *
971 soup_uri_get_password (SoupURI *uri)
972 {
973         g_return_val_if_fail (uri != NULL, NULL);
974
975         return uri->password;
976 }
977
978 /**
979  * soup_uri_set_password:
980  * @uri: a #SoupURI
981  * @password: (allow-none): the password, or %NULL
982  *
983  * Sets @uri's password to @password.
984  **/
985 void
986 soup_uri_set_password (SoupURI *uri, const char *password)
987 {
988         g_return_if_fail (uri != NULL);
989
990         g_free (uri->password);
991         uri->password = g_strdup (password);
992 }
993
994 /**
995  * soup_uri_get_host:
996  * @uri: a #SoupURI
997  *
998  * Gets @uri's host.
999  *
1000  * Return value: @uri's host.
1001  *
1002  * Since: 2.32
1003  **/
1004 const char *
1005 soup_uri_get_host (SoupURI *uri)
1006 {
1007         g_return_val_if_fail (uri != NULL, NULL);
1008
1009         return uri->host;
1010 }
1011
1012 /**
1013  * soup_uri_set_host:
1014  * @uri: a #SoupURI
1015  * @host: (allow-none): the hostname or IP address, or %NULL
1016  *
1017  * Sets @uri's host to @host.
1018  *
1019  * If @host is an IPv6 IP address, it should not include the brackets
1020  * required by the URI syntax; they will be added automatically when
1021  * converting @uri to a string.
1022  *
1023  * http and https URIs should not have a %NULL @host.
1024  **/
1025 void
1026 soup_uri_set_host (SoupURI *uri, const char *host)
1027 {
1028         g_return_if_fail (uri != NULL);
1029
1030         g_free (uri->host);
1031         uri->host = g_strdup (host);
1032 }
1033
1034 /**
1035  * soup_uri_get_port:
1036  * @uri: a #SoupURI
1037  *
1038  * Gets @uri's port.
1039  *
1040  * Return value: @uri's port.
1041  *
1042  * Since: 2.32
1043  **/
1044 guint
1045 soup_uri_get_port (SoupURI *uri)
1046 {
1047         g_return_val_if_fail (uri != NULL, 0);
1048
1049         return uri->port;
1050 }
1051
1052 /**
1053  * soup_uri_set_port:
1054  * @uri: a #SoupURI
1055  * @port: the port, or 0
1056  *
1057  * Sets @uri's port to @port. If @port is 0, @uri will not have an
1058  * explicitly-specified port.
1059  **/
1060 void
1061 soup_uri_set_port (SoupURI *uri, guint port)
1062 {
1063         g_return_if_fail (uri != NULL);
1064
1065         uri->port = port;
1066 }
1067
1068 /**
1069  * soup_uri_get_path:
1070  * @uri: a #SoupURI
1071  *
1072  * Gets @uri's path.
1073  *
1074  * Return value: @uri's path.
1075  *
1076  * Since: 2.32
1077  **/
1078 const char *
1079 soup_uri_get_path (SoupURI *uri)
1080 {
1081         g_return_val_if_fail (uri != NULL, NULL);
1082
1083         return uri->path;
1084 }
1085
1086 /**
1087  * soup_uri_set_path:
1088  * @uri: a #SoupURI
1089  * @path: the non-%NULL path
1090  *
1091  * Sets @uri's path to @path.
1092  **/
1093 void
1094 soup_uri_set_path (SoupURI *uri, const char *path)
1095 {
1096         g_return_if_fail (uri != NULL);
1097
1098         /* We allow a NULL path for compatibility, but warn about it. */
1099         if (!path) {
1100                 g_warn_if_fail (path != NULL);
1101                 path = "";
1102         }
1103
1104         g_free (uri->path);
1105         uri->path = g_strdup (path);
1106 }
1107
1108 /**
1109  * soup_uri_get_query:
1110  * @uri: a #SoupURI
1111  *
1112  * Gets @uri's query.
1113  *
1114  * Return value: @uri's query.
1115  *
1116  * Since: 2.32
1117  **/
1118 const char *
1119 soup_uri_get_query (SoupURI *uri)
1120 {
1121         g_return_val_if_fail (uri != NULL, NULL);
1122
1123         return uri->query;
1124 }
1125
1126 /**
1127  * soup_uri_set_query:
1128  * @uri: a #SoupURI
1129  * @query: (allow-none): the query
1130  *
1131  * Sets @uri's query to @query.
1132  **/
1133 void
1134 soup_uri_set_query (SoupURI *uri, const char *query)
1135 {
1136         g_return_if_fail (uri != NULL);
1137
1138         g_free (uri->query);
1139         uri->query = g_strdup (query);
1140 }
1141
1142 /**
1143  * soup_uri_set_query_from_form:
1144  * @uri: a #SoupURI
1145  * @form: (element-type utf8 utf8): a #GHashTable containing HTML form
1146  * information
1147  *
1148  * Sets @uri's query to the result of encoding @form according to the
1149  * HTML form rules. See soup_form_encode_hash() for more information.
1150  **/
1151 void
1152 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
1153 {
1154         g_return_if_fail (uri != NULL);
1155
1156         g_free (uri->query);
1157         uri->query = soup_form_encode_hash (form);
1158 }
1159
1160 /**
1161  * soup_uri_set_query_from_fields:
1162  * @uri: a #SoupURI
1163  * @first_field: name of the first form field to encode into query
1164  * @...: value of @first_field, followed by additional field names
1165  * and values, terminated by %NULL.
1166  *
1167  * Sets @uri's query to the result of encoding the given form fields
1168  * and values according to the * HTML form rules. See
1169  * soup_form_encode() for more information.
1170  **/
1171 void
1172 soup_uri_set_query_from_fields (SoupURI    *uri,
1173                                 const char *first_field,
1174                                 ...)
1175 {
1176         va_list args;
1177
1178         g_return_if_fail (uri != NULL);
1179
1180         g_free (uri->query);
1181         va_start (args, first_field);
1182         uri->query = soup_form_encode_valist (first_field, args);
1183         va_end (args);
1184 }
1185
1186 /**
1187  * soup_uri_get_fragment:
1188  * @uri: a #SoupURI
1189  *
1190  * Gets @uri's fragment.
1191  *
1192  * Return value: @uri's fragment.
1193  *
1194  * Since: 2.32
1195  **/
1196 const char *
1197 soup_uri_get_fragment (SoupURI *uri)
1198 {
1199         g_return_val_if_fail (uri != NULL, NULL);
1200
1201         return uri->fragment;
1202 }
1203
1204 /**
1205  * soup_uri_set_fragment:
1206  * @uri: a #SoupURI
1207  * @fragment: (allow-none): the fragment
1208  *
1209  * Sets @uri's fragment to @fragment.
1210  **/
1211 void
1212 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
1213 {
1214         g_return_if_fail (uri != NULL);
1215
1216         g_free (uri->fragment);
1217         uri->fragment = g_strdup (fragment);
1218 }
1219
1220 /**
1221  * soup_uri_copy_host:
1222  * @uri: a #SoupURI
1223  *
1224  * Makes a copy of @uri, considering only the protocol, host, and port
1225  *
1226  * Return value: the new #SoupURI
1227  *
1228  * Since: 2.28
1229  **/
1230 SoupURI *
1231 soup_uri_copy_host (SoupURI *uri)
1232 {
1233         SoupURI *dup;
1234
1235         g_return_val_if_fail (uri != NULL, NULL);
1236         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1237
1238         dup = soup_uri_new (NULL);
1239         dup->scheme = uri->scheme;
1240         dup->host   = g_strdup (uri->host);
1241         dup->port   = uri->port;
1242         dup->path   = g_strdup ("");
1243
1244         return dup;
1245 }
1246
1247 /**
1248  * soup_uri_host_hash:
1249  * @key: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1250  *
1251  * Hashes @key, considering only the scheme, host, and port.
1252  *
1253  * Return value: a hash
1254  *
1255  * Since: 2.28
1256  **/
1257 guint
1258 soup_uri_host_hash (gconstpointer key)
1259 {
1260         const SoupURI *uri = key;
1261
1262         g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
1263         g_warn_if_fail (SOUP_URI_IS_VALID (uri));
1264
1265         return GPOINTER_TO_UINT (uri->scheme) + uri->port +
1266                 soup_str_case_hash (uri->host);
1267 }
1268
1269 /**
1270  * soup_uri_host_equal:
1271  * @v1: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1272  * @v2: (type Soup.URI): a #SoupURI with a non-%NULL @host member
1273  *
1274  * Compares @v1 and @v2, considering only the scheme, host, and port.
1275  *
1276  * Return value: whether or not the URIs are equal in scheme, host,
1277  * and port.
1278  *
1279  * Since: 2.28
1280  **/
1281 gboolean
1282 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1283 {
1284         const SoupURI *one = v1;
1285         const SoupURI *two = v2;
1286
1287         g_return_val_if_fail (one != NULL && two != NULL, one == two);
1288         g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1289         g_warn_if_fail (SOUP_URI_IS_VALID (one));
1290         g_warn_if_fail (SOUP_URI_IS_VALID (two));
1291
1292         if (one->scheme != two->scheme)
1293                 return FALSE;
1294         if (one->port != two->port)
1295                 return FALSE;
1296
1297         return g_ascii_strcasecmp (one->host, two->host) == 0;
1298 }
1299
1300 gboolean
1301 soup_uri_is_http (SoupURI *uri, char **aliases)
1302 {
1303         int i;
1304
1305         if (uri->scheme == SOUP_URI_SCHEME_HTTP)
1306                 return TRUE;
1307         else if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
1308                 return FALSE;
1309         else if (!aliases)
1310                 return FALSE;
1311
1312         for (i = 0; aliases[i]; i++) {
1313                 if (uri->scheme == aliases[i])
1314                         return TRUE;
1315         }
1316
1317         if (!aliases[1] && !strcmp (aliases[0], "*"))
1318                 return TRUE;
1319         else
1320                 return FALSE;
1321 }
1322
1323 gboolean
1324 soup_uri_is_https (SoupURI *uri, char **aliases)
1325 {
1326         int i;
1327
1328         if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
1329                 return TRUE;
1330         else if (uri->scheme == SOUP_URI_SCHEME_HTTP)
1331                 return FALSE;
1332         else if (!aliases)
1333                 return FALSE;
1334
1335         for (i = 0; aliases[i]; i++) {
1336                 if (uri->scheme == aliases[i])
1337                         return TRUE;
1338         }
1339
1340         return FALSE;
1341 }
1342
1343 G_DEFINE_BOXED_TYPE (SoupURI, soup_uri, soup_uri_copy, soup_uri_free)