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-form.h"
14 #include "soup-misc.h"
18 * @short_description: URIs
20 * A #SoupURI represents a (parsed) URI.
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.
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
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
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
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.
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).
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 "/".
70 * @query and @fragment are optional for all URI types.
71 * soup_form_decode_urlencoded() may be useful for parsing @query.
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.)
83 * SOUP_URI_VALID_FOR_HTTP:
86 * Tests if @uri is a valid #SoupURI for HTTP communication; that is, if
87 * it can be used to construct a #SoupMessage.
89 * Return value: %TRUE if @uri is a valid "http" or "https" URI.
94 static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars);
95 static char *uri_decoded_copy (const char *str, int length);
96 static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra, gboolean fixup);
98 gpointer _SOUP_URI_SCHEME_HTTP, _SOUP_URI_SCHEME_HTTPS;
100 static inline const char *
101 soup_uri_get_scheme (const char *scheme, int len)
103 if (len == 4 && !g_ascii_strncasecmp (scheme, "http", len)) {
104 return SOUP_URI_SCHEME_HTTP;
105 } else if (len == 5 && !g_ascii_strncasecmp (scheme, "https", len)) {
106 return SOUP_URI_SCHEME_HTTPS;
110 lower_scheme = g_ascii_strdown (scheme, len);
111 scheme = g_intern_static_string (lower_scheme);
112 if (scheme != (const char *)lower_scheme)
113 g_free (lower_scheme);
119 soup_scheme_default_port (const char *scheme)
121 if (scheme == SOUP_URI_SCHEME_HTTP)
123 else if (scheme == SOUP_URI_SCHEME_HTTPS)
130 * soup_uri_new_with_base:
132 * @uri_string: the URI
134 * Parses @uri_string relative to @base.
136 * Return value: a parsed #SoupURI.
139 soup_uri_new_with_base (SoupURI *base, const char *uri_string)
142 const char *end, *hash, *colon, *at, *path, *question;
143 const char *p, *hostend;
144 gboolean remove_dot_segments = TRUE;
146 uri = g_slice_new0 (SoupURI);
148 /* See RFC 3986 for details. IF YOU CHANGE ANYTHING IN THIS
149 * FUNCTION, RUN tests/uri-parsing AFTERWARDS.
153 end = hash = strchr (uri_string, '#');
154 if (hash && hash[1]) {
155 uri->fragment = uri_normalized_copy (hash + 1, strlen (hash + 1),
157 if (!uri->fragment) {
162 end = uri_string + strlen (uri_string);
164 /* Find scheme: initial [a-z+.-]* substring until ":" */
166 while (p < end && (g_ascii_isalnum (*p) ||
167 *p == '.' || *p == '+' || *p == '-'))
170 if (p > uri_string && *p == ':') {
171 uri->scheme = soup_uri_get_scheme (uri_string, p - uri_string);
179 if (!*uri_string && !base)
182 /* Check for authority */
183 if (strncmp (uri_string, "//", 2) == 0) {
186 path = uri_string + strcspn (uri_string, "/?#");
187 at = strchr (uri_string, '@');
188 if (at && at < path) {
189 colon = strchr (uri_string, ':');
190 if (colon && colon < at) {
191 uri->password = uri_decoded_copy (colon + 1,
193 if (!uri->password) {
198 uri->password = NULL;
202 uri->user = uri_decoded_copy (uri_string,
210 uri->user = uri->password = NULL;
212 /* Find host and port. */
213 if (*uri_string == '[') {
215 hostend = strchr (uri_string, ']');
216 if (!hostend || hostend > path) {
220 if (*(hostend + 1) == ':')
225 colon = memchr (uri_string, ':', path - uri_string);
226 hostend = colon ? colon : path;
229 uri->host = uri_decoded_copy (uri_string, hostend - uri_string);
235 if (colon && colon != path - 1) {
237 uri->port = strtoul (colon + 1, &portend, 10);
238 if (portend != (char *)path) {
248 question = memchr (uri_string, '?', end - uri_string);
250 uri->query = uri_normalized_copy (question + 1,
251 end - (question + 1),
260 if (end != uri_string) {
261 uri->path = uri_normalized_copy (uri_string, end - uri_string,
269 /* Apply base URI. Again, this is spelled out in RFC 3986. */
270 if (base && !uri->scheme && uri->host)
271 uri->scheme = base->scheme;
272 else if (base && !uri->scheme) {
273 uri->scheme = base->scheme;
274 uri->user = g_strdup (base->user);
275 uri->password = g_strdup (base->password);
276 uri->host = g_strdup (base->host);
277 uri->port = base->port;
280 uri->path = g_strdup (base->path);
282 uri->query = g_strdup (base->query);
283 remove_dot_segments = FALSE;
284 } else if (*uri->path != '/') {
285 char *newpath, *last;
287 last = strrchr (base->path, '/');
289 newpath = g_strdup_printf ("%.*s/%s",
290 (int)(last - base->path),
294 newpath = g_strdup_printf ("/%s", uri->path);
301 if (remove_dot_segments && uri->path && *uri->path) {
302 char *p = uri->path, *q;
304 /* Remove "./" where "." is a complete segment. */
305 for (p = uri->path + 1; *p; ) {
306 if (*(p - 1) == '/' &&
307 *p == '.' && *(p + 1) == '/')
308 memmove (p, p + 2, strlen (p + 2) + 1);
312 /* Remove "." at end. */
313 if (p > uri->path + 2 &&
314 *(p - 1) == '.' && *(p - 2) == '/')
317 /* Remove "<segment>/../" where <segment> != ".." */
318 for (p = uri->path + 1; *p; ) {
319 if (!strncmp (p, "../", 3)) {
323 q = strchr (p + 1, '/');
326 if (strncmp (q, "/../", 4) != 0) {
330 memmove (p, q + 4, strlen (q + 4) + 1);
333 /* Remove "<segment>/.." at end where <segment> != ".." */
334 q = strrchr (uri->path, '/');
335 if (q && !strcmp (q, "/..")) {
337 while (p > uri->path && *p != '/')
339 if (strncmp (p, "/../", 4) != 0)
343 /* Remove extraneous initial "/.."s */
344 while (!strncmp (uri->path, "/../", 4))
345 memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
346 if (!strcmp (uri->path, "/.."))
350 /* HTTP-specific stuff */
351 if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
352 uri->scheme == SOUP_URI_SCHEME_HTTPS) {
353 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
358 uri->path = g_strdup ("/");
362 uri->port = soup_scheme_default_port (uri->scheme);
364 uri->path = g_strdup ("");
373 * Parses an absolute URI.
375 * You can also pass %NULL for @uri_string if you want to get back an
376 * "empty" #SoupURI that you can fill in by hand. (You will need to
377 * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
378 * those fields are required.)
380 * Return value: a #SoupURI, or %NULL.
383 soup_uri_new (const char *uri_string)
388 return g_slice_new0 (SoupURI);
390 uri = soup_uri_new_with_base (NULL, uri_string);
403 * soup_uri_to_string:
405 * @just_path_and_query: if %TRUE, output just the path and query portions
407 * Returns a string representing @uri.
409 * If @just_path_and_query is %TRUE, this concatenates the path and query
410 * together. That is, it constructs the string that would be needed in
411 * the Request-Line of an HTTP request for @uri.
413 * Return value: a string representing @uri, which the caller must free.
416 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
421 g_return_val_if_fail (uri != NULL, NULL);
423 /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
424 * tests/uri-parsing AFTERWARD.
427 str = g_string_sized_new (20);
429 if (uri->scheme && !just_path_and_query)
430 g_string_append_printf (str, "%s:", uri->scheme);
431 if (uri->host && !just_path_and_query) {
432 g_string_append (str, "//");
434 append_uri_encoded (str, uri->user, ":;@?/");
435 g_string_append_c (str, '@');
437 if (strchr (uri->host, ':')) {
438 g_string_append_c (str, '[');
439 g_string_append (str, uri->host);
440 g_string_append_c (str, ']');
442 append_uri_encoded (str, uri->host, ":/");
443 if (uri->port && uri->port != soup_scheme_default_port (uri->scheme))
444 g_string_append_printf (str, ":%d", uri->port);
445 if (!uri->path && (uri->query || uri->fragment))
446 g_string_append_c (str, '/');
449 if (uri->path && *uri->path)
450 g_string_append (str, uri->path);
453 g_string_append_c (str, '?');
454 g_string_append (str, uri->query);
456 if (uri->fragment && !just_path_and_query) {
457 g_string_append_c (str, '#');
458 g_string_append (str, uri->fragment);
461 return_result = str->str;
462 g_string_free (str, FALSE);
464 return return_result;
473 * Return value: a copy of @uri, which must be freed with soup_uri_free()
476 soup_uri_copy (SoupURI *uri)
480 g_return_val_if_fail (uri != NULL, NULL);
482 dup = g_slice_new0 (SoupURI);
483 dup->scheme = uri->scheme;
484 dup->user = g_strdup (uri->user);
485 dup->password = g_strdup (uri->password);
486 dup->host = g_strdup (uri->host);
487 dup->port = uri->port;
488 dup->path = g_strdup (uri->path);
489 dup->query = g_strdup (uri->query);
490 dup->fragment = g_strdup (uri->fragment);
495 static inline gboolean
496 parts_equal (const char *one, const char *two, gboolean insensitive)
502 return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
508 * @uri2: another #SoupURI
510 * Tests whether or not @uri1 and @uri2 are equal in all parts
512 * Return value: %TRUE or %FALSE
515 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
517 if (uri1->scheme != uri2->scheme ||
518 uri1->port != uri2->port ||
519 !parts_equal (uri1->user, uri2->user, FALSE) ||
520 !parts_equal (uri1->password, uri2->password, FALSE) ||
521 !parts_equal (uri1->host, uri2->host, TRUE) ||
522 !parts_equal (uri1->path, uri2->path, FALSE) ||
523 !parts_equal (uri1->query, uri2->query, FALSE) ||
524 !parts_equal (uri1->fragment, uri2->fragment, FALSE))
537 soup_uri_free (SoupURI *uri)
539 g_return_if_fail (uri != NULL);
542 g_free (uri->password);
546 g_free (uri->fragment);
548 g_slice_free (SoupURI, uri);
552 #define SOUP_URI_UNRESERVED 0
553 #define SOUP_URI_PCT_ENCODED 1
554 #define SOUP_URI_GEN_DELIMS 2
555 #define SOUP_URI_SUB_DELIMS 4
556 static const char uri_encoded_char[] = {
557 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 - 0x0f */
558 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 - 0x1f */
559 1, 4, 1, 2, 4, 1, 4, 4, 4, 4, 4, 4, 4, 0, 0, 2, /* !"#$%&'()*+,-./ */
560 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 4, 1, 2, /* 0123456789:;<=>? */
561 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ABCDEFGHIJKLMNO */
562 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 0, /* PQRSTUVWXYZ[\]^_ */
563 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* `abcdefghijklmno */
564 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* pqrstuvwxyz{|}~ */
565 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
566 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
567 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
568 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
569 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
570 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
571 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
572 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
576 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
578 const unsigned char *s = (const unsigned char *)in;
581 if ((uri_encoded_char[*s] & (SOUP_URI_PCT_ENCODED | SOUP_URI_GEN_DELIMS)) ||
582 (extra_enc_chars && strchr (extra_enc_chars, *s)))
583 g_string_append_printf (str, "%%%02X", (int)*s++);
585 g_string_append_c (str, *s++);
592 * @escape_extra: additional reserved characters to escape (or %NULL)
594 * This %<!-- -->-encodes the given URI part and returns the escaped
595 * version in allocated memory, which the caller must free when it is
598 * Return value: the encoded URI part
601 soup_uri_encode (const char *part, const char *escape_extra)
606 str = g_string_new (NULL);
607 append_uri_encoded (str, part, escape_extra);
609 g_string_free (str, FALSE);
614 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
615 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
618 uri_decoded_copy (const char *part, int length)
620 unsigned char *s, *d;
621 char *decoded = g_strndup (part, length);
623 s = d = (unsigned char *)decoded;
626 if (!g_ascii_isxdigit (s[1]) ||
627 !g_ascii_isxdigit (s[2])) {
644 * Fully %<!-- -->-decodes @part.
646 * Return value: the decoded URI part, or %NULL if an invalid percent
647 * code was encountered.
650 soup_uri_decode (const char *part)
652 return uri_decoded_copy (part, strlen (part));
656 uri_normalized_copy (const char *part, int length,
657 const char *unescape_extra, gboolean fixup)
659 unsigned char *s, *d, c;
660 char *normalized = g_strndup (part, length);
661 gboolean need_fixup = FALSE;
663 s = d = (unsigned char *)normalized;
666 if (!g_ascii_isxdigit (s[1]) ||
667 !g_ascii_isxdigit (s[2])) {
673 if (uri_encoded_char[c] == SOUP_URI_UNRESERVED ||
674 (unescape_extra && strchr (unescape_extra, c))) {
679 *d++ = g_ascii_toupper (*s++);
680 *d++ = g_ascii_toupper (*s);
689 if (fixup && need_fixup) {
691 /* This code is lame, but so are people who put
692 * unencoded spaces in URLs!
694 while ((sp = strchr (normalized, ' '))) {
695 tmp = g_strdup_printf ("%.*s%%20%s",
696 (int)(sp - normalized),
707 * soup_uri_normalize:
709 * @unescape_extra: reserved characters to unescape (or %NULL)
711 * %<!-- -->-decodes any "unreserved" characters (or characters in
712 * @unescape_extra) in @part.
714 * "Unreserved" characters are those that are not allowed to be used
715 * for punctuation according to the URI spec. For example, letters are
716 * unreserved, so soup_uri_normalize() will turn
717 * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
718 * <literal>http://example.com/foo/bar</literal>, which is guaranteed
719 * to mean the same thing. However, "/" is "reserved", so
720 * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
721 * be changed, because it might mean something different to the
724 * Return value: the normalized URI part, or %NULL if an invalid percent
725 * code was encountered.
728 soup_uri_normalize (const char *part, const char *unescape_extra)
730 return uri_normalized_copy (part, strlen (part), unescape_extra, FALSE);
735 * soup_uri_uses_default_port:
738 * Tests if @uri uses the default port for its scheme. (Eg, 80 for
739 * http.) (This only works for http and https; libsoup does not know
740 * the default ports of other protocols.)
742 * Return value: %TRUE or %FALSE
745 soup_uri_uses_default_port (SoupURI *uri)
747 g_return_val_if_fail (uri->scheme == SOUP_URI_SCHEME_HTTP ||
748 uri->scheme == SOUP_URI_SCHEME_HTTPS, FALSE);
750 return uri->port == soup_scheme_default_port (uri->scheme);
754 * SOUP_URI_SCHEME_HTTP:
756 * "http" as an interned string. This can be compared directly against
757 * the value of a #SoupURI's <structfield>scheme</structfield>
761 * SOUP_URI_SCHEME_HTTPS:
763 * "https" as an interned string. This can be compared directly
764 * against the value of a #SoupURI's <structfield>scheme</structfield>
768 * soup_uri_set_scheme:
770 * @scheme: the URI scheme
772 * Sets @uri's scheme to @scheme. This will also set @uri's port to
773 * the default port for @scheme, if known.
776 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
778 uri->scheme = soup_uri_get_scheme (scheme, strlen (scheme));
779 uri->port = soup_scheme_default_port (uri->scheme);
785 * @user: the username, or %NULL
787 * Sets @uri's user to @user.
790 soup_uri_set_user (SoupURI *uri, const char *user)
793 uri->user = g_strdup (user);
797 * soup_uri_set_password:
799 * @password: the password, or %NULL
801 * Sets @uri's password to @password.
804 soup_uri_set_password (SoupURI *uri, const char *password)
806 g_free (uri->password);
807 uri->password = g_strdup (password);
813 * @host: the hostname or IP address, or %NULL
815 * Sets @uri's host to @host.
817 * If @host is an IPv6 IP address, it should not include the brackets
818 * required by the URI syntax; they will be added automatically when
819 * converting @uri to a string.
822 soup_uri_set_host (SoupURI *uri, const char *host)
825 uri->host = g_strdup (host);
831 * @port: the port, or 0
833 * Sets @uri's port to @port. If @port is 0, @uri will not have an
834 * explicitly-specified port.
837 soup_uri_set_port (SoupURI *uri, guint port)
847 * Sets @uri's path to @path.
850 soup_uri_set_path (SoupURI *uri, const char *path)
853 uri->path = g_strdup (path);
857 * soup_uri_set_query:
861 * Sets @uri's query to @query.
864 soup_uri_set_query (SoupURI *uri, const char *query)
867 uri->query = g_strdup (query);
871 * soup_uri_set_query_from_form:
873 * @form: a #GHashTable containing HTML form information
875 * Sets @uri's query to the result of encoding @form according to the
876 * HTML form rules. See soup_form_encode_hash() for more information.
879 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
882 uri->query = soup_form_encode_urlencoded (form);
886 * soup_uri_set_query_from_fields:
888 * @first_field: name of the first form field to encode into query
889 * @...: value of @first_field, followed by additional field names
890 * and values, terminated by %NULL.
892 * Sets @uri's query to the result of encoding the given form fields
893 * and values according to the * HTML form rules. See
894 * soup_form_encode() for more information.
897 soup_uri_set_query_from_fields (SoupURI *uri,
898 const char *first_field,
904 va_start (args, first_field);
905 uri->query = soup_form_encode_valist (first_field, args);
910 * soup_uri_set_fragment:
912 * @fragment: the fragment
914 * Sets @uri's fragment to @fragment.
917 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
919 g_free (uri->fragment);
920 uri->fragment = g_strdup (fragment);
924 * soup_uri_copy_host:
927 * Makes a copy of @uri, considering only the protocol, host, and port
929 * Return value: the new #SoupUri
934 soup_uri_copy_host (SoupURI *uri)
938 g_return_val_if_fail (uri != NULL, NULL);
940 dup = soup_uri_new (NULL);
941 dup->scheme = uri->scheme;
942 dup->host = g_strdup (uri->host);
943 dup->port = uri->port;
944 if (dup->scheme == SOUP_URI_SCHEME_HTTP ||
945 dup->scheme == SOUP_URI_SCHEME_HTTPS)
946 dup->path = g_strdup ("");
952 * soup_uri_host_hash:
955 * Hashes @key, considering only the scheme, host, and port.
957 * Return value: a hash
962 soup_uri_host_hash (gconstpointer key)
964 const SoupURI *uri = key;
966 g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
968 return GPOINTER_TO_UINT (uri->scheme) + uri->port +
969 soup_str_case_hash (uri->host);
973 * soup_uri_host_equal:
977 * Compares @v1 and @v2, considering only the scheme, host, and port.
979 * Return value: whether or not the URIs are equal in scheme, host,
985 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
987 const SoupURI *one = v1;
988 const SoupURI *two = v2;
990 g_return_val_if_fail (one != NULL && two != NULL, one == two);
991 g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
993 if (one->scheme != two->scheme)
995 if (one->port != two->port)
998 return g_ascii_strcasecmp (one->host, two->host) == 0;
1003 soup_uri_get_type (void)
1005 static volatile gsize type_volatile = 0;
1007 if (g_once_init_enter (&type_volatile)) {
1008 GType type = g_boxed_type_register_static (
1009 g_intern_static_string ("SoupURI"),
1010 (GBoxedCopyFunc) soup_uri_copy,
1011 (GBoxedFreeFunc) soup_uri_free);
1012 g_once_init_leave (&type_volatile, type);
1014 return type_volatile;