[glib/danw/wip/guri: 1/2] guri: new URI parsing and generating functions



commit 7ed12e6d4dc4c037b109ea772ea92d7452fb289a
Author: Dan Winship <danw gnome org>
Date:   Sat Sep 21 17:15:00 2013 -0400

    guri: new URI parsing and generating functions
    
    Add a set of new URI parsing and generating functions, including a new
    parsed-URI type GUri. Move all the code from gurifuncs.c into guri.c,
    reimplementing some of those functions (and
    g_string_append_uri_encoded()) in terms of the new code.

 docs/reference/glib/glib-docs.xml           |    2 +-
 docs/reference/glib/glib-sections.txt       |   45 +-
 docs/reference/gobject/gobject-sections.txt |    1 +
 glib/Makefile.am                            |    4 +-
 glib/glib.h                                 |    2 +-
 glib/gstring.c                              |   84 -
 glib/guri-notes.txt                         |  220 +++
 glib/guri.c                                 | 2323 +++++++++++++++++++++++++++
 glib/guri.h                                 |  286 ++++
 glib/gurifuncs.c                            |  252 ---
 glib/gurifuncs.h                            |   83 -
 glib/tests/uri.c                            |  443 +++++-
 gobject/gboxed.c                            |    1 +
 gobject/glib-types.h                        |    2 +
 14 files changed, 3291 insertions(+), 457 deletions(-)
---
diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml
index 7132c5c..309c035 100644
--- a/docs/reference/glib/glib-docs.xml
+++ b/docs/reference/glib/glib-docs.xml
@@ -80,7 +80,7 @@
     <xi:include href="xml/timers.xml" />
     <xi:include href="xml/spawn.xml" />
     <xi:include href="xml/fileutils.xml" />
-    <xi:include href="xml/gurifuncs.xml" />
+    <xi:include href="xml/guri.xml" />
     <xi:include href="xml/ghostutils.xml" />
     <xi:include href="xml/shell.xml" />
     <xi:include href="xml/option.xml" />
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 19bae6f..3af8754 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -2856,19 +2856,60 @@ g_base64_decode_inplace
 
 <SECTION>
 <TITLE>URI Functions</TITLE>
-<FILE>gurifuncs</FILE>
+<FILE>guri</FILE>
+GUri
+g_uri_ref
+g_uri_unref
+<SUBSECTION>
+GUriFlags
+g_uri_split
+g_uri_split_with_user
+g_uri_split_network
+g_uri_is_valid
+g_uri_join
+g_uri_join_with_user
+g_uri_parse
+g_uri_parse_relative
+g_uri_resolve_relative
+g_uri_build
+g_uri_build_with_user
+g_uri_peek_scheme
+g_uri_parse_scheme
+<SUBSECTION>
+GUriHideFlags;
+g_uri_to_string
+g_uri_to_string_partial
+<SUBSECTION>
+g_uri_get_scheme
+g_uri_get_userinfo
+g_uri_get_user
+g_uri_get_password
+g_uri_get_auth_params
+g_uri_get_host
+g_uri_get_port
+g_uri_get_path
+g_uri_get_query
+g_uri_get_fragment
+<SUBSECTION>
+g_uri_parse_params
+<SUBSECTION>
 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH
 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT
 G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO
 G_URI_RESERVED_CHARS_GENERIC_DELIMITERS
 G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS
-g_uri_parse_scheme
 g_uri_escape_string
 g_uri_unescape_string
 g_uri_unescape_segment
+<SUBSECTION>
 g_uri_list_extract_uris
 g_filename_from_uri
 g_filename_to_uri
+<SUBSECTION>
+G_URI_ERROR
+GUriError
+<SUBSECTION Private>
+g_uri_error_quark
 </SECTION>
 
 <SECTION>
diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt
index 50f1a4c..218a982 100644
--- a/docs/reference/gobject/gobject-sections.txt
+++ b/docs/reference/gobject/gobject-sections.txt
@@ -390,6 +390,7 @@ G_TYPE_MARKUP_PARSE_CONTEXT
 G_TYPE_SOURCE
 G_TYPE_POLLFD
 G_TYPE_THREAD
+G_TYPE_URI
 GStrv
 
 <SUBSECTION Standard>
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 1b9e081..3f6f74d 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -184,7 +184,7 @@ libglib_2_0_la_SOURCES =    \
        gunidecomp.h            \
        gunidecomp.c            \
        gunicodeprivate.h       \
-       gurifuncs.c             \
+       guri.c                  \
        gutils.c                \
        gvariant.h              \
        gvariant.c              \
@@ -304,7 +304,7 @@ glibsubinclude_HEADERS = \
        gtree.h         \
        gtypes.h        \
        gunicode.h      \
-       gurifuncs.h     \
+       guri.h          \
        gutils.h        \
        gvarianttype.h  \
        gvariant.h      \
diff --git a/glib/glib.h b/glib/glib.h
index c7fc999..113bc08 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -88,7 +88,7 @@
 #include <glib/gtree.h>
 #include <glib/gtypes.h>
 #include <glib/gunicode.h>
-#include <glib/gurifuncs.h>
+#include <glib/guri.h>
 #include <glib/gutils.h>
 #include <glib/gvarianttype.h>
 #include <glib/gvariant.h>
diff --git a/glib/gstring.c b/glib/gstring.c
index f5890bf..2f69ad6 100644
--- a/glib/gstring.c
+++ b/glib/gstring.c
@@ -495,90 +495,6 @@ g_string_insert_len (GString     *string,
   return string;
 }
 
