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, gboolean fixup);
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;
99 gpointer _SOUP_URI_SCHEME_FTP;
100 gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA;
102 static inline const char *
103 soup_uri_get_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_strdup (uri_string), *bad;
162 while ((bad = strpbrk (clean, "\t\n\r")))
163 strcpy (bad, bad + 1);
164 uri = soup_uri_new_with_base (base, clean);
168 end = uri_string + len;
169 while (end > uri_string && g_ascii_isspace (end[-1]))
172 uri = g_slice_new0 (SoupURI);
175 hash = strchr (uri_string, '#');
177 uri->fragment = uri_normalized_copy (hash + 1, end - hash + 1,
182 /* Find scheme: initial [a-z+.-]* substring until ":" */
184 while (p < end && (g_ascii_isalnum (*p) ||
185 *p == '.' || *p == '+' || *p == '-'))
188 if (p > uri_string && *p == ':') {
189 uri->scheme = soup_uri_get_scheme (uri_string, p - uri_string);
193 if (uri_string == end && !base && !uri->fragment)
196 /* Check for authority */
197 if (strncmp (uri_string, "//", 2) == 0) {
200 path = uri_string + strcspn (uri_string, "/?#");
201 at = strchr (uri_string, '@');
202 if (at && at < path) {
203 colon = strchr (uri_string, ':');
204 if (colon && colon < at) {
205 uri->password = uri_decoded_copy (colon + 1,
209 uri->password = NULL;
213 uri->user = uri_decoded_copy (uri_string,
218 uri->user = uri->password = NULL;
220 /* Find host and port. */
221 if (*uri_string == '[') {
223 hostend = strchr (uri_string, ']');
224 if (!hostend || hostend > path) {
228 if (*(hostend + 1) == ':')
233 colon = memchr (uri_string, ':', path - uri_string);
234 hostend = colon ? colon : path;
237 uri->host = uri_decoded_copy (uri_string, hostend - uri_string,
240 if (colon && colon != path - 1) {
242 uri->port = strtoul (colon + 1, &portend, 10);
243 if (portend != (char *)path) {
253 question = memchr (uri_string, '?', end - uri_string);
255 uri->query = uri_normalized_copy (question + 1,
256 end - (question + 1),
261 if (end != uri_string) {
262 uri->path = uri_normalized_copy (uri_string, end - uri_string,
266 /* Apply base URI. This is spelled out in RFC 3986. */
267 if (base && !uri->scheme && uri->host)
268 uri->scheme = base->scheme;
269 else if (base && !uri->scheme) {
270 uri->scheme = base->scheme;
271 uri->user = g_strdup (base->user);
272 uri->password = g_strdup (base->password);
273 uri->host = g_strdup (base->host);
274 uri->port = base->port;
277 uri->path = g_strdup (base->path);
279 uri->query = g_strdup (base->query);
280 remove_dot_segments = FALSE;
281 } else if (*uri->path != '/') {
282 char *newpath, *last;
284 last = strrchr (base->path, '/');
286 newpath = g_strdup_printf ("%.*s/%s",
287 (int)(last - base->path),
291 newpath = g_strdup_printf ("/%s", uri->path);
298 if (remove_dot_segments && uri->path && *uri->path) {
301 /* Remove "./" where "." is a complete segment. */
302 for (p = uri->path + 1; *p; ) {
303 if (*(p - 1) == '/' &&
304 *p == '.' && *(p + 1) == '/')
305 memmove (p, p + 2, strlen (p + 2) + 1);
309 /* Remove "." at end. */
310 if (p > uri->path + 2 &&
311 *(p - 1) == '.' && *(p - 2) == '/')
314 /* Remove "<segment>/../" where <segment> != ".." */
315 for (p = uri->path + 1; *p; ) {
316 if (!strncmp (p, "../", 3)) {
320 q = strchr (p + 1, '/');
323 if (strncmp (q, "/../", 4) != 0) {
327 memmove (p, q + 4, strlen (q + 4) + 1);
330 /* Remove "<segment>/.." at end where <segment> != ".." */
331 q = strrchr (uri->path, '/');
332 if (q && !strcmp (q, "/..")) {
334 while (p > uri->path && *p != '/')
336 if (strncmp (p, "/../", 4) != 0)
340 /* Remove extraneous initial "/.."s */
341 while (!strncmp (uri->path, "/../", 4))
342 memmove (uri->path, uri->path + 3, strlen (uri->path) - 2);
343 if (!strcmp (uri->path, "/.."))
347 /* HTTP-specific stuff */
348 if (uri->scheme == SOUP_URI_SCHEME_HTTP ||
349 uri->scheme == SOUP_URI_SCHEME_HTTPS) {
351 uri->path = g_strdup ("/");
352 if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
358 if (uri->scheme == SOUP_URI_SCHEME_FTP) {
366 uri->port = soup_scheme_default_port (uri->scheme);
368 uri->path = g_strdup ("");
377 * Parses an absolute URI.
379 * You can also pass %NULL for @uri_string if you want to get back an
380 * "empty" #SoupURI that you can fill in by hand. (You will need to
381 * call at least soup_uri_set_scheme() and soup_uri_set_path(), since
382 * those fields are required.)
384 * Return value: a #SoupURI, or %NULL.
387 soup_uri_new (const char *uri_string)
392 return g_slice_new0 (SoupURI);
394 uri = soup_uri_new_with_base (NULL, uri_string);
407 * soup_uri_to_string:
409 * @just_path_and_query: if %TRUE, output just the path and query portions
411 * Returns a string representing @uri.
413 * If @just_path_and_query is %TRUE, this concatenates the path and query
414 * together. That is, it constructs the string that would be needed in
415 * the Request-Line of an HTTP request for @uri.
417 * Return value: a string representing @uri, which the caller must free.
420 soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query)
425 g_return_val_if_fail (uri != NULL, NULL);
427 /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
428 * tests/uri-parsing AFTERWARD.
431 str = g_string_sized_new (20);
433 if (uri->scheme && !just_path_and_query)
434 g_string_append_printf (str, "%s:", uri->scheme);
435 if (uri->host && !just_path_and_query) {
436 g_string_append (str, "//");
438 append_uri_encoded (str, uri->user, ":;@?/");
439 g_string_append_c (str, '@');
441 if (strchr (uri->host, ':')) {
442 g_string_append_c (str, '[');
443 g_string_append (str, uri->host);
444 g_string_append_c (str, ']');
446 append_uri_encoded (str, uri->host, ":/");
447 if (uri->port && uri->port != soup_scheme_default_port (uri->scheme))
448 g_string_append_printf (str, ":%d", uri->port);
449 if (!uri->path && (uri->query || uri->fragment))
450 g_string_append_c (str, '/');
453 if (uri->path && *uri->path)
454 g_string_append (str, uri->path);
457 g_string_append_c (str, '?');
458 g_string_append (str, uri->query);
460 if (uri->fragment && !just_path_and_query) {
461 g_string_append_c (str, '#');
462 g_string_append (str, uri->fragment);
465 return_result = str->str;
466 g_string_free (str, FALSE);
468 return return_result;
477 * Return value: a copy of @uri, which must be freed with soup_uri_free()
480 soup_uri_copy (SoupURI *uri)
484 g_return_val_if_fail (uri != NULL, NULL);
486 dup = g_slice_new0 (SoupURI);
487 dup->scheme = uri->scheme;
488 dup->user = g_strdup (uri->user);
489 dup->password = g_strdup (uri->password);
490 dup->host = g_strdup (uri->host);
491 dup->port = uri->port;
492 dup->path = g_strdup (uri->path);
493 dup->query = g_strdup (uri->query);
494 dup->fragment = g_strdup (uri->fragment);
499 static inline gboolean
500 parts_equal (const char *one, const char *two, gboolean insensitive)
506 return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
512 * @uri2: another #SoupURI
514 * Tests whether or not @uri1 and @uri2 are equal in all parts
516 * Return value: %TRUE or %FALSE
519 soup_uri_equal (SoupURI *uri1, SoupURI *uri2)
521 if (uri1->scheme != uri2->scheme ||
522 uri1->port != uri2->port ||
523 !parts_equal (uri1->user, uri2->user, FALSE) ||
524 !parts_equal (uri1->password, uri2->password, FALSE) ||
525 !parts_equal (uri1->host, uri2->host, TRUE) ||
526 !parts_equal (uri1->path, uri2->path, FALSE) ||
527 !parts_equal (uri1->query, uri2->query, FALSE) ||
528 !parts_equal (uri1->fragment, uri2->fragment, FALSE))
541 soup_uri_free (SoupURI *uri)
543 g_return_if_fail (uri != NULL);
546 g_free (uri->password);
550 g_free (uri->fragment);
552 g_slice_free (SoupURI, uri);
556 #define SOUP_URI_UNRESERVED 0
557 #define SOUP_URI_PCT_ENCODED 1
558 #define SOUP_URI_GEN_DELIMS 2
559 #define SOUP_URI_SUB_DELIMS 4
560 static const char uri_encoded_char[] = {
561 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 - 0x0f */
562 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 - 0x1f */
563 1, 4, 1, 2, 4, 1, 4, 4, 4, 4, 4, 4, 4, 0, 0, 2, /* !"#$%&'()*+,-./ */
564 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 4, 1, 2, /* 0123456789:;<=>? */
565 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ABCDEFGHIJKLMNO */
566 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 0, /* PQRSTUVWXYZ[\]^_ */
567 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* `abcdefghijklmno */
568 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* pqrstuvwxyz{|}~ */
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,
573 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
574 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
575 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
576 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
580 append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars)
582 const unsigned char *s = (const unsigned char *)in;
585 if ((uri_encoded_char[*s] & (SOUP_URI_PCT_ENCODED | SOUP_URI_GEN_DELIMS)) ||
586 (extra_enc_chars && strchr (extra_enc_chars, *s)))
587 g_string_append_printf (str, "%%%02X", (int)*s++);
589 g_string_append_c (str, *s++);
596 * @escape_extra: additional reserved characters to escape (or %NULL)
598 * This %<!-- -->-encodes the given URI part and returns the escaped
599 * version in allocated memory, which the caller must free when it is
602 * Return value: the encoded URI part
605 soup_uri_encode (const char *part, const char *escape_extra)
610 str = g_string_new (NULL);
611 append_uri_encoded (str, part, escape_extra);
613 g_string_free (str, FALSE);
618 #define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
619 #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
622 uri_decoded_copy (const char *part, int length, gboolean fixup)
624 unsigned char *s, *d;
625 char *decoded = g_strndup (part, length);
627 s = d = (unsigned char *)decoded;
630 if (!g_ascii_isxdigit (s[1]) ||
631 !g_ascii_isxdigit (s[2])) {
652 * Fully %<!-- -->-decodes @part.
654 * Return value: the decoded URI part, or %NULL if an invalid percent
655 * code was encountered.
658 soup_uri_decode (const char *part)
660 return uri_decoded_copy (part, strlen (part), FALSE);
664 uri_normalized_copy (const char *part, int length,
665 const char *unescape_extra, gboolean fixup)
667 unsigned char *s, *d, c;
668 char *normalized = g_strndup (part, length);
669 gboolean need_fixup = FALSE;
671 s = d = (unsigned char *)normalized;
674 if (!g_ascii_isxdigit (s[1]) ||
675 !g_ascii_isxdigit (s[2])) {
685 if (uri_encoded_char[c] == SOUP_URI_UNRESERVED ||
686 (unescape_extra && strchr (unescape_extra, c))) {
691 *d++ = g_ascii_toupper (*s++);
692 *d++ = g_ascii_toupper (*s);
701 if (fixup && need_fixup) {
703 /* This code is lame, but so are people who put
704 * unencoded spaces in URLs!
706 while ((sp = strchr (normalized, ' '))) {
707 tmp = g_strdup_printf ("%.*s%%20%s",
708 (int)(sp - normalized),
719 * soup_uri_normalize:
721 * @unescape_extra: reserved characters to unescape (or %NULL)
723 * %<!-- -->-decodes any "unreserved" characters (or characters in
724 * @unescape_extra) in @part.
726 * "Unreserved" characters are those that are not allowed to be used
727 * for punctuation according to the URI spec. For example, letters are
728 * unreserved, so soup_uri_normalize() will turn
729 * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
730 * <literal>http://example.com/foo/bar</literal>, which is guaranteed
731 * to mean the same thing. However, "/" is "reserved", so
732 * <literal>http://example.com/foo%<!-- -->2Fbar</literal> would not
733 * be changed, because it might mean something different to the
736 * Return value: the normalized URI part, or %NULL if an invalid percent
737 * code was encountered.
740 soup_uri_normalize (const char *part, const char *unescape_extra)
742 return uri_normalized_copy (part, strlen (part), unescape_extra, FALSE);
747 * soup_uri_uses_default_port:
750 * Tests if @uri uses the default port for its scheme. (Eg, 80 for
751 * http.) (This only works for http and https; libsoup does not know
752 * the default ports of other protocols.)
754 * Return value: %TRUE or %FALSE
757 soup_uri_uses_default_port (SoupURI *uri)
759 g_return_val_if_fail (uri->scheme == SOUP_URI_SCHEME_HTTP ||
760 uri->scheme == SOUP_URI_SCHEME_HTTPS ||
761 uri->scheme == SOUP_URI_SCHEME_FTP, FALSE);
763 return uri->port == soup_scheme_default_port (uri->scheme);
767 * SOUP_URI_SCHEME_HTTP:
769 * "http" as an interned string. This can be compared directly against
770 * the value of a #SoupURI's <structfield>scheme</structfield>
774 * SOUP_URI_SCHEME_HTTPS:
776 * "https" as an interned string. This can be compared directly
777 * against the value of a #SoupURI's <structfield>scheme</structfield>
781 * soup_uri_set_scheme:
783 * @scheme: the URI scheme
785 * Sets @uri's scheme to @scheme. This will also set @uri's port to
786 * the default port for @scheme, if known.
789 soup_uri_set_scheme (SoupURI *uri, const char *scheme)
791 uri->scheme = soup_uri_get_scheme (scheme, strlen (scheme));
792 uri->port = soup_scheme_default_port (uri->scheme);
798 * @user: the username, or %NULL
800 * Sets @uri's user to @user.
803 soup_uri_set_user (SoupURI *uri, const char *user)
806 uri->user = g_strdup (user);
810 * soup_uri_set_password:
812 * @password: the password, or %NULL
814 * Sets @uri's password to @password.
817 soup_uri_set_password (SoupURI *uri, const char *password)
819 g_free (uri->password);
820 uri->password = g_strdup (password);
826 * @host: the hostname or IP address, or %NULL
828 * Sets @uri's host to @host.
830 * If @host is an IPv6 IP address, it should not include the brackets
831 * required by the URI syntax; they will be added automatically when
832 * converting @uri to a string.
835 soup_uri_set_host (SoupURI *uri, const char *host)
838 uri->host = g_strdup (host);
844 * @port: the port, or 0
846 * Sets @uri's port to @port. If @port is 0, @uri will not have an
847 * explicitly-specified port.
850 soup_uri_set_port (SoupURI *uri, guint port)
860 * Sets @uri's path to @path.
863 soup_uri_set_path (SoupURI *uri, const char *path)
866 uri->path = g_strdup (path);
870 * soup_uri_set_query:
874 * Sets @uri's query to @query.
877 soup_uri_set_query (SoupURI *uri, const char *query)
880 uri->query = g_strdup (query);
884 * soup_uri_set_query_from_form:
886 * @form: a #GHashTable containing HTML form information
888 * Sets @uri's query to the result of encoding @form according to the
889 * HTML form rules. See soup_form_encode_hash() for more information.
892 soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form)
895 uri->query = soup_form_encode_urlencoded (form);
899 * soup_uri_set_query_from_fields:
901 * @first_field: name of the first form field to encode into query
902 * @...: value of @first_field, followed by additional field names
903 * and values, terminated by %NULL.
905 * Sets @uri's query to the result of encoding the given form fields
906 * and values according to the * HTML form rules. See
907 * soup_form_encode() for more information.
910 soup_uri_set_query_from_fields (SoupURI *uri,
911 const char *first_field,
917 va_start (args, first_field);
918 uri->query = soup_form_encode_valist (first_field, args);
923 * soup_uri_set_fragment:
925 * @fragment: the fragment
927 * Sets @uri's fragment to @fragment.
930 soup_uri_set_fragment (SoupURI *uri, const char *fragment)
932 g_free (uri->fragment);
933 uri->fragment = g_strdup (fragment);
937 * soup_uri_copy_host:
940 * Makes a copy of @uri, considering only the protocol, host, and port
942 * Return value: the new #SoupUri
947 soup_uri_copy_host (SoupURI *uri)
951 g_return_val_if_fail (uri != NULL, NULL);
953 dup = soup_uri_new (NULL);
954 dup->scheme = uri->scheme;
955 dup->host = g_strdup (uri->host);
956 dup->port = uri->port;
957 if (dup->scheme == SOUP_URI_SCHEME_HTTP ||
958 dup->scheme == SOUP_URI_SCHEME_HTTPS)
959 dup->path = g_strdup ("");
965 * soup_uri_host_hash:
968 * Hashes @key, considering only the scheme, host, and port.
970 * Return value: a hash
975 soup_uri_host_hash (gconstpointer key)
977 const SoupURI *uri = key;
979 g_return_val_if_fail (uri != NULL && uri->host != NULL, 0);
981 return GPOINTER_TO_UINT (uri->scheme) + uri->port +
982 soup_str_case_hash (uri->host);
986 * soup_uri_host_equal:
990 * Compares @v1 and @v2, considering only the scheme, host, and port.
992 * Return value: whether or not the URIs are equal in scheme, host,
998 soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
1000 const SoupURI *one = v1;
1001 const SoupURI *two = v2;
1003 g_return_val_if_fail (one != NULL && two != NULL, one == two);
1004 g_return_val_if_fail (one->host != NULL && two->host != NULL, one->host == two->host);
1006 if (one->scheme != two->scheme)
1008 if (one->port != two->port)
1011 return g_ascii_strcasecmp (one->host, two->host) == 0;
1016 soup_uri_get_type (void)
1018 static volatile gsize type_volatile = 0;
1020 if (g_once_init_enter (&type_volatile)) {
1021 GType type = g_boxed_type_register_static (
1022 g_intern_static_string ("SoupURI"),
1023 (GBoxedCopyFunc) soup_uri_copy,
1024 (GBoxedFreeFunc) soup_uri_free);
1025 g_once_init_leave (&type_volatile, type);
1027 return type_volatile;