| /* 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) |