From f3fe229c9d349d06083f9cdf1ae163b84b1ad1d8 Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Sat, 22 Jul 2023 12:19:56 -0300 Subject: [PATCH 1/4] style: GDB printers --- extra/gdb/README.adoc | 40 ++++ extra/gdb/__init__.py | 1 + extra/gdb/boost_url_printers.py | 399 ++++++++++++++++++++++++++++++++ 3 files changed, 440 insertions(+) create mode 100644 extra/gdb/README.adoc create mode 100644 extra/gdb/__init__.py create mode 100644 extra/gdb/boost_url_printers.py diff --git a/extra/gdb/README.adoc b/extra/gdb/README.adoc new file mode 100644 index 00000000..334957db --- /dev/null +++ b/extra/gdb/README.adoc @@ -0,0 +1,40 @@ += GDB pretty printers + +Create or modify your `.gdbinit` file to contain the following: + +[source,python] +---- +python +import sys +sys.path.insert(0, '/path/to/boost/url/extra/gdb') <1> +from boost_url_printers import register_boost_url_printers <2> +register_boost_url_printers() <3> +end +---- + +<1> Make GDB see the directory with the printers +<2> Import the function that registers the printers with GDB +<3> Effectively register the printers + +Note that this pattern does not register the printers unless the user explicitly asks for it by calling the `register_boost_url_printers` function. +This helps the scripts separate these concerns. + +The pretty printers will display all components of the URL, including the encoded and decoded versions of the host, path, query, and fragment. + +This can save users time trying to figure out why a string parsed successfully but the result of the `encoded_X` member function is not what they are expecting. + +NOTE: The printers require Python 3 + +== Development mode + +Developers can also register the printers with the `dev` option set to `True`: + +[source,python] +---- +register_boost_url_printers(dev=True) +---- + +In this mode, the fields will represent the internal offsets for all parts of the URLs in the underlying data structure and represent the substrings at each of these offsets. + + + diff --git a/extra/gdb/__init__.py b/extra/gdb/__init__.py new file mode 100644 index 00000000..bb7b160d --- /dev/null +++ b/extra/gdb/__init__.py @@ -0,0 +1 @@ +# Intentionally empty diff --git a/extra/gdb/boost_url_printers.py b/extra/gdb/boost_url_printers.py new file mode 100644 index 00000000..c3c3ca65 --- /dev/null +++ b/extra/gdb/boost_url_printers.py @@ -0,0 +1,399 @@ +# +# Copyright (c) 2023 alandefreitas (alandefreitas@gmail.com) +# +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt +# + +import gdb +import urllib.parse + + +class utils: + @staticmethod + def resolve_type(t): + if t.code == gdb.TYPE_CODE_REF: + t = t.target() + t = t.unqualified().strip_typedefs() + typename = t.tag + if typename is None: + return None + return t + + @staticmethod + def resolved_typename(val): + t = val.type + t = utils.resolve_type(t) + if t is not None: + return str(t) + else: + return str(val.type) + + @staticmethod + def pct_decode(s: str): + return urllib.parse.unquote(s) + + sv_pool = [] + + @staticmethod + def make_string_view(cstr: gdb.Value, n: int): + sv_ptr: gdb.Value = gdb.parse_and_eval('malloc(sizeof(class boost::core::basic_string_view))') + sv_ptr_str = cstr.format_string(format='x') + gdb.execute( + f'call ((boost::core::basic_string_view*){sv_ptr})->basic_string_view((const char*){sv_ptr_str}, {n})', + to_string=True) + sv = gdb.parse_and_eval(f'*((boost::core::basic_string_view*){sv_ptr})') + copy: gdb.Value = sv + utils.sv_pool.append(sv_ptr) + if len(utils.sv_pool) > 5000: + gdb.execute(f'call free({utils.sv_pool[0]})', to_string=True) + utils.sv_pool.pop(0) + return copy + + pct_sv_pool = [] + + @staticmethod + def make_pct_string_view(cstr: gdb.Value, n: int): + sv_ptr: gdb.Value = gdb.parse_and_eval('malloc(sizeof(class boost::urls::pct_string_view))') + sv_ptr_str = cstr.format_string(format='x') + gdb.execute( + f'call ((boost::urls::pct_string_view*){sv_ptr})->pct_string_view((const char*){sv_ptr_str}, {n})', + to_string=True) + sv = gdb.parse_and_eval(f'*((boost::urls::pct_string_view*){sv_ptr})') + copy: gdb.Value = sv + utils.pct_sv_pool.append(sv_ptr) + if len(utils.sv_pool) > 5000: + gdb.execute(f'call free({utils.pct_sv_pool[0]})', to_string=True) + utils.sv_pool.pop(0) + return copy + + +class StringViewPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + s = str(self.value['p_']) + ptr, s = s.split(' ', 1) + s = s[:self.value['n_'] + 1] + '"' + return f"{ptr} {s} ({self.value['n_']})" + # return f"{{ptr} {s} ({self.value['n_']})" + + +class PctStringViewPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + if self.value['dn_'] != self.value['s_']['n_']: + sd = utils.pct_decode(str(self.value['s_']['p_'])) + ptr, sd = sd.split(' ', 1) + sd = sd[:self.value['dn_'] + 1] + '"' + return f"{self.value['s_']}: {sd} ({self.value['dn_']})" + else: + return f"{self.value['s_']}" + + +class UrlImplPrinter: + parts = ['scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment'] + grammar_part = ['scheme ":"', '"//" user', '":" pass "@"', 'host', '":" port', 'path', '"?" query', + '"#" fragment'] + part_delimiters = [1, 2, 2, 0, 1, 0, 1, 1] + show_offset_parts = False + + def __init__(self, value, prefix=None, print_cstr=None): + self.value = value + self.prefix = '' if prefix is None else prefix + '::' + self.print_cstr = True if print_cstr is None else print_cstr + + def to_string(self): + return self.value['cs_'] + + def children(self): + if self.print_cstr: + yield 'buffer', self.value['cs_'] + + yield 'size', self.value['offset_'][7] + + # Parts + part_name = self.grammar_part[0] + component_name = self.parts[0] + part_size = self.value['offset_'][0] + if part_size != 0: + if self.show_offset_parts: + yield f"{part_name}", utils.make_string_view(self.value['cs_'], part_size) + else: + yield f"{component_name}", utils.make_string_view(self.value['cs_'], part_size - 1) + yield f"scheme id", self.value['scheme_'] + for i in range(7): + cs_part = self.value['cs_'] + self.value['offset_'][i] + part_name = self.grammar_part[i + 1] + component_name = self.parts[i + 1] + part_offset = self.value['offset_'][i] + next_part_offset = self.value['offset_'][i + 1] + part_size = next_part_offset - part_offset + if self.show_offset_parts: + yield f"{part_name}", utils.make_pct_string_view(cs_part, part_size) + elif part_size != 0: + component_part_offset = { + 'user': 2, + 'pass': 1, + 'port': 1, + 'query': 1, + 'fragment': 1 + } + component_part_size_diff = { + 'user': 2, + 'pass': 2, + 'port': 1, + 'query': 1, + 'fragment': 1 + } + off = component_part_offset[component_name] if component_name in component_part_offset else 0 + diff = component_part_size_diff[component_name] if component_name in component_part_size_diff else 0 + if part_size - diff > 0: + yield f"{component_name}", utils.make_pct_string_view(cs_part + off, part_size - diff) + if component_name == 'host': + yield f"host type", self.value['host_type_'] + if not str(self.value['host_type_']).endswith('name'): + yield f"ip address", self.value['ip_addr_'] + elif component_name == 'port': + yield f"port number", self.value['port_number_'] + elif component_name == 'path': + yield f"# segments", self.value['nseg_'] + elif component_name == 'query': + yield f"# params", self.value['nparam_'] + + already_yielded = ['zero_', 'cs_', 'offset_', 'decoded_', 'nseg_', 'nparam_', 'scheme_', + 'port_number_', 'ip_addr_', 'host_type_'] + for field in self.value.type.fields(): + if field.is_base_class: + if not field.name.endswith('parts_base'): + yield self.prefix + field.name, self.value.cast(field.type) + elif field.artificial: + continue + elif field.name not in already_yielded: + name = field.name if not field.name.endswith('_') else field.name[:-1] + yield name, self.value[field.name] + + +class UrlViewBasePrinter: + def __init__(self, value, prefix=None, print_cstr=None): + self.value = value + self.prefix = '' if prefix is None else prefix + '::' + self.print_cstr = True if print_cstr is None else print_cstr + + def to_string(self): + return self.value['s_'] + + def children(self): + obj_type = self.value.type + + inline_impl: bool = self.value['pi_'] == self.value['impl_'].address + if inline_impl: + yield from UrlImplPrinter(self.value['impl_'], 'url_impl', print_cstr=self.print_cstr).children() + else: + yield 'reference', self.value['pi_'] + + for field in obj_type.fields(): + if field.is_base_class: + if not field.name.endswith('parts_base'): + yield self.prefix + field.name, self.value.cast(field.type) + elif field.artificial: + continue + elif field.name not in ['pi_', 'impl_']: + yield self.prefix + field.name, self.value[field.name] + + +class UrlBasePrinter: + def __init__(self, value, prefix=None): + self.value = value + self.prefix = '' if prefix is None else prefix + '::' + + def to_string(self): + return self.value['s_'] + + def children(self): + yield 'buffer', self.value['s_'] + yield 'capacity', self.value['cap_'] + for field in self.value.type.fields(): + if field.is_base_class or field.artificial: + continue + elif field.name not in ['s_', 'cap_']: + yield self.prefix + field.name, self.value[field.name] + for field in self.value.type.fields(): + if field.is_base_class: + if field.name == 'boost::urls::url_view_base': + yield from UrlViewBasePrinter(self.value.cast(field.type), 'url_view_base', + print_cstr=False).children() + else: + yield self.prefix + field.name, self.value.cast(field.type) + + +class UrlPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + return self.value['pi_']['s_'] + + def children(self): + obj_type = self.value.type + + for field in obj_type.fields(): + if not field.is_base_class and not field.artificial: + yield field.name, self.value[field.name] + + for field in obj_type.fields(): + if field.is_base_class: + if field.name == 'boost::urls::url_base': + yield from UrlBasePrinter(self.value.cast(field.type), 'url_base').children() + else: + yield field.name, self.value.cast(field.type) + + +class UrlViewPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + return self.value['pi_']['cs_'] + + def children(self): + obj_type = self.value.type + + for field in obj_type.fields(): + if not field.is_base_class and not field.artificial: + yield field.name, self.value[field.name] + + for field in obj_type.fields(): + if field.is_base_class: + if field.name == 'boost::urls::url_view_base': + yield from UrlViewBasePrinter(self.value.cast(field.type), 'url_view_base', + print_cstr=True).children() + else: + yield field.name, self.value.cast(field.type) + + +class Variant2Printer: + def __init__(self, value): + self.value = value + + def children(self): + cur_value = self.value + while cur_value is not None and not self.has_member(cur_value, 'st_') and not self.has_member(cur_value, 'ix_'): + cur_value = self.get_impl(cur_value) + + if cur_value is not None: + # Print the value of the st_ field + index = cur_value['ix_'] + storage = cur_value['st_'] + i = 0 + storage_value = storage['first_'] + while i < index: + storage = storage['rest_'] + storage_value = storage['first_'] + i += 1 + yield 'storage', storage_value + yield 'index', index + else: + # We could not find the st_ field, so we just print the value + obj_type = self.value.type + for field in obj_type.fields(): + if not field.is_base_class and not field.artificial: + yield field.name, self.value[field.name] + + for field in obj_type.fields(): + if field.is_base_class: + yield field.name, self.value.cast(field.type) + + @staticmethod + def has_member(value, member_name): + obj_type = value.type + for field in obj_type.fields(): + if field.name == member_name: + return True + return False + + # static function to get the value casted to the first base class type whose the substring before the first < ends with _impl + @staticmethod + def get_impl(value): + obj_type = value.type + for field in obj_type.fields(): + if field.is_base_class: + if field.name.startswith('boost::variant2::detail::variant_'): + # Get substring of field.name before the first < + name = field.name.split('<', 1)[0] + if name.endswith('_impl'): + return value.cast(field.type) + return None + + +class SystemResultPrinter: + def __init__(self, value): + self.value = value + + def children(self): + yield from Variant2Printer(self.value['v_']).children() + + +class EnumPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + s: str = self.value.format_string(raw=True) + return s.rsplit(':', 1)[-1] + + +class UrlHostTypePrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + s: str = self.value.format_string(raw=True) + s = s.rsplit(':', 1)[-1] + return 'reg-name' if s == 'name' else s + + +if __name__ != "__main__": + def lookup_function(val: gdb.Value): + typename: str = utils.resolved_typename(val) + + if typename == 'boost::urls::url_view': + return UrlViewPrinter(val) + elif typename == 'boost::urls::url': + return UrlPrinter(val) + elif typename == 'boost::urls::url_base': + return UrlBasePrinter(val) + elif typename == 'boost::urls::url_view_base': + return UrlViewBasePrinter(val) + elif typename == 'boost::urls::detail::url_impl': + return UrlImplPrinter(val) + elif typename == 'boost::core::basic_string_view': + return StringViewPrinter(val) + elif typename == 'boost::urls::pct_string_view': + return PctStringViewPrinter(val) + elif typename == 'boost::urls::detail::parts_base::from': + return EnumPrinter(val) + elif typename == 'boost::urls::scheme': + return EnumPrinter(val) + elif typename == 'boost::urls::host_type': + return UrlHostTypePrinter(val) + elif typename.startswith('boost::system::result<'): + return SystemResultPrinter(val) + # elif typename.startswith('boost::variant2::variant<'): + # return UrlHostTypePrinter(val) + return None + + +def register_boost_url_printers(obj=None, dev: bool = None): + if obj is None: + obj = gdb + if dev is not None: + UrlImplPrinter.show_offset_parts = dev + obj.pretty_printers.append(lookup_function) + + +if __name__ == "__main__": + assert utils.pct_decode("Hello%20World%21") == 'Hello World!' From 1dc789839f34f9a13451c3aaa5bf4cacec04fd78 Mon Sep 17 00:00:00 2001 From: Ed Catmur Date: Mon, 14 Aug 2023 15:06:34 -0500 Subject: [PATCH 2/4] Fix Windows build of file_router example On Windows, fs::path::c_str() returns wchar_t const* so cannot be streamed to std::cout. fs::path is output streamable anyway (it might quote it, but that should be fine). --- example/file_router/file_router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/file_router/file_router.cpp b/example/file_router/file_router.cpp index 1c10f9b8..e5038cf2 100644 --- a/example/file_router/file_router.cpp +++ b/example/file_router/file_router.cpp @@ -154,7 +154,7 @@ main(int argc, char **argv) fs::path exec = argv[0]; exec = exec.filename(); std::cerr - << "Usage: " << exec.c_str() + << "Usage: " << exec << " \n" "target: path to make a request\n" "prefix: url prefix\n" From 19f46710cb41de21f4f4c16e1d15a6f30374b531 Mon Sep 17 00:00:00 2001 From: Daniel Richard G Date: Fri, 6 Oct 2023 12:02:12 -0400 Subject: [PATCH 3/4] build: default test flags --- test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 868ebbb8..64f9b5cd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,6 +21,7 @@ if(NOT TARGET boost_url_all_tests) endif() # Replicate error flags from Jamfile +set(BOOST_URL_TEST_FLAGS " ") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION_MAJOR EQUAL 7) set(BOOST_URL_TEST_FLAGS "-Wall -Werror -Wno-unused-but-set-variable -Wno-maybe-uninitialized") elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") From 7e47e9fef6fecce45f7c65277601b7e7ff38c365 Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Fri, 13 Oct 2023 16:27:54 -0300 Subject: [PATCH 4/4] docs: sync README and documentation Update the content to match the documentation and remove old information that is currently incorrect. fix #780 --- README.adoc | 673 +++++++++++++++++++++++++++++++++++++ README.md | 140 -------- doc/qbk/1.0.overview.qbk | 4 - doc/qbk/2.0.quicklook.qbk | 34 +- doc/qbk/3.8.formatting.qbk | 5 +- test/unit/snippets.cpp | 51 +-- test/unit/url_view.cpp | 5 + 7 files changed, 728 insertions(+), 184 deletions(-) create mode 100644 README.adoc delete mode 100644 README.md diff --git a/README.adoc b/README.adoc new file mode 100644 index 00000000..e2d3613f --- /dev/null +++ b/README.adoc @@ -0,0 +1,673 @@ +// +// Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/url +// + +https://www.boost.org/doc/libs/master/libs/url/doc/html/[image:https://raw.githubusercontent.com/vinniefalco/url/master/doc/images/repo-logo.png[Boost.URL]] + +[width="100%",cols="7%,66%,27%",options="header",] +|=== +|Branch |https://github.com/boostorg/url/tree/master[`master`] +|https://github.com/boostorg/url/tree/develop[`develop`] +|Docs +|https://www.boost.org/doc/libs/master/libs/url/doc/html/[image:https://img.shields.io/badge/docs-master-brightgreen.svg[Documentation]] +|https://www.boost.org/doc/libs/develop/libs/url/doc/html/[image:https://img.shields.io/badge/docs-develop-brightgreen.svg[Documentation]] + +|https://drone.io/[Drone] +|https://drone.cpp.al/boostorg/url[image:https://drone.cpp.al/api/badges/boostorg/url/status.svg?ref=refs/heads/master[Build +Status]] +|https://drone.cpp.al/boostorg/url[image:https://drone.cpp.al/api/badges/boostorg/url/status.svg?ref=refs/heads/develop[Build +Status]] + +|https://github.com/[GitHub Actions] +|https://github.com/boostorg/url/actions/workflows/ci.yml[image:https://github.com/boostorg/url/actions/workflows/ci.yml/badge.svg?branch=master[CI]] +|https://github.com/boostorg/url/actions/workflows/ci.yml[image:https://github.com/boostorg/url/actions/workflows/ci.yml/badge.svg?branch=develop[CI]] + +|https://codecov.io[codecov.io] +|https://codecov.io/gh/boostorg/url/branch/master[image:https://codecov.io/gh/boostorg/url/branch/master/graph/badge.svg[codecov]] +|https://codecov.io/gh/boostorg/url/branch/develop[image:https://codecov.io/gh/boostorg/url/branch/develop/graph/badge.svg[codecov]] + +|Matrix +|http://www.boost.org/development/tests/master/developer/url.html[image:https://img.shields.io/badge/matrix-master-brightgreen.svg[Matrix]] +|http://www.boost.org/development/tests/develop/developer/url.html[image:https://img.shields.io/badge/matrix-develop-brightgreen.svg[Matrix]] +|=== + +== Boost.URL + +Boost.URL is a portable C++ library which provides containers and algorithms which model a "URL," more formally described using the +https://datatracker.ietf.org/doc/html/rfc3986[Uniform Resource Identifier (URI)] +specification (henceforth referred to as _rfc3986_). +A URL is a compact sequence of characters that identifies an abstract or physical resource. +For example, this is a valid URL: + +[source] +---- +https://www.example.com/path/to/file.txt?userid=1001&pages=3&results=full#page1 +---- + +This library understands the grammars related to URLs and provides functionality to validate, parse, examine, and modify urls, and apply normalization or resolution algorithms. + +=== Features + +While the library is general purpose, special care has been taken to ensure that the implementation and data representation are friendly to network programs which need to handle URLs efficiently and securely, including the case where the inputs come from untrusted sources. +Interfaces are provided for using error codes instead of exceptions as needed, and most algorithms have the means to opt-out of dynamic memory allocation. +Another feature of the library is that all modifications leave the URL in a valid state. +Code which uses this library is easy to read, flexible, and performant. + +Network programs such as those using Boost.Asio or Boost.Beast often encounter the need to process, generate, or modify URLs. +This library provides a very much needed modular component for handling these use-cases. + +Boost.URL offers these features: + +* C++11 as only requirement +* Fast compilation, few templates +* Strict compliance with _rfc3986_ +* Containers that maintain valid URLs +* Parsing algorithms that work without exceptions +* Control over storage and allocation for URLs +* Support for `-fno-exceptions`, detected automatically +* Features that work well on embedded devices + +[NOTE] +==== +Currently, the library does not handle +https://www.rfc-editor.org/rfc/rfc3987.html[Internationalized Resource Identifiers (IRIs)]. +These are different from URLs, come from Unicode strings instead of low-ASCII strings, and are covered by a separate specification. +==== + +=== Requirements + +The library requires a compiler supporting at least C++11. + +Aliases for standard types, such as _error_code_ or `string_view`, use their Boost equivalents. + +Boost.URL works great on embedded devices. +It can be used in a way that avoids all dynamic memory allocations. +Furthermore, it offers alternative interfaces that work without exceptions if desired. + +=== Tested Compilers + +Boost.URL has been tested with the following compilers: + +* clang: 3.8, 4, 5, 6, 7, 8, 9, 10, 11, 12 +* gcc: 4.8, 4.9, 5, 6, 7, 8, 9, 10, 11 +* msvc: 14.1, 14.2, 14.3 + +and these architectures: x86, x64, ARM64, S390x. + +We do not test and support gcc 8.0.1. + +=== Quality Assurance + +The development infrastructure for the library includes these per-commit analyses: + +* Coverage reports +* Compilation and tests on Drone.io and GitHub Actions +* Regular code audits for security + +=== Nomenclature + +Various names have been used historically to refer to different flavors of resource identifiers, including _URI_, _URL_, _URN_, and even _IRI_. +Over time, the distinction between URIs and URLs has disappeared when discussed in technical documents and informal works. +In this library we use the term _URL_ to refer to all strings which are valid according to the top-level grammar rules found in _rfc3986_. + +==== ABNF + +This documentation uses the +https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form[Augmented Backus-Naur Form] +(ABNF) notation of +https://datatracker.ietf.org/doc/html/rfc5234[rfc5234] +to specify particular grammars used by algorithms and containers. +While a complete understanding of the notation is not a requirement for using the library, it may help for an understanding of how valid components of URLs are defined. +In particular, this is of interest to users who wish to compose parsing algorithms using the combinators provided by the library. + +=== Visual Studio Solution Generation + +[source,bash] +---- +cmake -G "Visual Studio 16 2019" -A Win32 -B bin -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/msvc.cmake +cmake -G "Visual Studio 16 2019" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/msvc.cmake +---- + +== Quick Look + +=== Integration + +[NOTE] +==== +Sample code and identifiers used throughout are written as if the following declarations are in effect: + +[source,c++] +---- +#include +using namespace boost::urls; +---- +==== + +We begin by including the library header file which brings all the symbols into scope. + +[source,c++] +---- +#include +---- + +Alternatively, individual headers may be included to obtain the declarations for specific types. + +Boost.URL is a compiled library. +You need to install binaries in a location that can be found by your linker and link your program with the Boost.URL built library. +If you followed the [@http://www.boost.org/doc/libs/release/more/getting_started/index.html Boost Getting Started] +instructions, that's already been done for you. + +For example, if you are using CMake, you can use the following commands to find and link the library: + +[source,cmake] +---- +find_package(Boost REQUIRED COMPONENTS url) +target_link_libraries(my_program PRIVATE Boost::url) +---- + +=== Parsing + +Say you have the following URL that you want to parse: + +[source,c++] +---- +boost::core::string_view s = "https://user:pass@example.com:443/path/to/my%2dfile.txt?id=42&name=John%20Doe+Jingleheimer%2DSchmidt#page%20anchor"; +---- + +In this example, `string_view` is an alias to `boost::core::string_view`, a +`string_view` implementation that is implicitly convertible from and to `std::string_view`. + +You can parse the string by calling this function: + +[source,c++] +---- +boost::system::result r = parse_uri( s ); +---- + +The function _parse_uri_ returns an object of type `result` +which is a container resembling a variant that holds either an error or an object. +A number of functions are available to parse different types of URL. + +We can immediately call `result::value` to obtain a `url_view`. + +[source,c++] +---- +url_view u = r.value(); +---- + +Or simply + +[source,c++] +---- +url_view u = *r; +---- + +for unchecked access. + +When there are no errors, `result::value` +returns an instance of _url_view_, which holds the parsed result. + +`result::value` throws an exception on a parsing error. +Alternatively, the functions `result::has_value` and `result::has_error` could also be used to check if the string has been parsed without errors. + +[NOTE] +==== +It is worth noting that _parse_uri_ does not allocate any memory dynamically. +Like a `string_view`, a _url_view_ does not retain ownership of the underlying string buffer. + +As long as the contents of the original string are unmodified, constructed URL views always contain a valid URL in its correctly serialized form. + +If the input does not match the URL grammar, an error code is reported through _result_ rather than exceptions. +Exceptions only thrown on excessive input length. +==== + +=== Accessing + +Accessing the parts of the URL is easy: + +[source,c++] +---- +url_view u( "https://user:pass@example.com:443/path/to/my%2dfile.txt?id=42&name=John%20Doe+Jingleheimer%2DSchmidt#page%20anchor" ); +assert(u.scheme() == "https"); +assert(u.authority().buffer() == "user:pass@example.com:443"); +assert(u.userinfo() == "user:pass"); +assert(u.user() == "user"); +assert(u.password() == "pass"); +assert(u.host() == "example.com"); +assert(u.port() == "443"); +assert(u.path() == "/path/to/my-file.txt"); +assert(u.query() == "id=42&name=John Doe Jingleheimer-Schmidt"); +assert(u.fragment() == "page anchor"); +---- + +URL paths can be further divided into path segments with the function `url_view::segments`. + +Although URL query strings are often used to represent key/value pairs, this interpretation is not defined by _rfc3986_. +Users can treat the query as a single entity. +_url_view_ provides the function +`url_view::params` to extract this view of key/value pairs. + +[source,c++] +---- +for (auto seg: u.segments()) +std::cout << seg << "\n"; +std::cout << "\n"; + +for (auto param: u.params()) +std::cout << param.key << ": " << param.value << "\n"; +std::cout << "\n"; +---- + +The output is: + +[source] +---- +path +to +my-file.txt + +id: 42 +name: John Doe Jingleheimer-Schmidt + +---- + +These functions return views referring to substrings and sub-ranges of the underlying URL. +By simply referencing the relevant portion of the URL string internally, its components can represent percent-decoded strings and be converted to other types without any previous memory allocation. + +[source,c++] +---- +std::string h = u.host(); +assert(h == "example.com"); +---- + +A special `string_token` type can also be used to specify how a portion of the URL should be encoded and returned. + +[source,c++] +---- +std::string h = "host: "; +u.host(string_token::append_to(h)); +assert(h == "host: example.com"); +---- + +These functions might also return empty strings + +[source,c++] +---- +url_view u1 = parse_uri( "http://www.example.com" ).value(); +assert(u1.fragment().empty()); +assert(!u1.has_fragment()); +---- + +for both empty and absent components + +[source,c++] +---- +url_view u2 = parse_uri( "http://www.example.com/#" ).value(); +assert(u2.fragment().empty()); +assert(u2.has_fragment()); +---- + +Many components do not have corresponding functions such as +`has_authority` to check for their existence. +This happens because some URL components are mandatory. + +When applicable, the encoded components can also be directly accessed through a `string_view` without any need to allocate memory: + +[source,c++] +---- +std::cout << + "url : " << u << "\n" + "scheme : " << u.scheme() << "\n" + "authority : " << u.encoded_authority() << "\n" + "userinfo : " << u.encoded_userinfo() << "\n" + "user : " << u.encoded_user() << "\n" + "password : " << u.encoded_password() << "\n" + "host : " << u.encoded_host() << "\n" + "port : " << u.port() << "\n" + "path : " << u.encoded_path() << "\n" + "query : " << u.encoded_query() << "\n" + "fragment : " << u.encoded_fragment() << "\n"; +---- + +The output is: + +[source] +---- +url : https://user:pass@example.com:443/path/to/my%2dfile.txt?id=42&name=John%20Doe+Jingleheimer%2DSchmidt#page%20anchor +scheme : https +authority : user:pass@example.com:443 +userinfo : user:pass +user : user +password : pass +host : example.com +port : 443 +path : /path/to/my%2dfile.txt +query : id=42&name=John%20Doe+Jingleheimer%2DSchmidt +fragment : page%20anchor +---- + +=== Percent-Encoding + +An instance of `decode_view` provides a number of functions to persist a decoded string: + +[source,c++] +---- +decode_view dv("id=42&name=John%20Doe%20Jingleheimer%2DSchmidt"); +std::cout << dv << "\n"; +---- + +The output is: + +[source] +---- +id=42&name=John Doe Jingleheimer-Schmidt +---- + +`decode_view` and its decoding functions are designed to perform no memory allocations unless the algorithm where its being used needs the result to be in another container. +The design also permits recycling objects to reuse their memory, and at least minimize the number of allocations by deferring them until the result is in fact needed by the application. + +In the example above, the memory owned by `str` can be reused to store other results. +This is also useful when manipulating URLs: + +[source,c++] +---- +u1.set_host(u2.host()); +---- + +If `u2.host()` returned a value type, then two memory allocations would be necessary for this operation. +Another common use case is converting URL path segments into filesystem paths: + +[source,c++] +---- +boost::filesystem::path p; +for (auto seg: u.segments()) +p.append(seg.begin(), seg.end()); +std::cout << "path: " << p << "\n"; +---- + +The output is: + +[source] +---- +path: "path/to/my-file.txt" +---- + +In this example, only the internal allocations of `filesystem::path` need to happen. +In many common use cases, no allocations are necessary at all, such as finding the appropriate route for a URL in a web server: + +[source,c++] +---- +auto match = []( +std::vector const& route, +url_view u) +{ +auto segs = u.segments(); +if (route.size() != segs.size()) +return false; +return std::equal( +route.begin(), +route.end(), +segs.begin()); +}; +---- + +This allows us to easily match files in the document root directory of a web server: + +[source,c++] +---- +std::vector route = +{"community", "reviews.html"}; +if (match(route, u)) +{ +handle_route(route, u); +} +---- + +=== Compound elements + +The path and query parts of the URL are treated specially by the library. +While they can be accessed as individual encoded strings, they can also be accessed through special view types. + +This code calls `encoded_segments` to obtain the path segments as a container that returns encoded strings: + +[source,c++] +---- +segments_view segs = u.encoded_segments(); +for( auto v : segs ) +{ +std::cout << v << "\n"; +} +---- + +The output is: + +[source] +---- +path to my-file.txt +---- + +As with other `url_view` functions which return encoded strings, the encoded segments container does not allocate memory. +Instead, it returns views to the corresponding portions of the underlying encoded buffer referenced by the URL. + +As with other library functions, `decode_view` permits accessing elements of composed elements while avoiding memory allocations entirely: + +[source,c++] +---- +segments_view segs = u.encoded_segments(); +for( pct_string_view v : segs ) +{ +decode_view dv = *v; +std::cout << dv << "\n"; +} +---- + +The output is: + +[source] +---- +path to my-file.txt +---- + +Or with the encoded query parameters: + +[source,c++] +---- +params_encoded_view params_ref = u.encoded_params(); + +for( auto v : params_ref ) +{ + decode_view dk(v.key); + decode_view dv(v.value); + std::cout << + "key = " << dk << + ", value = " << dv << "\n"; +} +---- + +The output is: + +[source] +---- +key = id, value = 42 +key = name, value = John Doe +---- + +=== Modifying + +The library provides the containers `url` and `static_url` which supporting modification of the URL contents. +A `url` or `static_url` must be constructed from an existing `url_view`. + +Unlike the `url_view`, which does not gain ownership of the underlying character buffer, the `url` container uses the default allocator to control a resizable character buffer which it owns. + +[source,c++] +---- +url u = parse_uri( s ).value(); +---- + +On the other hand, a `static_url` has fixed-capacity storage and does not require dynamic memory allocations. + +[source,c++] +---- +static_url<1024> su = parse_uri( s ).value(); +---- + +Objects of type `url` are https://en.cppreference.com/w/cpp/concepts/regular[std::regular]. +Similarly to built-in types, such as `int`, a `url` is copyable, movable, assignable, default constructible, and equality comparable. +They support all the inspection functions of `url_view`, and also provide functions to modify all components of the URL. + +Changing the scheme is easy: + +[source,c++] +---- +u.set_scheme( "https" ); +---- + +Or we can use a predefined constant: + +[source,c++] +---- +u.set_scheme_id( scheme::https ); // equivalent to u.set_scheme( "https" ); +---- + +The scheme must be valid, however, or an exception is thrown. +All modifying functions perform validation on their input. + +* Attempting to set the URL scheme or port to an invalid string results in an exception. +* Attempting to set other URL components to invalid strings will get the original input properly percent-encoded for that component. + +It is not possible for a `url` to hold syntactically illegal text. + +Modification functions return a reference to the object, so chaining is possible: + +[source,c++] +---- +u.set_host_ipv4( ipv4_address( "192.168.0.1" ) ) + .set_port_number( 8080 ) + .remove_userinfo(); +std::cout << u << "\n"; +---- + +The output is: + +[source] +---- +https://192.168.0.1:8080/path/to/my%2dfile.txt?id=42&name=John%20Doe#page%20anchor +---- + +All non-const operations offer the strong exception safety guarantee. + +The path segment and query parameter containers returned by a `url` offer modifiable range functionality, using member functions of the container: + +[source,c++] +---- +params_ref p = u.params(); +p.replace(p.find("name"), {"name", "John Doe"}); +std::cout << u << "\n"; +---- + +The output is: + +[source] +---- +https://192.168.0.1:8080/path/to/my%2dfile.txt?id=42&name=Vinnie%20Falco#page%20anchor +---- + +=== Formatting + +Algorithms to format URLs construct a mutable URL by parsing and applying arguments to a URL template. +The following example uses the `format` +function to construct an absolute URL: + +[source,c++] +---- +url u = format("{}://{}:{}/rfc/{}", "https", "www.ietf.org", 80, "rfc2396.txt"); +assert(u.buffer() == "https://www.ietf.org:80/rfc/rfc2396.txt"); +---- + +The rules for a format URL string are the same as for a `std::format_string`, where replacement fields are delimited by curly braces. +The URL type is inferred from the format string. + +The URL components to which replacement fields belong are identified before replacement is applied and any invalid characters for that formatted argument are percent-escaped: + +[source,c++] +---- +url u = format("https://{}/{}", "www.boost.org", "Hello world!"); +assert(u.buffer() == "https://www.boost.org/Hello%20world!"); +---- + +Delimiters in the URL template, such as `":"`, `"//"`, `"?"`, and `"#"`, unambiguously associate each replacement field to a URL component. +All other characters are normalized to ensure the URL is valid: + +[source,c++] +---- +url u = format("{}:{}", "mailto", "someone@example.com"); +assert(u.buffer() == "mailto:someone@example.com"); +assert(u.scheme() == "mailto"); +assert(u.path() == "someone@example.com"); +---- + +[source,c++] +---- +url u = format("{}{}", "mailto:", "someone@example.com"); +assert(u.buffer() == "mailto%3Asomeone@example.com"); +assert(!u.has_scheme()); +assert(u.path() == "mailto:someone@example.com"); +assert(u.encoded_path() == "mailto%3Asomeone@example.com"); +---- + +The function `format_to` can be used to format URLs into any modifiable URL container. + +[source,c++] +---- +static_url<50> u; +format_to(u, "{}://{}:{}/rfc/{}", "https", "www.ietf.org", 80, "rfc2396.txt"); +assert(u.buffer() == "https://www.ietf.org:80/rfc/rfc2396.txt"); +---- + +As with `std::format`, positional and named arguments are supported. + +[source,c++] +---- +url u = format("{0}://{2}:{1}/{3}{4}{3}", "https", 80, "www.ietf.org", "abra", "cad"); +assert(u.buffer() == "https://www.ietf.org:80/abracadabra"); +---- + +The `arg` function can be used to associate names with arguments: + +[source,c++] +---- +url u = format("https://example.com/~{username}", arg("username", "mark")); +assert(u.buffer() == "https://example.com/~mark"); +---- + +A second overload based on `std::initializer_list` +is provided for both `format` and `format_to`. + +These overloads can help with lists of named arguments: + +[source,c++] +---- +boost::core::string_view fmt = "{scheme}://{host}:{port}/{dir}/{file}"; +url u = format(fmt, {{"scheme", "https"}, {"port", 80}, {"host", "example.com"}, {"dir", "path/to"}, {"file", "file.txt"}}); +assert(u.buffer() == "https://example.com:80/path/to/file.txt"); +---- + +== Documentation + +The complete library documentation is available online at https://www.boost.org/doc/libs/master/libs/url/doc/html/index.html[boost.org]. + +== Acknowledgments + +This library wouldn't be where it is today without the help of +https://github.com/pdimov[Peter Dimov], for design advice and general assistance. + +== License + +Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +https://www.boost.org/LICENSE_1_0.txt) + diff --git a/README.md b/README.md deleted file mode 100644 index 58a9bdee..00000000 --- a/README.md +++ /dev/null @@ -1,140 +0,0 @@ -[![Boost.URL](https://raw.githubusercontent.com/vinniefalco/url/master/doc/images/repo-logo.png)](https://www.boost.org/doc/libs/master/libs/url/doc/html/) - -Branch | [`master`](https://github.com/boostorg/url/tree/master) | [`develop`](https://github.com/boostorg/url/tree/develop) | ---------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------- | -Docs | [![Documentation](https://img.shields.io/badge/docs-master-brightgreen.svg)](https://www.boost.org/doc/libs/master/libs/url/doc/html/) | [![Documentation](https://img.shields.io/badge/docs-develop-brightgreen.svg)](https://www.boost.org/doc/libs/develop/libs/url/doc/html/) -[Drone](https://drone.io/) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/url/status.svg?ref=refs/heads/master)](https://drone.cpp.al/boostorg/url) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/url/status.svg?ref=refs/heads/develop)](https://drone.cpp.al/boostorg/url) -[GitHub Actions](https://github.com/) | [![CI](https://github.com/boostorg/url/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/boostorg/url/actions/workflows/ci.yml) | [![CI](https://github.com/boostorg/url/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/boostorg/url/actions/workflows/ci.yml) -[codecov.io](https://codecov.io) | [![codecov](https://codecov.io/gh/boostorg/url/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/url/branch/master) | [![codecov](https://codecov.io/gh/boostorg/url/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/url/branch/develop) -Matrix | [![Matrix](https://img.shields.io/badge/matrix-master-brightgreen.svg)](http://www.boost.org/development/tests/master/developer/url.html) | [![Matrix](https://img.shields.io/badge/matrix-develop-brightgreen.svg)](http://www.boost.org/development/tests/develop/developer/url.html) - -# Boost.URL - -## Overview - -Boost.URL is a portable C++ library which provides containers and algorithms -which model a "URL", more formally described using the Uniform Resource -Identifier (URI) specification (henceforth referred to as rfc3986). A URL -is a compact sequence of characters that identifies an abstract or physical -resource. For example, this is a valid URL which satisfies the -absolute-URI grammar: - -``` -https://www.example.com/path/to/file.txt?userid=1001&page=2&results=full -``` - -This library understands the various grammars related to URLs and provides -for validating and parsing of strings, manipulation of URL strings, and -algorithms operating on URLs such as normalization and resolution. While -the library is general purpose, special care has been taken to ensure that -the implementation and data representation are friendly to network programs -which need to handle URLs efficiently and securely, including the case where -the inputs come from untrusted sources. Interfaces are provided for using -error codes instead of exceptions as needed, and all algorithms provide a -mechanism for avoiding memory allocations entirely if desired. Another -feature of the library is that all container mutations leave the URL in -a valid state. Code which uses Boost.URL will be easy to read, flexible, -and performant. - -Network programs such as those using Boost.Asio or Boost.Beast often -encounter the need to process, generate, or modify URLs. This library -provides a very much needed modular component for handling these -use-cases. - -## Example -```cpp -using namespace boost::urls; - -// Parse a URL. This allocates no memory. The view -// references the character buffer without taking ownership. -// -url_view uv( "https://www.example.com/path/to/file.txt?id=1001&name=John%20Doe&results=full" ); - -// Print the query parameters with percent-decoding applied -// -for( auto v : uv.params() ) - std::cout << v.key << "=" << v.value << " "; - -// Prints: id=1001 name=John Doe results=full - -// Create a modifiable copy of `uv`, with ownership of the buffer -// -url u = uv; - -// Change some elements in the URL -// -u.set_scheme( "http" ) - .set_encoded_host( "boost.org" ) - .set_encoded_path( "/index.htm" ) - .remove_query() - .remove_fragment() - .params().append( {"key", "value"} ); - -std::cout << u; - -// Prints: http://boost.org/index.htm?key=value -``` - -## Design Goals - -The library achieves these goals: - -* Require only C++11 -* Works without exceptions -* Fast compilation, no templates -* Strict compliance with rfc3986 -* Allocate memory or use inline storage -* Optional header-only, without linking to a library - -## Requirements - -* Requires Boost and a compiler supporting at least C++11 -* Aliases for standard types use their Boost equivalents -* Link to a built static or dynamic Boost library, or use header-only (see below) -* Supports -fno-exceptions, detected automatically - -### Header-Only - -To use as header-only; that is, to eliminate the requirement to -link a program to a static or dynamic Boost.URL library, simply -place the following line in exactly one new or existing source -file in your project. -```cpp -#include -``` - -### Embedded - -Boost.URL works great on embedded devices. It can be used in a way -that avoids all dynamic memory allocations. Furthermore it is designed -to work without exceptions if desired. - -### Supported Compilers - -Boost.URL is tested with the following compilers: - -* clang: 3.8, 4, 5, 6, 7, 8, 9, 10, 11, 12 -* gcc: 4.8, 4.9, 5, 6, 7, 8, 9, 10, 11 -* msvc: 14.0, 14.1, 14.2, 14.3 - -and these architectures: x86, x64, ARM64, S390x - -### Quality Assurance - -The development infrastructure for the library includes -these per-commit analyses: - -* Coverage reports -* Benchmark performance comparisons -* Compilation and tests on Drone.io - -## Visual Studio Solution Generation - - cmake -G "Visual Studio 16 2019" -A Win32 -B bin -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/msvc.cmake - cmake -G "Visual Studio 16 2019" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/msvc.cmake - -## License - -Distributed under the Boost Software License, Version 1.0. -(See accompanying file [LICENSE_1_0.txt](LICENSE_1_0.txt) or copy at -https://www.boost.org/LICENSE_1_0.txt) diff --git a/doc/qbk/1.0.overview.qbk b/doc/qbk/1.0.overview.qbk index 30038105..31f4965e 100644 --- a/doc/qbk/1.0.overview.qbk +++ b/doc/qbk/1.0.overview.qbk @@ -129,10 +129,6 @@ the library. [/-----------------------------------------------------------------------------] -[section:security_review Security Review (Bishop Fox)] -To be announced -[endsect] - [heading Acknowledgments] This library wouldn't be where it is today without the help of diff --git a/doc/qbk/2.0.quicklook.qbk b/doc/qbk/2.0.quicklook.qbk index c9f2fee1..114158d1 100644 --- a/doc/qbk/2.0.quicklook.qbk +++ b/doc/qbk/2.0.quicklook.qbk @@ -118,11 +118,17 @@ of key/value pairs. ``` ]]] -These functions return __decode_view__, which are constant -views referring to sub-ranges of the underlying URL string. -By simply referencing the relevant portion of the URL string, -its components can represent percent-decoded strings without any -need to allocate memory. +These functions return views referring to substrings and sub-ranges +of the underlying URL. +By simply referencing the relevant portion of the URL string internally, +its components can represent percent-decoded strings and be converted +to other types without any previous memory allocation. + +[snippet_token_1] + +A special `string_token` type can also be used to specify how a portion of the URL should be encoded and returned. + +[snippet_token_2] These functions might also return empty strings @@ -138,7 +144,8 @@ to check for their existence. This happens because some URL components are mandatory. When applicable, the encoded components can also be directly -accessed through a __string_view__: +accessed through a __string_view__ without any +need to allocate memory: [table [[Code][Output]] [[ [c++] @@ -219,12 +226,6 @@ root directory of a web server: [c++] [snippet_decoding_4b] -For many simpler use cases, converting the view to a -string might be sufficient: - -[c++] -[snippet_decoding_5a] - [#compound-elements] [h3 Compound elements] @@ -297,7 +298,7 @@ not require dynamic memory allocations. Objects of type __url__ are [@https://en.cppreference.com/w/cpp/concepts/regular std::regular]. Similarly to built-in types, such as `int`, a __url__ is copyable, movable, assignable, default -constructible, and equality comparable. They support all of the inspection functions of +constructible, and equality comparable. They support all the inspection functions of __url_view__, and also provide functions to modify all components of the URL. Changing the scheme is easy: @@ -309,8 +310,11 @@ Or we can use a predefined constant: [snippet_quicklook_modifying_3] The scheme must be valid, however, or an exception is thrown. -All modifying functions perform validation on their input. Attemping -to set part of the URL to an invalid string results in an exception. +All modifying functions perform validation on their input. + +* Attempting to set the URL scheme or port to an invalid string results in an exception. +* Attempting to set other URL components to invalid strings will get the original input properly percent-encoded for that component. + It is not possible for a __url__ to hold syntactically illegal text. Modification functions return a reference to the object, so chaining diff --git a/doc/qbk/3.8.formatting.qbk b/doc/qbk/3.8.formatting.qbk index 5961fc69..221a55d5 100644 --- a/doc/qbk/3.8.formatting.qbk +++ b/doc/qbk/3.8.formatting.qbk @@ -42,14 +42,13 @@ valid: [snippet_format_3b] The function __format_to__ can be used to format URLs -into any modifiable container that inherits from -__url_base__. +into any modifiable URL container. [c++] [snippet_format_4] As with __std_format__, positional and named arguments are -also supported. +supported. [c++] [snippet_format_5a] diff --git a/test/unit/snippets.cpp b/test/unit/snippets.cpp index 0315e693..8e89d79d 100644 --- a/test/unit/snippets.cpp +++ b/test/unit/snippets.cpp @@ -14,6 +14,7 @@ #include #include #include +#include //[snippet_headers_1 #include @@ -62,11 +63,6 @@ using_url_views() //[code_urls_parsing_2 boost::system::result r = parse_uri( s ); //] - boost::ignore_unused(r); - } - - { - boost::system::result r = parse_uri( s ); //[snippet_parsing_3 url_view u = r.value(); //] @@ -92,6 +88,23 @@ using_url_views() std::cout << "\n"; //] + { + //[snippet_token_1 + std::string h = u.host(); + assert(h == "example.com"); + //] + boost::ignore_unused(h); + } + + { + //[snippet_token_2 + std::string h = "host: "; + u.host(string_token::append_to(h)); + assert(h == "host: example.com"); + //] + boost::ignore_unused(h); + } + { //[snippet_accessing_2a url_view u1 = parse_uri( "http://www.example.com" ).value(); @@ -194,19 +207,9 @@ using_url_views() //] } #endif - { - //[snippet_decoding_5a - auto function = [](boost::core::string_view str) - { - std::cout << str << "\n"; - }; - //] - (void)function; - } - { //[snippet_compound_elements_1 - segments_view segs = u.segments(); + segments_encoded_view segs = u.encoded_segments(); for( auto v : segs ) { std::cout << v << "\n"; @@ -216,24 +219,28 @@ using_url_views() { //[snippet_encoded_compound_elements_1 - segments_view segs = u.segments(); + segments_encoded_view segs = u.encoded_segments(); - for( auto v : segs ) + for( pct_string_view v : segs ) { - std::cout << v << "\n"; + decode_view dv = *v; + std::cout << dv << "\n"; } //] } { //[snippet_encoded_compound_elements_2 - params_view params_ref = u.params(); + params_encoded_view params_ref = u.encoded_params(); for( auto v : params_ref ) { + decode_view dk(v.key); + decode_view dv(v.value); + std::cout << - "key = " << v.key << - ", value = " << v.value << "\n"; + "key = " << dk << + ", value = " << dv << "\n"; } //] } diff --git a/test/unit/url_view.cpp b/test/unit/url_view.cpp index d7673e7a..dfe90c9e 100644 --- a/test/unit/url_view.cpp +++ b/test/unit/url_view.cpp @@ -1052,6 +1052,11 @@ class url_view_test testParseOriginForm(); testJavadocs(); + + { + auto r = parse_uri("https://us%65rnam%65:password@%65xampl%65.com:8080/path/to/r%65sourc%65?qu%65ry_param=valu%65#s%65ction"); + ignore_unused(r); + } } };