Hello libcode-uri
This commit is contained in:
commit
a1836c8569
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.yaml]
|
||||||
|
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
24
.gitea/workflows/on-push.yaml
Normal file
24
.gitea/workflows/on-push.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: on-push
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: linux
|
||||||
|
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
|
||||||
|
volumes:
|
||||||
|
- /build
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Authenticate
|
||||||
|
run: |
|
||||||
|
git config unset http.https://code.helloryan.se/.extraheader
|
||||||
|
echo "${{ secrets.NETRC }}" >> ~/.netrc
|
||||||
|
- name: Initialize
|
||||||
|
run: |
|
||||||
|
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
|
||||||
|
bdep init -A /build
|
||||||
|
- name: Build
|
||||||
|
run: b
|
||||||
|
- name: Test
|
||||||
|
run: b test
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.bdep/
|
||||||
|
Doxyfile
|
||||||
|
|
||||||
|
# Local default options files.
|
||||||
|
#
|
||||||
|
.build2/local/
|
||||||
|
|
||||||
|
# Compiler/linker output.
|
||||||
|
#
|
||||||
|
*.d
|
||||||
|
*.t
|
||||||
|
*.i
|
||||||
|
*.i.*
|
||||||
|
*.ii
|
||||||
|
*.ii.*
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.gcm
|
||||||
|
*.pcm
|
||||||
|
*.ifc
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
*.exp
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.exe
|
||||||
|
*.exe.dlls/
|
||||||
|
*.exe.manifest
|
||||||
|
*.pc
|
31
LICENSE
Normal file
31
LICENSE
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Copyright © 2024 Ryan. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. All advertising materials mentioning features or use of this software must
|
||||||
|
display the following acknowledgement:
|
||||||
|
|
||||||
|
This product includes software developed by Ryan, http://helloryan.se/.
|
||||||
|
|
||||||
|
4. Neither the name(s) of the copyright holder(s) nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from this
|
||||||
|
software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||||
|
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# libcode-uri
|
||||||
|
|
||||||
|
![Build status](https://code.helloryan.se/code/libcode-uri/actions/workflows/on-push.yaml/badge.svg)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
None, other than a modern C++-compiler.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
|
||||||
|
build instructions.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please send an e-mail to ryan@helloryan.se to request an account and
|
||||||
|
write-access to the libcode-uri repository.
|
4
build/.gitignore
vendored
Normal file
4
build/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/config.build
|
||||||
|
/root/
|
||||||
|
/bootstrap/
|
||||||
|
build/
|
7
build/bootstrap.build
Normal file
7
build/bootstrap.build
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
project = libcode-uri
|
||||||
|
|
||||||
|
using version
|
||||||
|
using config
|
||||||
|
using test
|
||||||
|
using install
|
||||||
|
using dist
|
6
build/export.build
Normal file
6
build/export.build
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
$out_root/
|
||||||
|
{
|
||||||
|
include code/uri/
|
||||||
|
}
|
||||||
|
|
||||||
|
export $out_root/code/uri/$import.target
|
16
build/root.build
Normal file
16
build/root.build
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Uncomment to suppress warnings coming from external libraries.
|
||||||
|
#
|
||||||
|
#cxx.internal.scope = current
|
||||||
|
|
||||||
|
cxx.std = latest
|
||||||
|
|
||||||
|
using cxx
|
||||||
|
|
||||||
|
hxx{*}: extension = hxx
|
||||||
|
ixx{*}: extension = ixx
|
||||||
|
txx{*}: extension = txx
|
||||||
|
cxx{*}: extension = cxx
|
||||||
|
|
||||||
|
# The test target for cross-testing (running tests under Wine, etc).
|
||||||
|
#
|
||||||
|
test.target = $cxx.target
|
5
buildfile
Normal file
5
buildfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
|
||||||
|
|
||||||
|
# Don't install tests.
|
||||||
|
#
|
||||||
|
tests/: install = false
|
9
code/uri/.gitignore
vendored
Normal file
9
code/uri/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Generated version header.
|
||||||
|
#
|
||||||
|
version.hxx
|
||||||
|
|
||||||
|
# Unit test executables and Testscript output directories
|
||||||
|
# (can be symlinks).
|
||||||
|
#
|
||||||
|
*.test
|
||||||
|
test-*.test
|
66
code/uri/buildfile
Normal file
66
code/uri/buildfile
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
intf_libs = # Interface dependencies.
|
||||||
|
impl_libs = # Implementation dependencies.
|
||||||
|
|
||||||
|
./: lib{code-uri}: libul{code-uri}
|
||||||
|
|
||||||
|
libul{code-uri}: {hxx ixx txx cxx}{** -**.test... -version} \
|
||||||
|
{hxx }{ version}
|
||||||
|
|
||||||
|
libul{code-uri}: $impl_libs $intf_libs
|
||||||
|
|
||||||
|
# Unit tests.
|
||||||
|
#
|
||||||
|
exe{*.test}:
|
||||||
|
{
|
||||||
|
test = true
|
||||||
|
install = false
|
||||||
|
}
|
||||||
|
|
||||||
|
test_libs = # Test dependencies.
|
||||||
|
import test_libs =+ libcode-validation%lib{code-validation}
|
||||||
|
|
||||||
|
for t: cxx{**.test...}
|
||||||
|
{
|
||||||
|
d = $directory($t)
|
||||||
|
n = $name($t)...
|
||||||
|
|
||||||
|
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} $test_libs
|
||||||
|
$d/exe{$n}: lib{code-uri}: bin.whole = false
|
||||||
|
$d/exe{$n}: test.arguments = --print-failure
|
||||||
|
}
|
||||||
|
|
||||||
|
hxx{version}: in{version} $src_root/manifest
|
||||||
|
{
|
||||||
|
dist = true
|
||||||
|
clean = ($src_root != $out_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build options.
|
||||||
|
#
|
||||||
|
cxx.poptions =+ "-I$out_root" "-I$src_root"
|
||||||
|
|
||||||
|
# Export options.
|
||||||
|
#
|
||||||
|
lib{code-uri}:
|
||||||
|
{
|
||||||
|
cxx.export.poptions = "-I$out_root" "-I$src_root"
|
||||||
|
cxx.export.libs = $intf_libs
|
||||||
|
}
|
||||||
|
|
||||||
|
# For pre-releases use the complete version to make sure they cannot
|
||||||
|
# be used in place of another pre-release or the final version. See
|
||||||
|
# the version module for details on the version.* variable values.
|
||||||
|
#
|
||||||
|
if $version.pre_release
|
||||||
|
lib{code-uri}: bin.lib.version = "-$version.project_id"
|
||||||
|
else
|
||||||
|
lib{code-uri}: bin.lib.version = "-$version.major.$version.minor"
|
||||||
|
|
||||||
|
# Install into the code/uri/ subdirectory of, say, /usr/include/
|
||||||
|
# recreating subdirectories.
|
||||||
|
#
|
||||||
|
{hxx ixx txx}{*}:
|
||||||
|
{
|
||||||
|
install = include/code/uri/
|
||||||
|
install.subdirs = true
|
||||||
|
}
|
133
code/uri/grammar.hxx
Normal file
133
code/uri/grammar.hxx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#ifndef code__uri__grammar_hxx_
|
||||||
|
#define code__uri__grammar_hxx_
|
||||||
|
|
||||||
|
namespace code::uri::grammar
|
||||||
|
{
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_alpha(char c)
|
||||||
|
{
|
||||||
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_digit(char c)
|
||||||
|
{
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_unreserved(char c)
|
||||||
|
{
|
||||||
|
if (is_alpha(c) || is_digit(c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '_':
|
||||||
|
case '~':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_subdelim(char c)
|
||||||
|
{
|
||||||
|
switch (c) {
|
||||||
|
case '!':
|
||||||
|
case '$':
|
||||||
|
case '&':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
case '=':
|
||||||
|
case '\'':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_scheme_start(char c)
|
||||||
|
{
|
||||||
|
return is_alpha(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_scheme(char c)
|
||||||
|
{
|
||||||
|
return is_alpha(c) || is_digit(c) || c == '+' || c == '-' || c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_userinfo(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_regname(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c) || is_subdelim(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_host(char c)
|
||||||
|
{
|
||||||
|
return is_regname(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_port(char c)
|
||||||
|
{
|
||||||
|
return is_digit(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_pchar(char c)
|
||||||
|
{
|
||||||
|
return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '@' || c == '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_segment_nc(char c)
|
||||||
|
{
|
||||||
|
return is_pchar(c) && c != ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_query(char c)
|
||||||
|
{
|
||||||
|
return is_pchar(c) || c == '/' || c == '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
bool
|
||||||
|
is_fragment(char c)
|
||||||
|
{
|
||||||
|
return is_pchar(c) || c == '/' || c == '?';
|
||||||
|
}
|
||||||
|
} // namespace code::uri::grammar
|
||||||
|
|
||||||
|
#endif
|
284
code/uri/uri.cxx
Normal file
284
code/uri/uri.cxx
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
#include <code/uri/uri.hxx>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace code::uri
|
||||||
|
{
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t()
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::string scheme, std::string host, std::string path)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
path_{std::move(path)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::string scheme,
|
||||||
|
std::string host,
|
||||||
|
std::string port,
|
||||||
|
std::string path)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
port_{std::move(port)},
|
||||||
|
path_{std::move(path)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::string scheme,
|
||||||
|
std::string host,
|
||||||
|
std::string port,
|
||||||
|
std::string path,
|
||||||
|
std::string query)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
port_{std::move(port)},
|
||||||
|
path_{std::move(path)},
|
||||||
|
query_{std::move(query)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::string scheme,
|
||||||
|
std::string host,
|
||||||
|
std::string port,
|
||||||
|
std::string path,
|
||||||
|
std::string query,
|
||||||
|
std::string fragment)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
port_{std::move(port)},
|
||||||
|
path_{std::move(path)},
|
||||||
|
query_{std::move(query)},
|
||||||
|
fragment_{std::move(fragment)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::string scheme,
|
||||||
|
std::string userinfo,
|
||||||
|
std::string host,
|
||||||
|
std::string port,
|
||||||
|
std::string path,
|
||||||
|
std::string query,
|
||||||
|
std::string fragment)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
userinfo_{std::move(userinfo)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
port_{std::move(port)},
|
||||||
|
path_{std::move(path)},
|
||||||
|
query_{std::move(query)},
|
||||||
|
fragment_{std::move(fragment)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uri_t::
|
||||||
|
uri_t(std::optional<std::string> scheme,
|
||||||
|
std::optional<std::string> userinfo,
|
||||||
|
std::optional<std::string> host,
|
||||||
|
std::optional<std::string> port,
|
||||||
|
std::string path,
|
||||||
|
std::optional<std::string> query,
|
||||||
|
std::optional<std::string> fragment)
|
||||||
|
: scheme_{std::move(scheme)},
|
||||||
|
userinfo_{std::move(userinfo)},
|
||||||
|
host_{std::move(host)},
|
||||||
|
port_{std::move(port)},
|
||||||
|
path_{std::move(path)},
|
||||||
|
query_{std::move(query)},
|
||||||
|
fragment_{std::move(fragment)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
scheme() const
|
||||||
|
{
|
||||||
|
return scheme_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
scheme_str() const
|
||||||
|
{
|
||||||
|
return scheme().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
userinfo() const
|
||||||
|
{
|
||||||
|
return userinfo_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
userinfo_str() const
|
||||||
|
{
|
||||||
|
return userinfo().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
host() const
|
||||||
|
{
|
||||||
|
return host_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
host_str() const
|
||||||
|
{
|
||||||
|
return host().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
port() const
|
||||||
|
{
|
||||||
|
return port_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
port_str() const
|
||||||
|
{
|
||||||
|
return port().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
path_str() const
|
||||||
|
{
|
||||||
|
return path_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
query() const
|
||||||
|
{
|
||||||
|
return query_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
query_str() const
|
||||||
|
{
|
||||||
|
return query().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
uri_t::
|
||||||
|
fragment() const
|
||||||
|
{
|
||||||
|
return fragment_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uri_t::
|
||||||
|
fragment_str() const
|
||||||
|
{
|
||||||
|
return fragment().value_or(std::string{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
to_string(uri_t const& uri)
|
||||||
|
{
|
||||||
|
std::ostringstream str;
|
||||||
|
|
||||||
|
// Scheme
|
||||||
|
//
|
||||||
|
if (auto scheme = uri.scheme(); scheme) {
|
||||||
|
str <<*scheme <<':';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authority
|
||||||
|
//
|
||||||
|
if (auto host = uri.host(); host) {
|
||||||
|
str <<"//";
|
||||||
|
|
||||||
|
// Userinfo
|
||||||
|
//
|
||||||
|
if (auto userinfo = uri.userinfo(); userinfo) {
|
||||||
|
str <<*userinfo <<'@';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host
|
||||||
|
//
|
||||||
|
str <<*host;
|
||||||
|
|
||||||
|
// Port
|
||||||
|
if (auto port = uri.port(); port) {
|
||||||
|
str <<':' <<*port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path
|
||||||
|
//
|
||||||
|
str <<uri.path_str();
|
||||||
|
|
||||||
|
// Query
|
||||||
|
//
|
||||||
|
if (auto query = uri.query(); query) {
|
||||||
|
str <<'?' <<*query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fragment
|
||||||
|
//
|
||||||
|
if (auto fragment = uri.fragment(); fragment) {
|
||||||
|
str <<'#' <<*fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
uri_t
|
||||||
|
normalize_path(uri_t const& uri)
|
||||||
|
{
|
||||||
|
std::stringstream path{uri.path_str()};
|
||||||
|
std::vector<std::string> segments;
|
||||||
|
|
||||||
|
for (std::string segment; std::getline(path, segment, '/');) {
|
||||||
|
std::cout << "found segment: " << segment << '\n';
|
||||||
|
|
||||||
|
if (segment.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (segment == ".") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (segment == "..") {
|
||||||
|
if (!segments.empty()) {
|
||||||
|
segments.pop_back();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
segments.push_back(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string normalized;
|
||||||
|
|
||||||
|
for (auto const& j : segments) {
|
||||||
|
normalized += '/';
|
||||||
|
normalized += j;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri_t{
|
||||||
|
uri.scheme(),
|
||||||
|
uri.userinfo(),
|
||||||
|
uri.host(),
|
||||||
|
uri.port(),
|
||||||
|
normalized.empty() ? "/" : normalized,
|
||||||
|
uri.query(),
|
||||||
|
uri.fragment()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uri_t>
|
||||||
|
try_parse(std::string const& str)
|
||||||
|
{
|
||||||
|
return try_parse(str.begin(), str.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uri
|
118
code/uri/uri.hxx
Normal file
118
code/uri/uri.hxx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#ifndef code__uri__uri_hxx_
|
||||||
|
#define code__uri__uri_hxx_
|
||||||
|
|
||||||
|
#include <code/uri/grammar.hxx>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace code::uri
|
||||||
|
{
|
||||||
|
|
||||||
|
class uri_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uri_t();
|
||||||
|
|
||||||
|
uri_t(std::string, std::string, std::string);
|
||||||
|
|
||||||
|
uri_t(std::string, std::string, std::string, std::string);
|
||||||
|
|
||||||
|
uri_t(std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string);
|
||||||
|
|
||||||
|
uri_t(std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string);
|
||||||
|
|
||||||
|
uri_t(std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string,
|
||||||
|
std::string);
|
||||||
|
|
||||||
|
uri_t(std::optional<std::string>,
|
||||||
|
std::optional<std::string>,
|
||||||
|
std::optional<std::string>,
|
||||||
|
std::optional<std::string>,
|
||||||
|
std::string,
|
||||||
|
std::optional<std::string>,
|
||||||
|
std::optional<std::string>);
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
scheme() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
scheme_str() const;
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
userinfo() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
userinfo_str() const;
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
host() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
host_str() const;
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
port() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
port_str() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
path_str() const;
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
query() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
query_str() const;
|
||||||
|
|
||||||
|
std::optional<std::string> const&
|
||||||
|
fragment() const;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
fragment_str() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<std::string> scheme_;
|
||||||
|
std::optional<std::string> userinfo_;
|
||||||
|
std::optional<std::string> host_;
|
||||||
|
std::optional<std::string> port_;
|
||||||
|
std::string path_;
|
||||||
|
std::optional<std::string> query_;
|
||||||
|
std::optional<std::string> fragment_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string
|
||||||
|
to_string(uri_t const&);
|
||||||
|
|
||||||
|
uri_t
|
||||||
|
normalize_path(uri_t const&);
|
||||||
|
|
||||||
|
template<typename Iterator>
|
||||||
|
std::optional<uri_t>
|
||||||
|
try_parse(Iterator first, Iterator last);
|
||||||
|
|
||||||
|
std::optional<uri_t>
|
||||||
|
try_parse(std::string const&);
|
||||||
|
|
||||||
|
} // namespace code::uri
|
||||||
|
|
||||||
|
#include <code/uri/uri.txx>
|
||||||
|
|
||||||
|
#endif
|
327
code/uri/uri.test.cxx
Normal file
327
code/uri/uri.test.cxx
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
#include <code/uri/uri.hxx>
|
||||||
|
|
||||||
|
#include <code/validation/main.hxx>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_01)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE(uri.path_str().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_02)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("http:///index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), true);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), true);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), false);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "http");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_03)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("http://host.domain./index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "http");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_04)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("https://host.domain.:8443/index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "https");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.port_str(), "8443");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_05)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("https://host.domain.:8443/secodeh?q=hamsters");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "https");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.port_str(), "8443");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/secodeh");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.query_str(), "q=hamsters");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_06)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("https://host.domain.:8443/secodeh?q=hamsters#results");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "https");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.port_str(), "8443");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/secodeh");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.query_str(), "q=hamsters");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.fragment_str(), "results");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_07)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse(
|
||||||
|
"https://admin:qwerty@host.domain.:8443/secodeh?q=hamsters#results"
|
||||||
|
);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.scheme_str(), "https");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.userinfo_str(), "admin:qwerty");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.port_str(), "8443");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/secodeh");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.query_str(), "q=hamsters");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.fragment_str(), "results");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_08)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("//host.domain./index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.scheme());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.host_str(), "host.domain.");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_09)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("/index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_10)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("index.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.userinfo());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.host());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.port());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.query());
|
||||||
|
VALIDATION_ASSERT_FALSE((bool)uri.fragment());
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_11)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("/files/index:1.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), false);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "/files/index:1.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_12)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("files/index:1.html");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), false);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.path_str(), "files/index:1.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_13)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("?q=hamsters");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), true);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), false);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.query_str(), "q=hamsters");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_14)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("?q=hamsters#results");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), true);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), true);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.query_str(), "q=hamsters");
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.fragment_str(), "results");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_15)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("#results");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), true);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.fragment_str(), "results");
|
||||||
|
}
|
||||||
|
|
||||||
|
VALIDATION_TEST(test_16)
|
||||||
|
{
|
||||||
|
auto opt_uri = code::uri::try_parse("#results?gui-sort=asc");
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_TRUE((bool)opt_uri);
|
||||||
|
|
||||||
|
auto uri = *opt_uri;
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.scheme(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.userinfo(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.host(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.port(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.query(), false);
|
||||||
|
VALIDATION_ASSERT_EQUAL((bool)uri.fragment(), true);
|
||||||
|
|
||||||
|
VALIDATION_ASSERT_EQUAL(uri.fragment_str(), "results?gui-sort=asc");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
return code::validation::main(argc, argv);
|
||||||
|
}
|
242
code/uri/uri.txx
Normal file
242
code/uri/uri.txx
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
namespace code::uri
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename Iterator>
|
||||||
|
std::optional<uri_t>
|
||||||
|
try_parse(Iterator first, Iterator last)
|
||||||
|
{
|
||||||
|
std::optional<std::string> opt_scheme;
|
||||||
|
std::optional<std::string> opt_userinfo;
|
||||||
|
std::optional<std::string> opt_host;
|
||||||
|
std::optional<std::string> opt_port;
|
||||||
|
std::string path;
|
||||||
|
std::optional<std::string> opt_query;
|
||||||
|
std::optional<std::string> opt_fragment;
|
||||||
|
|
||||||
|
auto try_parse_scheme = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
std::string scheme;
|
||||||
|
while (c != last && grammar::is_scheme(*c)) {
|
||||||
|
scheme += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != last && *c == ':') {
|
||||||
|
++c; // skips ':'
|
||||||
|
opt_scheme = std::move(scheme);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_userinfo = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
std::string userinfo;
|
||||||
|
|
||||||
|
while (c != last && grammar::is_userinfo(*c)) {
|
||||||
|
userinfo += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != last && *c == '@') {
|
||||||
|
++c; // skips '@'
|
||||||
|
opt_userinfo = std::move(userinfo);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_host = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
opt_host = std::string{};
|
||||||
|
|
||||||
|
while (c != last && grammar::is_host(*c)) {
|
||||||
|
*opt_host += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_port = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
if (c != last && *c == ':') {
|
||||||
|
++c; // skips ':'
|
||||||
|
|
||||||
|
opt_port = std::string{};
|
||||||
|
|
||||||
|
while (c != last && grammar::is_digit(*c)) {
|
||||||
|
*opt_port += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_authority = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
if (c == last || *c != '/') {
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
|
||||||
|
++c; // skips first '/'
|
||||||
|
|
||||||
|
if (c == last || *c != '/') {
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
|
||||||
|
++c; // skips second '/'
|
||||||
|
|
||||||
|
c = try_parse_userinfo(c);
|
||||||
|
c = try_parse_host(c);
|
||||||
|
c = try_parse_port(c);
|
||||||
|
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_path = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
while (c != last && (grammar::is_pchar(*c) || *c == '/')) {
|
||||||
|
path += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_query = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
if (c != last && *c == '?') {
|
||||||
|
++c; // skips '?'
|
||||||
|
opt_query = std::string{};
|
||||||
|
|
||||||
|
while (c != last && grammar::is_query(*c)) {
|
||||||
|
*opt_query += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto try_parse_fragment = [&](auto init)
|
||||||
|
{
|
||||||
|
auto c = init;
|
||||||
|
|
||||||
|
if (c != last && *c == '#') {
|
||||||
|
++c; // skips '?#'
|
||||||
|
|
||||||
|
opt_fragment = std::string{};
|
||||||
|
|
||||||
|
while (c != last && grammar::is_fragment(*c)) {
|
||||||
|
*opt_fragment += *c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return init;
|
||||||
|
};
|
||||||
|
|
||||||
|
first = try_parse_scheme(first);
|
||||||
|
first = try_parse_authority(first);
|
||||||
|
first = try_parse_path(first);
|
||||||
|
first = try_parse_query(first);
|
||||||
|
first = try_parse_fragment(first);
|
||||||
|
|
||||||
|
if (first != last) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto percent_decode = [](std::string const& input)
|
||||||
|
{
|
||||||
|
auto hex_to_char = [](char c)
|
||||||
|
{
|
||||||
|
if (c>= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
|
||||||
|
if (c>= 'a' && c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
|
||||||
|
if (c>= 'A' && c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
|
||||||
|
throw std::invalid_argument{"invalid hex character"};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto make_byte = [&](char a, char b)
|
||||||
|
{
|
||||||
|
return hex_to_char(a) << 4 | hex_to_char(b);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string str;
|
||||||
|
|
||||||
|
auto j = input.begin();
|
||||||
|
|
||||||
|
while (j != input.end()) {
|
||||||
|
if ('%' == *j) {
|
||||||
|
++j;
|
||||||
|
|
||||||
|
if (j == input.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char one = *j++;
|
||||||
|
|
||||||
|
if (j == input.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char two = *j++;
|
||||||
|
|
||||||
|
str += make_byte(one, two);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
str += *j;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opt_userinfo) {
|
||||||
|
opt_userinfo = percent_decode(*opt_userinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
path = percent_decode(path);
|
||||||
|
|
||||||
|
if (opt_query) {
|
||||||
|
opt_query = percent_decode(*opt_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_fragment) {
|
||||||
|
opt_fragment = percent_decode(*opt_fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri_t{opt_scheme,
|
||||||
|
opt_userinfo,
|
||||||
|
opt_host,
|
||||||
|
opt_port,
|
||||||
|
path,
|
||||||
|
opt_query,
|
||||||
|
opt_fragment};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::uri
|
37
code/uri/version.hxx.in
Normal file
37
code/uri/version.hxx.in
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef code__uri__version_hxx_
|
||||||
|
#define code__uri__version_hxx_
|
||||||
|
|
||||||
|
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
|
||||||
|
//
|
||||||
|
// AAAAA - major version number
|
||||||
|
// BBBBB - minor version number
|
||||||
|
// CCCCC - bugfix version number
|
||||||
|
// DDD - alpha / beta (DDD + 500) version number
|
||||||
|
// E - final (0) / snapshot (1)
|
||||||
|
//
|
||||||
|
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
|
||||||
|
//
|
||||||
|
// Version AAAAABBBBBCCCCCDDDE
|
||||||
|
//
|
||||||
|
// 0.1.0 0000000001000000000
|
||||||
|
// 0.1.2 0000000001000020000
|
||||||
|
// 1.2.3 0000100002000030000
|
||||||
|
// 2.2.0-a.1 0000200001999990010
|
||||||
|
// 3.0.0-b.2 0000299999999995020
|
||||||
|
// 2.2.0-a.1.z 0000200001999990011
|
||||||
|
//
|
||||||
|
#define LIBCODE_URI_VERSION $libcode_uri.version.project_number$ULL
|
||||||
|
#define LIBCODE_URI_VERSION_STR "$libcode_uri.version.project$"
|
||||||
|
#define LIBCODE_URI_VERSION_ID "$libcode_uri.version.project_id$"
|
||||||
|
#define LIBCODE_URI_VERSION_FULL "$libcode_uri.version$"
|
||||||
|
|
||||||
|
#define LIBCODE_URI_VERSION_MAJOR $libcode_uri.version.major$
|
||||||
|
#define LIBCODE_URI_VERSION_MINOR $libcode_uri.version.minor$
|
||||||
|
#define LIBCODE_URI_VERSION_PATCH $libcode_uri.version.patch$
|
||||||
|
|
||||||
|
#define LIBCODE_URI_PRE_RELEASE $libcode_uri.version.pre_release$
|
||||||
|
|
||||||
|
#define LIBCODE_URI_SNAPSHOT_SN $libcode_uri.version.snapshot_sn$ULL
|
||||||
|
#define LIBCODE_URI_SNAPSHOT_ID "$libcode_uri.version.snapshot_id$"
|
||||||
|
|
||||||
|
#endif
|
12
manifest
Normal file
12
manifest
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
: 1
|
||||||
|
name: libcode-uri
|
||||||
|
version: 0.1.0-a.0.z
|
||||||
|
language: c++
|
||||||
|
summary: code-uri C++ library
|
||||||
|
license: BSD-4-Clause
|
||||||
|
description-file: README.md
|
||||||
|
url: https://helloryan.se/code/
|
||||||
|
email: ryan@helloryan.se
|
||||||
|
depends: * build2 >= 0.17.0
|
||||||
|
depends: * bpkg >= 0.17.0
|
||||||
|
depends: libcode-validation ^0.1.0-
|
6
repositories.manifest
Normal file
6
repositories.manifest
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
: 1
|
||||||
|
summary: libcode-uri project repository
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://code.helloryan.se/code/libcode-validation.git##HEAD
|
8
tests/.gitignore
vendored
Normal file
8
tests/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Test executables.
|
||||||
|
#
|
||||||
|
driver
|
||||||
|
|
||||||
|
# Testscript output directories (can be symlinks).
|
||||||
|
#
|
||||||
|
test
|
||||||
|
test-*
|
4
tests/build/.gitignore
vendored
Normal file
4
tests/build/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/config.build
|
||||||
|
/root/
|
||||||
|
/bootstrap/
|
||||||
|
build/
|
5
tests/build/bootstrap.build
Normal file
5
tests/build/bootstrap.build
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
project = # Unnamed tests subproject.
|
||||||
|
|
||||||
|
using config
|
||||||
|
using test
|
||||||
|
using dist
|
16
tests/build/root.build
Normal file
16
tests/build/root.build
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cxx.std = latest
|
||||||
|
|
||||||
|
using cxx
|
||||||
|
|
||||||
|
hxx{*}: extension = hxx
|
||||||
|
ixx{*}: extension = ixx
|
||||||
|
txx{*}: extension = txx
|
||||||
|
cxx{*}: extension = cxx
|
||||||
|
|
||||||
|
# Every exe{} in this subproject is by default a test.
|
||||||
|
#
|
||||||
|
exe{*}: test = true
|
||||||
|
|
||||||
|
# The test target for cross-testing (running tests under Wine, etc).
|
||||||
|
#
|
||||||
|
test.target = $cxx.target
|
1
tests/buildfile
Normal file
1
tests/buildfile
Normal file
@ -0,0 +1 @@
|
|||||||
|
./: {*/ -build/}
|
Loading…
x
Reference in New Issue
Block a user