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