1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* soup-uri.c : utility functions to parse URLs */
5 * Copyright 1999-2003 Ximian, Inc.
13 #include "soup-misc-private.h"
14 #include "soup-form.h"
15 #include "soup-misc.h"
19 * @short_description: URIs
21 * A #SoupURI represents a (parsed) URI.
23 * Many applications will not need to use #SoupURI directly at all; on
24 * the client side, soup_message_new() takes a stringified URI, and on
25 * the server side, the path and query components are provided for you
26 * in the server callback.
31 * @scheme: the URI scheme (eg, "http")
32 * @user: a username, or %NULL
33 * @password: a password, or %NULL
34 * @host: the hostname or IP address
35 * @port: the port number on @host
36 * @path: the path on @host
37 * @query: a query for @path, or %NULL
38 * @fragment: a fragment identifier within @path, or %NULL
40 * A #SoupURI represents a (parsed) URI. #SoupURI supports RFC 3986
41 * (URI Generic Syntax), and can parse any valid URI. However, libsoup
42 * only uses "http" and "https" URIs internally; You can use
43 * SOUP_URI_VALID_FOR_HTTP() to test if a #SoupURI is a valid HTTP
46 * @scheme will always be set in any URI. It is an interned string and
47 * is always all lowercase. (If you parse a URI with a non-lowercase
48 * scheme, it will be converted to lowercase.) The macros
49 * %SOUP_URI_SCHEME_HTTP and %SOUP_URI_SCHEME_HTTPS provide the
50 * interned values for "http" and "https" and can be compared against
53 * @user and @password are parsed as defined in the older URI specs
54 * (ie, separated by a colon; RFC 3986 only talks about a single
55 * "userinfo" field). Note that @password is not included in the
56 * output of soup_uri_to_string(). libsoup does not normally use these
57 * fields; authentication is handled via #SoupSession signals.
59 * @host contains the hostname, and @port the port specified in the
60 * URI. If the URI doesn't contain a hostname, @host will be %NULL,
61 * and if it doesn't specify a port, @port may be 0. However, for
62 * "http" and "https" URIs, @host is guaranteed to be non-%NULL
63 * (trying to parse an http URI with no @host will return %NULL), and
64 * @port will always be non-0 (because libsoup knows the default value
65 * to use when it is not specified in the URI).
67 * @path is always non-%NULL. For http/https URIs, @path will never be
68 * an empty string either; if the input URI has no path, the parsed
69 * #SoupURI will have a @path of "/".
71 * @query and @fragment are optional for all URI types.
72 * soup_form_decode() may be useful for parsing @query.
74 * Note that @path, @query, and @fragment may contain
75 * %<!-- -->-encoded characters. soup_uri_new() calls
76 * soup_uri_normalize() on them, but not soup_uri_decode(). This is
77 * necessary to ensure that soup_uri_to_string() will generate a URI
78 * that has exactly the same meaning as the original. (In theory,
79 * #SoupURI should leave @user, @password, and @host partially-encoded
80 * as well, but this would be more annoying than useful.)
84 * SOUP_URI_VALID_FOR_HTTP:
87 * Tests if @uri is a valid #SoupURI for HTTP communication; that is, if
88 * it can be used to construct a #SoupMessage.
90 * Return value: %TRUE if @uri is a valid "http" or "https" URI.
95 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
96 static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra);
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;
102 static inline const char *
103 soup_uri_parse_scheme (const char *scheme, int len)
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;
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);
121 soup_scheme_default_port (const char *scheme)
123 if (scheme == SOUP_URI_SCHEME_HTTP)
125 else if (scheme == SOUP_URI_SCHEME_HTTPS)
127 else if (scheme == SOUP_URI_SCHEME_FTP)
134 * soup_uri_new_with_base:
136 * @uri_string: the URI
138 * Parses @uri_string relative to @base.
140 * Return value: a parsed #SoupURI.
143 soup_uri_new_with_base (SoupURI *base, const char *uri_string)
146 const char *end, *hash, *colon, *at, *path, *question;
147 const char *p, *hostend;
148 gboolean remove_dot_segments = TRUE;
151 /* First some cleanup steps (which are supposed to all be no-ops,
152 * but...). Skip initial whitespace, strip out internal tabs and
153 * line breaks, and ignore trailing whitespace.
155 while (g_ascii_isspace (*uri_string))
158 len = strcspn (uri_string, "\t\n\r");
159 if (uri_string[len]) {
160 char *clean = g_malloc (strlen (uri_string) + 1), *d;
163 for (s = uri_string, d = clean; *s; s++) {
164 if (*s != '\t' && *s != '\n' && *s != '\r')
169 uri = soup_uri_new_with_base (base, clean);
173 end = uri_string + len;
174 while (end > uri_string && g_ascii_isspace (end[-1]))
177 uri = g_slice_new0 (SoupURI);
180 hash = strchr (uri_string, '#');
182 uri->fragment = uri_normalized_copy (hash + 1, end - hash + 1,
187 /* Find scheme: initial [a-z+.-]* substring until ":" */
189 while (p < end && (g_ascii_isalnum (*p) ||
190 *p == '.' || *p == '+' || *p == '-'))
193 if (p > uri_string && *p == ':') {
194 uri->scheme = soup_uri_parse_scheme (uri_string, p - uri_string);
198 if (uri_string == end && !base && !uri->fragment)
201 /* Check for authority */
202 if (strncmp (uri_string, "//", 2) == 0) {
205 path = uri_string + strcspn (uri_string, "/?#");
208 at = strchr (uri_string, '@');
209 if (at && at < path) {
210 colon = strchr (uri_string, ':');
211 if (colon && colon < at) {
212 uri->password = uri_decoded_copy (colon + 1,
215 uri->password = NULL;
219 uri->user = uri_decoded_copy (uri_string,
223 uri->user = uri->password = NULL;
225 /* Find host and port. */
226 if (*uri_string == '[') {
228 hostend = strchr (uri_string, ']');
229 if (!hostend || hostend > path) {
233 if (*(hostend + 1) == ':')
238 colon = memchr (uri_string, ':', path - uri_string);
239 hostend = colon ? colon : path;
242 uri->host = uri_decoded_copy (uri_string, hostend - uri_string);
244 if (colon && colon != path - 1) {
246 uri->port = strtoul (colon + 1, &portend, 10);
247 if (portend != (char *)path) {
257 question = memchr (uri_string, '?', end - uri_string);
259 uri->query = uri_normalized_copy (question + 1,
260 end - (question + 1),
265 if (end != uri_string) {
266 uri->path = uri_normalized_copy (uri_string, end - uri_string,
270 /* Apply base URI. This is spelled out in RFC 3986. */
271 if (base && !uri->scheme && uri->host)
272 uri->scheme = base->scheme;
273 else if (base && !uri->scheme) {
274 uri->scheme = base->scheme;
275 uri->user = g_strdup (base->user);
276 uri->password = g_strdup (base->password);
277 uri->host = g_strdup (base->host);
278 uri->port = base->port;
281 uri->path = g_strdup (base->path);
283 uri->query = g_strdup (base->query);
284 remove_dot_segments = FALSE;
285 } else if (*uri->path != '/') {
286 char *newpath, *last;
288 last = strrchr (base->path, '/');
290 newpath = g_strdup_printf ("%.*s%s",
291 (int)(last + 1 - base->path),
295 newpath = g_strdup_printf ("/%s", uri->path);
302 if (remove_dot_segments && uri->path && *uri->path) {
305 /* Remove "./" where "." is a complete segment. */
306 for (p = uri->path + 1; *p; ) {
307 if (*(p - 1) == '/' &&
308 *p == '.' && *(p + 1) == '/')
309 memmove (p, p + 2, strlen (p + 2) + 1);
313 /* Remove "." at end. */
314 if (p > uri->path + 2 &&
315 *(p - 1) == '.' && *(p - 2) == '/')
318 /* Remove "<segment>/../" where <segment> != ".." */
319 for (p = uri->path + 1; *p; ) {
320 if (!strncmp (p, "../", 3)) {
324 q = strchr (p + 1, '/');
327 if (strncmp (q, "/../", 4) != 0) {
331 memmove (p, q + 4, strlen (q + 4) + 1);
334 /* Remove "<segment>/.." at end where <segment> != ".." */
335 q = strrchr (uri->path, '/');
336 if (q && !strcmp (q, "/..")) {
338 while (p > uri->path && *p != '/')
340 if (strncmp (p, "/../", 4) != 0)
344 /* Remove extraneous initial "/.."s */
345 while (!strncmp (uri->path, "/../", 4))
346 memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
347 if (!strcmp (uri->path, "/.."))
351 /* HTTP-specific stuff */
352 if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
353 uri->scheme == SOUP_URI_SCHEME_HTTPS) {
355 uri->path = g_strdup ("/");
356 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
362 if (uri->scheme == SOUP_URI_SCHEME_FTP) {
370 uri->port = soup_scheme_default_port (uri->scheme);
372 uri->path = g_strdup ("");
381 * Parses an absolute URI.
383 * You can also pass %NULL for @uri_string if you want to get back an
384 * "empty" #SoupURI that you can fill in by hand. (You will need to
385 * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
386 * those fields are required.)
388 * Return value: a #SoupURI, or %NULL.
391 soup_uri_new (const char *uri_string)
396 return g_slice_new0 (SoupURI);
398 uri = soup_uri_new_with_base (NULL, uri_string);
411 * soup_uri_to_string:
413 * @just_path_and_query: if %TRUE, output just the path and query portions
415 * Returns a string representing @uri.
417 * If @just_path_and_query is %TRUE, this concatenates the path and query
418 * together. That is, it constructs the string that would be needed in
419 * the Request-Line of an HTTP request for @uri.
421 * Return value: a string representing @uri, which the caller must free.
424 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
429 g_return_val_if_fail (uri != NULL, NULL);
431 /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
432 * tests/uri-parsing AFTERWARD.
435 str = g_string_sized_new (20);
437 if (uri->scheme && !just_path_and_query)
438 g_string_append_printf (str, "%s:", uri->scheme);
439 if (uri->host && !just_path_and_query) {
440 g_string_append (str, "//");
442 append_uri_encoded (str, uri->user, ":;@?/");
443 g_string_append_c (str, '@');
445 if (strchr (uri->host, ':')) {
446 g_string_append_c (str, '[');
447 g_string_append (str, uri->host);
448 g_string_append_c (str, ']');
450 append_uri_encoded (str, uri->host, ":/");
451 if (uri->port && uri->port != soup_scheme_default_port (uri->scheme))
452 g_string_append_printf (str, ":%u", uri->port);
453 if (!uri->path && (uri->query || uri->fragment))
454 g_string_append_c (str, '/');
457 if (uri->path && *uri->path)
458 g_string_append (str, uri->path);
461 g_string_append_c (str, '?');
462 g_string_append (str, uri->query);
464 if (uri->fragment && !just_path_and_query) {
465 g_string_append_c (str, '#');
466 g_string_append (str, uri->fragment);
469 return_result = str->str;
470 g_string_free (str, FALSE);
472 return return_result;
481 * Return value: a copy of @uri, which must be freed with soup_uri_free()
484 soup_uri_copy (SoupURI *uri)
488 g_return_val_if_fail (uri != NULL, NULL);
490 dup = g_slice_new0 (SoupURI);
491 dup->scheme = uri->scheme;
492 dup->user = g_strdup (uri->user);
493 dup->password = g_strdup (uri->password);
494 dup->host = g_strdup (uri->host);
495 dup->port = uri->port;
496 dup->path = g_strdup (uri->path);
497 dup->query = g_strdup (uri->query);
498 dup->fragment = g_strdup (uri->fragment);
503 static inline gboolean
504 parts_equal (const char *one, const char *two, gboolean insensitive)
510 return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
516 * @uri2: another #SoupURI
518 * Tests whether or not @uri1 and @uri2 are equal in all parts
520 * Return value: %TRUE or %FALSE
523 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
525 if (uri1->scheme != uri2->scheme ||
526 uri1->port != uri2->port ||
527 !parts_equal (uri1->user, uri2->user, FALSE) ||
528 !parts_equal (uri1->password, uri2->password, FALSE) ||
529 !parts_equal (uri1->host, uri2->host, TRUE) ||
530 !parts_equal (uri1->path, uri2->path, FALSE) ||
531 !parts_equal (uri1->query, uri2->query, FALSE) ||
532 !parts_equal (uri1->fragment, uri2->fragment, FALSE))
545 soup_uri_free (SoupURI *uri)
547 g_return_if_fail (uri != NULL);
550 g_free (uri->password);
554 g_free (uri->fragment);
556 g_slice_free (SoupURI, uri);
560 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
562 const unsigned char *s = (const unsigned char *)in;
565 if (soup_char_is_uri_percent_encoded (*s) ||
566 soup_char_is_uri_gen_delims (*s) ||
567 (extra_enc_chars && strchr (extra_enc_chars, *s)))
568 g_string_append_printf (str, "%%%02X", (int)*s++);
570 g_string_append_c (str, *s++);
577 * @escape_extra: (allow-none): additional reserved characters to
580 * This %<!-- -->-encodes the given URI part and returns the escaped
581 * version in allocated memory, which the caller must free when it is
584 * Return value: the encoded URI part
587 soup_uri_encode (const char *part, const char *escape_extra)
592 str = g_string_new (NULL);
593 append_uri_encoded (str, part, escape_extra);
595 g_string_free (str, FALSE);
600 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
601 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
604 uri_decoded_copy (const char *part, int length)
606 unsigned char *s, *d;
607 char *decoded = g_strndup (part, length);
609 s = d = (unsigned char *)decoded;
612 if (!g_ascii_isxdigit (s[1]) ||
613 !g_ascii_isxdigit (s[2])) {
630 * Fully %<!-- -->-decodes @part.
632 * In the past, this would return %NULL if @part contained invalid
633 * percent-encoding, but now it just ignores the problem (as
634 * soup_uri_new() already did).
636 * Return value: the decoded URI part.
639 soup_uri_decode (const char *part)
641 return uri_decoded_copy (part, strlen (part));
645 uri_normalized_copy (const char *part, int length,
646 const char *unescape_extra)
648 unsigned char *s, *d, c;
649 char *normalized = g_strndup (part, length);
650 gboolean need_fixup = FALSE;
652 s = d = (unsigned char *)normalized;
655 if (!g_ascii_isxdigit (s[1]) ||
656 !g_ascii_isxdigit (s[2])) {
662 if (soup_char_is_uri_unreserved (c) ||
663 (unescape_extra && strchr (unescape_extra, c))) {
667 /* We leave it unchanged. We used to uppercase percent-encoded
668 * triplets but we do not do it any more as RFC3986 Section 6.2.2.1
669 * says that they only SHOULD be case normalized.
686 fixed = g_string_new (NULL);
688 while ((sp = strchr (p, ' '))) {
689 g_string_append_len (fixed, p, sp - p);
690 g_string_append (fixed, "%20");
693 g_string_append (fixed, p);
695 normalized = g_string_free (fixed, FALSE);
702 * soup_uri_normalize:
704 * @unescape_extra: reserved characters to unescape (or %NULL)
706 * %<!-- -->-decodes any "unreserved" characters (or characters in
707 * @unescape_extra) in @part.
709 * "Unreserved" characters are those that are not allowed to be used
710 * for punctuation according to the URI spec. For example, letters are
711 * unreserved, so soup_uri_normalize() will turn
712 * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
713 * <literal>http://example.com/foo/bar</literal>, which is guaranteed
714 * to mean the same thing. However, "/" is "reserved", so
715 * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
716 * be changed, because it might mean something different to the
719 * In the past, this would return %NULL if @part contained invalid
720 * percent-encoding, but now it just ignores the problem (as
721 * soup_uri_new() already did).
723 * Return value: the normalized URI part
726 soup_uri_normalize (const char *part, const char *unescape_extra)
728 return uri_normalized_copy (part, strlen (part), unescape_extra);
733 * soup_uri_uses_default_port:
736 * Tests if @uri uses the default port for its scheme. (Eg, 80 for
737 * http.) (This only works for http and https; libsoup does not know
738 * the default ports of other protocols.)
740 * Return value: %TRUE or %FALSE
743 soup_uri_uses_default_port (SoupURI *uri)
745 g_return_val_if_fail (uri->scheme == SOUP_URI_SCHEME_HTTP ||
746 uri->scheme == SOUP_URI_SCHEME_HTTPS ||
747 uri->scheme == SOUP_URI_SCHEME_FTP, FALSE);
749 return uri->port == soup_scheme_default_port (uri->scheme);
753 * SOUP_URI_SCHEME_HTTP:
755 * "http" as an interned string. This can be compared directly against
756 * the value of a #SoupURI's <structfield>scheme</structfield>
760 * SOUP_URI_SCHEME_HTTPS:
762 * "https" as an interned string. This can be compared directly
763 * against the value of a #SoupURI's <structfield>scheme</structfield>
767 * soup_uri_get_scheme:
770 * Gets @uri's scheme.
772 * Return value: @uri's scheme.
777 soup_uri_get_scheme (SoupURI *uri)
783 * soup_uri_set_scheme:
785 * @scheme: the URI scheme
787 * Sets @uri's scheme to @scheme. This will also set @uri's port to
788 * the default port for @scheme, if known.
791 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
793 uri->scheme = soup_uri_parse_scheme (scheme, strlen (scheme));
794 uri->port = soup_scheme_default_port (uri->scheme);
803 * Return value: @uri's user.
808 soup_uri_get_user (SoupURI *uri)
816 * @user: the username, or %NULL
818 * Sets @uri's user to @user.
821 soup_uri_set_user (SoupURI *uri, const char *user)
824 uri->user = g_strdup (user);
828 * soup_uri_get_password:
831 * Gets @uri's password.
833 * Return value: @uri's password.
838 soup_uri_get_password (SoupURI *uri)
840 return uri->password;
844 * soup_uri_set_password:
846 * @password: the password, or %NULL
848 * Sets @uri's password to @password.
851 soup_uri_set_password (SoupURI *uri, const char *password)
853 g_free (uri->password);
854 uri->password = g_strdup (password);
863 * Return value: @uri's host.
868 soup_uri_get_host (SoupURI *uri)
876 * @host: the hostname or IP address, or %NULL
878 * Sets @uri's host to @host.
880 * If @host is an IPv6 IP address, it should not include the brackets
881 * required by the URI syntax; they will be added automatically when
882 * converting @uri to a string.
885 soup_uri_set_host (SoupURI *uri, const char *host)
888 uri->host = g_strdup (host);
897 * Return value: @uri's port.
902 soup_uri_get_port (SoupURI *uri)
910 * @port: the port, or 0
912 * Sets @uri's port to @port. If @port is 0, @uri will not have an
913 * explicitly-specified port.
916 soup_uri_set_port (SoupURI *uri, guint port)
927 * Return value: @uri's path.
932 soup_uri_get_path (SoupURI *uri)
942 * Sets @uri's path to @path.
945 soup_uri_set_path (SoupURI *uri, const char *path)
948 uri->path = g_strdup (path);
952 * soup_uri_get_query:
957 * Return value: @uri's query.
962 soup_uri_get_query (SoupURI *uri)
968 * soup_uri_set_query:
972 * Sets @uri's query to @query.
975 soup_uri_set_query (SoupURI *uri, const char *query)
978 uri->query = g_strdup (query);
982 * soup_uri_set_query_from_form:
984 * @form: (element-type utf8 utf8): a #GHashTable containing HTML form
987 * Sets @uri's query to the result of encoding @form according to the
988 * HTML form rules. See soup_form_encode_hash() for more information.
991 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
994 uri->query = soup_form_encode_hash (form);
998 * soup_uri_set_query_from_fields:
1000 * @first_field: name of the first form field to encode into query
1001 * @...: value of @first_field, followed by additional field names
1002 * and values, terminated by %NULL.
1004 * Sets @uri's query to the result of encoding the given form fields
1005 * and values according to the * HTML form rules. See
1006 * soup_form_encode() for more information.
1009 soup_uri_set_query_from_fields (SoupURI *uri,
1010 const char *first_field,
1015 g_free (uri->query);
1016 va_start (args, first_field);
1017 uri->query = soup_form_encode_valist (first_field, args);
1022 * soup_uri_get_fragment:
1025 * Gets @uri's fragment.
1027 * Return value: @uri's fragment.
1032 soup_uri_get_fragment (SoupURI *uri)
1034 return uri->fragment;
1038 * soup_uri_set_fragment:
1040 * @fragment: the fragment
1042 * Sets @uri's fragment to @fragment.
1045 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
1047 g_free (uri->fragment);
1048 uri->fragment = g_strdup (fragment);
1052 * soup_uri_copy_host:
1055 * Makes a copy of @uri, considering only the protocol, host, and port
1057 * Return value: the new #SoupUri
1062 soup_uri_copy_host (SoupURI *uri)
1066 g_return_val_if_fail (uri != NULL, NULL);
1068 dup = soup_uri_new (NULL);
1069 dup->scheme = uri->scheme;
1070 dup->host = g_strdup (uri->host);
1071 dup->port = uri->port;
1072 if (dup->scheme == SOUP_URI_SCHEME_HTTP ||
1073 dup->scheme == SOUP_URI_SCHEME_HTTPS)
1074 dup->path = g_strdup ("");
1080 * soup_uri_host_hash:
1081 * @key: (type Soup.URI): a #SoupURI
1083 * Hashes @key, considering only the scheme, host, and port.
1085 * Return value: a hash
1090 soup_uri_host_hash (gconstpointer key)
1092 const SoupURI *uri = key;
1094 g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
1096 return GPOINTER_TO_UINT (uri->scheme) + uri->port +
1097 soup_str_case_hash (uri->host);
1101 * soup_uri_host_equal:
1102 * @v1: (type Soup.URI): a #SoupURI
1103 * @v2: (type Soup.URI): a #SoupURI
1105 * Compares @v1 and @v2, considering only the scheme, host, and port.
1107 * Return value: whether or not the URIs are equal in scheme, host,
1113 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1115 const SoupURI *one = v1;
1116 const SoupURI *two = v2;
1118 g_return_val_if_fail (one != NULL && two != NULL, one == two);
1119 g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1121 if (one->scheme != two->scheme)
1123 if (one->port != two->port)
1126 return g_ascii_strcasecmp (one->host, two->host) == 0;
1131 soup_uri_get_type (void)
1133 static volatile gsize type_volatile = 0;
1135 if (g_once_init_enter (&type_volatile)) {
1136 GType type = g_boxed_type_register_static (
1137 g_intern_static_string ("SoupURI"),
1138 (GBoxedCopyFunc) soup_uri_copy,
1139 (GBoxedFreeFunc) soup_uri_free);
1140 g_once_init_leave (&type_volatile, type);
1142 return type_volatile;