-#define SUB_DELIM_CHARS  "!$&'()*+,;="
-
-static gboolean
-is_valid (char        c,
-          const char *reserved_chars_allowed)
-{
-  if (g_ascii_isalnum (c) ||
-      c == '-' ||
-      c == '.' ||
-      c == '_' ||
-      c == '~')
-    return TRUE;
-
-  if (reserved_chars_allowed &&
-      strchr (reserved_chars_allowed, c) != NULL)
-    return TRUE;
-
-  return FALSE;
-}
-
-static gboolean
-gunichar_ok (gunichar c)
-{
-  return
-    (c != (gunichar) -2) &&
-    (c != (gunichar) -1);
-}
-
-/**
- * g_string_append_uri_escaped:
- * @string: a #GString
- * @unescaped: a string
- * @reserved_chars_allowed: a string of reserved characters allowed
- *     to be used, or %NULL
- * @allow_utf8: set %TRUE if the escaped string may include UTF8 characters
- *
- * Appends @unescaped to @string, escaped any characters that
- * are reserved in URIs using URI-style escape sequences.
- *
- * Returns: @string
- *
- * Since: 2.16
- */
-GString *
-g_string_append_uri_escaped (GString     *string,
-                             const gchar *unescaped,
-                             const gchar *reserved_chars_allowed,
-                             gboolean     allow_utf8)
-{
-  unsigned char c;
-  const gchar *end;
-  static const gchar hex[16] = "0123456789ABCDEF";
-
-  g_return_val_if_fail (string != NULL, NULL);
-  g_return_val_if_fail (unescaped != NULL, NULL);
-
-  end = unescaped + strlen (unescaped);
-
-  while ((c = *unescaped) != 0)
-    {
-      if (c >= 0x80 && allow_utf8 &&
-          gunichar_ok (g_utf8_get_char_validated (unescaped, end - unescaped)))
-        {
-          int len = g_utf8_skip [c];
-          g_string_append_len (string, unescaped, len);
-          unescaped += len;
-        }
-      else if (is_valid (c, reserved_chars_allowed))
-        {
-          g_string_append_c (string, c);
-          unescaped++;
-        }
-      else
-        {
-          g_string_append_c (string, '%');
-          g_string_append_c (string, hex[((guchar)c) >> 4]);
-          g_string_append_c (string, hex[((guchar)c) & 0xf]);
-          unescaped++;
-        }
-    }
-
-  return string;
-}
-
 /**
  * g_string_append:
  * @string: a #GString
diff --git a/glib/guri-notes.txt b/glib/guri-notes.txt
new file mode 100644
index 0000000..6a807ae
--- /dev/null
+++ b/glib/guri-notes.txt
@@ -0,0 +1,220 @@
+aaa/aaas (rfc3588):
+aaa://host[:port][;transport=tcp][;protocol=diameter]
+  technically violates 3986, since ";" could appear in authority
+
+acap (rfc2244): 
+acap://[user[;AUTH=mech] ]host[:port]/data
+
+cap (rfc4324):
+cap://host[:port][/data]
+
+cid/mid (rfc2392):
+cid:content-id-data
+mid:message-id-data[/content-id-data]
+
+crid (rfc4078):
+crid://host/data
+
+data (rfc2397):
+data:[type/subtype][;attr=value]*[;base64],data
+
+dav (rfc4918):
+dav:data
+opaquelocktoken:uuid[path]
+
+dict (rfc2229):
+dict://[user[;authmech] ]host[:port]/d:word[:database[:nth]]
+dict://[user[;authmech] ]host[:port]/m:word[:database[:strategy[:nth]]]
+
+dns (rfc4501):
+dns:[//host[:port]/]name[?[attr=value[;attr=value]*]]
+
+file (rfc1738):
+file://[host]/path
+
+ftp (rfc1738):
+ftp://[user[:pass] ]host[:port][/path[;type=type]]
+
+geo (draft...):
+geo:data
+
+go (rfc3368):
+go:[//host[:port]?]data[;attr=[type,]value]*
+
+gopher (rfc4266):
+gopher://host[:port]/path
+
+h323 (rfc3508):
+h323:[user ]host[:port][;params]*
+
+http (rfc 2616):
+http://host[:port][/path[?query]]
+
+https (rfc 2818):
+https://host[:port][/path[?query]]
+
+iax (rfc 5456):
+iax:[user ]host[:port][/number[?context]]
+
+icap (rfc3507):
+icap://[userinfo ]host[:port]/path[?query]
+
+im (rfc3860):
+im:mailbox[?[header=value[;header=value]*]]
+
+imap (rfc5092):
+imap://[user[;AUTH=mech] ]host[:port][/[mailbox[validity]]]
+imap://[user[;AUTH=mech] ]host[:port]/mailbox[validity]?search
+imap://[user[;AUTH=mech] ]host[:port]/mailbox[validity]uid[sect][part][auth]
+
+info (rfc4452):
+info:namespace/identifier[#fragment]
+
+ipp (rfc3510):
+ipp://host[:port][/path[?query]]
+
+iris (rfc3981, 3983, 4992, 4993):
+iris[.transport]:urn/[method]/[userinfo ]host[:port][/class/name]
+
+ldap (rfc4516):
+ldap://[host[:port]][/dn[?[attrs][?[scope][?[filter][?exts]]]]]
+
+mailto (rfc2368):
+mailto:mailbox[?[header=value[;header=value]*]]
+
+msrp (rfc4975):
+msrp://authority[/id];tcp[;attr=value]*
+msrps://authority[/id];tcp[;attr=value]*
+  technically violates 3986, since ";" could appear in authority
+
+mtqp (rfc3887):
+mtqp://authority/track/id/secret
+
+mupdate (rfc3656):
+based on imap
+
+news/nntp (rfc5538):
+news:[//authority/]article-or-groups
+nntp://authority/group[/article]
+
+nfs (rfc2224):
+nfs://host[:port][path]
+
+pop (rfc2384):
+pop://[[user][;auth=mech] ]host[:port]
+
+pres (rfc3859):
+pres:mailbox[?[header=value[;header=value]*]]
+
+rtsp (rfc2326):
+rtsp://host[:port][path]
+rtspu://host[:port][path]
+
+service (rfc2609):
+service:... (possibly including authority)
+
+shttp (rfc2660):
+http-ish
+
+sieve (draft...):
+sieve://authority[path]
+
+sip (3261)
+sip:[user[:pass] ]host[:port][;name=val]*[?hname=hval[&hname=hval]*]
+sips:[user[:pass] ]host[:port][;name=val]*[?hname=hval[&hname=hval]*]
+
+sms (5724)
+sms:phone[,phone]*[?name=val[&name=val]*]
+
+snmp (4088)
+snmp://[userinfo ]host[:port][/context...]
+
+soap.beep (4227)
+standard
+
+tag (4151)
+tag:host-or-email,8601date:data[#fragment]
+
+tel (3966)
+tel:number[;attr=value]*
+
+telnet (4248)
+telnet://[user[:pass] ]host[:port][/]
+
+tftp (3617)
+tftp://host/file[;mode=type]
+
+tip (2371)
+tip://host[:port]/path?trans
+
+tv (2838)
+tv:dnsid
+
+urn (2141)
+urn:data
+
+vemmi (2122)
+vemmi://host[:port]/service[;attr=val]*
+
+xmlrpc.beep (3529)
+standard
+
+xmpp (5122)
+xmpp:[//node host[/]][[node ]host[/resource]][?query[;attr=val]*][#fragment]
+
+x39.50 (2056)
+z39.50r://host[:port][/[database[?docid]][;esn=data][;rs=data]]
+z39.50s://host[:port][/[database[?docid]][;esn=data][;rs=data]]
+
+
+
+HTML5 rules:
+ - strip leading and trailing spaces
+ - chars less than U+0020 or greater than U+007F in unreserved
+ - U+0022, U+003C, U+003E, U+005B..E, U+0060, U+007B..D in unreserved
+   (   "      <       >     [ \ ] ^      `    { | } ~   )
+ - Allow single % except in hostname
+ - Allow # in fragment
+ - if host has non-UTF8 %-encoded, fail
+ - if host fails IDNA ToASCII, fail
+ - forcibly %-encode all reserved/non-ASCII path chars
+ - re-encode query to the URL's encoding (from enclosing doc) and
+   %-encode. replace unencodable chars with '?'
+ - if fully-resolved URI uses authority, replace all \ with /
+
+
+gvfs and EUri both allow '@' in username
+EUri does "parameters", but seems unused
+hide_password in to_string
+CamelURL does hide_auth too
+CamelURL does params and uses them
+CamelURL special-cases file URLs on win32 (via g_filename_to/from_uri)
+
+
+http://www.freedesktop.org/wiki/Specifications/file-uri-spec says:
+  non-ASCII chars are %-encoded
+  URIs are in filesystem encoding, NOT UTF-8
+  accept "file:/path" for compat
+http://blogs.msdn.com/ie/archive/2006/12/06/file-uris-in-windows.aspx:
+  IE allows "file://" + windows path (eg, "file://D:\blah blah\bl%h")
+  file://// == file://
+  don't use %-encoding for non-ASCII, just use non-ASCII.
+    (http://en.wikipedia.org/wiki/File_URI_scheme contradicts this?)
+  IE allows "|" instead of ":" for drive separator, but very deprecated
+  MUST NOT use %-encoding where not required
+
+Bug 489862 - Basic URI operations
+https://bugzilla.gnome.org/show_bug.cgi?id=489862
+
+Bug 620417 - g_uri_unescape_string cannot unescape %00
+https://bugzilla.gnome.org/show_bug.cgi?id=620417
+
+Bug 611687 - gconvert g_filename_to_uri doesn't do what the documentation says
+https://bugzilla.gnome.org/show_bug.cgi?id=611687
+
+Bug 550110 - RFC: g_uri_is_valid (from GStreamer gst_uri_is_valid)
+https://bugzilla.gnome.org/show_bug.cgi?id=550110
+
+Add fireball URI matcher
+(make sure it does email addresses too)
+
diff --git a/glib/guri.c b/glib/guri.c
new file mode 100644
index 0000000..6cc3ac1
--- /dev/null
+++ b/glib/guri.c
@@ -0,0 +1,2323 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* GLIB - Library of useful routines for C programming
+ * Copyright 2010-2014 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "glib.h"
+#include "glibintl.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * SECTION:guri
+ * @short_description: URI-handling utilities
+ * @include: glib.h
+ *
+ * The #GUri type and related functions can be used to parse URIs into
+ * their components, and build valid URIs from individual components.
+ *
+ * ## Parsing URIs
+ *
+ * The most minimalist APIs for parsing URIs are g_uri_split() and
+ * g_uri_split_with_user(). These split a URI into its component
+ * parts, and return the parts; the difference between the two is that
+ * g_uri_split() treats the "userinfo" component of the URI as a
+ * single element, while g_uri_split_with_user() can (depending on the
+ * #GUriFlags you pass) treat it as containing a username, password,
+ * and authentication parameters. Alternatively, g_uri_split_network()
+ * can be used when you are only interested in the components that are
+ * needed to initiate a network connection to the service (scheme,
+ * host, and port).
+ *
+ * g_uri_parse() is similar to g_uri_split(), but instead of returning
+ * individual strings, it returns a #GUri structure (and it requires
+ * that the URI be an absolute URI).
+ *
+ * g_uri_resolve_relative() and g_uri_parse_relative() allow you to
+ * resolve a relative URI relative to a base URI.
+ * g_uri_resolve_relative() takes two strings and returns a string,
+ * and g_uri_parse_relative() takes a #GUri and a string and returns a
+ * #GUri.
+ *
+ * All of the parsing functions take a #GUriFlags argument describing
+ * exactly how to parse the URI; see the documentation for that type
+ * for more details on the specific flags that you can pass. If you
+ * need to choose different flags based on the type of URI, you can
+ * use g_uri_peek_scheme() on the URI string to check the scheme
+ * first, and use that to decide what flags to parse it with.
+ *
+ * ## Building URIs
+ *
+ * g_uri_join() and g_uri_join_with_user() can be used to construct
+ * valid URI strings from a set of component strings; they are the
+ * inverse of g_uri_split() and g_uri_split_with_user().
+ *
+ * Similarly, g_uri_build() and g_uri_build_with_user() can be used to
+ * construct a #GUri from a set of component strings.
+ *
+ * As with the parsing functions, the building functions take a
+ * #GUriFlags argument; in particular, it is important to keep in mind
+ * whether the URI components you are using have `%`-encoded
+ * characters in them or not, and pass the appropriate flags
+ * accordingly.
+ *
+ * ## `file://` URIs
+ *
+ * Note that Windows and Unix both define special rules for parsing
+ * `file://` URIs (involving non-UTF-8 character sets on Unix, and the
+ * interpretation of path separators on Windows). #GUri does not
+ * implement these rules. Use g_filename_from_uri() and
+ * g_filename_to_uri() if you want to properly convert between
+ * `file://` URIs and local filenames.
+ *
+ * ## URI Equality
+ *
+ * Note that there is no `g_uri_equal ()` function, because comparing
+ * URIs usefully requires scheme-specific knowledge that #GUri does
+ * not have. For example, "`http://example.com/`"; and
+ * "`http://EXAMPLE.COM:80`"; have exactly the same meaning according
+ * to the HTTP specification, and "`data:,foo`" and
+ * "`data:;base64,Zm9v`" resolve to the same thing according to the
+ * `data:` URI specification.
+ */
+
+/**
+ * GUri:
+ *
+ * A parsed absolute URI.
+ *
+ * Since #GUri only represents absolute URIs, all #GUris will have a
+ * URI scheme, so g_uri_get_scheme() will always return a non-%NULL
+ * answer. Likewise, by definition, all URIs have a path component, so
+ * g_uri_get_path() will always return non-%NULL (though it may return
+ * the empty string).
+ *
+ * If the URI string has an "authority" component (that is, if the
+ * scheme is followed by "`://`" rather than just "`:`"), then the
+ * #GUri will contain a hostname, and possibly a port and "userinfo".
+ * Additionally, depending on how the #GUri was constructed/parsed,
+ * the userinfo may be split out into a username, password, and
+ * additional authorization-related parameters.
+ *
+ * Normally, the components of a #GUri will have all `%`-encoded
+ * characters decoded. However, if you construct/parse a #GUri with
+ * %G_URI_ENCODED, then the `%`-encoding will be preserved instead in
+ * the userinfo, path, and query fields (and in the host field if also
+ * created with %G_URI_NON_DNS). In particular, this is necessary if
+ * the URI may contain binary data or non-UTF-8 text, or if decoding
+ * the components might change the interpretation of the URI.
+ *
+ * Since: 2.44
+ */
+struct _GUri {
+  gchar     *scheme;
+  gchar     *userinfo;
+  gchar     *host;
+  gint       port;
+  gchar     *path;
+  gchar     *query;
+  gchar     *fragment;
+
+  gchar     *user;
+  gchar     *password;
+  gchar     *auth_params;
+
+  GUriFlags  flags;
+  gint       ref_count;
+};
+
+/**
+ * GUriFlags:
+ * @G_URI_PARSE_STRICT: Parse the URI strictly according to the RFC
+ *     3986 grammar, rather than fixing up or ignoring common mistakes.
+ * @G_URI_HAS_PASSWORD: The userinfo field may contain a password,
+ *     which will be separated from the username by ':'.
+ * @G_URI_HAS_AUTH_PARAMS: The userinfo may contain additional
+ *     authentication-related parameters, which will be separated from
+ *     the username and/or password by ';'.
+ * @G_URI_NON_DNS: The host component should not be assumed to be a
+ *     DNS hostname or IP address. (Eg, for `smb` URIs with NetBIOS
+ *     hostnames).
+ * @G_URI_ENCODED: When parsing a URI, this indicates that `%`-encoded
+ *     characters in the userinfo, path, query, and fragment fields
+ *     should not be decoded. (And likewise the host field if
+ *     %G_URI_NON_DNS is also set.) When building a URI, it indicates
+ *     that you have already `%`-encoded the components, and so #GUri
+ *     should not do any encoding itself.
+ *
+ * Flags that describe a URI.
+ *
+ * When parsing a URI, if you need to choose different flags based on
+ * the type of URI, you can use g_uri_peek_scheme() on the URI string
+ * to check the scheme first, and use that to decide what flags to
+ * parse it with.
+ *
+ * Since: 2.44
+ */
+
+/**
+ * g_uri_ref: (skip)
+ * @uri: a #GUri
+ *
+ * Increments the reference count of @uri by one.
+ *
+ * Returns: @uri
+ *
+ * Since: 2.44
+ */
+GUri *
+g_uri_ref (GUri *uri)
+{
+  g_return_val_if_fail (uri != NULL, NULL);
+  g_return_val_if_fail (uri->ref_count > 0, NULL);
+
+  g_atomic_int_inc (&uri->ref_count);
+
+  return uri;
+}
+
+/**
+ * g_uri_unref: (skip)
+ * @uri: a #GUri
+ *
+ * Atomically decrements the reference count of @uri by one.
+ *
+ * When the reference count reaches zero, the resources allocated by
+ * @uri are freed
+ *
+ * Since: 2.44
+ */
+void
+g_uri_unref (GUri *uri)
+{
+  g_return_if_fail (uri != NULL);
+  g_return_if_fail (uri->ref_count > 0);
+
+  if (!g_atomic_int_dec_and_test (&uri->ref_count))
+    return;
+
+  g_free (uri->scheme);
+  g_free (uri->userinfo);
+  g_free (uri->host);
+  g_free (uri->path);
+  g_free (uri->query);
+  g_free (uri->fragment);
+
+  g_free (uri->user);
+  g_free (uri->password);
+  g_free (uri->auth_params);
+
+  g_slice_free (GUri, uri);
+}
+
+static gboolean
+g_uri_char_is_unreserved (gchar ch)
+{
+  if (g_ascii_isalnum (ch))
+    return TRUE;
+  return ch == '-' || ch == '.' || ch == '_' || ch == '~';
+}
+
+#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
+#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2]))
+
+static int
+uri_decoder (gchar       **out,
+             const gchar  *start,
+             gsize         length,
+             gboolean      just_normalize,
+             GUriFlags     flags,
+             GUriError     parse_error,
+             GError      **error)
+{
+  gchar *decoded;
+  guchar *s, *end, *d, c;
+  const gchar *invalid;
+
+  if (!(flags & G_URI_ENCODED))
+    just_normalize = FALSE;
+
+  decoded = g_malloc (length + 1);
+  for (s = (guchar *)start, end = s + length, d = (guchar *)decoded; s < end; s++)
+    {
+      if (*s == '%')
+        {
+          if (!g_ascii_isxdigit (s[1]) ||
+              !g_ascii_isxdigit (s[2]))
+            {
+              /* % followed by non-hex; this is an error */
+              if (flags & G_URI_PARSE_STRICT)
+                {
+                  g_set_error_literal (error, G_URI_ERROR, parse_error,
+                                       _("Invalid %-encoding in URI"));
+                  g_free (decoded);
+                  return -1;
+                }
+
+              /* In non-strict mode, just let it through; we *don't*
+               * fix it to "%25", since that might change the way that
+               * the URI's owner would interpret it.
+               */
+              *d++ = *s;
+              continue;
+            }
+
+          c = HEXCHAR (s);
+          if (just_normalize && !g_uri_char_is_unreserved (c))
+            {
+              /* Leave the % sequence there. */
+              *d++ = *s;
+            }
+          else
+            {
+              *d++ = c;
+              s += 2;
+            }
+        }
+      else
+        *d++ = *s;
+    }
+  *d = '\0';
+
+  if (!g_utf8_validate (decoded, (gchar *)d - decoded, &invalid))
+    {
+      g_set_error_literal (error, G_URI_ERROR, parse_error,
+                           _("Non-UTF-8 characters in URI"));
+      g_free (decoded);
+      return -1;
+    }
+
+  if (out)
+    *out = decoded;
+  else
+    g_free (decoded);
+  return d - (guchar *)decoded;
+}
+
+static gboolean
+uri_decode (gchar       **out,
+            const gchar  *start,
+            gsize         length,
+            GUriFlags     flags,
+            GUriError     parse_error,
+            GError      **error)
+{
+  return uri_decoder (out, start, length, FALSE, flags,
+                      parse_error, error) != -1;
+}
+
+static gboolean
+uri_normalize (gchar       **out,
+               const gchar  *start,
+               gsize         length,
+               GUriFlags     flags,
+               GUriError     parse_error,
+               GError      **error)
+{
+  return uri_decoder (out, start, length, TRUE, flags,
+                      parse_error, error) != -1;
+}
+
+static gboolean
+is_valid (guchar       c,
+          const gchar *reserved_chars_allowed)
+{
+  if (g_uri_char_is_unreserved (c))
+    return TRUE;
+
+  if (reserved_chars_allowed && strchr (reserved_chars_allowed, c))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+uri_encoder (GString      *out,
+             const guchar *start,
+             gsize         length,
+             const gchar  *reserved_chars_allowed,
+             gboolean      allow_utf8)
+{
+  static const gchar hex[16] = "0123456789ABCDEF";
+  const guchar *p = start;
+  const guchar *end = p + length;
+
+  while (p < end)
+    {
+      if (allow_utf8 && *p >= 0x80 &&
+          g_utf8_get_char_validated ((gchar *)p, end - p) > 0)
+        {
+          gint len = g_utf8_skip [*p];
+          g_string_append_len (out, (gchar *)p, len);
+          p += len;
+        }
+      else if (is_valid (*p, reserved_chars_allowed))
+        {
+          g_string_append_c (out, *p);
+          p++;
+        }
+      else
+        {
+          g_string_append_c (out, '%');
+          g_string_append_c (out, hex[*p >> 4]);
+          g_string_append_c (out, hex[*p & 0xf]);
+          p++;
+        }
+    }
+}
+
+static gboolean
+parse_host (const gchar  *start,
+            gsize         length,
+            GUriFlags     flags,
+            gchar       **out,
+            GError      **error)
+{
+  gchar *decoded, *host, *pct;
+  gchar *addr = NULL;
+
+  if (*start == '[')
+    {
+      if (start[length - 1] != ']')
+        {
+        bad_ipv6_literal:
+          g_free (addr);
+          g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_HOST,
+                       _("Invalid IPv6 address '%.*s' in URI"),
+                       (gint)length, start);
+          return FALSE;
+        }
+
+      addr = g_strndup (start + 1, length - 2);
+
+      /* If there's an IPv6 scope id, ignore it for the moment. */
+      pct = strchr (addr, '%');
+      if (pct)
+        *pct = '\0';
+
+      /* addr must be an IPv6 address */
+      if (!g_hostname_is_ip_address (addr) || !strchr (addr, ':'))
+        goto bad_ipv6_literal;
+
+      if (pct)
+        {
+          *pct = '%';
+          if (strchr (pct + 1, '%'))
+            goto bad_ipv6_literal;
+          /* If the '%' is encoded as '%25' (which it should be), decode it */
+          if (pct[1] == '2' && pct[2] == '5' && pct[3])
+            memmove (pct + 1, pct + 3, strlen (pct + 3) + 1);
+        }
+
+      host = addr;
+      goto ok;
+    }
+
+  if (g_ascii_isdigit (*start))
+    {
+      addr = g_strndup (start, length);
+      if (g_hostname_is_ip_address (addr))
+        {
+          host = addr;
+          goto ok;
+        }
+      g_free (addr);
+    }
+
+  if (flags & G_URI_NON_DNS)
+    {
+      if (!uri_normalize (&decoded, start, length, flags,
+                          G_URI_ERROR_BAD_HOST, error))
+        return FALSE;
+      host = decoded;
+      goto ok;
+    }
+
+  flags &= ~G_URI_ENCODED;
+  if (!uri_decode (&decoded, start, length, flags,
+                   G_URI_ERROR_BAD_HOST, error))
+    return FALSE;
+
+  /* You're not allowed to %-encode an IP address, so if it wasn't
+   * one before, it better not be one now.
+   */
+  if (g_hostname_is_ip_address (decoded))
+    {
+      g_free (decoded);
+      g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_HOST,
+                   _("Illegal encoded IP address '%.*s' in URI"),
+                   (gint)length, start);
+      return FALSE;
+    }
+
+  if (g_hostname_is_non_ascii (decoded))
+    {
+      host = g_hostname_to_ascii (decoded);
+      g_free (decoded);
+    }
+  else
+    host = decoded;
+
+ ok:
+  if (out)
+    *out = host;
+  else
+    g_free (host);
+  return TRUE;
+}
+
+static gboolean
+parse_port (const gchar  *start,
+            gsize         length,
+            gint         *out,
+            GError      **error)
+{
+  gchar *end;
+  gulong parsed_port;
+
+  /* strtoul() allows leading + or -, so we have to check this first. */
+  if (!g_ascii_isdigit (*start))
+    {
+      g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_PORT,
+                   _("Could not parse port '%.*s' in URI"),
+                   (gint)length, start);
+      return FALSE;
+    }
+
+  /* We know that *(start + length) is either '\0' or a non-numeric
+   * character, so strtoul() won't scan beyond it.
+   */
+  parsed_port = strtoul (start, &end, 10);
+  if (end != (gchar *)start + length)
+    {
+      g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_PORT,
+                   _("Could not parse port '%.*s' in URI"),
+                   (gint)length, start);
+      return FALSE;
+    }
+  else if (parsed_port > 65535)
+    {
+      g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_PORT,
+                   _("Port '%.*s' in URI is out of range"),
+                   (gint)length, start);
+      return FALSE;
+    }
+
+  if (out)
+    *out = parsed_port;
+  return TRUE;
+}
+
+static gboolean
+parse_userinfo (const gchar  *start,
+                gsize         length,
+                GUriFlags     flags,
+                gchar       **user,
+                gchar       **password,
+                gchar       **auth_params,
+                GError      **error)
+{
+  const gchar *user_end = NULL, *password_end = NULL, *auth_params_end;
+
+  auth_params_end = start + length;
+  if (flags & G_URI_HAS_AUTH_PARAMS)
+    password_end = memchr (start, ';', auth_params_end - start);
+  if (!password_end)
+    password_end = auth_params_end;
+  if (flags & G_URI_HAS_PASSWORD)
+    user_end = memchr (start, ':', password_end - start);
+  if (!user_end)
+    user_end = password_end;
+
+  if (!uri_normalize (user, start, user_end - start, flags,
+                      G_URI_ERROR_BAD_USER, error))
+    return FALSE;
+
+  if (*user_end == ':')
+    {
+      start = user_end + 1;
+      if (!uri_normalize (password, start, password_end - start, flags,
+                          G_URI_ERROR_BAD_PASSWORD, error))
+        {
+          g_clear_pointer (user, g_free);
+          return FALSE;
+        }
+    }
+  else if (password)
+    *password = NULL;
+
+  if (*password_end == ';')
+    {
+      start = password_end + 1;
+      if (!uri_normalize (auth_params, start, auth_params_end - start, flags,
+                          G_URI_ERROR_BAD_AUTH_PARAMS, error))
+        {
+          g_clear_pointer (user, g_free);
+          g_clear_pointer (password, g_free);
+          return FALSE;
+        }
+    }
+  else if (auth_params)
+    *auth_params = NULL;
+
+  return TRUE;
+}
+
+static gchar *
+uri_cleanup (const gchar *uri_string)
+{
+  GString *copy;
+  const gchar *end;
+
+  /* Skip leading whitespace */
+  while (g_ascii_isspace (*uri_string))
+    uri_string++;
+
+  /* Ignore trailing whitespace */
+  end = uri_string + strlen (uri_string);
+  while (end > uri_string && g_ascii_isspace (*(end - 1)))
+    end--;
+
+  /* Copy the rest, encoding unencoded spaces and stripping other whitespace */
+  copy = g_string_sized_new (end - uri_string);
+  while (uri_string < end)
+    {
+      if (*uri_string == ' ')
+        g_string_append (copy, "%20");
+      else if (g_ascii_isspace (*uri_string))
+        ;
+      else
+        g_string_append_c (copy, *uri_string);
+      uri_string++;
+    }
+
+  return g_string_free (copy, FALSE);
+}
+
+static gboolean
+g_uri_split_internal (const gchar  *uri_string,
+                      GUriFlags     flags,
+                      gchar       **scheme,
+                      gchar       **userinfo,
+                      gchar       **user,
+                      gchar       **password,
+                      gchar       **auth_params,
+                      gchar       **host,
+                      gint         *port,
+                      gchar       **path,
+                      gchar       **query,
+                      gchar       **fragment,
+                      GError      **error)
+{
+  const gchar *end, *colon, *at, *path_start, *semi, *question;
+  const gchar *p, *bracket, *hostend;
+  gchar *cleaned_uri_string = NULL;
+
+  if (scheme)
+    *scheme = NULL;
+  if (userinfo)
+    *userinfo = NULL;
+  if (password)
+    *password = NULL;
+  if (auth_params)
+    *auth_params = NULL;
+  if (host)
+    *host = NULL;
+  if (port)
+    *port = -1;
+  if (path)
+    *path = NULL;
+  if (query)
+    *query = NULL;
+  if (fragment)
+    *fragment = NULL;
+
+  if (!(flags & G_URI_PARSE_STRICT) && strpbrk (uri_string, " \t\n\r"))
+    {
+      cleaned_uri_string = uri_cleanup (uri_string);
+      uri_string = cleaned_uri_string;
+    }
+
+  /* Find scheme: initial [a-z+.-]* substring until ":" */
+  p = uri_string;
+  while (*p && (g_ascii_isalnum (*p) ||
+                *p == '.' || *p == '+' || *p == '-'))
+    p++;
+
+  if (p > uri_string && *p == ':')
+    {
+      if (scheme)
+        *scheme = g_ascii_strdown (uri_string, p - uri_string);
+      p++;
+    }
+  else
+    {
+      if (scheme)
+        *scheme = NULL;
+      p = uri_string;
+    }
+
+  /* Check for authority */
+  if (strncmp (p, "//", 2) == 0)
+    {
+      p += 2;
+
+      path_start = p + strcspn (p, "/?#");
+      at = memchr (p, '@', path_start - p);
+      if (at)
+        {
+          if (!(flags & G_URI_PARSE_STRICT))
+            {
+              gchar *next_at;
+
+              /* Any "@"s in the userinfo must be %-encoded, but
+               * people get this wrong sometimes. Since "@"s in the
+               * hostname are unlikely (and also wrong anyway), assume
+               * that if there are extra "@"s, they belong in the
+               * userinfo.
+               */
+              do
+                {
+                  next_at = memchr (at + 1, '@', path_start - (at + 1));
+                  if (next_at)
+                    at = next_at;
+                }
+              while (next_at);
+            }
+
+          if (!uri_normalize (userinfo, p, at - p, flags,
+                              G_URI_ERROR_BAD_USER, error))
+            goto fail;
+
+          if (user || password || auth_params)
+            {
+              if (!parse_userinfo (p, at - p, flags,
+                                   user, password, auth_params,
+                                   error))
+                goto fail;
+            }
+          p = at + 1;
+        }
+
+      if (!(flags & G_URI_PARSE_STRICT))
+        {
+          semi = strchr (p, ';');
+          if (semi && semi < path_start)
+            {
+              /* Technically, semicolons are allowed in the "host"
+               * production, but no one ever does this, and some
+               * schemes mistakenly use semicolon as a delimiter
+               * marking the start of the path. We have to check this
+               * after checking for userinfo though, because a
+               * semicolon before the "@" must be part of the
+               * userinfo.
+               */
+              path_start = semi;
+            }
+        }
+
+      /* Find host and port. The host may be a bracket-delimited IPv6
+       * address, in which case the colon delimiting the port must come
+       * (immediately) after the close bracket.
+       */
+      if (*p == '[')
+        {
+          bracket = memchr (p, ']', path_start - p);
+          if (bracket && *(bracket + 1) == ':')
+            colon = bracket + 1;
+          else
+            colon = NULL;
+        }
+      else
+        colon = memchr (p, ':', path_start - p);
+
+      hostend = colon ? colon : path_start;
+      if (!parse_host (p, hostend - p, flags, host, error))
+        goto fail;
+
+      if (colon && colon != path_start - 1)
+        {
+          p = colon + 1;
+          if (!parse_port (p, path_start - p, port, error))
+            goto fail;
+        }
+
+      p = path_start;
+    }
+
+  /* Find fragment. */
+  end = p + strcspn (p, "#");
+  if (*end == '#')
+    {
+      if (!uri_decode (fragment, end + 1, strlen (end + 1), flags,
+                       G_URI_ERROR_BAD_FRAGMENT, error))
+        goto fail;
+    }
+
+  /* Find query */
+  question = memchr (p, '?', end - p);
+  if (question)
+    {
+      if (!uri_normalize (query, question + 1, end - (question + 1), flags,
+                          G_URI_ERROR_BAD_QUERY, error))
+        goto fail;
+      end = question;
+    }
+
+  if (!uri_normalize (path, p, end - p, flags,
+                      G_URI_ERROR_BAD_PATH, error))
+    goto fail;
+
+  g_free (cleaned_uri_string);
+  return TRUE;
+
+ fail:
+  if (scheme)
+    g_clear_pointer (scheme, g_free);
+  if (userinfo)
+    g_clear_pointer (userinfo, g_free);
+  if (host)
+    g_clear_pointer (host, g_free);
+  if (port)
+    *port = -1;
+  if (path)
+    g_clear_pointer (path, g_free);
+  if (query)
+    g_clear_pointer (query, g_free);
+  if (fragment)
+    g_clear_pointer (fragment, g_free);
+
+  g_free (cleaned_uri_string);
+  return FALSE;
+}
+
+/**
+ * g_uri_split:
+ * @uri_string: a string containing a relative or absolute URI
+ * @flags: flags for parsing @uri_string
+ * @scheme: (out) (allow-none) (transfer full): on return, contains
+ *    the scheme (converted to lowercase), or %NULL
+ * @userinfo: (out) (allow-none) (transfer full): on return, contains
+ *    the userinfo, or %NULL
+ * @host: (out) (allow-none) (transfer full): on return, contains the
+ *    host, or %NULL
+ * @port: (out) (allow-none) (transfer full): on return, contains the
+ *    port, or -1
+ * @path: (out) (allow-none) (transfer full): on return, contains the
+ *    path
+ * @query: (out) (allow-none) (transfer full): on return, contains the
+ *    query, or %NULL
+ * @fragment: (out) (allow-none) (transfer full): on return, contains
+ *    the fragment, or %NULL
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string (which can be an absolute or relative URI)
+ * according to @flags, and returns the pieces. Any component that
+ * doesn't appear in @uri_string will be returned as %NULL (but note
+ * that all URIs always have a path component, though it may be the
+ * empty string).
+ *
+ * If @flags contains %G_URI_ENCODED, then `%`-encoded characters in
+ * @uri_string will remain encoded in the output strings. (If not,
+ * then all such characters will be decoded.) Note that decoding will
+ * only work if the URI components are ASCII or UTF-8, so you will
+ * need to use %G_URI_ENCODED if they are not.
+
+ * Note that the %G_URI_HAS_PASSWORD and %G_URI_HAS_AUTH_PARAMS @flags
+ * are ignored by g_uri_split(), since it always returns only the full
+ * userinfo; use g_uri_split_with_user() if you want it split up.
+ *
+ * Returns: (skip): %TRUE if @uri_string parsed successfully, %FALSE
+ *   on error.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_uri_split (const gchar  *uri_string,
+             GUriFlags     flags,
+             gchar       **scheme,
+             gchar       **userinfo,
+             gchar       **host,
+             gint         *port,
+             gchar       **path,
+             gchar       **query,
+             gchar       **fragment,
+             GError      **error)
+{
+  return g_uri_split_internal (uri_string, flags,
+                               scheme, userinfo, NULL, NULL, NULL,
+                               host, port, path, query, fragment,
+                               error);
+}
+
+/**
+ * g_uri_split_with_user:
+ * @uri_string: a string containing a relative or absolute URI
+ * @flags: flags for parsing @uri_string
+ * @scheme: (out) (allow-none) (transfer full): on return, contains
+ *    the scheme (converted to lowercase), or %NULL
+ * @user: (out) (allow-none) (transfer full): on return, contains
+ *    the user, or %NULL
+ * @password: (out) (allow-none) (transfer full): on return, contains
+ *    the password, or %NULL
+ * @auth_params: (out) (allow-none) (transfer full): on return, contains
+ *    the auth_params, or %NULL
+ * @host: (out) (allow-none) (transfer full): on return, contains the
+ *    host, or %NULL
+ * @port: (out) (allow-none) (transfer full): on return, contains the
+ *    port, or -1
+ * @path: (out) (allow-none) (transfer full): on return, contains the
+ *    path
+ * @query: (out) (allow-none) (transfer full): on return, contains the
+ *    query, or %NULL
+ * @fragment: (out) (allow-none) (transfer full): on return, contains
+ *    the fragment, or %NULL
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string (which can be an absolute or relative URI)
+ * according to @flags, and returns the pieces. Any component that
+ * doesn't appear in @uri_string will be returned as %NULL (but note
+ * that all URIs always have a path component, though it may be the
+ * empty string).
+ *
+ * See g_uri_split(), and the definition of #GUriFlags, for more
+ * information on the effect of @flags. Note that @password will only
+ * be parsed out if @flags contains %G_URI_HAS_PASSWORD, and
+ * @auth_params will only be parsed out if @flags contains
+ * %G_URI_HAS_AUTH_PARAMS.
+ *
+ * Returns: (skip): %TRUE if @uri_string parsed successfully, %FALSE
+ *   on error.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_uri_split_with_user (const gchar  *uri_string,
+                       GUriFlags     flags,
+                       gchar       **scheme,
+                       gchar       **user,
+                       gchar       **password,
+                       gchar       **auth_params,
+                       gchar       **host,
+                       gint         *port,
+                       gchar       **path,
+                       gchar       **query,
+                       gchar       **fragment,
+                       GError      **error)
+{
+  return g_uri_split_internal (uri_string, flags,
+                               scheme, NULL, user, password, auth_params,
+                               host, port, path, query, fragment,
+                               error);
+}
+
+
+/**
+ * g_uri_split_network:
+ * @uri_string: a string containing a relative or absolute URI
+ * @flags: flags for parsing @uri_string
+ * @scheme: (out) (allow-none) (transfer full): on return, contains
+ *    the scheme (converted to lowercase), or %NULL
+ * @host: (out) (allow-none) (transfer full): on return, contains the
+ *    host, or %NULL
+ * @port: (out) (allow-none) (transfer full): on return, contains the
+ *    port, or -1
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string (which must be an absolute URI) according to
+ * @flags, and returns the pieces relevant to connecting to a host.
+ * See the documentation for g_uri_split() for more details; this is
+ * mostly a wrapper around that function with simpler arguments.
+ * However, it will return an error if @uri_string is a relative URI,
+ * or does not contain a hostname component.
+ *
+ * Returns: (skip): %TRUE if @uri_string parsed successfully,
+ *   %FALSE on error.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_uri_split_network (const gchar  *uri_string,
+                     GUriFlags     flags,
+                     gchar       **scheme,
+                     gchar       **host,
+                     gint         *port,
+                     GError      **error)
+{
+  gchar *my_scheme, *my_host;
+
+  if (!g_uri_split_internal (uri_string, flags,
+                             &my_scheme, NULL, NULL, NULL, NULL,
+                             &my_host, port, NULL, NULL, NULL,
+                             error))
+    return FALSE;
+
+  if (!my_scheme || !my_host)
+    {
+      if (!my_scheme)
+        {
+          g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_SCHEME,
+                       _("URI '%s' is not an absolute URI"),
+                       uri_string);
+        }
+      else
+        {
+          g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_HOST,
+                       _("URI '%s' has no host component"),
+                       uri_string);
+        }
+      g_free (my_scheme);
+      g_free (my_host);
+
+      return FALSE;
+    }
+
+  if (scheme)
+    *scheme = my_scheme;
+  else
+    g_free (my_scheme);
+  if (host)
+    *host = my_host;
+  else
+    g_free (my_host);
+  return TRUE;
+}
+
+/**
+ * g_uri_is_valid:
+ * @uri_string: a string containing a relative or absolute URI
+ * @flags: flags for parsing @uri_string
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string (which can be an absolute or relative URI)
+ * according to @flags, to determine whether it is valid.
+ *
+ * See g_uri_split(), and the definition of #GUriFlags, for more
+ * information on the effect of @flags.
+ *
+ * Returns: %TRUE if @uri_string parsed successfully, %FALSE on error.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_uri_is_valid (const gchar  *uri_string,
+                GUriFlags     flags,
+                GError      **error)
+{
+  return g_uri_split_internal (uri_string, flags,
+                               NULL, NULL, NULL, NULL, NULL,
+                               NULL, NULL, NULL, NULL, NULL,
+                               error);
+}
+
+
+/* This does the "Remove Dot Segments" algorithm from section 5.2.4 of
+ * RFC 3986, except that @path is modified in place.
+ */
+static void
+remove_dot_segments (gchar *path)
+{
+  gchar *p, *q;
+
+  /* Remove "./" where "." is a complete segment. */
+  for (p = path + 1; *p; )
+    {
+      if (*(p - 1) == '/' &&
+          *p == '.' && *(p + 1) == '/')
+        memmove (p, p + 2, strlen (p + 2) + 1);
+      else
+        p++;
+    }
+  /* Remove "." at end. */
+  if (p > path + 2 &&
+      *(p - 1) == '.' && *(p - 2) == '/')
+    *(p - 1) = '\0';
+
+  /* Remove "<segment>/../" where <segment> != ".." */
+  for (p = path + 1; *p; )
+    {
+      if (!strncmp (p, "../", 3))
+        {
+          p += 3;
+          continue;
+        }
+      q = strchr (p + 1, '/');
+      if (!q)
+        break;
+      if (strncmp (q, "/../", 4) != 0)
+        {
+          p = q + 1;
+          continue;
+        }
+      memmove (p, q + 4, strlen (q + 4) + 1);
+      p = path + 1;
+    }
+  /* Remove "<segment>/.." at end where <segment> != ".." */
+  q = strrchr (path, '/');
+  if (q && !strcmp (q, "/.."))
+    {
+      p = q - 1;
+      while (p > path && *p != '/')
+        p--;
+      if (strncmp (p, "/../", 4) != 0)
+        *(p + 1) = 0;
+    }
+
+  /* Remove extraneous initial "/.."s */
+  while (!strncmp (path, "/../", 4))
+    memmove (path, path + 3, strlen (path) - 2);
+  if (!strcmp (path, "/.."))
+    path[1] = '\0';
+}
+
+/**
+ * g_uri_parse:
+ * @uri_string: a string representing an absolute URI
+ * @flags: flags describing how to parse @uri_string
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string according to @flags. If the result is not a
+ * valid absolute URI, it will be discarded, and an error returned.
+ *
+ * Return value: (transfer full): a new #GUri.
+ *
+ * Since: 2.44
+ */
+GUri *
+g_uri_parse (const gchar  *uri_string,
+             GUriFlags     flags,
+             GError      **error)
+{
+  return g_uri_parse_relative (NULL, uri_string, flags, error);
+}
+
+/**
+ * g_uri_parse_relative:
+ * @base_uri: (allow-none): a base URI
+ * @uri_string: a string representing a relative or absolute URI
+ * @flags: flags describing how to parse @uri_string
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string according to @flags and, if it is a relative
+ * URI, resolves it relative to @base_uri. If the result is not a
+ * valid absolute URI, it will be discarded, and an error returned.
+ *
+ * Return value: (transfer full): a new #GUri.
+ *
+ * Since: 2.44
+ */
+GUri *
+g_uri_parse_relative (GUri         *base_uri,
+                      const gchar  *uri_string,
+                      GUriFlags     flags,
+                      GError      **error)
+{
+  GUri *uri = NULL;
+
+  if (base_uri && !base_uri->scheme)
+    {
+      g_set_error_literal (error, G_URI_ERROR, G_URI_ERROR_MISC,
+                           _("Base URI is not absolute"));
+      return NULL;
+    }
+
+  uri = g_slice_new0 (GUri);
+  uri->ref_count = 1;
+  uri->flags = flags;
+
+  if (!g_uri_split_internal (uri_string, flags,
+                             &uri->scheme, &uri->userinfo,
+                             &uri->user, &uri->password, &uri->auth_params,
+                             &uri->host, &uri->port,
+                             &uri->path, &uri->query, &uri->fragment,
+                             error))
+    goto fail;
+
+  if (!uri->scheme && !base_uri)
+    {
+      g_set_error_literal (error, G_URI_ERROR, G_URI_ERROR_MISC,
+                           _("URI is not absolute, and no base URI was provided"));
+      goto fail;
+    }
+
+  if (base_uri)
+    {
+      /* This is section 5.2.2 of RFC 3986, except that we're doing
+       * it in place in @uri rather than copying from R to T.
+       */
+      if (uri->scheme)
+        remove_dot_segments (uri->path);
+      else
+        {
+          uri->scheme = g_strdup (base_uri->scheme);
+          if (uri->host)
+            remove_dot_segments (uri->path);
+          else
+            {
+              if (!*uri->path)
+                {
+                  g_free (uri->path);
+                  uri->path = g_strdup (base_uri->path);
+                  if (!uri->query)
+                    uri->query = g_strdup (base_uri->query);
+                }
+              else
+                {
+                  if (*uri->path == '/')
+                    remove_dot_segments (uri->path);
+                  else
+                    {
+                      gchar *newpath, *last;
+
+                      last = strrchr (base_uri->path, '/');
+                      if (last)
+                        {
+                          newpath = g_strdup_printf ("%.*s/%s",
+                                                     (gint)(last - base_uri->path),
+                                                     base_uri->path,
+                                                     uri->path);
+                        }
+                      else
+                        newpath = g_strdup_printf ("/%s", uri->path);
+
+                      g_free (uri->path);
+                      uri->path = newpath;
+
+                      remove_dot_segments (uri->path);
+                    }
+                }
+
+              uri->userinfo = g_strdup (base_uri->userinfo);
+              uri->user = g_strdup (base_uri->user);
+              uri->password = g_strdup (base_uri->password);
+              uri->auth_params = g_strdup (base_uri->auth_params);
+              uri->host = g_strdup (base_uri->host);
+              uri->port = base_uri->port;
+            }
+        }
+    }
+
+  return uri;
+
+ fail:
+  if (uri)
+    g_uri_unref (uri);
+  return NULL;
+}
+
+/**
+ * g_uri_resolve_relative:
+ * @base_uri_string: (allow-none): a string representing a base URI
+ * @uri_string: a string representing a relative or absolute URI
+ * @flags: flags describing how to parse @uri_string
+ * @error: #GError for error reporting, or %NULL to ignore.
+ *
+ * Parses @uri_string according to @flags and, if it is a relative
+ * URI, resolves it relative to @base_uri_string. If the result is not
+ * a valid absolute URI, it will be discarded, and an error returned.
+ *
+ * (If @base_uri_string is %NULL, this just returns @uri_string, or
+ * %NULL if @uri_string is invalid or not absolute.)
+ *
+ * Return value: the resolved URI string.
+ *
+ * Since: 2.44
+ */
+gchar *
+g_uri_resolve_relative (const gchar  *base_uri_string,
+                        const gchar  *uri_string,
+                        GUriFlags     flags,
+                        GError      **error)
+{
+  GUri *base_uri, *resolved_uri;
+  gchar *resolved_uri_string;
+
+  flags |= G_URI_ENCODED;
+
+  if (base_uri_string)
+    {
+      base_uri = g_uri_parse (base_uri_string, flags, error);
+      if (!base_uri)
+        return NULL;
+    }
+  else
+    base_uri = NULL;
+
+  resolved_uri = g_uri_parse_relative (base_uri, uri_string, flags, error);
+  if (base_uri)
+    g_uri_unref (base_uri);
+  if (!resolved_uri)
+    return NULL;
+
+  resolved_uri_string = g_uri_to_string (resolved_uri);
+  g_uri_unref (resolved_uri);
+  return resolved_uri_string;
+}
+
+/* userinfo as a whole can contain sub-delims + ":", but split-out
+ * user can't contain ":" or ";", and split-out password can't contain
+ * ";".
+ */
+#define USERINFO_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO
+#define USER_ALLOWED_CHARS "!$&'()*+,="
+#define PASSWORD_ALLOWED_CHARS "!$&'()*+,=:"
+#define AUTH_PARAMS_ALLOWED_CHARS USERINFO_ALLOWED_CHARS
+#define IP_ADDR_ALLOWED_CHARS ":"
+#define HOST_ALLOWED_CHARS G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS
+#define PATH_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH
+#define QUERY_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?"
+#define FRAGMENT_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?"
+
+static gchar *
+g_uri_join_internal (GUriFlags    flags,
+                     const gchar *scheme,
+                     const gchar *user,
+                     const gchar *password,
+                     const gchar *auth_params,
+                     const gchar *host,
+                     gint         port,
+                     const gchar *path,
+                     const gchar *query,
+                     const gchar *fragment)
+{
+  gboolean encoded = (flags & G_URI_ENCODED);
+  GString *str;
+
+  str = g_string_new (scheme);
+  g_string_append_c (str, ':');
+
+  if (host)
+    {
+      g_string_append (str, "//");
+
+      if (user)
+        {
+          if (encoded)
+            g_string_append (str, user);
+          else
+            {
+              /* Encode ':' and ';' regardless of whether we have a
+               * password or auth params, since it may be parsed later
+               * under the assumption that it does.
+               */
+              g_string_append_uri_escaped (str, user, USER_ALLOWED_CHARS, TRUE);
+            }
+
+          if (password)
+            {
+              g_string_append_c (str, ':');
+              if (encoded)
+                g_string_append (str, password);
+              else
+                g_string_append_uri_escaped (str, user, PASSWORD_ALLOWED_CHARS, TRUE);
+            }
+
+          if (auth_params)
+            {
+              g_string_append_c (str, ';');
+              if (encoded)
+                g_string_append (str, auth_params);
+              else
+                g_string_append_uri_escaped (str, user, AUTH_PARAMS_ALLOWED_CHARS, TRUE);
+            }
+
+          g_string_append_c (str, '@');
+        }
+
+      if (strchr (host, ':'))
+        {
+          g_string_append_c (str, '[');
+          if (encoded)
+            g_string_append (str, host);
+          else
+            g_string_append_uri_escaped (str, host, IP_ADDR_ALLOWED_CHARS, TRUE);
+          g_string_append_c (str, ']');
+        }
+      else
+        {
+          if (encoded)
+            g_string_append (str, host);
+          else
+            g_string_append_uri_escaped (str, host, HOST_ALLOWED_CHARS, TRUE);
+        }
+
+      if (port != -1)
+        g_string_append_printf (str, ":%d", port);
+    }
+
+  if (encoded)
+    g_string_append (str, path);
+  else
+    g_string_append_uri_escaped (str, path, PATH_ALLOWED_CHARS, TRUE);
+
+  if (query)
+    {
+      g_string_append_c (str, '?');
+      if (encoded)
+        g_string_append (str, query);
+      else
+        g_string_append_uri_escaped (str, query, QUERY_ALLOWED_CHARS, TRUE);
+    }
+  if (fragment)
+    {
+      g_string_append_c (str, '#');
+      if (encoded)
+        g_string_append (str, fragment);
+      else
+        g_string_append_uri_escaped (str, fragment, FRAGMENT_ALLOWED_CHARS, TRUE);
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+/**
+ * g_uri_join:
+ * @flags: flags describing how to build the URI string
+ * @scheme: the URI scheme
+ * @userinfo: (allow-none): the userinfo component, or %NULL
+ * @host: (allow-none): the host component, or %NULL
+ * @port: the port, or -1
+ * @path: the path component
+ * @query: (allow-none): the query component, or %NULL
+ * @fragment: (allow-none): the fragment, or %NULL
+ *
+ * Joins the given components together according to @flags to create
+ * a complete URI string. At least @scheme must be specified, and
+ * @path may not be %NULL (though it may be "").
+ *
+ * See also g_uri_join_with_user(), which allows specifying the
+ * components of the "userinfo" separately.
+ *
+ * Return value: a URI string
+ */
+gchar *
+g_uri_join (GUriFlags    flags,
+            const gchar *scheme,
+            const gchar *userinfo,
+            const gchar *host,
+            gint         port,
+            const gchar *path,
+            const gchar *query,
+            const gchar *fragment)
+{
+  g_return_val_if_fail (scheme != NULL, NULL);
+  g_return_val_if_fail (port >= -1 && port <= 65535, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  return g_uri_join_internal (flags,
+                              scheme,
+                              userinfo, NULL, NULL,
+                              host,
+                              port,
+                              path,
+                              query,
+                              fragment);
+}
+
+/**
+ * g_uri_join_with_user:
+ * @flags: flags describing how to build the URI string
+ * @scheme: the URI scheme
+ * @user: (allow-none): the user component of the userinfo, or %NULL
+ * @password: (allow-none): the password component of the userinfo, or
+ *   %NULL
+ * @auth_params: (allow-none): the auth params of the userinfo, or
+ *   %NULL
+ * @host: (allow-none): the host component, or %NULL
+ * @port: the port, or -1
+ * @path: the path component
+ * @query: (allow-none): the query component, or %NULL
+ * @fragment: (allow-none): the fragment, or %NULL
+ *
+ * Joins the given components together according to @flags to create
+ * a complete URI string. At least @scheme must be specified, and
+ * @path may not be %NULL (though it may be "").
+ *
+ * In constrast to g_uri_join(), this allows specifying the components
+ * of the "userinfo" separately.
+ *
+ * Return value: a URI string
+ */
+gchar *
+g_uri_join_with_user (GUriFlags    flags,
+                      const gchar *scheme,
+                      const gchar *user,
+                      const gchar *password,
+                      const gchar *auth_params,
+                      const gchar *host,
+                      gint         port,
+                      const gchar *path,
+                      const gchar *query,
+                      const gchar *fragment)
+{
+  g_return_val_if_fail (scheme != NULL, NULL);
+  g_return_val_if_fail (port >= -1 && port <= 65535, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  return g_uri_join_internal (flags,
+                              scheme,
+                              user, password, auth_params,
+                              host,
+                              port,
+                              path,
+                              query,
+                              fragment);
+}
+
+/**
+ * g_uri_build:
+ * @flags: flags describing how to build the #GUri
+ * @scheme: the URI scheme
+ * @userinfo: the userinfo component, or %NULL
+ * @host: the host component, or %NULL
+ * @port: the port, or -1
+ * @path: the path component
+ * @query: the query component, or %NULL
+ * @fragment: the fragment, or %NULL
+ *
+ * Creates a new #GUri from the given components according to @flags.
+ *
+ * See also g_uri_build_with_user(), which allows specifying the
+ * components of the "userinfo" separately.
+ *
+ * Return value: (transfer full): a new #GUri
+ */
+GUri *
+g_uri_build (GUriFlags    flags,
+             const gchar *scheme,
+             const gchar *userinfo,
+             const gchar *host,
+             gint         port,
+             const gchar *path,
+             const gchar *query,
+             const gchar *fragment)
+{
+  GUri *uri;
+
+  g_return_val_if_fail (scheme != NULL, NULL);
+  g_return_val_if_fail (port >= -1 && port <= 65535, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  uri = g_slice_new0 (GUri);
+  uri->ref_count = 1;
+  uri->flags = flags;
+  uri->scheme = g_ascii_strdown (scheme, -1);
+  uri->userinfo = g_strdup (userinfo);
+  uri->host = g_strdup (host);
+  uri->port = port;
+  uri->path = g_strdup (path);
+  uri->query = g_strdup (query);
+  uri->fragment = g_strdup (fragment);
+
+  return uri;
+}
+
+/**
+ * g_uri_build_with_user:
+ * @flags: flags describing how to build the #GUri
+ * @scheme: the URI scheme
+ * @user: the user component of the userinfo, or %NULL
+ * @password: the password component of the userinfo, or %NULL
+ * @auth_params: the auth params of the userinfo, or %NULL
+ * @host: the host component, or %NULL
+ * @port: the port, or -1
+ * @path: the path component
+ * @query: the query component, or %NULL
+ * @fragment: the fragment, or %NULL
+ *
+ * Creates a new #GUri from the given components according to @flags.
+
+ * In constrast to g_uri_build(), this allows specifying the components
+ * of the "userinfo" field separately. Note that @user must be non-%NULL
+ * if either @password or @auth_params is non-%NULL.
+ *
+ * Return value: (transfer full): a new #GUri
+ *
+ * Since: 2.44
+ */
+GUri *
+g_uri_build_with_user (GUriFlags    flags,
+                       const gchar *scheme,
+                       const gchar *user,
+                       const gchar *password,
+                       const gchar *auth_params,
+                       const gchar *host,
+                       gint         port,
+                       const gchar *path,
+                       const gchar *query,
+                       const gchar *fragment)
+{
+  GUri *uri;
+  GString *userinfo;
+
+  g_return_val_if_fail (scheme != NULL, NULL);
+  g_return_val_if_fail (password == NULL || user != NULL, NULL);
+  g_return_val_if_fail (auth_params == NULL || user != NULL, NULL);
+  g_return_val_if_fail (port >= -1 && port <= 65535, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+
+  uri = g_slice_new0 (GUri);
+  uri->ref_count = 1;
+  uri->flags = flags;
+  uri->scheme = g_ascii_strdown (scheme, -1);
+  uri->user = g_strdup (user);
+  uri->password = g_strdup (password);
+  uri->auth_params = g_strdup (auth_params);
+  uri->host = g_strdup (host);
+  uri->port = port;
+  uri->path = g_strdup (path);
+  uri->query = g_strdup (query);
+  uri->fragment = g_strdup (fragment);
+
+  if (user)
+    {
+      userinfo = g_string_new (NULL);
+      if (flags & G_URI_ENCODED)
+        g_string_append (userinfo, uri->user);
+      else
+        g_string_append_uri_escaped (userinfo, uri->user, USER_ALLOWED_CHARS, TRUE);
+      if (password)
+        {
+          g_string_append_c (userinfo, ':');
+          if (flags & G_URI_ENCODED)
+            g_string_append (userinfo, uri->password);
+          else
+            g_string_append_uri_escaped (userinfo, uri->password, PASSWORD_ALLOWED_CHARS, TRUE);
+        }
+      if (auth_params)
+        {
+          g_string_append_c (userinfo, ';');
+          if (flags & G_URI_ENCODED)
+            g_string_append (userinfo, uri->auth_params);
+          else
+            g_string_append_uri_escaped (userinfo, uri->auth_params, AUTH_PARAMS_ALLOWED_CHARS, TRUE);
+        }
+      uri->userinfo = g_string_free (userinfo, FALSE);
+    }
+  else
+    uri->userinfo = NULL;
+
+  return uri;
+}
+
+/**
+ * GUriHideFlags:
+ * @G_URI_HIDE_USERINFO: Hide the userinfo
+ * @G_URI_HIDE_PASSWORD: Hide the password
+ * @G_URI_HIDE_AUTH_PARAMS: Hide the auth_params
+ * @G_URI_HIDE_FRAGMENT: Hide the fragment
+ *
+ * Flags describing what parts of the URI to hide in
+ * g_uri_to_string_partial(). Note that %G_URI_HIDE_PASSWORD and
+ * %G_URI_HIDE_AUTH_PARAMS will only work if the #GUri was parsed with
+ * the corresponding flags.
+ *
+ * Since: 2.44
+ */
+
+/**
+ * g_uri_to_string:
+ * @uri: a #GUri
+ *
+ * Returns a string representing @uri.
+ *
+ * This is not guaranteed to return a string which is identical to the
+ * string that @uri was parsed from. However, if the source URI was
+ * syntactically correct (according to RFC 3986), and it was parsed
+ * with %G_URI_ENCODED, then g_uri_to_string() is guaranteed to return
+ * a string which is at least semantically equivalent to the source
+ * URI (according to RFC 3986).
+ *
+ * Return value: a string representing @uri, which the caller must
+ * free.
+ *
+ * Since: 2.44
+ */
+gchar *
+g_uri_to_string (GUri *uri)
+{
+  return g_uri_to_string_partial (uri, 0);
+}
+
+/**
+ * g_uri_to_string_partial:
+ * @uri: a #GUri
+ * @flags: flags describing what parts of @uri to hide
+ *
+ * Returns a string representing @uri, subject to the options in
+ * @flags. See g_uri_to_string() and #GUriHideFlags for more details.
+
+ * Return value: a string representing @uri, which the caller must
+ * free.
+ *
+ * Since: 2.44
+ */
+gchar *
+g_uri_to_string_partial (GUri          *uri,
+                         GUriHideFlags  flags)
+{
+  gboolean hide_user = (flags & G_URI_HIDE_USERINFO);
+  gboolean hide_password = (flags & (G_URI_HIDE_USERINFO | G_URI_HIDE_PASSWORD));
+  gboolean hide_auth_params = (flags & (G_URI_HIDE_USERINFO | G_URI_HIDE_AUTH_PARAMS));
+  gboolean hide_fragment = (flags & G_URI_HIDE_FRAGMENT);
+
+  g_return_val_if_fail (uri != NULL, NULL);
+
+  if (uri->flags & (G_URI_HAS_PASSWORD | G_URI_HAS_AUTH_PARAMS))
+    {
+      return g_uri_join_with_user (uri->flags,
+                                   uri->scheme,
+                                   hide_user ? NULL : uri->user,
+                                   hide_password ? NULL : uri->password,
+                                   hide_auth_params ? NULL : uri->auth_params,
+                                   uri->host,
+                                   uri->port,
+                                   uri->path,
+                                   uri->query,
+                                   hide_fragment ? NULL : uri->fragment);
+    }
+  else
+    {
+      return g_uri_join (uri->flags,
+                         uri->scheme,
+                         hide_user ? NULL : uri->userinfo,
+                         uri->host,
+                         uri->port,
+                         uri->path,
+                         uri->query,
+                         hide_fragment ? NULL : uri->fragment);
+    }
+}
+
+/* This is just a copy of g_str_hash() with g_ascii_toupper() added */
+static guint
+str_ascii_case_hash (gconstpointer v)
+{
+  const signed char *p;
+  guint32 h = 5381;
+
+  for (p = v; *p != '\0'; p++)
+    h = (h << 5) + h + g_ascii_toupper (*p);
+
+  return h;
+}
+
+static gboolean
+str_ascii_case_equal (gconstpointer v1,
+                      gconstpointer v2)
+{
+  const gchar *string1 = v1;
+  const gchar *string2 = v2;
+
+  return g_ascii_strcasecmp (string1, string2) == 0;
+}
+
+/**
+ * g_uri_parse_params:
+ * @params: a `%`-encoded string containing "attribute=value"
+ *   parameters
+ * @length: the length of @params, or -1 if it is NUL-terminated
+ * @separator: the separator character between parameters.
+ *   (usually ';', but sometimes '&')
+ * @case_insensitive: whether parameter names are case insensitive
+ *
+ * Many URI schemes include one or more attribute/value pairs
+ * as part of the URI value. This method can be used to parse them
+ * into a hash table.
+ *
+ * The @params string is assumed to still be `%`-encoded, but the
+ * returned values will be fully decoded. (Thus it is possible that
+ * the returned values may contain '=' or @separator, if the value was
+ * encoded in the input.) Invalid `%`-encoding is treated as with the
+ * non-%G_URI_PARSE_STRICT rules for g_uri_new(). (However, if @params
+ * is the path or query string from a #GUri that was parsed with
+ * %G_URI_PARSE_STRICT and %G_URI_ENCODED, then you already know that
+ * it does not contain any invalid encoding.)
+ *
+ * Return value: (transfer full) (element-type utf8 utf8): a hash
+ * table of attribute/value pairs. Both names and values will be
+ * fully-decoded. If @params cannot be parsed (eg, it contains two
+ * @separator characters in a row), then %NULL is returned.
+ *
+ * Since: 2.44
+ */
+GHashTable *
+g_uri_parse_params (const gchar     *params,
+                    gssize           length,
+                    gchar            separator,
+                    gboolean         case_insensitive)
+{
+  GHashTable *hash;
+  const gchar *end, *attr, *attr_end, *value, *value_end;
+  gchar *decoded_attr, *decoded_value;
+
+  if (case_insensitive)
+    {
+      hash = g_hash_table_new_full (str_ascii_case_hash,
+                                    str_ascii_case_equal,
+                                    g_free, g_free);
+    }
+  else
+    {
+      hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                    g_free, g_free);
+    }
+
+  if (length == -1)
+    end = params + strlen (params);
+  else
+    end = params + length;
+
+  attr = params;
+  while (attr < end)
+    {
+      value_end = memchr (attr, separator, end - attr);
+      if (!value_end)
+        value_end = end;
+
+      attr_end = memchr (attr, '=', value_end - attr);
+      if (!attr_end)
+        {
+          g_hash_table_destroy (hash);
+          return NULL;
+        }
+      if (!uri_decode (&decoded_attr, attr, attr_end - attr,
+                       0, G_URI_ERROR_MISC, NULL))
+        {
+          g_hash_table_destroy (hash);
+          return NULL;
+        }
+
+      value = attr_end + 1;
+      if (!uri_decode (&decoded_value, value, value_end - value,
+                       0, G_URI_ERROR_MISC, NULL))
+        {
+          g_free (decoded_attr);
+          g_hash_table_destroy (hash);
+          return NULL;
+        }
+
+      g_hash_table_insert (hash, decoded_attr, decoded_value);
+      attr = value_end + 1;
+    }
+
+  return hash;
+}
+
+/**
+ * g_uri_get_scheme:
+ * @uri: a #GUri
+ *
+ * Gets @uri's scheme. Note that this will always be all-lowercase,
+ * regardless of the string or strings that @uri was created from.
+ *
+ * Return value: @uri's scheme.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_scheme (GUri *uri)
+{
+  return uri->scheme;
+}
+
+/**
+ * g_uri_get_userinfo:
+ * @uri: a #GUri
+ *
+ * Gets @uri's userinfo, which may contain `%`-encoding, depending on
+ * the flags with which @uri was created.
+ *
+ * Return value: @uri's userinfo.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_userinfo (GUri *uri)
+{
+  return uri->userinfo;
+}
+
+/**
+ * g_uri_get_user:
+ * @uri: a #GUri
+ *
+ * Gets the "username" component of @uri's userinfo, which may contain
+ * `%`-encoding, depending on the flags with which @uri was created.
+ * If @uri was not created with %G_URI_HAS_PASSWORD or
+ * %G_URI_HAS_AUTH_PARAMS, this is the same as g_uri_get_userinfo().
+ *
+ * Return value: @uri's user.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_user (GUri *uri)
+{
+  return uri->user;
+}
+
+/**
+ * g_uri_get_password:
+ * @uri: a #GUri
+ *
+ * Gets @uri's password, which may contain `%`-encoding, depending on
+ * the flags with which @uri was created. (If @uri was not created
+ * with %G_URI_HAS_PASSWORD then this will be %NULL.)
+ *
+ * Return value: @uri's password.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_password (GUri *uri)
+{
+  return uri->password;
+}
+
+/**
+ * g_uri_get_auth_params:
+ * @uri: a #GUri
+ *
+ * Gets @uri's authentication parameters, which may contain
+ * `%`-encoding, depending on the flags with which @uri was created.
+ * (If @uri was not created with %G_URI_HAS_AUTH_PARAMS then this will
+ * be %NULL.)
+ *
+ * Depending on the URI scheme, g_uri_parse_params() may be useful for
+ * further parsing this information.
+ *
+ * Return value: @uri's authentication parameters.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_auth_params (GUri *uri)
+{
+  return uri->auth_params;
+}
+
+/**
+ * g_uri_get_host:
+ * @uri: a #GUri
+ *
+ * Gets @uri's host. This will never have `%`-encoded characters,
+ * unless it is non-UTF-8 (which can only be the case if @uri was
+ * created with %G_URI_NON_DNS).
+ *
+ * If @uri contained an IPv6 address literal, this value will be just
+ * that address, without the brackets around it that are necessary in
+ * the string form of the URI. Note that in this case there may also
+ * be a scope ID attached to the address. Eg, "`fe80::1234%``em1`" (or
+ * "`fe80::1234%``25em1" if the string is still encoded).
+ *
+ * Return value: @uri's host.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_host (GUri *uri)
+{
+  return uri->host;
+}
+
+/**
+ * g_uri_get_port:
+ * @uri: a #GUri
+ *
+ * Gets @uri's port.
+ *
+ * Return value: @uri's port, or -1 if no port was specified.
+ *
+ * Since: 2.44
+ */
+gint
+g_uri_get_port (GUri *uri)
+{
+  return uri->port;
+}
+
+/**
+ * g_uri_get_path:
+ * @uri: a #GUri
+ *
+ * Gets @uri's path, which may contain `%`-encoding, depending on the
+ * flags with which @uri was created.
+ *
+ * Return value: @uri's path.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_path (GUri *uri)
+{
+  return uri->path;
+}
+
+/**
+ * g_uri_get_query:
+ * @uri: a #GUri
+ *
+ * Gets @uri's query, which may contain `%`-encoding, depending on the
+ * flags with which @uri was created.
+ *
+ * For queries consisting of a series of "`name=value`" parameters,
+ * g_uri_parse_params() may be useful.
+ *
+ * Return value: @uri's query.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_query (GUri *uri)
+{
+  return uri->query;
+}
+
+/**
+ * g_uri_get_fragment:
+ * @uri: a #GUri
+ *
+ * Gets @uri's fragment, which may contain `%`-encoding, depending on
+ * the flags with which @uri was created.
+ *
+ * Return value: @uri's fragment.
+ *
+ * Since: 2.44
+ */
+const gchar *
+g_uri_get_fragment (GUri *uri)
+{
+  return uri->fragment;
+}
+
+
+/**
+ * g_uri_unescape_segment:
+ * @escaped_string: (allow-none): A string, may be %NULL
+ * @escaped_string_end: (allow-none): Pointer to end of @escaped_string,
+ *   may be %NULL
+ * @illegal_characters: (allow-none): An optional string of illegal
+ *   characters not to be allowed, may be %NULL
+ *
+ * Unescapes a segment of an escaped string.
+ *
+ * If any of the characters in @illegal_characters or the NUL
+ * character appears as an escaped character in @escaped_string, then
+ * that is an error and %NULL will be returned. This is useful if you
+ * want to avoid for instance having a slash being expanded in an
+ * escaped path element, which might confuse pathname handling.
+ *
+ * Returns: an unescaped version of @escaped_string or %NULL on error.
+ * The returned string should be freed when no longer needed.  As a
+ * special case if %NULL is given for @escaped_string, this function
+ * will return %NULL.
+ *
+ * Since: 2.16
+ **/
+gchar *
+g_uri_unescape_segment (const gchar *escaped_string,
+                       const gchar *escaped_string_end,
+                       const gchar *illegal_characters)
+{
+  gchar *unescaped, *p;
+  gsize length;
+
+  if (!escaped_string)
+    return NULL;
+
+  if (escaped_string_end)
+    length = escaped_string_end - escaped_string;
+  else
+    length = strlen (escaped_string);
+
+  if (!uri_decode (&unescaped,
+                   escaped_string, length,
+                   G_URI_PARSE_STRICT,
+                   0, NULL))
+    return NULL;
+
+  if (illegal_characters)
+    {
+      for (p = unescaped; *p; p++)
+        {
+          if (strchr (illegal_characters, *p))
+            {
+              g_free (unescaped);
+              return NULL;
+            }
+        }
+    }
+
+  return unescaped;
+}
+
+/**
+ * g_uri_unescape_string:
+ * @escaped_string: an escaped string to be unescaped.
+ * @illegal_characters: (allow-none): a string of illegal characters
+ *   not to be allowed, or %NULL.
+ *
+ * Unescapes a whole escaped string.
+ *
+ * If any of the characters in @illegal_characters or the NUL
+ * character appears as an escaped character in @escaped_string, then
+ * that is an error and %NULL will be returned. This is useful if you
+ * want to avoid for instance having a slash being expanded in an
+ * escaped path element, which might confuse pathname handling.
+ *
+ * Returns: an unescaped version of @escaped_string. The returned string
+ * should be freed when no longer needed.
+ *
+ * Since: 2.16
+ **/
+gchar *
+g_uri_unescape_string (const gchar *escaped_string,
+                      const gchar *illegal_characters)
+{
+  return g_uri_unescape_segment (escaped_string, NULL, illegal_characters);
+}
+
+/**
+ * g_string_append_uri_escaped:
+ * @string: a #GString
+ * @unescaped: a string
+ * @reserved_chars_allowed: a string of reserved characters allowed
+ *     to be used, or %NULL
+ * @allow_utf8: set %TRUE if the escaped string may include UTF8 characters
+ *
+ * Appends @unescaped to @string, escaping any characters that
+ * are reserved in URIs using URI-style escape sequences.
+ *
+ * Returns: @string
+ *
+ * Since: 2.16
+ */
+GString *
+g_string_append_uri_escaped (GString     *string,
+                             const gchar *unescaped,
+                             const gchar *reserved_chars_allowed,
+                             gboolean     allow_utf8)
+{
+  uri_encoder (string, (const guchar *) unescaped, strlen (unescaped),
+               reserved_chars_allowed, allow_utf8);
+  return string;
+}
+
+/**
+ * g_uri_escape_string:
+ * @unescaped: the unescaped input string.
+ * @reserved_chars_allowed: (allow-none): a string of reserved
+ *   characters that are allowed to be used, or %NULL.
+ * @allow_utf8: %TRUE if the result can include UTF-8 characters.
+ *
+ * Escapes a string for use in a URI.
+ *
+ * Normally all characters that are not "unreserved" (i.e. ASCII
+ * alphanumerical characters plus dash, dot, underscore and tilde) are
+ * escaped. But if you specify characters in @reserved_chars_allowed
+ * they are not escaped. This is useful for the "reserved" characters
+ * in the URI specification, since those are allowed unescaped in some
+ * portions of a URI.
+ *
+ * Returns: an escaped version of @unescaped. The returned string
+ * should be freed when no longer needed.
+ *
+ * Since: 2.16
+ **/
+gchar *
+g_uri_escape_string (const gchar *unescaped,
+                    const gchar *reserved_chars_allowed,
+                    gboolean     allow_utf8)
+{
+  GString *s;
+
+  g_return_val_if_fail (unescaped != NULL, NULL);
+
+  s = g_string_sized_new (strlen (unescaped) * 1.25);
+
+  g_string_append_uri_escaped (s, unescaped, reserved_chars_allowed, allow_utf8);
+
+  return g_string_free (s, FALSE);
+}
+
+/**
+ * g_uri_unescape_bytes:
+ * @escaped_string: A URI-escaped string
+ * @length: the length of @escaped_string to escape, or -1 if it
+ *   is NUL-terminated.
+ *
+ * Unescapes a segment of an escaped string as binary data.
+ *
+ * Note that in contrast to g_uri_unescape_string(), this does allow
+ * `NUL` bytes to appear in the output.
+ *
+ * Returns: (transfer full): an unescaped version of @escaped_string
+ * or %NULL on error. The returned #GBytes should be unreffed when no
+ * longer needed.
+ *
+ * Since: 2.44
+ **/
+GBytes *
+g_uri_unescape_bytes (const gchar *escaped_string,
+                      gssize       length,
+                      const gchar *illegal_characters)
+{
+  gchar *buf;
+  gssize unescaped_length;
+
+  if (length == -1)
+    length = strlen (escaped_string);
+
+  unescaped_length = uri_decoder (&buf,
+                                  escaped_string, length,
+                                  FALSE, G_URI_PARSE_STRICT,
+                                  0, NULL);
+  if (unescaped_length == -1)
+    return NULL;
+  else
+    return g_bytes_new_take (buf, unescaped_length);
+}
+
+/**
+ * g_uri_escape_bytes:
+ * @unescaped: (array length=length): the unescaped input data.
+ * @length: the length of @unescaped
+ * @reserved_chars_allowed: (allow-none): a string of reserved
+ *   characters that are allowed to be used, or %NULL.
+ *
+ * Escapes arbitrary data for use in a URI.
+ *
+ * Normally all characters that are not "unreserved" (i.e. ASCII
+ * alphanumerical characters plus dash, dot, underscore and tilde) are
+ * escaped. But if you specify characters in @reserved_chars_allowed
+ * they are not escaped. This is useful for the "reserved" characters
+ * in the URI specification, since those are allowed unescaped in some
+ * portions of a URI.
+ *
+ * Though technically incorrect, this will also allow escaping "0"
+ * bytes as "`%``00`".
+ *
+ * Returns: an escaped version of @unescaped. The returned string
+ * should be freed when no longer needed.
+ *
+ * Since: 2.44
+ */
+gchar *
+g_uri_escape_bytes (const guchar *unescaped,
+                    gsize         length,
+                    const gchar  *reserved_chars_allowed)
+{
+  GString *string;
+
+  g_return_val_if_fail (unescaped != NULL, NULL);
+
+  string = g_string_sized_new (length * 1.25);
+
+  uri_encoder (string, unescaped, length,
+               reserved_chars_allowed, FALSE);
+
+  return g_string_free (string, FALSE);
+}
+
+static gint
+g_uri_scheme_length (const gchar *uri)
+{
+  const gchar *p;
+
+  p = uri;
+  if (!g_ascii_isalpha (*p))
+    return -1;
+  p++;
+  while (g_ascii_isalnum (*p) || *p == '.' || *p == '+' || *p == '-')
+    p++;
+
+  if (p > uri && *p == ':')
+    return p - uri;
+  else
+    return -1;
+}
+
+/**
+ * g_uri_parse_scheme:
+ * @uri: a valid URI.
+ *
+ * Gets the scheme portion of a URI string. RFC 3986 decodes the scheme as:
+ * |[
+ * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ * ]|
+ * Common schemes include "file", "http", "svn+ssh", etc.
+ *
+ * Returns: The "scheme" component of the URI, or %NULL on error.
+ * The returned string should be freed when no longer needed.
+ *
+ * Since: 2.16
+ *
+ * Deprecated: Use g_uri_peek_scheme().
+ **/
+gchar *
+g_uri_parse_scheme (const gchar *uri)
+{
+  gint len;
+
+  g_return_val_if_fail (uri != NULL, NULL);
+
+  len = g_uri_scheme_length (uri);
+  return len == -1 ? NULL : g_strndup (uri, len);
+}
+
+/**
+ * g_uri_peek_scheme:
+ * @uri: a valid URI.
+ *
+ * Gets the scheme portion of a URI string. RFC 3986 decodes the scheme as:
+ * |[
+ * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ * ]|
+ * Common schemes include "file", "http", "svn+ssh", etc.
+ *
+ * Returns: The "scheme" component of the URI, or %NULL on error. The
+ * returned string is normalized to all-lowercase, and interned via
+ * g_intern_string(), so it does not need to be freed.
+ *
+ * Since: 2.44
+ **/
+const gchar *
+g_uri_peek_scheme (const gchar *uri)
+{
+  gint len;
+  gchar *lower_scheme;
+  const gchar *scheme;
+
+  g_return_val_if_fail (uri != NULL, NULL);
+
+  len = g_uri_scheme_length (uri);
+  if (len == -1)
+    return NULL;
+
+  lower_scheme = g_ascii_strdown (uri, len);
+  scheme = g_intern_string (lower_scheme);
+  g_free (lower_scheme);
+
+  return scheme;
+}
+
+G_DEFINE_QUARK (g-uri-quark, g_uri_error)
diff --git a/glib/guri.h b/glib/guri.h
new file mode 100644
index 0000000..85d9eb9
--- /dev/null
+++ b/glib/guri.h
@@ -0,0 +1,286 @@
+/* GLIB - Library of useful routines for C programming
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright 2010-2013 Red Hat, Inc.
+ */
+
+#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+#ifndef __G_URI_H__
+#define __G_URI_H__
+
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GUri GUri;
+
+GLIB_AVAILABLE_IN_2_44
+GUri *       g_uri_ref              (GUri *uri);
+GLIB_AVAILABLE_IN_2_44
+void         g_uri_unref            (GUri *uri);
+
+typedef enum {
+  G_URI_PARSE_STRICT    = 1 << 0,
+  G_URI_HAS_PASSWORD    = 1 << 1,
+  G_URI_HAS_AUTH_PARAMS = 1 << 2,
+  G_URI_ENCODED         = 1 << 3,
+  G_URI_NON_DNS         = 1 << 4,
+} GUriFlags;
+
+GLIB_AVAILABLE_IN_2_44
+gboolean     g_uri_split            (const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     gchar       **scheme,
+                                     gchar       **userinfo,
+                                     gchar       **host,
+                                     gint         *port,
+                                     gchar       **path,
+                                     gchar       **query,
+                                     gchar       **fragment,
+                                     GError      **error);
+GLIB_AVAILABLE_IN_2_44
+gboolean     g_uri_split_with_user  (const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     gchar       **scheme,
+                                     gchar       **user,
+                                     gchar       **password,
+                                     gchar       **auth_params,
+                                     gchar       **host,
+                                     gint         *port,
+                                     gchar       **path,
+                                     gchar       **query,
+                                     gchar       **fragment,
+                                     GError      **error);
+GLIB_AVAILABLE_IN_2_44
+gboolean     g_uri_split_network    (const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     gchar       **scheme,
+                                     gchar       **host,
+                                     gint         *port,
+                                     GError      **error);
+
+GLIB_AVAILABLE_IN_2_44
+gboolean     g_uri_is_valid         (const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     GError      **error);
+
+GLIB_AVAILABLE_IN_2_44
+gchar *      g_uri_join             (GUriFlags     flags,
+                                     const gchar  *scheme,
+                                     const gchar  *userinfo,
+                                     const gchar  *host,
+                                     gint          port,
+                                     const gchar  *path,
+                                     const gchar  *query,
+                                     const gchar  *fragment);
+GLIB_AVAILABLE_IN_2_44
+gchar *      g_uri_join_with_user   (GUriFlags     flags,
+                                     const gchar  *scheme,
+                                     const gchar  *user,
+                                     const gchar  *password,
+                                     const gchar  *auth_params,
+                                     const gchar  *host,
+                                     gint          port,
+                                     const gchar  *path,
+                                     const gchar  *query,
+                                     const gchar  *fragment);
+
+GLIB_AVAILABLE_IN_2_44
+GUri *       g_uri_parse            (const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     GError      **error);
+GLIB_AVAILABLE_IN_2_44
+GUri *       g_uri_parse_relative   (GUri         *base_uri,
+                                     const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     GError      **error);
+
+GLIB_AVAILABLE_IN_2_44
+gchar *      g_uri_resolve_relative (const gchar  *base_uri_string,
+                                     const gchar  *uri_string,
+                                     GUriFlags     flags,
+                                     GError      **error);
+
+GLIB_AVAILABLE_IN_2_44
+GUri *       g_uri_build            (GUriFlags     flags,
+                                     const gchar  *scheme,
+                                     const gchar  *userinfo,
+                                     const gchar  *host,
+                                     gint          port,
+                                     const gchar  *path,
+                                     const gchar  *query,
+                                     const gchar  *fragment);
+GLIB_AVAILABLE_IN_2_44
+GUri *       g_uri_build_with_user  (GUriFlags     flags,
+                                     const gchar  *scheme,
+                                     const gchar  *user,
+                                     const gchar  *password,
+                                     const gchar  *auth_params,
+                                     const gchar  *host,
+                                     gint          port,
+                                     const gchar  *path,
+                                     const gchar  *query,
+                                     const gchar  *fragment);
+
+typedef enum {
+  G_URI_HIDE_USERINFO    = 1 << 0,
+  G_URI_HIDE_PASSWORD    = 1 << 1,
+  G_URI_HIDE_AUTH_PARAMS = 1 << 2,
+  G_URI_HIDE_FRAGMENT    = 1 << 3,
+} GUriHideFlags;
+
+GLIB_AVAILABLE_IN_2_44
+char *       g_uri_to_string         (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+char *       g_uri_to_string_partial (GUri          *uri,
+                                      GUriHideFlags  flags);
+
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_scheme        (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_userinfo      (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_user          (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_password      (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_auth_params   (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_host          (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+gint         g_uri_get_port          (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_path          (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_query         (GUri          *uri);
+GLIB_AVAILABLE_IN_2_44
+const gchar *g_uri_get_fragment      (GUri          *uri);
+
+GLIB_AVAILABLE_IN_2_44
+GHashTable * g_uri_parse_params      (const gchar   *params,
+                                      gssize         length,
+                                      gchar          separator,
+                                      gboolean       case_insensitive);
+
+/**
+ * G_URI_ERROR:
+ *
+ * Error domain for URI methods. Errors in this domain will be from
+ * the #GUriError enumeration. See #GError for information on error
+ * domains.
+ */
+#define G_URI_ERROR (g_uri_error_quark ())
+GLIB_AVAILABLE_IN_2_44
+GQuark g_uri_error_quark (void);
+
+/**
+ * GUriError:
+ * @G_URI_ERROR_MISC: miscellaneous error
+ * @G_URI_ERROR_BAD_SCHEME: the scheme of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_USER: the user/userinfo of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_PASSWORD: the password of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_AUTH_PARAMS: the authentication parameters of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_HOST: the host of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_PORT: the port of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_PATH: the path of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_QUERY: the query of a URI could not be parsed.
+ * @G_URI_ERROR_BAD_FRAGMENT: the fragment of a URI could not be parsed.
+ *
+ * Error codes returned by #GUri methods.
+ */
+typedef enum
+{
+  G_URI_ERROR_MISC,
+  G_URI_ERROR_BAD_SCHEME,
+  G_URI_ERROR_BAD_USER,
+  G_URI_ERROR_BAD_PASSWORD,
+  G_URI_ERROR_BAD_AUTH_PARAMS,
+  G_URI_ERROR_BAD_HOST,
+  G_URI_ERROR_BAD_PORT,
+  G_URI_ERROR_BAD_PATH,
+  G_URI_ERROR_BAD_QUERY,
+  G_URI_ERROR_BAD_FRAGMENT
+} GUriError;
+
+/**
+ * G_URI_RESERVED_CHARS_GENERIC_DELIMITERS:
+ * 
+ * Generic delimiters characters as defined in RFC 3986. Includes ":/?#[]@".
+ **/
+#define G_URI_RESERVED_CHARS_GENERIC_DELIMITERS ":/?#[]@"
+
+/**
+ * G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS:
+ * 
+ * Subcomponent delimiter characters as defined in RFC 3986. Includes "!$&'()*+,;=".
+ **/
+#define G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS "!$&'()*+,;="
+
+/**
+ * G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT:
+ * 
+ * Allowed characters in path elements. Includes "!$&'()*+,;=:@".
+ **/
+#define G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":@"
+
+/**
+ * G_URI_RESERVED_CHARS_ALLOWED_IN_PATH:
+ * 
+ * Allowed characters in a path. Includes "!$&'()*+,;=:@/".
+ **/
+#define G_URI_RESERVED_CHARS_ALLOWED_IN_PATH G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT "/"
+
+/**
+ * G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO:
+ * 
+ * Allowed characters in userinfo as defined in RFC 3986. Includes "!$&'()*+,;=:".
+ **/
+#define G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":"
+
+GLIB_AVAILABLE_IN_ALL
+char *      g_uri_unescape_string  (const char *escaped_string,
+                                   const char *illegal_characters);
+GLIB_AVAILABLE_IN_ALL
+char *      g_uri_unescape_segment (const char *escaped_string,
+                                   const char *escaped_string_end,
+                                   const char *illegal_characters);
+
+GLIB_DEPRECATED_IN_2_44_FOR(g_uri_peek_scheme)
+char *      g_uri_parse_scheme     (const char *uri);
+GLIB_AVAILABLE_IN_2_44
+const char *g_uri_peek_scheme      (const char *uri);
+
+GLIB_AVAILABLE_IN_ALL
+char *      g_uri_escape_string    (const char *unescaped,
+                                    const char *reserved_chars_allowed,
+                                    gboolean    allow_utf8);
+
+GLIB_AVAILABLE_IN_2_44
+GBytes *    g_uri_unescape_bytes   (const char *escaped_string,
+                                    gssize      length,
+                                   const char *illegal_characters);
+GLIB_AVAILABLE_IN_2_44
+char *      g_uri_escape_bytes     (const guchar *unescaped,
+                                    gsize         length,
+                                    const char   *reserved_chars_allowed);
+
+G_END_DECLS
+
+#endif /* __G_URI_H__ */
diff --git a/glib/tests/uri.c b/glib/tests/uri.c
index 83a2f10..1d034d0 100644
--- a/glib/tests/uri.c
+++ b/glib/tests/uri.c
@@ -33,10 +33,10 @@ typedef struct
   char *hostname;
   char *expected_result;
   GConvertError expected_error; /* If failed */
-}  ToUriTest;
+} FileToUriTest;
 
-ToUriTest
-to_uri_tests[] = {
+FileToUriTest
+file_to_uri_tests[] = {
   { "/etc", NULL, "file:///etc"},
   { "/etc", "", "file:///etc"},
   { "/etc", "otherhost", "file://otherhost/etc"},
@@ -101,10 +101,10 @@ typedef struct
   char *expected_filename;
   char *expected_hostname;
   GConvertError expected_error; /* If failed */
-}  FromUriTest;
+} FileFromUriTest;
 
-FromUriTest
-from_uri_tests[] = {
+FileFromUriTest
+file_from_uri_tests[] = {
   { "file:///etc", "/etc"},
   { "file:/etc", "/etc"},
 #ifdef G_OS_WIN32
@@ -163,23 +163,23 @@ from_uri_tests[] = {
 };
 
 static void
-run_to_uri_tests (void)
+run_file_to_uri_tests (void)
 {
   int i;
   gchar *res;
   GError *error;
 
-  for (i = 0; i < G_N_ELEMENTS (to_uri_tests); i++)
+  for (i = 0; i < G_N_ELEMENTS (file_to_uri_tests); i++)
     {
       error = NULL;
-      res = g_filename_to_uri (to_uri_tests[i].filename,
-                               to_uri_tests[i].hostname,
+      res = g_filename_to_uri (file_to_uri_tests[i].filename,
+                               file_to_uri_tests[i].hostname,
                                &error);
 
       if (res)
-        g_assert_cmpstr (res, ==, to_uri_tests[i].expected_result);
+        g_assert_cmpstr (res, ==, file_to_uri_tests[i].expected_result);
       else
-        g_assert_error (error, G_CONVERT_ERROR, to_uri_tests[i].expected_error);
+        g_assert_error (error, G_CONVERT_ERROR, file_to_uri_tests[i].expected_error);
 
       g_free (res);
       g_clear_error (&error);
@@ -187,25 +187,26 @@ run_to_uri_tests (void)
 }
 
 static void
-run_from_uri_tests (void)
+run_file_from_uri_tests (void)
 {
   int i;
   gchar *res;
   gchar *hostname;
   GError *error;
 
-  for (i = 0; i < G_N_ELEMENTS (from_uri_tests); i++)
+  for (i = 0; i < G_N_ELEMENTS (file_from_uri_tests); i++)
     {
       error = NULL;
-      res = g_filename_from_uri (from_uri_tests[i].uri,
+      res = g_filename_from_uri (file_from_uri_tests[i].uri,
                                  &hostname,
                                  &error);
 
 #ifdef G_OS_WIN32
-      if (from_uri_tests[i].expected_filename)
+      if (file_from_uri_tests[i].expected_filename)
         {
           gchar *p, *slash;
-          p = from_uri_tests[i].expected_filename = g_strdup (from_uri_tests[i].expected_filename);
+          p = file_from_uri_tests[i].expected_filename =
+            g_strdup (file_from_uri_tests[i].expected_filename);
           while ((slash = strchr (p, '/')) != NULL)
             {
               *slash = '\\';
@@ -214,10 +215,10 @@ run_from_uri_tests (void)
         }
 #endif
       if (res)
-        g_assert_cmpstr (res, ==, from_uri_tests[i].expected_filename);
+        g_assert_cmpstr (res, ==, file_from_uri_tests[i].expected_filename);
       else
-        g_assert_error (error, G_CONVERT_ERROR, from_uri_tests[i].expected_error);
-      g_assert_cmpstr (hostname, ==, from_uri_tests[i].expected_hostname);
+        g_assert_error (error, G_CONVERT_ERROR, file_from_uri_tests[i].expected_error);
+      g_assert_cmpstr (hostname, ==, file_from_uri_tests[i].expected_hostname);
 
       g_free (res);
       g_free (hostname);
@@ -266,20 +267,20 @@ safe_strcmp_hostname (const gchar *a, const gchar *b)
 }
 
 static void
-run_roundtrip_tests (void)
+run_file_roundtrip_tests (void)
 {
   int i;
   gchar *uri, *hostname, *res;
   GError *error;
 
-  for (i = 0; i < G_N_ELEMENTS (to_uri_tests); i++)
+  for (i = 0; i < G_N_ELEMENTS (file_to_uri_tests); i++)
     {
-      if (to_uri_tests[i].expected_error != 0)
+      if (file_to_uri_tests[i].expected_error != 0)
         continue;
 
       error = NULL;
-      uri = g_filename_to_uri (to_uri_tests[i].filename,
-                               to_uri_tests[i].hostname,
+      uri = g_filename_to_uri (file_to_uri_tests[i].filename,
+                               file_to_uri_tests[i].hostname,
                                &error);
       g_assert_no_error (error);
 
@@ -287,8 +288,8 @@ run_roundtrip_tests (void)
       res = g_filename_from_uri (uri, &hostname, &error);
       g_assert_no_error (error);
 
-      g_assert (safe_strcmp_filename (to_uri_tests[i].filename, res) == 0);
-      g_assert (safe_strcmp_hostname (to_uri_tests[i].hostname, hostname) == 0);
+      g_assert (safe_strcmp_filename (file_to_uri_tests[i].filename, res) == 0);
+      g_assert (safe_strcmp_hostname (file_to_uri_tests[i].hostname, hostname) == 0);
       g_free (res);
       g_free (uri);
       g_free (hostname);
@@ -364,15 +365,391 @@ test_uri_escape (void)
 static void
 test_uri_scheme (void)
 {
+  const gchar *s1, *s2;
   gchar *s;
 
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
   s = g_uri_parse_scheme ("ftp://ftp.gtk.org";);
   g_assert_cmpstr (s, ==, "ftp");
   g_free (s);
   s = g_uri_parse_scheme ("1bad:");
-  g_assert (s == NULL);
+  g_assert_cmpstr (s, ==, NULL);
   s = g_uri_parse_scheme ("bad");
-  g_assert (s == NULL);
+  g_assert_cmpstr (s, ==, NULL);
+  G_GNUC_END_IGNORE_DEPRECATIONS;
+
+  s1 = g_uri_peek_scheme ("ftp://ftp.gtk.org";);
+  g_assert_cmpstr (s1, ==, "ftp");
+  s2 = g_uri_peek_scheme ("FTP://ftp.gtk.org");
+  g_assert_cmpstr (s2, ==, "ftp");
+  g_assert_true (s1 == s2);
+  s1 = g_uri_peek_scheme ("1bad:");
+  g_assert_cmpstr (s, ==, NULL);
+  s1 = g_uri_peek_scheme ("bad");
+  g_assert_cmpstr (s, ==, NULL);
+}
+
+typedef struct {
+  const gchar *scheme;
+  const gchar *userinfo;
+  const gchar *host;
+  gint         port;
+  const gchar *path;
+  const gchar *query;
+  const gchar *fragment;
+} UriParts;
+
+typedef struct {
+  const gchar *orig;
+  const UriParts parts;
+} UriAbsoluteTest;
+
+static const UriAbsoluteTest absolute_tests[] = {
+  { "foo:",
+    { "foo", NULL, NULL, -1, "", NULL, NULL }
+  },
+  { "file:/dev/null",
+    { "file", NULL, NULL, -1, "/dev/null", NULL, NULL }
+  },
+  { "file:///dev/null",
+    { "file", NULL, "", -1, "/dev/null", NULL, NULL }
+  },
+  { "ftp://user host/path",
+    { "ftp", "user", "host", -1, "/path", NULL, NULL }
+  },
+  { "ftp://user host:9999/path",
+    { "ftp", "user", "host", 9999, "/path", NULL, NULL }
+  },
+  { "ftp://user:password host/path",
+    { "ftp", "user:password", "host", -1, "/path", NULL, NULL }
+  },
+  { "ftp://user:password host:9999/path",
+    { "ftp", "user:password", "host", 9999, "/path", NULL, NULL }
+  },
+  { "ftp://user:password host",
+    { "ftp", "user:password", "host", -1, "", NULL, NULL }
+  },
+  { "http://us%65r host",
+    { "http", "user", "host", -1, "", NULL, NULL }
+  },
+  { "http://us%40r host",
+    { "http", "us r", "host", -1, "", NULL, NULL }
+  },
+  { "http://us%3ar host",
+    { "http", "us:r", "host", -1, "", NULL, NULL }
+  },
+  { "http://us%2fr host",
+    { "http", "us/r", "host", -1, "", NULL, NULL }
+  },
+  { "http://us%3fr host",
+    { "http", "us?r", "host", -1, "", NULL, NULL }
+  },
+  { "http://host?query";,
+    { "http", NULL, "host", -1, "", "query", NULL }
+  },
+  { "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue&param=value";,
+    { "http", NULL, "host", -1, "/path", "query=http://host/path?childparam=childvalue&param=value";, NULL }
+  },
+  { 
"http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F";,
+    { "http", NULL, "control-chars", -1, 
"/\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F",
 NULL, NULL }
+  },
+  { "http://space/%20";,
+    { "http", NULL, "space", -1, "/ ", NULL, NULL }
+  },
+  { "http://delims/%3C%3E%23%25%22";,
+    { "http", NULL, "delims", -1, "/<>#%\"", NULL, NULL }
+  },
+  { "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60";,
+    { "http", NULL, "unwise-chars", -1, "/{}|\\^[]`", NULL, NULL }
+  },
+
+  /* From RFC 2732 */
+  { "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html";,
+    { "http", NULL, "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", 80, "/index.html", NULL, NULL }
+  },
+  { "http://[1080:0:0:0:8:800:200C:417A]/index.html";,
+    { "http", NULL, "1080:0:0:0:8:800:200C:417A", -1, "/index.html", NULL, NULL }
+  },
+  { "http://[3ffe:2a00:100:7031::1]";,
+    { "http", NULL, "3ffe:2a00:100:7031::1", -1, "", NULL, NULL }
+  },
+  { "http://[1080::8:800:200C:417A]/foo";,
+    { "http", NULL, "1080::8:800:200C:417A", -1, "/foo", NULL, NULL }
+  },
+  { "http://[::192.9.5.5]/ipng";,
+    { "http", NULL, "::192.9.5.5", -1, "/ipng", NULL, NULL }
+  },
+  { "http://[::FFFF:129.144.52.38]:80/index.html";,
+    { "http", NULL, "::FFFF:129.144.52.38", 80, "/index.html", NULL, NULL }
+  },
+  { "http://[2010:836B:4179::836B:4179]";,
+    { "http", NULL, "2010:836B:4179::836B:4179", -1, "", NULL, NULL }
+  },
+
+#if 0
+  /* Try to recover certain kinds of invalid URIs */
+  { "http://host/path with spaces",
+    { "http", NULL, "host", -1, "/path%20with%20spaces", NULL, NULL }
+    "http://host/path%20with%20spaces";,
+  },
+  { "  http://host/path";,
+    { "http", NULL, "host", -1, "/path", NULL, NULL }
+    "http://host/path";
+  },
+  { "http://host/path  ",
+    { "http", NULL, "host", -1, "/path", NULL, NULL }
+    "http://host/path";
+  },
+  { "http://host  ",
+    { "http", NULL, "host", -1, "", NULL, NULL }
+    "http://host";
+  },
+  { "http://host:999  ",
+    { "http", NULL, "host", 999, "", NULL, NULL }
+    "http://host:999";
+  },
+  { "http://host/pa\nth";,
+    { "http", NULL, "host", -1, "/path", NULL, NULL }
+    "http://host/path";
+  },
+  { "http:\r\n//host/path",
+    { "http", NULL, "host", -1, "/path", NULL, NULL }
+    "http://host/path";
+  },
+  { "http://\thost/path";,
+    { "http", NULL, "host", -1, "/path", NULL, NULL }
+    "http://host/path";
+  },
+#endif
+
+  /* Bug 594405; 0-length is different from not-present */
+  { "http://host/path?";,
+    { "http", NULL, "host", -1, "/path", "", NULL }
+  },
+  { "http://host/path#";,
+    { "http", NULL, "host", -1, "/path", NULL, "" },
+  },
+
+  /* Bug 590524; ignore bad %-encoding */
+  { "http://host/path%";,
+    { "http", NULL, "host", -1, "/path%", NULL, NULL }
+  },
+  { "http://h%ost/path";,
+    { "http", NULL, "h%ost", -1, "/path", NULL, NULL }
+  },
+  { "http://host/path%%";,
+    { "http", NULL, "host", -1, "/path%%", NULL, NULL }
+  },
+  { "http://host/path%%%";,
+    { "http", NULL, "host", -1, "/path%%%", NULL, NULL }
+  },
+  { "http://host/path%/x/";,
+    { "http", NULL, "host", -1, "/path%/x/", NULL, NULL }
+  },
+  { "http://host/path%0x/";,
+    { "http", NULL, "host", -1, "/path%0x/", NULL, NULL }
+  },
+  { "http://host/path%ax";,
+    { "http", NULL, "host", -1, "/path%ax", NULL, NULL }
+  },
+
+#if 0
+  /* Bug 662806; %-encode non-ASCII characters */
+  { "http://host/p\xc3\xa4th/";,
+    { "http", NULL, "host", -1, "/p%C3%A4th/", NULL, NULL }
+    { "http", NULL, "host", -1, "/p%C3%A4th/", NULL, NULL }
+  },
+#endif
+
+  { "HTTP:////////////////",
+    { "http", NULL, "", -1, "//////////////", NULL, NULL }
+  },
+
+  { "http://@host";,
+    { "http", "", "host", -1, "", NULL, NULL }
+  },
+  { "http://:@host";,
+    { "http", ":", "host", -1, "", NULL, NULL }
+  },
+
+#if 0
+  { "http://host/keep%00nuls";,
+    { "http", NULL, "host", -1, "/keep%00nuls", NULL, NULL }
+  },
+#endif
+
+  /* IPv6 scope ID parsing (both correct and incorrect) */
+  { "http://[fe80::dead:beef%em1]/";,
+    { "http", NULL, "fe80::dead:beef%em1", -1, "/", NULL, NULL }
+  },
+  { "http://[fe80::dead:beef%25em1]/";,
+    { "http", NULL, "fe80::dead:beef%em1", -1, "/", NULL, NULL }
+  },
+  { "http://[fe80::dead:beef%10]/";,
+    { "http", NULL, "fe80::dead:beef%10", -1, "/", NULL, NULL }
+  }
+};
+static int num_absolute_tests = G_N_ELEMENTS (absolute_tests);
+
+static void
+test_uri_parsing_absolute (void)
+{
+  int i;
+
+  for (i = 0; i < num_absolute_tests; i++)
+    {
+      const UriAbsoluteTest *test = &absolute_tests[i];
+      GError *error = NULL;
+      GUri *uri;
+
+      uri = g_uri_parse (test->orig, 0, &error);
+      g_assert_no_error (error);
+
+      g_assert_cmpstr (g_uri_get_scheme (uri),   ==, test->parts.scheme);
+      g_assert_cmpstr (g_uri_get_userinfo (uri), ==, test->parts.userinfo);
+      g_assert_cmpstr (g_uri_get_host (uri),     ==, test->parts.host);
+      g_assert_cmpint (g_uri_get_port (uri),     ==, test->parts.port);
+      g_assert_cmpstr (g_uri_get_path (uri),     ==, test->parts.path);
+      g_assert_cmpstr (g_uri_get_query (uri),    ==, test->parts.query);
+      g_assert_cmpstr (g_uri_get_fragment (uri), ==, test->parts.fragment);
+
+      g_uri_unref (uri);
+    }
+}
+
+typedef struct {
+  const gchar *orig, *resolved;
+  UriParts parts;
+} UriRelativeTest;
+
+/* This all comes from RFC 3986 */
+static const char *relative_test_base = "http://a/b/c/d;p?q";;
+static const UriRelativeTest relative_tests[] = {
+  { "g:h", "g:h",
+    { "g", NULL, NULL, -1, "h", NULL, NULL } },
+  { "g", "http://a/b/c/g";,
+    { "http", NULL, "a", -1, "/b/c/g", NULL, NULL } },
+  { "./g", "http://a/b/c/g";,
+    { "http", NULL, "a", -1, "/b/c/g", NULL, NULL } },
+  { "g/", "http://a/b/c/g/";,
+    { "http", NULL, "a", -1, "/b/c/g/", NULL, NULL } },
+  { "/g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "//g", "http://g";,
+    { "http", NULL, "g", -1, "", NULL, NULL } },
+  { "?y", "http://a/b/c/d;p?y";,
+    { "http", NULL, "a", -1, "/b/c/d;p", "y", NULL } },
+  { "g?y", "http://a/b/c/g?y";,
+    { "http", NULL, "a", -1, "/b/c/g", "y", NULL } },
+  { "#s", "http://a/b/c/d;p?q#s";,
+    { "http", NULL, "a", -1, "/b/c/d;p", "q", "s" } },
+  { "g#s", "http://a/b/c/g#s";,
+    { "http", NULL, "a", -1, "/b/c/g", NULL, "s" } },
+  { "g?y#s", "http://a/b/c/g?y#s";,
+    { "http", NULL, "a", -1, "/b/c/g", "y", "s" } },
+  { ";x", "http://a/b/c/;x";,
+    { "http", NULL, "a", -1, "/b/c/;x", NULL, NULL } },
+  { "g;x", "http://a/b/c/g;x";,
+    { "http", NULL, "a", -1, "/b/c/g;x", NULL, NULL } },
+  { "g;x?y#s", "http://a/b/c/g;x?y#s";,
+    { "http", NULL, "a", -1, "/b/c/g;x", "y", "s" } },
+  { ".", "http://a/b/c/";,
+    { "http", NULL, "a", -1, "/b/c/", NULL, NULL } },
+  { "./", "http://a/b/c/";,
+    { "http", NULL, "a", -1, "/b/c/", NULL, NULL } },
+  { "..", "http://a/b/";,
+    { "http", NULL, "a", -1, "/b/", NULL, NULL } },
+  { "../", "http://a/b/";,
+    { "http", NULL, "a", -1, "/b/", NULL, NULL } },
+  { "../g", "http://a/b/g";,
+    { "http", NULL, "a", -1, "/b/g", NULL, NULL } },
+  { "../..", "http://a/";,
+    { "http", NULL, "a", -1, "/", NULL, NULL } },
+  { "../../", "http://a/";,
+    { "http", NULL, "a", -1, "/", NULL, NULL } },
+  { "../../g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "", "http://a/b/c/d;p?q";,
+    { "http", NULL, "a", -1, "/b/c/d;p", "q", NULL } },
+  { "../../../g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "../../../../g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "/./g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "/../g", "http://a/g";,
+    { "http", NULL, "a", -1, "/g", NULL, NULL } },
+  { "g.", "http://a/b/c/g.";,
+    { "http", NULL, "a", -1, "/b/c/g.", NULL, NULL } },
+  { ".g", "http://a/b/c/.g";,
+    { "http", NULL, "a", -1, "/b/c/.g", NULL, NULL } },
+  { "g..", "http://a/b/c/g..";,
+    { "http", NULL, "a", -1, "/b/c/g..", NULL, NULL } },
+  { "..g", "http://a/b/c/..g";,
+    { "http", NULL, "a", -1, "/b/c/..g", NULL, NULL } },
+  { "./../g", "http://a/b/g";,
+    { "http", NULL, "a", -1, "/b/g", NULL, NULL } },
+  { "./g/.", "http://a/b/c/g/";,
+    { "http", NULL, "a", -1, "/b/c/g/", NULL, NULL } },
+  { "g/./h", "http://a/b/c/g/h";,
+    { "http", NULL, "a", -1, "/b/c/g/h", NULL, NULL } },
+  { "g/../h", "http://a/b/c/h";,
+    { "http", NULL, "a", -1, "/b/c/h", NULL, NULL } },
+  { "g;x=1/./y", "http://a/b/c/g;x=1/y";,
+    { "http", NULL, "a", -1, "/b/c/g;x=1/y", NULL, NULL } },
+  { "g;x=1/../y", "http://a/b/c/y";,
+    { "http", NULL, "a", -1, "/b/c/y", NULL, NULL } },
+  { "g?y/./x", "http://a/b/c/g?y/./x";,
+    { "http", NULL, "a", -1, "/b/c/g", "y/./x", NULL } },
+  { "g?y/../x", "http://a/b/c/g?y/../x";,
+    { "http", NULL, "a", -1, "/b/c/g", "y/../x", NULL } },
+  { "g#s/./x", "http://a/b/c/g#s/./x";,
+    { "http", NULL, "a", -1, "/b/c/g", NULL, "s/./x" } },
+  { "g#s/../x", "http://a/b/c/g#s/../x";,
+    { "http", NULL, "a", -1, "/b/c/g", NULL, "s/../x" } },
+  { "http:g", "http:g",
+    { "http", NULL, NULL, -1, "g", NULL, NULL } }
+};
+static int num_relative_tests = G_N_ELEMENTS (relative_tests);
+
+static void
+test_uri_parsing_relative (void)
+{
+  int i;
+  GUri *base;
+  GError *error = NULL;
+
+  base = g_uri_parse (relative_test_base, 0, &error);
+  g_assert_no_error (error);
+
+  for (i = 0; i < num_relative_tests; i++)
+    {
+      const UriRelativeTest *test = &relative_tests[i];
+      gchar *tostring, *resolved;
+      GUri *uri;
+
+      uri = g_uri_parse_relative (base, test->orig, 0, &error);
+      g_assert_no_error (error);
+
+      g_assert_cmpstr (g_uri_get_scheme (uri),   ==, test->parts.scheme);
+      g_assert_cmpstr (g_uri_get_userinfo (uri), ==, test->parts.userinfo);
+      g_assert_cmpstr (g_uri_get_host (uri),     ==, test->parts.host);
+      g_assert_cmpint (g_uri_get_port (uri),     ==, test->parts.port);
+      g_assert_cmpstr (g_uri_get_path (uri),     ==, test->parts.path);
+      g_assert_cmpstr (g_uri_get_query (uri),    ==, test->parts.query);
+      g_assert_cmpstr (g_uri_get_fragment (uri), ==, test->parts.fragment);
+
+      tostring = g_uri_to_string (uri);
+      g_assert_cmpstr (tostring, ==, test->resolved);
+      g_free (tostring);
+
+      g_uri_unref (uri);
+
+      resolved = g_uri_resolve_relative (relative_test_base, test->orig, 0, &error);
+      g_assert_no_error (error);
+      g_assert_cmpstr (resolved, ==, test->resolved);
+      g_free (resolved);
+    }
 }
 
 int
@@ -381,13 +758,15 @@ main (int   argc,
 {
   g_test_init (&argc, &argv, NULL);
 
-  g_test_add_func ("/uri/to-uri", run_to_uri_tests);
-  g_test_add_func ("/uri/from-uri", run_from_uri_tests);
-  g_test_add_func ("/uri/roundtrip", run_roundtrip_tests);
+  g_test_add_func ("/uri/file-to-uri", run_file_to_uri_tests);
+  g_test_add_func ("/uri/file-from-uri", run_file_from_uri_tests);
+  g_test_add_func ("/uri/file-roundtrip", run_file_roundtrip_tests);
   g_test_add_func ("/uri/list", run_uri_list_tests);
   g_test_add_func ("/uri/unescape", test_uri_unescape);
   g_test_add_func ("/uri/escape", test_uri_escape);
   g_test_add_func ("/uri/scheme", test_uri_scheme);
+  g_test_add_func ("/uri/parsing/absolute", test_uri_parsing_absolute);
+  g_test_add_func ("/uri/parsing/relative", test_uri_parsing_relative);
 
   return g_test_run ();
 }
diff --git a/gobject/gboxed.c b/gobject/gboxed.c
index ab41951..5207073 100644
--- a/gobject/gboxed.c
+++ b/gobject/gboxed.c
@@ -164,6 +164,7 @@ G_DEFINE_BOXED_TYPE (GMarkupParseContext, g_markup_parse_context, g_markup_parse
 
 G_DEFINE_BOXED_TYPE (GThread, g_thread, g_thread_ref, g_thread_unref)
 G_DEFINE_BOXED_TYPE (GChecksum, g_checksum, g_checksum_copy, g_checksum_free)
+G_DEFINE_BOXED_TYPE (GUri, g_uri, g_uri_ref, g_uri_unref)
 
 /* This one can't use G_DEFINE_BOXED_TYPE (GStrv, g_strv, g_strdupv, g_strfreev) */
 GType
diff --git a/gobject/glib-types.h b/gobject/glib-types.h
index e9f6472..309ccd1 100644
--- a/gobject/glib-types.h
+++ b/gobject/glib-types.h
@@ -342,6 +342,8 @@ GLIB_AVAILABLE_IN_2_36
 GType   g_markup_parse_context_get_type (void) G_GNUC_CONST;
 GLIB_AVAILABLE_IN_2_40
 GType   g_mapped_file_get_type (void) G_GNUC_CONST;
+GLIB_AVAILABLE_IN_2_40
+GType   g_uri_get_type (void) G_GNUC_CONST;
 
 GLIB_DEPRECATED_FOR('G_TYPE_VARIANT')
 GType   g_variant_get_gtype        (void) G_GNUC_CONST;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]