mirror of https://gitee.com/openkylin/glib2.0.git
2896 lines
89 KiB
C
2896 lines
89 KiB
C
/* GLIB - Library of useful routines for C programming
|
||
* Copyright © 2020 Red Hat, Inc.
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*
|
||
* 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 <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include "glib.h"
|
||
#include "glibintl.h"
|
||
#include "glib-private.h"
|
||
#include "guriprivate.h"
|
||
|
||
/**
|
||
* GUri:
|
||
*
|
||
* The `GUri` type and related functions can be used to parse URIs into
|
||
* their components, and build valid URIs from individual components.
|
||
*
|
||
* Since `GUri` only represents absolute URIs, all `GUri`s will have a
|
||
* URI scheme, so [method@GLib.Uri.get_scheme] will always return a non-`NULL`
|
||
* answer. Likewise, by definition, all URIs have a path component, so
|
||
* [method@GLib.Uri.get_path] will always return a non-`NULL` string (which may
|
||
* be empty).
|
||
*
|
||
* If the URI string has an
|
||
* [‘authority’ component](https://tools.ietf.org/html/rfc3986#section-3) (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 (for example,
|
||
* using the `G_URI_FLAGS_HAS_PASSWORD` and `G_URI_FLAGS_HAS_AUTH_PARAMS` flags),
|
||
* 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_FLAGS_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_FLAGS_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.
|
||
*
|
||
* For example, with the encoded flag:
|
||
*
|
||
* ```c
|
||
* g_autoptr(GUri) uri = g_uri_parse ("http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fparam%3Dvalue", G_URI_FLAGS_ENCODED, &err);
|
||
* g_assert_cmpstr (g_uri_get_query (uri), ==, "query=http%3A%2F%2Fhost%2Fpath%3Fparam%3Dvalue");
|
||
* ```
|
||
*
|
||
* While the default `%`-decoding behaviour would give:
|
||
*
|
||
* ```c
|
||
* g_autoptr(GUri) uri = g_uri_parse ("http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fparam%3Dvalue", G_URI_FLAGS_NONE, &err);
|
||
* g_assert_cmpstr (g_uri_get_query (uri), ==, "query=http://host/path?param=value");
|
||
* ```
|
||
*
|
||
* During decoding, if an invalid UTF-8 string is encountered, parsing will fail
|
||
* with an error indicating the bad string location:
|
||
*
|
||
* ```c
|
||
* g_autoptr(GUri) uri = g_uri_parse ("http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fbad%3D%00alue", G_URI_FLAGS_NONE, &err);
|
||
* g_assert_error (err, G_URI_ERROR, G_URI_ERROR_BAD_QUERY);
|
||
* ```
|
||
*
|
||
* You should pass `G_URI_FLAGS_ENCODED` or `G_URI_FLAGS_ENCODED_QUERY` if you
|
||
* need to handle that case manually. In particular, if the query string
|
||
* contains `=` characters that are `%`-encoded, you should let
|
||
* [func@GLib.Uri.parse_params] do the decoding once of the query.
|
||
*
|
||
* `GUri` is immutable once constructed, and can safely be accessed from
|
||
* multiple threads. Its reference counting is atomic.
|
||
*
|
||
* Note that the scope of `GUri` is to help manipulate URIs in various applications,
|
||
* following [RFC 3986](https://tools.ietf.org/html/rfc3986). In particular,
|
||
* it doesn't intend to cover web browser needs, and doesn’t implement the
|
||
* [WHATWG URL](https://url.spec.whatwg.org/) standard. No APIs are provided to
|
||
* help prevent
|
||
* [homograph attacks](https://en.wikipedia.org/wiki/IDN_homograph_attack), so
|
||
* `GUri` is not suitable for formatting URIs for display to the user for making
|
||
* security-sensitive decisions.
|
||
*
|
||
* ## Relative and absolute URIs
|
||
*
|
||
* As defined in [RFC 3986](https://tools.ietf.org/html/rfc3986#section-4), the
|
||
* hierarchical nature of URIs means that they can either be ‘relative
|
||
* references’ (sometimes referred to as ‘relative URIs’) or ‘URIs’ (for
|
||
* clarity, ‘URIs’ are referred to in this documentation as
|
||
* ‘absolute URIs’ — although
|
||
* [in constrast to RFC 3986](https://tools.ietf.org/html/rfc3986#section-4.3),
|
||
* fragment identifiers are always allowed).
|
||
*
|
||
* Relative references have one or more components of the URI missing. In
|
||
* particular, they have no scheme. Any other component, such as hostname,
|
||
* query, etc. may be missing, apart from a path, which has to be specified (but
|
||
* may be empty). The path may be relative, starting with `./` rather than `/`.
|
||
*
|
||
* For example, a valid relative reference is `./path?query`,
|
||
* `/?query#fragment` or `//example.com`.
|
||
*
|
||
* Absolute URIs have a scheme specified. Any other components of the URI which
|
||
* are missing are specified as explicitly unset in the URI, rather than being
|
||
* resolved relative to a base URI using [method@GLib.Uri.parse_relative].
|
||
*
|
||
* For example, a valid absolute URI is `file:///home/bob` or
|
||
* `https://search.com?query=string`.
|
||
*
|
||
* A `GUri` instance is always an absolute URI. A string may be an absolute URI
|
||
* or a relative reference; see the documentation for individual functions as to
|
||
* what forms they accept.
|
||
*
|
||
* ## Parsing URIs
|
||
*
|
||
* The most minimalist APIs for parsing URIs are [func@GLib.Uri.split] and
|
||
* [func@GLib.Uri.split_with_user]. These split a URI into its component
|
||
* parts, and return the parts; the difference between the two is that
|
||
* [func@GLib.Uri.split] treats the ‘userinfo’ component of the URI as a
|
||
* single element, while [func@GLib.Uri.split_with_user] can (depending on the
|
||
* [flags@GLib.UriFlags] you pass) treat it as containing a username, password,
|
||
* and authentication parameters. Alternatively, [func@GLib.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).
|
||
*
|
||
* [func@GLib.Uri.parse] is similar to [func@GLib.Uri.split], but instead of
|
||
* returning individual strings, it returns a `GUri` structure (and it requires
|
||
* that the URI be an absolute URI).
|
||
*
|
||
* [func@GLib.Uri.resolve_relative] and [method@GLib.Uri.parse_relative] allow
|
||
* you to resolve a relative URI relative to a base URI.
|
||
* [func@GLib.Uri.resolve_relative] takes two strings and returns a string,
|
||
* and [method@GLib.Uri.parse_relative] takes a `GUri` and a string and returns a
|
||
* `GUri`.
|
||
*
|
||
* All of the parsing functions take a [flags@GLib.UriFlags] 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 [func@GLib.Uri.peek_scheme] on the URI string to check the scheme
|
||
* first, and use that to decide what flags to parse it with.
|
||
*
|
||
* For example, you might want to use `G_URI_PARAMS_WWW_FORM` when parsing the
|
||
* params for a web URI, so compare the result of [func@GLib.Uri.peek_scheme]
|
||
* against `http` and `https`.
|
||
*
|
||
* ## Building URIs
|
||
*
|
||
* [func@GLib.Uri.join] and [func@GLib.Uri.join_with_user] can be used to construct
|
||
* valid URI strings from a set of component strings. They are the
|
||
* inverse of [func@GLib.Uri.split] and [func@GLib.Uri.split_with_user].
|
||
*
|
||
* Similarly, [func@GLib.Uri.build] and [func@GLib.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
|
||
* [flags@GLib.UriFlags] argument. In particular, it is important to keep in mind
|
||
* whether the URI components you are using are already `%`-encoded. If so,
|
||
* you must pass the `G_URI_FLAGS_ENCODED` flag.
|
||
*
|
||
* ## `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 [func@GLib.filename_from_uri] and
|
||
* [func@GLib.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. `GUri` can help with normalization if you use the various
|
||
* encoded [flags@GLib.UriFlags] as well as `G_URI_FLAGS_SCHEME_NORMALIZE`
|
||
* however it is not comprehensive.
|
||
* For example, `data:,foo` and `data:;base64,Zm9v` resolve to the same
|
||
* thing according to the `data:` URI specification which GLib does not
|
||
* handle.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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;
|
||
};
|
||
|
||
/**
|
||
* g_uri_ref: (skip)
|
||
* @uri: a #GUri
|
||
*
|
||
* Increments the reference count of @uri by one.
|
||
*
|
||
* Returns: @uri
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
GUri *
|
||
g_uri_ref (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
return g_atomic_rc_box_acquire (uri);
|
||
}
|
||
|
||
static void
|
||
g_uri_clear (GUri *uri)
|
||
{
|
||
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_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.66
|
||
*/
|
||
void
|
||
g_uri_unref (GUri *uri)
|
||
{
|
||
g_return_if_fail (uri != NULL);
|
||
|
||
g_atomic_rc_box_release_full (uri, (GDestroyNotify)g_uri_clear);
|
||
}
|
||
|
||
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 gssize
|
||
uri_decoder (gchar **out,
|
||
const gchar *illegal_chars,
|
||
const gchar *start,
|
||
gsize length,
|
||
gboolean just_normalize,
|
||
gboolean www_form,
|
||
GUriFlags flags,
|
||
GUriError parse_error,
|
||
GError **error)
|
||
{
|
||
gchar c;
|
||
GString *decoded;
|
||
const gchar *invalid, *s, *end;
|
||
gssize len;
|
||
|
||
if (!(flags & G_URI_FLAGS_ENCODED))
|
||
just_normalize = FALSE;
|
||
|
||
decoded = g_string_sized_new (length + 1);
|
||
for (s = start, end = s + length; s < end; s++)
|
||
{
|
||
if (*s == '%')
|
||
{
|
||
if (s + 2 >= end ||
|
||
!g_ascii_isxdigit (s[1]) ||
|
||
!g_ascii_isxdigit (s[2]))
|
||
{
|
||
/* % followed by non-hex or the end of the string; this is an error */
|
||
if (!(flags & G_URI_FLAGS_PARSE_RELAXED))
|
||
{
|
||
g_set_error_literal (error, G_URI_ERROR, parse_error,
|
||
/* xgettext: no-c-format */
|
||
_("Invalid %-encoding in URI"));
|
||
g_string_free (decoded, TRUE);
|
||
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.
|
||
*/
|
||
g_string_append_c (decoded, *s);
|
||
continue;
|
||
}
|
||
|
||
c = HEXCHAR (s);
|
||
if (illegal_chars && strchr (illegal_chars, c))
|
||
{
|
||
g_set_error_literal (error, G_URI_ERROR, parse_error,
|
||
_("Illegal character in URI"));
|
||
g_string_free (decoded, TRUE);
|
||
return -1;
|
||
}
|
||
if (just_normalize && !g_uri_char_is_unreserved (c))
|
||
{
|
||
/* Leave the % sequence there but normalize it. */
|
||
g_string_append_c (decoded, *s);
|
||
g_string_append_c (decoded, g_ascii_toupper (s[1]));
|
||
g_string_append_c (decoded, g_ascii_toupper (s[2]));
|
||
s += 2;
|
||
}
|
||
else
|
||
{
|
||
g_string_append_c (decoded, c);
|
||
s += 2;
|
||
}
|
||
}
|
||
else if (www_form && *s == '+')
|
||
g_string_append_c (decoded, ' ');
|
||
/* Normalize any illegal characters. */
|
||
else if (just_normalize && (!g_ascii_isgraph (*s)))
|
||
g_string_append_printf (decoded, "%%%02X", (guchar)*s);
|
||
else
|
||
g_string_append_c (decoded, *s);
|
||
}
|
||
|
||
len = decoded->len;
|
||
g_assert (len >= 0);
|
||
|
||
if (!(flags & G_URI_FLAGS_ENCODED) &&
|
||
!g_utf8_validate (decoded->str, len, &invalid))
|
||
{
|
||
g_set_error_literal (error, G_URI_ERROR, parse_error,
|
||
_("Non-UTF-8 characters in URI"));
|
||
g_string_free (decoded, TRUE);
|
||
return -1;
|
||
}
|
||
|
||
if (out)
|
||
*out = g_string_free (decoded, FALSE);
|
||
else
|
||
g_string_free (decoded, TRUE);
|
||
|
||
return len;
|
||
}
|
||
|
||
static gboolean
|
||
uri_decode (gchar **out,
|
||
const gchar *illegal_chars,
|
||
const gchar *start,
|
||
gsize length,
|
||
gboolean www_form,
|
||
GUriFlags flags,
|
||
GUriError parse_error,
|
||
GError **error)
|
||
{
|
||
return uri_decoder (out, illegal_chars, start, length, FALSE, www_form, 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, NULL, start, length, TRUE, FALSE, 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;
|
||
}
|
||
|
||
void
|
||
_uri_encoder (GString *out,
|
||
const guchar *start,
|
||
gsize length,
|
||
const gchar *reserved_chars_allowed,
|
||
gboolean allow_utf8)
|
||
{
|
||
static const gchar hex[] = "0123456789ABCDEF";
|
||
const guchar *p = start;
|
||
const guchar *end = p + length;
|
||
|
||
while (p < end)
|
||
{
|
||
gunichar multibyte_utf8_char = 0;
|
||
|
||
if (allow_utf8 && *p >= 0x80)
|
||
multibyte_utf8_char = g_utf8_get_char_validated ((gchar *)p, end - p);
|
||
|
||
if (multibyte_utf8_char > 0 &&
|
||
multibyte_utf8_char != (gunichar) -1 && multibyte_utf8_char != (gunichar) -2)
|
||
{
|
||
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++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Parse the IP-literal construction from RFC 6874 (which extends RFC 3986 to
|
||
* support IPv6 zone identifiers.
|
||
*
|
||
* Currently, IP versions beyond 6 (i.e. the IPvFuture rule) are unsupported.
|
||
* There’s no point supporting them until (a) they exist and (b) the rest of the
|
||
* stack (notably, sockets) supports them.
|
||
*
|
||
* Rules:
|
||
*
|
||
* IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
|
||
*
|
||
* ZoneID = 1*( unreserved / pct-encoded )
|
||
*
|
||
* IPv6addrz = IPv6address "%25" ZoneID
|
||
*
|
||
* If %G_URI_FLAGS_PARSE_RELAXED is specified, this function also accepts:
|
||
*
|
||
* IPv6addrz = IPv6address "%" ZoneID
|
||
*/
|
||
static gboolean
|
||
parse_ip_literal (const gchar *start,
|
||
gsize length,
|
||
GUriFlags flags,
|
||
gchar **out,
|
||
GError **error)
|
||
{
|
||
gchar *pct, *zone_id = NULL;
|
||
gchar *addr = NULL;
|
||
gsize addr_length = 0;
|
||
gsize zone_id_length = 0;
|
||
gchar *decoded_zone_id = NULL;
|
||
|
||
if (start[length - 1] != ']')
|
||
goto bad_ipv6_literal;
|
||
|
||
/* Drop the square brackets */
|
||
addr = g_strndup (start + 1, length - 2);
|
||
addr_length = length - 2;
|
||
|
||
/* If there's an IPv6 scope ID, split out the zone. */
|
||
pct = strchr (addr, '%');
|
||
if (pct != NULL)
|
||
{
|
||
*pct = '\0';
|
||
|
||
if (addr_length - (pct - addr) >= 4 &&
|
||
*(pct + 1) == '2' && *(pct + 2) == '5')
|
||
{
|
||
zone_id = pct + 3;
|
||
zone_id_length = addr_length - (zone_id - addr);
|
||
}
|
||
else if (flags & G_URI_FLAGS_PARSE_RELAXED &&
|
||
addr_length - (pct - addr) >= 2)
|
||
{
|
||
zone_id = pct + 1;
|
||
zone_id_length = addr_length - (zone_id - addr);
|
||
}
|
||
else
|
||
goto bad_ipv6_literal;
|
||
|
||
g_assert (zone_id_length >= 1);
|
||
}
|
||
|
||
/* addr must be an IPv6 address */
|
||
if (!g_hostname_is_ip_address (addr) || !strchr (addr, ':'))
|
||
goto bad_ipv6_literal;
|
||
|
||
/* Zone ID must be valid. It can contain %-encoded characters. */
|
||
if (zone_id != NULL &&
|
||
!uri_decode (&decoded_zone_id, NULL, zone_id, zone_id_length, FALSE,
|
||
flags, G_URI_ERROR_BAD_HOST, NULL))
|
||
goto bad_ipv6_literal;
|
||
|
||
/* Success */
|
||
if (out != NULL && decoded_zone_id != NULL)
|
||
*out = g_strconcat (addr, "%", decoded_zone_id, NULL);
|
||
else if (out != NULL)
|
||
*out = g_steal_pointer (&addr);
|
||
|
||
g_free (addr);
|
||
g_free (decoded_zone_id);
|
||
|
||
return TRUE;
|
||
|
||
bad_ipv6_literal:
|
||
g_free (addr);
|
||
g_free (decoded_zone_id);
|
||
g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_HOST,
|
||
_("Invalid IPv6 address ‘%.*s’ in URI"),
|
||
(gint)length, start);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
parse_host (const gchar *start,
|
||
gsize length,
|
||
GUriFlags flags,
|
||
gchar **out,
|
||
GError **error)
|
||
{
|
||
gchar *decoded = NULL, *host;
|
||
gchar *addr = NULL;
|
||
|
||
if (*start == '[')
|
||
{
|
||
if (!parse_ip_literal (start, length, flags, &host, error))
|
||
return FALSE;
|
||
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_FLAGS_NON_DNS)
|
||
{
|
||
if (!uri_normalize (&decoded, start, length, flags,
|
||
G_URI_ERROR_BAD_HOST, error))
|
||
return FALSE;
|
||
host = g_steal_pointer (&decoded);
|
||
goto ok;
|
||
}
|
||
|
||
flags &= ~G_URI_FLAGS_ENCODED;
|
||
if (!uri_decode (&decoded, NULL, start, length, FALSE, 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);
|
||
if (host == NULL)
|
||
{
|
||
g_free (decoded);
|
||
g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_HOST,
|
||
_("Illegal internationalized hostname ‘%.*s’ in URI"),
|
||
(gint) length, start);
|
||
return FALSE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
host = g_steal_pointer (&decoded);
|
||
}
|
||
|
||
ok:
|
||
if (out)
|
||
*out = g_steal_pointer (&host);
|
||
g_free (host);
|
||
g_free (decoded);
|
||
|
||
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 != 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_FLAGS_HAS_AUTH_PARAMS)
|
||
password_end = memchr (start, ';', auth_params_end - start);
|
||
if (!password_end)
|
||
password_end = auth_params_end;
|
||
if (flags & G_URI_FLAGS_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))
|
||
{
|
||
if (user)
|
||
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))
|
||
{
|
||
if (user)
|
||
g_clear_pointer (user, g_free);
|
||
if (password)
|
||
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
|
||
should_normalize_empty_path (const char *scheme)
|
||
{
|
||
const char * const schemes[] = { "https", "http", "wss", "ws" };
|
||
gsize i;
|
||
for (i = 0; i < G_N_ELEMENTS (schemes); ++i)
|
||
{
|
||
if (!strcmp (schemes[i], scheme))
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
static int
|
||
normalize_port (const char *scheme,
|
||
int port)
|
||
{
|
||
const char *default_schemes[3] = { NULL };
|
||
int i;
|
||
|
||
switch (port)
|
||
{
|
||
case 21:
|
||
default_schemes[0] = "ftp";
|
||
break;
|
||
case 80:
|
||
default_schemes[0] = "http";
|
||
default_schemes[1] = "ws";
|
||
break;
|
||
case 443:
|
||
default_schemes[0] = "https";
|
||
default_schemes[1] = "wss";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
for (i = 0; default_schemes[i]; ++i)
|
||
{
|
||
if (!strcmp (scheme, default_schemes[i]))
|
||
return -1;
|
||
}
|
||
|
||
return port;
|
||
}
|
||
|
||
int
|
||
g_uri_get_default_scheme_port (const char *scheme)
|
||
{
|
||
if (strcmp (scheme, "http") == 0 || strcmp (scheme, "ws") == 0)
|
||
return 80;
|
||
|
||
if (strcmp (scheme, "https") == 0 || strcmp (scheme, "wss") == 0)
|
||
return 443;
|
||
|
||
if (strcmp (scheme, "ftp") == 0)
|
||
return 21;
|
||
|
||
if (strstr (scheme, "socks") == scheme)
|
||
return 1080;
|
||
|
||
return -1;
|
||
}
|
||
|
||
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;
|
||
gchar *normalized_scheme = NULL;
|
||
|
||
if (scheme)
|
||
*scheme = NULL;
|
||
if (userinfo)
|
||
*userinfo = NULL;
|
||
if (user)
|
||
*user = 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_FLAGS_PARSE_RELAXED) && strpbrk (uri_string, " \t\n\r"))
|
||
{
|
||
cleaned_uri_string = uri_cleanup (uri_string);
|
||
uri_string = cleaned_uri_string;
|
||
}
|
||
|
||
/* Find scheme */
|
||
p = uri_string;
|
||
while (*p && (g_ascii_isalpha (*p) ||
|
||
(p > uri_string && (g_ascii_isdigit (*p) ||
|
||
*p == '.' || *p == '+' || *p == '-'))))
|
||
p++;
|
||
|
||
if (p > uri_string && *p == ':')
|
||
{
|
||
normalized_scheme = g_ascii_strdown (uri_string, p - uri_string);
|
||
if (scheme)
|
||
*scheme = g_steal_pointer (&normalized_scheme);
|
||
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_FLAGS_PARSE_RELAXED)
|
||
{
|
||
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 (user || password || auth_params ||
|
||
(flags & (G_URI_FLAGS_HAS_PASSWORD|G_URI_FLAGS_HAS_AUTH_PARAMS)))
|
||
{
|
||
if (!parse_userinfo (p, at - p, flags,
|
||
user, password, auth_params,
|
||
error))
|
||
goto fail;
|
||
}
|
||
|
||
if (!uri_normalize (userinfo, p, at - p, flags,
|
||
G_URI_ERROR_BAD_USER, error))
|
||
goto fail;
|
||
|
||
p = at + 1;
|
||
}
|
||
|
||
if (flags & G_URI_FLAGS_PARSE_RELAXED)
|
||
{
|
||
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_normalize (fragment, end + 1, strlen (end + 1),
|
||
flags | (flags & G_URI_FLAGS_ENCODED_FRAGMENT ? G_URI_FLAGS_ENCODED : 0),
|
||
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 | (flags & G_URI_FLAGS_ENCODED_QUERY ? G_URI_FLAGS_ENCODED : 0),
|
||
G_URI_ERROR_BAD_QUERY, error))
|
||
goto fail;
|
||
end = question;
|
||
}
|
||
|
||
if (!uri_normalize (path, p, end - p,
|
||
flags | (flags & G_URI_FLAGS_ENCODED_PATH ? G_URI_FLAGS_ENCODED : 0),
|
||
G_URI_ERROR_BAD_PATH, error))
|
||
goto fail;
|
||
|
||
/* Scheme-based normalization */
|
||
if (flags & G_URI_FLAGS_SCHEME_NORMALIZE && ((scheme && *scheme) || normalized_scheme))
|
||
{
|
||
const char *scheme_str = scheme && *scheme ? *scheme : normalized_scheme;
|
||
|
||
if (should_normalize_empty_path (scheme_str) && path && !**path)
|
||
{
|
||
g_free (*path);
|
||
*path = g_strdup ("/");
|
||
}
|
||
|
||
if (port && *port == -1)
|
||
*port = g_uri_get_default_scheme_port (scheme_str);
|
||
}
|
||
|
||
g_free (normalized_scheme);
|
||
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 (normalized_scheme);
|
||
g_free (cleaned_uri_string);
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* g_uri_split:
|
||
* @uri_ref: a string containing a relative or absolute URI
|
||
* @flags: flags for parsing @uri_ref
|
||
* @scheme: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the scheme (converted to lowercase), or %NULL
|
||
* @userinfo: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the userinfo, or %NULL
|
||
* @host: (out) (nullable) (optional) (transfer full): on return, contains the
|
||
* host, or %NULL
|
||
* @port: (out) (optional) (transfer full): on return, contains the
|
||
* port, or `-1`
|
||
* @path: (out) (not nullable) (optional) (transfer full): on return, contains the
|
||
* path
|
||
* @query: (out) (nullable) (optional) (transfer full): on return, contains the
|
||
* query, or %NULL
|
||
* @fragment: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the fragment, or %NULL
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Parses @uri_ref (which can be an
|
||
* [absolute or relative URI](#relative-and-absolute-uris)) according to @flags, and
|
||
* returns the pieces. Any component that doesn't appear in @uri_ref 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_FLAGS_ENCODED, then `%`-encoded characters in
|
||
* @uri_ref 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_FLAGS_ENCODED if they are not.
|
||
*
|
||
* Note that the %G_URI_FLAGS_HAS_PASSWORD and
|
||
* %G_URI_FLAGS_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_ref parsed successfully, %FALSE
|
||
* on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gboolean
|
||
g_uri_split (const gchar *uri_ref,
|
||
GUriFlags flags,
|
||
gchar **scheme,
|
||
gchar **userinfo,
|
||
gchar **host,
|
||
gint *port,
|
||
gchar **path,
|
||
gchar **query,
|
||
gchar **fragment,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (uri_ref != NULL, FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
return g_uri_split_internal (uri_ref, flags,
|
||
scheme, userinfo, NULL, NULL, NULL,
|
||
host, port, path, query, fragment,
|
||
error);
|
||
}
|
||
|
||
/**
|
||
* g_uri_split_with_user:
|
||
* @uri_ref: a string containing a relative or absolute URI
|
||
* @flags: flags for parsing @uri_ref
|
||
* @scheme: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the scheme (converted to lowercase), or %NULL
|
||
* @user: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the user, or %NULL
|
||
* @password: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the password, or %NULL
|
||
* @auth_params: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the auth_params, or %NULL
|
||
* @host: (out) (nullable) (optional) (transfer full): on return, contains the
|
||
* host, or %NULL
|
||
* @port: (out) (optional) (transfer full): on return, contains the
|
||
* port, or `-1`
|
||
* @path: (out) (not nullable) (optional) (transfer full): on return, contains the
|
||
* path
|
||
* @query: (out) (nullable) (optional) (transfer full): on return, contains the
|
||
* query, or %NULL
|
||
* @fragment: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the fragment, or %NULL
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Parses @uri_ref (which can be an
|
||
* [absolute or relative URI](#relative-and-absolute-uris)) according to @flags, and
|
||
* returns the pieces. Any component that doesn't appear in @uri_ref 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_FLAGS_HAS_PASSWORD, and
|
||
* @auth_params will only be parsed out if @flags contains
|
||
* %G_URI_FLAGS_HAS_AUTH_PARAMS.
|
||
*
|
||
* Returns: (skip): %TRUE if @uri_ref parsed successfully, %FALSE
|
||
* on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gboolean
|
||
g_uri_split_with_user (const gchar *uri_ref,
|
||
GUriFlags flags,
|
||
gchar **scheme,
|
||
gchar **user,
|
||
gchar **password,
|
||
gchar **auth_params,
|
||
gchar **host,
|
||
gint *port,
|
||
gchar **path,
|
||
gchar **query,
|
||
gchar **fragment,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (uri_ref != NULL, FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
return g_uri_split_internal (uri_ref, flags,
|
||
scheme, NULL, user, password, auth_params,
|
||
host, port, path, query, fragment,
|
||
error);
|
||
}
|
||
|
||
|
||
/**
|
||
* g_uri_split_network:
|
||
* @uri_string: a string containing an absolute URI
|
||
* @flags: flags for parsing @uri_string
|
||
* @scheme: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the scheme (converted to lowercase), or %NULL
|
||
* @host: (out) (nullable) (optional) (transfer full): on return, contains the
|
||
* host, or %NULL
|
||
* @port: (out) (optional) (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](#relative-and-absolute-uris))
|
||
* 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.66
|
||
*/
|
||
gboolean
|
||
g_uri_split_network (const gchar *uri_string,
|
||
GUriFlags flags,
|
||
gchar **scheme,
|
||
gchar **host,
|
||
gint *port,
|
||
GError **error)
|
||
{
|
||
gchar *my_scheme = NULL, *my_host = NULL;
|
||
|
||
g_return_val_if_fail (uri_string != NULL, FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
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 = g_steal_pointer (&my_scheme);
|
||
if (host)
|
||
*host = g_steal_pointer (&my_host);
|
||
|
||
g_free (my_scheme);
|
||
g_free (my_host);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_uri_is_valid:
|
||
* @uri_string: a string containing an absolute URI
|
||
* @flags: flags for parsing @uri_string
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Parses @uri_string according to @flags, to determine whether it is a valid
|
||
* [absolute URI](#relative-and-absolute-uris), i.e. it does not need to be resolved
|
||
* relative to another URI using g_uri_parse_relative().
|
||
*
|
||
* If it’s not a valid URI, an error is returned explaining how it’s invalid.
|
||
*
|
||
* See g_uri_split(), and the definition of #GUriFlags, for more
|
||
* information on the effect of @flags.
|
||
*
|
||
* Returns: %TRUE if @uri_string is a valid absolute URI, %FALSE on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gboolean
|
||
g_uri_is_valid (const gchar *uri_string,
|
||
GUriFlags flags,
|
||
GError **error)
|
||
{
|
||
gchar *my_scheme = NULL;
|
||
|
||
g_return_val_if_fail (uri_string != NULL, FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
if (!g_uri_split_internal (uri_string, flags,
|
||
&my_scheme, NULL, NULL, NULL, NULL,
|
||
NULL, NULL, NULL, NULL, NULL,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (!my_scheme)
|
||
{
|
||
g_set_error (error, G_URI_ERROR, G_URI_ERROR_BAD_SCHEME,
|
||
_("URI ‘%s’ is not an absolute URI"),
|
||
uri_string);
|
||
return FALSE;
|
||
}
|
||
|
||
g_free (my_scheme);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* Implements the "Remove Dot Segments" algorithm from section 5.2.4 of
|
||
* RFC 3986.
|
||
*
|
||
* See https://tools.ietf.org/html/rfc3986#section-5.2.4
|
||
*/
|
||
static void
|
||
remove_dot_segments (gchar *path)
|
||
{
|
||
/* The output can be written to the same buffer that the input
|
||
* is read from, as the output pointer is only ever increased
|
||
* when the input pointer is increased as well, and the input
|
||
* pointer is never decreased. */
|
||
gchar *input = path;
|
||
gchar *output = path;
|
||
|
||
if (!*path)
|
||
return;
|
||
|
||
while (*input)
|
||
{
|
||
/* A. If the input buffer begins with a prefix of "../" or "./",
|
||
* then remove that prefix from the input buffer; otherwise,
|
||
*/
|
||
if (strncmp (input, "../", 3) == 0)
|
||
input += 3;
|
||
else if (strncmp (input, "./", 2) == 0)
|
||
input += 2;
|
||
|
||
/* B. if the input buffer begins with a prefix of "/./" or "/.",
|
||
* where "." is a complete path segment, then replace that
|
||
* prefix with "/" in the input buffer; otherwise,
|
||
*/
|
||
else if (strncmp (input, "/./", 3) == 0)
|
||
input += 2;
|
||
else if (strcmp (input, "/.") == 0)
|
||
input[1] = '\0';
|
||
|
||
/* C. if the input buffer begins with a prefix of "/../" or "/..",
|
||
* where ".." is a complete path segment, then replace that
|
||
* prefix with "/" in the input buffer and remove the last
|
||
* segment and its preceding "/" (if any) from the output
|
||
* buffer; otherwise,
|
||
*/
|
||
else if (strncmp (input, "/../", 4) == 0)
|
||
{
|
||
input += 3;
|
||
if (output > path)
|
||
{
|
||
do
|
||
{
|
||
output--;
|
||
}
|
||
while (*output != '/' && output > path);
|
||
}
|
||
}
|
||
else if (strcmp (input, "/..") == 0)
|
||
{
|
||
input[1] = '\0';
|
||
if (output > path)
|
||
{
|
||
do
|
||
{
|
||
output--;
|
||
}
|
||
while (*output != '/' && output > path);
|
||
}
|
||
}
|
||
|
||
/* D. if the input buffer consists only of "." or "..", then remove
|
||
* that from the input buffer; otherwise,
|
||
*/
|
||
else if (strcmp (input, "..") == 0 || strcmp (input, ".") == 0)
|
||
input[0] = '\0';
|
||
|
||
/* E. move the first path segment in the input buffer to the end of
|
||
* the output buffer, including the initial "/" character (if
|
||
* any) and any subsequent characters up to, but not including,
|
||
* the next "/" character or the end of the input buffer.
|
||
*/
|
||
else
|
||
{
|
||
*output++ = *input++;
|
||
while (*input && *input != '/')
|
||
*output++ = *input++;
|
||
}
|
||
}
|
||
*output = '\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](#relative-and-absolute-uris), it will be discarded, and an
|
||
* error returned.
|
||
*
|
||
* Return value: (transfer full): a new #GUri, or NULL on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
GUri *
|
||
g_uri_parse (const gchar *uri_string,
|
||
GUriFlags flags,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (uri_string != NULL, NULL);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||
|
||
return g_uri_parse_relative (NULL, uri_string, flags, error);
|
||
}
|
||
|
||
/**
|
||
* g_uri_parse_relative:
|
||
* @base_uri: (nullable) (transfer none): a base absolute URI
|
||
* @uri_ref: a string representing a relative or absolute URI
|
||
* @flags: flags describing how to parse @uri_ref
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Parses @uri_ref according to @flags and, if it is a
|
||
* [relative URI](#relative-and-absolute-uris), 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, or NULL on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
GUri *
|
||
g_uri_parse_relative (GUri *base_uri,
|
||
const gchar *uri_ref,
|
||
GUriFlags flags,
|
||
GError **error)
|
||
{
|
||
GUri *uri = NULL;
|
||
|
||
g_return_val_if_fail (uri_ref != NULL, NULL);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||
g_return_val_if_fail (base_uri == NULL || base_uri->scheme != NULL, NULL);
|
||
|
||
/* Use GUri struct to construct the return value: there is no guarantee it is
|
||
* actually correct within the function body. */
|
||
uri = g_atomic_rc_box_new0 (GUri);
|
||
uri->flags = flags;
|
||
|
||
if (!g_uri_split_internal (uri_ref, flags,
|
||
&uri->scheme, &uri->userinfo,
|
||
&uri->user, &uri->password, &uri->auth_params,
|
||
&uri->host, &uri->port,
|
||
&uri->path, &uri->query, &uri->fragment,
|
||
error))
|
||
{
|
||
g_uri_unref (uri);
|
||
return NULL;
|
||
}
|
||
|
||
if (!uri->scheme && !base_uri)
|
||
{
|
||
g_set_error_literal (error, G_URI_ERROR, G_URI_ERROR_FAILED,
|
||
_("URI is not absolute, and no base URI was provided"));
|
||
g_uri_unref (uri);
|
||
return NULL;
|
||
}
|
||
|
||
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.
|
||
*
|
||
* See https://tools.ietf.org/html/rfc3986#section-5.2.2
|
||
*/
|
||
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 = g_steal_pointer (&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;
|
||
}
|
||
}
|
||
|
||
/* Scheme normalization couldn't have been done earlier
|
||
* as the relative URI may not have had a scheme */
|
||
if (flags & G_URI_FLAGS_SCHEME_NORMALIZE)
|
||
{
|
||
if (should_normalize_empty_path (uri->scheme) && !*uri->path)
|
||
{
|
||
g_free (uri->path);
|
||
uri->path = g_strdup ("/");
|
||
}
|
||
|
||
uri->port = normalize_port (uri->scheme, uri->port);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
remove_dot_segments (uri->path);
|
||
}
|
||
|
||
return g_steal_pointer (&uri);
|
||
}
|
||
|
||
/**
|
||
* g_uri_resolve_relative:
|
||
* @base_uri_string: (nullable): a string representing a base URI
|
||
* @uri_ref: a string representing a relative or absolute URI
|
||
* @flags: flags describing how to parse @uri_ref
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Parses @uri_ref according to @flags and, if it is a
|
||
* [relative URI](#relative-and-absolute-uris), 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_ref, or
|
||
* %NULL if @uri_ref is invalid or not absolute.)
|
||
*
|
||
* Return value: (transfer full): the resolved URI string,
|
||
* or NULL on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gchar *
|
||
g_uri_resolve_relative (const gchar *base_uri_string,
|
||
const gchar *uri_ref,
|
||
GUriFlags flags,
|
||
GError **error)
|
||
{
|
||
GUri *base_uri, *resolved_uri;
|
||
gchar *resolved_uri_string;
|
||
|
||
g_return_val_if_fail (uri_ref != NULL, NULL);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||
|
||
flags |= G_URI_FLAGS_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_ref, 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 g_steal_pointer (&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,
|
||
gboolean userinfo,
|
||
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_FLAGS_ENCODED);
|
||
GString *str;
|
||
char *normalized_scheme = NULL;
|
||
|
||
/* Restrictions on path prefixes. See:
|
||
* https://tools.ietf.org/html/rfc3986#section-3
|
||
*/
|
||
g_return_val_if_fail (path != NULL, NULL);
|
||
g_return_val_if_fail (host == NULL || (path[0] == '\0' || path[0] == '/'), NULL);
|
||
g_return_val_if_fail (host != NULL || (path[0] != '/' || path[1] != '/'), NULL);
|
||
|
||
/* Arbitrarily chosen default size which should handle most average length
|
||
* URIs. This should avoid a few reallocations of the buffer in most cases.
|
||
* It’s 1B shorter than a power of two, since GString will add a
|
||
* nul-terminator byte. */
|
||
str = g_string_sized_new (127);
|
||
|
||
if (scheme)
|
||
{
|
||
g_string_append (str, scheme);
|
||
g_string_append_c (str, ':');
|
||
}
|
||
|
||
if (flags & G_URI_FLAGS_SCHEME_NORMALIZE && scheme && ((host && port != -1) || path[0] == '\0'))
|
||
normalized_scheme = g_ascii_strdown (scheme, -1);
|
||
|
||
if (host)
|
||
{
|
||
g_string_append (str, "//");
|
||
|
||
if (user)
|
||
{
|
||
if (encoded)
|
||
g_string_append (str, user);
|
||
else
|
||
{
|
||
if (userinfo)
|
||
g_string_append_uri_escaped (str, user, USERINFO_ALLOWED_CHARS, TRUE);
|
||
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, password,
|
||
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, auth_params,
|
||
AUTH_PARAMS_ALLOWED_CHARS, TRUE);
|
||
}
|
||
|
||
g_string_append_c (str, '@');
|
||
}
|
||
|
||
if (strchr (host, ':') && g_hostname_is_ip_address (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 && (!normalized_scheme || normalize_port (normalized_scheme, port) != -1))
|
||
g_string_append_printf (str, ":%d", port);
|
||
}
|
||
|
||
if (path[0] == '\0' && normalized_scheme && should_normalize_empty_path (normalized_scheme))
|
||
g_string_append (str, "/");
|
||
else if (encoded || flags & G_URI_FLAGS_ENCODED_PATH)
|
||
g_string_append (str, path);
|
||
else
|
||
g_string_append_uri_escaped (str, path, PATH_ALLOWED_CHARS, TRUE);
|
||
|
||
g_free (normalized_scheme);
|
||
|
||
if (query)
|
||
{
|
||
g_string_append_c (str, '?');
|
||
if (encoded || flags & G_URI_FLAGS_ENCODED_QUERY)
|
||
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 || flags & G_URI_FLAGS_ENCODED_FRAGMENT)
|
||
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: (nullable): the URI scheme, or %NULL
|
||
* @userinfo: (nullable): the userinfo component, or %NULL
|
||
* @host: (nullable): the host component, or %NULL
|
||
* @port: the port, or `-1`
|
||
* @path: (not nullable): the path component
|
||
* @query: (nullable): the query component, or %NULL
|
||
* @fragment: (nullable): the fragment, or %NULL
|
||
*
|
||
* Joins the given components together according to @flags to create
|
||
* an absolute URI string. @path may not be %NULL (though it may be the empty
|
||
* string).
|
||
*
|
||
* When @host is present, @path must either be empty or begin with a slash (`/`)
|
||
* character. When @host is not present, @path cannot begin with two slash
|
||
* characters (`//`). See
|
||
* [RFC 3986, section 3](https://tools.ietf.org/html/rfc3986#section-3).
|
||
*
|
||
* See also g_uri_join_with_user(), which allows specifying the
|
||
* components of the ‘userinfo’ separately.
|
||
*
|
||
* %G_URI_FLAGS_HAS_PASSWORD and %G_URI_FLAGS_HAS_AUTH_PARAMS are ignored if set
|
||
* in @flags.
|
||
*
|
||
* Return value: (not nullable) (transfer full): an absolute URI string
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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 (port >= -1 && port <= 65535, NULL);
|
||
g_return_val_if_fail (path != NULL, NULL);
|
||
|
||
return g_uri_join_internal (flags,
|
||
scheme,
|
||
TRUE, userinfo, NULL, NULL,
|
||
host,
|
||
port,
|
||
path,
|
||
query,
|
||
fragment);
|
||
}
|
||
|
||
/**
|
||
* g_uri_join_with_user:
|
||
* @flags: flags describing how to build the URI string
|
||
* @scheme: (nullable): the URI scheme, or %NULL
|
||
* @user: (nullable): the user component of the userinfo, or %NULL
|
||
* @password: (nullable): the password component of the userinfo, or
|
||
* %NULL
|
||
* @auth_params: (nullable): the auth params of the userinfo, or
|
||
* %NULL
|
||
* @host: (nullable): the host component, or %NULL
|
||
* @port: the port, or `-1`
|
||
* @path: (not nullable): the path component
|
||
* @query: (nullable): the query component, or %NULL
|
||
* @fragment: (nullable): the fragment, or %NULL
|
||
*
|
||
* Joins the given components together according to @flags to create
|
||
* an absolute URI string. @path may not be %NULL (though it may be the empty
|
||
* string).
|
||
*
|
||
* In contrast to g_uri_join(), this allows specifying the components
|
||
* of the ‘userinfo’ separately. It otherwise behaves the same.
|
||
*
|
||
* %G_URI_FLAGS_HAS_PASSWORD and %G_URI_FLAGS_HAS_AUTH_PARAMS are ignored if set
|
||
* in @flags.
|
||
*
|
||
* Return value: (not nullable) (transfer full): an absolute URI string
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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 (port >= -1 && port <= 65535, NULL);
|
||
g_return_val_if_fail (path != NULL, NULL);
|
||
|
||
return g_uri_join_internal (flags,
|
||
scheme,
|
||
FALSE, user, password, auth_params,
|
||
host,
|
||
port,
|
||
path,
|
||
query,
|
||
fragment);
|
||
}
|
||
|
||
/**
|
||
* g_uri_build:
|
||
* @flags: flags describing how to build the #GUri
|
||
* @scheme: (not nullable): the URI scheme
|
||
* @userinfo: (nullable): the userinfo component, or %NULL
|
||
* @host: (nullable): the host component, or %NULL
|
||
* @port: the port, or `-1`
|
||
* @path: (not nullable): the path component
|
||
* @query: (nullable): the query component, or %NULL
|
||
* @fragment: (nullable): 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: (not nullable) (transfer full): a new #GUri
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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_atomic_rc_box_new0 (GUri);
|
||
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 g_steal_pointer (&uri);
|
||
}
|
||
|
||
/**
|
||
* g_uri_build_with_user:
|
||
* @flags: flags describing how to build the #GUri
|
||
* @scheme: (not nullable): the URI scheme
|
||
* @user: (nullable): the user component of the userinfo, or %NULL
|
||
* @password: (nullable): the password component of the userinfo, or %NULL
|
||
* @auth_params: (nullable): the auth params of the userinfo, or %NULL
|
||
* @host: (nullable): the host component, or %NULL
|
||
* @port: the port, or `-1`
|
||
* @path: (not nullable): the path component
|
||
* @query: (nullable): the query component, or %NULL
|
||
* @fragment: (nullable): the fragment, or %NULL
|
||
*
|
||
* Creates a new #GUri from the given components according to @flags
|
||
* (%G_URI_FLAGS_HAS_PASSWORD is added unconditionally). The @flags must be
|
||
* coherent with the passed values, in particular use `%`-encoded values with
|
||
* %G_URI_FLAGS_ENCODED.
|
||
*
|
||
* In contrast 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: (not nullable) (transfer full): a new #GUri
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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_atomic_rc_box_new0 (GUri);
|
||
uri->flags = flags | G_URI_FLAGS_HAS_PASSWORD;
|
||
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 (user);
|
||
if (password)
|
||
{
|
||
g_string_append_c (userinfo, ':');
|
||
g_string_append (userinfo, uri->password);
|
||
}
|
||
if (auth_params)
|
||
{
|
||
g_string_append_c (userinfo, ';');
|
||
g_string_append (userinfo, uri->auth_params);
|
||
}
|
||
uri->userinfo = g_string_free (userinfo, FALSE);
|
||
}
|
||
|
||
return g_steal_pointer (&uri);
|
||
}
|
||
|
||
/**
|
||
* 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_FLAGS_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).
|
||
*
|
||
* If @uri might contain sensitive details, such as authentication parameters,
|
||
* or private data in its query string, and the returned string is going to be
|
||
* logged, then consider using g_uri_to_string_partial() to redact parts.
|
||
*
|
||
* Return value: (not nullable) (transfer full): a string representing @uri,
|
||
* which the caller must free.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gchar *
|
||
g_uri_to_string (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
return g_uri_to_string_partial (uri, G_URI_HIDE_NONE);
|
||
}
|
||
|
||
/**
|
||
* 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: (not nullable) (transfer full): a string representing
|
||
* @uri, which the caller must free.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
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_query = (flags & G_URI_HIDE_QUERY);
|
||
gboolean hide_fragment = (flags & G_URI_HIDE_FRAGMENT);
|
||
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
if (uri->flags & (G_URI_FLAGS_HAS_PASSWORD | G_URI_FLAGS_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,
|
||
hide_query ? NULL : uri->query,
|
||
hide_fragment ? NULL : uri->fragment);
|
||
}
|
||
|
||
return g_uri_join (uri->flags,
|
||
uri->scheme,
|
||
hide_user ? NULL : uri->userinfo,
|
||
uri->host,
|
||
uri->port,
|
||
uri->path,
|
||
hide_query ? NULL : 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;
|
||
}
|
||
|
||
/**
|
||
* GUriParamsIter:
|
||
*
|
||
* Many URI schemes include one or more attribute/value pairs as part of the URI
|
||
* value. For example `scheme://server/path?query=string&is=there` has two
|
||
* attributes – `query=string` and `is=there` – in its query part.
|
||
*
|
||
* A #GUriParamsIter structure represents an iterator that can be used to
|
||
* iterate over the attribute/value pairs of a URI query string. #GUriParamsIter
|
||
* structures are typically allocated on the stack and then initialized with
|
||
* g_uri_params_iter_init(). See the documentation for g_uri_params_iter_init()
|
||
* for a usage example.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
typedef struct
|
||
{
|
||
GUriParamsFlags flags;
|
||
const gchar *attr;
|
||
const gchar *end;
|
||
guint8 sep_table[256]; /* 1 = index is a separator; 0 otherwise */
|
||
} RealIter;
|
||
|
||
G_STATIC_ASSERT (sizeof (GUriParamsIter) == sizeof (RealIter));
|
||
G_STATIC_ASSERT (G_ALIGNOF (GUriParamsIter) >= G_ALIGNOF (RealIter));
|
||
|
||
/**
|
||
* g_uri_params_iter_init:
|
||
* @iter: an uninitialized #GUriParamsIter
|
||
* @params: a `%`-encoded string containing `attribute=value`
|
||
* parameters
|
||
* @length: the length of @params, or `-1` if it is nul-terminated
|
||
* @separators: the separator byte character set between parameters. (usually
|
||
* `&`, but sometimes `;` or both `&;`). Note that this function works on
|
||
* bytes not characters, so it can't be used to delimit UTF-8 strings for
|
||
* anything but ASCII characters. You may pass an empty set, in which case
|
||
* no splitting will occur.
|
||
* @flags: flags to modify the way the parameters are handled.
|
||
*
|
||
* Initializes an attribute/value pair iterator.
|
||
*
|
||
* The iterator keeps pointers to the @params and @separators arguments, those
|
||
* variables must thus outlive the iterator and not be modified during the
|
||
* iteration.
|
||
*
|
||
* If %G_URI_PARAMS_WWW_FORM is passed in @flags, `+` characters in the param
|
||
* string will be replaced with spaces in the output. For example, `foo=bar+baz`
|
||
* will give attribute `foo` with value `bar baz`. This is commonly used on the
|
||
* web (the `https` and `http` schemes only), but is deprecated in favour of
|
||
* the equivalent of encoding spaces as `%20`.
|
||
*
|
||
* Unlike with g_uri_parse_params(), %G_URI_PARAMS_CASE_INSENSITIVE has no
|
||
* effect if passed to @flags for g_uri_params_iter_init(). The caller is
|
||
* responsible for doing their own case-insensitive comparisons.
|
||
*
|
||
* |[<!-- language="C" -->
|
||
* GUriParamsIter iter;
|
||
* GError *error = NULL;
|
||
* gchar *unowned_attr, *unowned_value;
|
||
*
|
||
* g_uri_params_iter_init (&iter, "foo=bar&baz=bar&Foo=frob&baz=bar2", -1, "&", G_URI_PARAMS_NONE);
|
||
* while (g_uri_params_iter_next (&iter, &unowned_attr, &unowned_value, &error))
|
||
* {
|
||
* g_autofree gchar *attr = g_steal_pointer (&unowned_attr);
|
||
* g_autofree gchar *value = g_steal_pointer (&unowned_value);
|
||
* // do something with attr and value; this code will be called 4 times
|
||
* // for the params string in this example: once with attr=foo and value=bar,
|
||
* // then with baz/bar, then Foo/frob, then baz/bar2.
|
||
* }
|
||
* if (error)
|
||
* // handle parsing error
|
||
* ]|
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
void
|
||
g_uri_params_iter_init (GUriParamsIter *iter,
|
||
const gchar *params,
|
||
gssize length,
|
||
const gchar *separators,
|
||
GUriParamsFlags flags)
|
||
{
|
||
RealIter *ri = (RealIter *)iter;
|
||
const gchar *s;
|
||
|
||
g_return_if_fail (iter != NULL);
|
||
g_return_if_fail (length == 0 || params != NULL);
|
||
g_return_if_fail (length >= -1);
|
||
g_return_if_fail (separators != NULL);
|
||
|
||
ri->flags = flags;
|
||
|
||
if (length == -1)
|
||
ri->end = params + strlen (params);
|
||
else
|
||
ri->end = params + length;
|
||
|
||
memset (ri->sep_table, FALSE, sizeof (ri->sep_table));
|
||
for (s = separators; *s != '\0'; ++s)
|
||
ri->sep_table[*(guchar *)s] = TRUE;
|
||
|
||
ri->attr = params;
|
||
}
|
||
|
||
/**
|
||
* g_uri_params_iter_next:
|
||
* @iter: an initialized #GUriParamsIter
|
||
* @attribute: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the attribute, or %NULL.
|
||
* @value: (out) (nullable) (optional) (transfer full): on return, contains
|
||
* the value, or %NULL.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Advances @iter and retrieves the next attribute/value. %FALSE is returned if
|
||
* an error has occurred (in which case @error is set), or if the end of the
|
||
* iteration is reached (in which case @attribute and @value are set to %NULL
|
||
* and the iterator becomes invalid). If %TRUE is returned,
|
||
* g_uri_params_iter_next() may be called again to receive another
|
||
* attribute/value pair.
|
||
*
|
||
* Note that the same @attribute may be returned multiple times, since URIs
|
||
* allow repeated attributes.
|
||
*
|
||
* Returns: %FALSE if the end of the parameters has been reached or an error was
|
||
* encountered. %TRUE otherwise.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gboolean
|
||
g_uri_params_iter_next (GUriParamsIter *iter,
|
||
gchar **attribute,
|
||
gchar **value,
|
||
GError **error)
|
||
{
|
||
RealIter *ri = (RealIter *)iter;
|
||
const gchar *attr_end, *val, *val_end;
|
||
gchar *decoded_attr, *decoded_value;
|
||
gboolean www_form = ri->flags & G_URI_PARAMS_WWW_FORM;
|
||
GUriFlags decode_flags = G_URI_FLAGS_NONE;
|
||
|
||
g_return_val_if_fail (iter != NULL, FALSE);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
/* Pre-clear these in case of failure or finishing. */
|
||
if (attribute)
|
||
*attribute = NULL;
|
||
if (value)
|
||
*value = NULL;
|
||
|
||
if (ri->attr >= ri->end)
|
||
return FALSE;
|
||
|
||
if (ri->flags & G_URI_PARAMS_PARSE_RELAXED)
|
||
decode_flags |= G_URI_FLAGS_PARSE_RELAXED;
|
||
|
||
/* Check if each character in @attr is a separator, by indexing by the
|
||
* character value into the @sep_table, which has value 1 stored at an
|
||
* index if that index is a separator. */
|
||
for (val_end = ri->attr; val_end < ri->end; val_end++)
|
||
if (ri->sep_table[*(guchar *)val_end])
|
||
break;
|
||
|
||
attr_end = memchr (ri->attr, '=', val_end - ri->attr);
|
||
if (!attr_end)
|
||
{
|
||
g_set_error_literal (error, G_URI_ERROR, G_URI_ERROR_FAILED,
|
||
_("Missing ‘=’ and parameter value"));
|
||
return FALSE;
|
||
}
|
||
if (!uri_decode (&decoded_attr, NULL, ri->attr, attr_end - ri->attr,
|
||
www_form, decode_flags, G_URI_ERROR_FAILED, error))
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
val = attr_end + 1;
|
||
if (!uri_decode (&decoded_value, NULL, val, val_end - val,
|
||
www_form, decode_flags, G_URI_ERROR_FAILED, error))
|
||
{
|
||
g_free (decoded_attr);
|
||
return FALSE;
|
||
}
|
||
|
||
if (attribute)
|
||
*attribute = g_steal_pointer (&decoded_attr);
|
||
if (value)
|
||
*value = g_steal_pointer (&decoded_value);
|
||
|
||
g_free (decoded_attr);
|
||
g_free (decoded_value);
|
||
|
||
ri->attr = val_end + 1;
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_uri_parse_params:
|
||
* @params: a `%`-encoded string containing `attribute=value`
|
||
* parameters
|
||
* @length: the length of @params, or `-1` if it is nul-terminated
|
||
* @separators: the separator byte character set between parameters. (usually
|
||
* `&`, but sometimes `;` or both `&;`). Note that this function works on
|
||
* bytes not characters, so it can't be used to delimit UTF-8 strings for
|
||
* anything but ASCII characters. You may pass an empty set, in which case
|
||
* no splitting will occur.
|
||
* @flags: flags to modify the way the parameters are handled.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* 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. When an
|
||
* attribute has multiple occurrences, the last value is the final returned
|
||
* value. If you need to handle repeated attributes differently, use
|
||
* #GUriParamsIter.
|
||
*
|
||
* 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 @separators, if the value was encoded in the input.)
|
||
* Invalid `%`-encoding is treated as with the %G_URI_FLAGS_PARSE_RELAXED
|
||
* rules for g_uri_parse(). (However, if @params is the path or query string
|
||
* from a #GUri that was parsed without %G_URI_FLAGS_PARSE_RELAXED and
|
||
* %G_URI_FLAGS_ENCODED, then you already know that it does not contain any
|
||
* invalid encoding.)
|
||
*
|
||
* %G_URI_PARAMS_WWW_FORM is handled as documented for g_uri_params_iter_init().
|
||
*
|
||
* If %G_URI_PARAMS_CASE_INSENSITIVE is passed to @flags, attributes will be
|
||
* compared case-insensitively, so a params string `attr=123&Attr=456` will only
|
||
* return a single attribute–value pair, `Attr=456`. Case will be preserved in
|
||
* the returned attributes.
|
||
*
|
||
* If @params cannot be parsed (for example, it contains two @separators
|
||
* characters in a row), then @error is set and %NULL is returned.
|
||
*
|
||
* Return value: (transfer full) (element-type utf8 utf8):
|
||
* A hash table of attribute/value pairs, with both names and values
|
||
* fully-decoded; or %NULL on error.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
GHashTable *
|
||
g_uri_parse_params (const gchar *params,
|
||
gssize length,
|
||
const gchar *separators,
|
||
GUriParamsFlags flags,
|
||
GError **error)
|
||
{
|
||
GHashTable *hash;
|
||
GUriParamsIter iter;
|
||
gchar *attribute, *value;
|
||
GError *err = NULL;
|
||
|
||
g_return_val_if_fail (length == 0 || params != NULL, NULL);
|
||
g_return_val_if_fail (length >= -1, NULL);
|
||
g_return_val_if_fail (separators != NULL, NULL);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||
|
||
if (flags & G_URI_PARAMS_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);
|
||
}
|
||
|
||
g_uri_params_iter_init (&iter, params, length, separators, flags);
|
||
|
||
while (g_uri_params_iter_next (&iter, &attribute, &value, &err))
|
||
g_hash_table_insert (hash, attribute, value);
|
||
|
||
if (err)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&err));
|
||
g_hash_table_destroy (hash);
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (&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: (not nullable): @uri's scheme.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_scheme (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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: (nullable): @uri's userinfo.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_userinfo (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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_FLAGS_HAS_PASSWORD or
|
||
* %G_URI_FLAGS_HAS_AUTH_PARAMS, this is the same as g_uri_get_userinfo().
|
||
*
|
||
* Return value: (nullable): @uri's user.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_user (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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_FLAGS_HAS_PASSWORD then this will be %NULL.)
|
||
*
|
||
* Return value: (nullable): @uri's password.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_password (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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_FLAGS_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: (nullable): @uri's authentication parameters.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_auth_params (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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_FLAGS_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: (nullable): @uri's host.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_host (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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.66
|
||
*/
|
||
gint
|
||
g_uri_get_port (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, -1);
|
||
|
||
if (uri->port == -1 && uri->flags & G_URI_FLAGS_SCHEME_NORMALIZE)
|
||
return g_uri_get_default_scheme_port (uri->scheme);
|
||
|
||
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: (not nullable): @uri's path.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_path (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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,
|
||
* #GUriParamsIter or g_uri_parse_params() may be useful.
|
||
*
|
||
* Return value: (nullable): @uri's query.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_query (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
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: (nullable): @uri's fragment.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
const gchar *
|
||
g_uri_get_fragment (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
return uri->fragment;
|
||
}
|
||
|
||
|
||
/**
|
||
* g_uri_get_flags:
|
||
* @uri: a #GUri
|
||
*
|
||
* Gets @uri's flags set upon construction.
|
||
*
|
||
* Return value: @uri's flags.
|
||
*
|
||
* Since: 2.66
|
||
**/
|
||
GUriFlags
|
||
g_uri_get_flags (GUri *uri)
|
||
{
|
||
g_return_val_if_fail (uri != NULL, G_URI_FLAGS_NONE);
|
||
|
||
return uri->flags;
|
||
}
|
||
|
||
/**
|
||
* g_uri_unescape_segment:
|
||
* @escaped_string: (nullable): A string, may be %NULL
|
||
* @escaped_string_end: (nullable): Pointer to end of @escaped_string,
|
||
* may be %NULL
|
||
* @illegal_characters: (nullable): 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.
|
||
*
|
||
* Note: `NUL` byte is not accepted in the output, in contrast to
|
||
* g_uri_unescape_bytes().
|
||
*
|
||
* Returns: (nullable): 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;
|
||
gsize length;
|
||
gssize decoded_len;
|
||
|
||
if (!escaped_string)
|
||
return NULL;
|
||
|
||
if (escaped_string_end)
|
||
length = escaped_string_end - escaped_string;
|
||
else
|
||
length = strlen (escaped_string);
|
||
|
||
decoded_len = uri_decoder (&unescaped,
|
||
illegal_characters,
|
||
escaped_string, length,
|
||
FALSE, FALSE,
|
||
G_URI_FLAGS_ENCODED,
|
||
0, NULL);
|
||
if (decoded_len < 0)
|
||
return NULL;
|
||
|
||
if (memchr (unescaped, '\0', decoded_len))
|
||
{
|
||
g_free (unescaped);
|
||
return NULL;
|
||
}
|
||
|
||
return unescaped;
|
||
}
|
||
|
||
/**
|
||
* g_uri_unescape_string:
|
||
* @escaped_string: an escaped string to be unescaped.
|
||
* @illegal_characters: (nullable): 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: (nullable): 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_uri_escape_string:
|
||
* @unescaped: the unescaped input string.
|
||
* @reserved_chars_allowed: (nullable): 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: (not nullable): 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 (in bytes) of @escaped_string to escape, or `-1` if it
|
||
* is nul-terminated.
|
||
* @illegal_characters: (nullable): a string of illegal characters
|
||
* not to be allowed, or %NULL.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* 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.
|
||
*
|
||
* If any of the characters in @illegal_characters 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: (transfer full): an unescaped version of @escaped_string
|
||
* or %NULL on error (if decoding failed, using %G_URI_ERROR_FAILED error
|
||
* code). The returned #GBytes should be unreffed when no longer needed.
|
||
*
|
||
* Since: 2.66
|
||
**/
|
||
GBytes *
|
||
g_uri_unescape_bytes (const gchar *escaped_string,
|
||
gssize length,
|
||
const char *illegal_characters,
|
||
GError **error)
|
||
{
|
||
gchar *buf;
|
||
gssize unescaped_length;
|
||
|
||
g_return_val_if_fail (escaped_string != NULL, NULL);
|
||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||
|
||
if (length == -1)
|
||
length = strlen (escaped_string);
|
||
|
||
unescaped_length = uri_decoder (&buf,
|
||
illegal_characters,
|
||
escaped_string, length,
|
||
FALSE,
|
||
FALSE,
|
||
G_URI_FLAGS_ENCODED,
|
||
G_URI_ERROR_FAILED, error);
|
||
if (unescaped_length == -1)
|
||
return NULL;
|
||
|
||
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: (nullable): 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 nul
|
||
* bytes as `%``00`.
|
||
*
|
||
* Returns: (not nullable) (transfer full): an escaped version of @unescaped.
|
||
* The returned string should be freed when no longer needed.
|
||
*
|
||
* Since: 2.66
|
||
*/
|
||
gchar *
|
||
g_uri_escape_bytes (const guint8 *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 gssize
|
||
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;
|
||
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* g_uri_parse_scheme:
|
||
* @uri: a valid URI.
|
||
*
|
||
* Gets the scheme portion of a URI string.
|
||
* [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3) decodes the scheme
|
||
* as:
|
||
* |[
|
||
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||
* ]|
|
||
* Common schemes include `file`, `https`, `svn+ssh`, etc.
|
||
*
|
||
* Returns: (transfer full) (nullable): The ‘scheme’ component of the URI, or
|
||
* %NULL on error. The returned string should be freed when no longer needed.
|
||
*
|
||
* Since: 2.16
|
||
**/
|
||
gchar *
|
||
g_uri_parse_scheme (const gchar *uri)
|
||
{
|
||
gssize 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](https://tools.ietf.org/html/rfc3986#section-3) decodes the scheme
|
||
* as:
|
||
* |[
|
||
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||
* ]|
|
||
* Common schemes include `file`, `https`, `svn+ssh`, etc.
|
||
*
|
||
* Unlike g_uri_parse_scheme(), the returned scheme is normalized to
|
||
* all-lowercase and does not need to be freed.
|
||
*
|
||
* Returns: (transfer none) (nullable): 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.66
|
||
**/
|
||
const gchar *
|
||
g_uri_peek_scheme (const gchar *uri)
|
||
{
|
||
gssize 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)
|