Hello Seafire

This commit is contained in:
R.Y.A.N 2025-03-07 02:25:53 +01:00
commit 22dd4500e4
Signed by: R.Y.A.N
GPG Key ID: 3BD93EABD1407B82
90 changed files with 6338 additions and 0 deletions

17
.editorconfig Normal file
View 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
View File

@ -0,0 +1 @@
* text=auto

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
.bdep/
# 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

32
LICENSE.md Normal file
View File

@ -0,0 +1,32 @@
Copyright © 2025 Per Ryan Edin. 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 Seafire, an HTTP/1.1 implementation
> for C++ applications. 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.

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# Seafire
## 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 repository
write access.

4
build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@ -0,0 +1,7 @@
project = seafire-protocol
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@ -0,0 +1,6 @@
$out_root/
{
include seafire/protocol/
}
export $out_root/seafire/protocol/$import.target

16
build/root.build Normal file
View 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
View File

@ -0,0 +1,5 @@
./: {seafire/ tests/} doc{README.md} legal{LICENSE.md} manifest
# Don't install tests.
#
tests/: install = false

12
manifest Normal file
View File

@ -0,0 +1,12 @@
: 1
name: seafire-protocol
version: 0.1.0-a.0.z
language: c++
summary: Seafire Protocol C++ library
license: BSD-4-Clause
description-file: README.md
url: https://helloryan.se/
email: ryan@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libasio ^1.29.0

15
repositories.manifest Normal file
View File

@ -0,0 +1,15 @@
: 1
summary: Seafire-Protocol project repository
:
role: prerequisite
location: https://pkg.cppget.org/1/beta
trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-uri.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/creatures/Seafire-Common.git##HEAD

9
seafire/protocol/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

View File

@ -0,0 +1,302 @@
#ifndef seafire__protocol__basic_parser_hxx_
#define seafire__protocol__basic_parser_hxx_
#include <seafire/protocol/error.hxx>
#include <seafire/protocol/grammar.hxx>
#include <seafire/protocol/message.hxx>
#include <seafire/protocol/protocol-version.hxx>
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <code/uri/uri.hxx>
#include <stdexcept>
#include <vector>
namespace seafire::protocol
{
/// Implements a basic HTTP message parser.
///
template<typename Iterator>
class basic_parser_t {
public:
/// Holds an iterator pair.
///
struct iterator_pair_t
{
Iterator first;
Iterator last;
};
/// Holds iterator pairs for a header/value.
///
struct header_t
{
iterator_pair_t field;
iterator_pair_t value;
};
/// Attempt to parse a message.
///
Iterator
parse(Iterator, Iterator, std::error_code&);
iterator_pair_t const&
version() const
{
return version_;
}
std::vector<header_t> const&
headers() const
{
return headers_;
}
protected:
enum expectation_t
{
// Requests.
//
expect_method_start = 10,
expect_method,
expect_target_start = 20,
expect_target,
expect_version_h = 30,
expect_version_ht,
expect_version_htt,
expect_version_http,
expect_version_slash,
expect_version_major,
expect_version_period,
expect_version_minor,
expect_version_cr,
expect_version_lf,
// Response
//
expect_response_version_h,
expect_response_version_ht,
expect_response_version_htt,
expect_response_version_http,
expect_response_version_slash,
expect_response_version_major,
expect_response_version_period,
expect_response_version_minor,
expect_response_version_space,
expect_response_status_1,
expect_response_status_2,
expect_response_status_3,
expect_response_status_space,
expect_response_reason_start,
expect_response_reason,
expect_response_reason_lf,
// Headers.
//
expect_header_start = 100,
expect_header_field,
expect_header_value_start,
expect_header_value,
expect_header_terminating_lf,
expect_terminating_lf,
done = 9999
};
/// Construct a new parser.
///
explicit
basic_parser_t(expectation_t expect)
: expect_{expect}
{}
/// Copy-construct a new parser.
///
basic_parser_t(basic_parser_t const&) = default;
/// Move-construct a new parser.
///
basic_parser_t(basic_parser_t&&) = default;
/// Copy-assign this parser.
///
basic_parser_t&
operator=(basic_parser_t const&) = default;
/// Move-assign this parser.
///
basic_parser_t&
operator=(basic_parser_t&&) = default;
expectation_t expect_;
iterator_pair_t method_;
iterator_pair_t target_;
iterator_pair_t version_;
iterator_pair_t status_;
iterator_pair_t reason_;
std::vector<header_t> headers_;
private:
expectation_t
parse_char(Iterator it, std::error_code&);
};
/// Implements a basic request parser.
///
template<typename Iterator>
class basic_request_parser_t
: public basic_parser_t<Iterator>
{
public:
basic_request_parser_t()
: basic_parser_t<Iterator>{basic_parser_t<Iterator>::expect_method_start}
{}
typename basic_parser_t<Iterator>::iterator_pair_t const&
method() const
{
return basic_parser_t<Iterator>::method_;
}
typename basic_parser_t<Iterator>::iterator_pair_t const&
target() const
{
return basic_parser_t<Iterator>::target_;
}
};
/// Implements a basic request parser.
///
template<typename Iterator>
class basic_response_parser_t
: public basic_parser_t<Iterator>
{
public:
basic_response_parser_t()
: basic_parser_t<Iterator>{basic_parser_t<Iterator>::expect_response_version_h}
{}
typename basic_parser_t<Iterator>::iterator_pair_t const&
status() const
{
return basic_parser_t<Iterator>::status_;
}
typename basic_parser_t<Iterator>::iterator_pair_t const&
reason() const
{
return basic_parser_t<Iterator>::reason_;
}
};
template<typename Iterator>
void
extract_version(basic_parser_t<Iterator> const&, message_t&);
template<typename Iterator>
void
extract_headers(basic_parser_t<Iterator> const&, message_t&);
template<typename Iterator>
void
extract_method(basic_request_parser_t<Iterator> const&, request_t&);
template<typename Iterator>
void
extract_target(basic_request_parser_t<Iterator> const&, request_t&);
template<typename Iterator>
void
extract_status(basic_response_parser_t<Iterator> const&, response_t&);
template<typename Iterator>
void
extract_reason(basic_response_parser_t<Iterator> const&, response_t&);
template<typename Iterator>
void
extract_message(basic_parser_t<Iterator> const&, message_t&);
template<typename Iterator>
void
extract_message(basic_request_parser_t<Iterator> const&, request_t&);
template<typename Iterator>
void
extract_message(basic_response_parser_t<Iterator> const&, response_t&);
template<typename InputIterator>
InputIterator
parse_request(request_t&, InputIterator, InputIterator);
template<typename InputIterator>
InputIterator
parse_request(request_t&,
InputIterator,
InputIterator,
std::error_code&);
template<typename InputIterator>
InputIterator
parse_request(request_t&,
basic_request_parser_t<InputIterator>&,
InputIterator,
InputIterator);
template<typename InputIterator>
InputIterator
parse_request(request_t&,
basic_request_parser_t<InputIterator>&,
InputIterator,
InputIterator,
std::error_code&);
template<typename InputIterator>
InputIterator
parse_response(response_t&,
InputIterator,
InputIterator,
std::error_code&);
template<typename InputIterator>
InputIterator
parse_response(response_t&,
basic_response_parser_t<InputIterator>&,
InputIterator,
InputIterator);
template<typename InputIterator>
InputIterator
parse_response(response_t&,
basic_response_parser_t<InputIterator>&,
InputIterator,
InputIterator,
std::error_code&);
class parser_not_ready
: public std::logic_error
{
public:
parser_not_ready()
: std::logic_error{"parser not ready"}
{}
};
} // namespace seafire::protocol
#include <seafire/protocol/basic-parser.txx>
#endif

View File

@ -0,0 +1,546 @@
namespace seafire::protocol
{
template<typename Iterator>
Iterator
basic_parser_t<Iterator>::
parse(Iterator first,
Iterator last,
std::error_code& ec)
{
while (first != last) {
expect_ = parse_char(first++, ec);
if (expect_ == done || ec) {
return first;
}
}
if (first == last) {
ec = parse_error_t::incomplete_message;
}
return first;
}
template<typename Iterator>
typename basic_parser_t<Iterator>::expectation_t
basic_parser_t<Iterator>::
parse_char(Iterator it, std::error_code& ec)
{
char const c{*it};
switch (expect_) {
case expect_method_start:
if (grammar::is_cr_or_lf(c)) { // ignore before request
return expect_method_start;
}
if (!grammar::is_tchar(c)) {
ec = parse_error_t::bad_method;
return done;
}
method_.first = it;
return expect_method;
case expect_method:
if (grammar::is_space(c)) {
method_.last = it;
return expect_target_start;
}
if (!grammar::is_tchar(c)) {
ec = parse_error_t::bad_method;
return done;
}
return expect_method;
case expect_target_start:
if (!grammar::is_target_char(c)) {
ec = parse_error_t::bad_target;
return done;
}
target_.first = it;
return expect_target;
case expect_target:
if (grammar::is_space(c)) {
target_.last = it;
return expect_version_h;
}
if (!grammar::is_target_char(c)) {
ec = parse_error_t::bad_target;
return done;
}
return expect_target;
case expect_version_h:
if ('H' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_ht;
case expect_version_ht:
if ('T' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_htt;
case expect_version_htt:
if ('T' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_http;
case expect_version_http:
if ('P' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_slash;
case expect_version_slash:
if ('/' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_major;
case expect_version_major:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_version;
return done;
}
version_.first = it;
return expect_version_period;
case expect_version_period:
if ('.' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_minor;
case expect_version_minor:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_version;
return done;
}
version_.last = it;
return expect_version_cr;
case expect_version_cr:
if (!grammar::is_cr(c)) {
ec = parse_error_t::bad_version;
return done;
}
return expect_version_lf;
case expect_version_lf:
if (!grammar::is_lf(c)) {
ec = parse_error_t::bad_version;
return done;
}
return expect_header_start;
case expect_response_version_h:
if ('H' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_ht;
case expect_response_version_ht:
if ('T' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_htt;
case expect_response_version_htt:
if ('T' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_http;
case expect_response_version_http:
if ('P' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_slash;
case expect_response_version_slash:
if ('/' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_major;
case expect_response_version_major:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_version;
return done;
}
version_.first = it;
return expect_response_version_period;
case expect_response_version_period:
if ('.' != c) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_version_minor;
case expect_response_version_minor:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_version;
return done;
}
version_.last = it;
return expect_response_version_space;
case expect_response_version_space:
if (!grammar::is_space(c)) {
ec = parse_error_t::bad_version;
return done;
}
return expect_response_status_1;
case expect_response_status_1:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_status;
return done;
}
status_.first = status_.last = it;
return expect_response_status_2;
case expect_response_status_2:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_status;
return done;
}
return expect_response_status_3;
case expect_response_status_3:
if (!grammar::is_digit(c)) {
ec = parse_error_t::bad_status;
return done;
}
return expect_response_status_space;
case expect_response_status_space:
if (' ' != c) {
ec = parse_error_t::bad_status;
return done;
}
status_.last = it;
return expect_response_reason_start;
case expect_response_reason_start:
if (c != '\t' && c != ' ' && !grammar::is_vchar(c) &&
grammar::is_obs_text(c)) {
ec = parse_error_t::bad_reason;
return done;
}
reason_.first = it;
return expect_response_reason;
case expect_response_reason:
if ('\r' == c) {
reason_.last = it;
return expect_response_reason_lf;
}
if (c != '\t' && c != ' ' && !grammar::is_vchar(c) &&
grammar::is_obs_text(c)) {
ec = parse_error_t::bad_reason;
return done;
}
return expect_response_reason;
case expect_response_reason_lf:
if (c != '\t' && c != ' ' && !grammar::is_vchar(c) &&
grammar::is_obs_text(c)) {
ec = parse_error_t::bad_reason;
return done;
}
return expect_header_start;
case expect_header_start:
if (grammar::is_cr(c)) {
return expect_terminating_lf;
}
if (!grammar::is_tchar(c)) {
ec = parse_error_t::bad_header_field;
return done;
}
headers_.emplace_back();
headers_.back().field.first = it;
return expect_header_field;
case expect_header_field:
if (':' == c) {
headers_.back().field.last = it;
return expect_header_value_start;
}
if (!grammar::is_tchar(c)) {
ec = parse_error_t::bad_header_field;
return done;
}
return expect_header_field;
case expect_header_value_start:
// TODO check through grammar
if (*it == ' ' || *it == '\t') {
return expect_header_value_start;
}
headers_.back().value.first = it;
if (grammar::is_cr(c)) {
headers_.back().value.last = it;
return expect_header_terminating_lf;
}
if (!grammar::is_space(c) && grammar::is_control_char(c)) {
throw __LINE__;
ec = parse_error_t::bad_header_value;
return done;
}
return expect_header_value;
case expect_header_value:
if (grammar::is_cr(c)) {
headers_.back().value.last = it;
return expect_header_terminating_lf;
}
if (!grammar::is_space(c) && grammar::is_control_char(c)) {
ec = parse_error_t::bad_header_value;
return done;
}
return expect_header_value;
case expect_header_terminating_lf:
if (!grammar::is_lf(c)) {
ec = parse_error_t::bad_header_value;
return done;
}
return expect_header_start;
case expect_terminating_lf:
if (!grammar::is_lf(c)) {
ec = parse_error_t::bad_terminator;
}
return done;
case done: throw parser_not_ready{};
}
throw std::logic_error{"parser in invalid state"};
}
template<typename Iterator>
void
extract_version(basic_parser_t<Iterator> const& parser, message_t& m)
{
version_t v{static_cast<unsigned short>(*parser.version().first - '0'),
static_cast<unsigned short>(*parser.version().last - '0')};
m.set_version(std::move(v));
}
template<typename Iterator>
void
extract_headers(basic_parser_t<Iterator> const& parser, message_t& m)
{
header_collection_t headers;
for (auto const& j : parser.headers()) {
std::string key{j.field.first, j.field.last};
std::string value{j.value.first, j.value.last};
// Calling header.set() would replace any earlier set
// header with the same key.
//
headers.append(std::move(key), std::move(value));
}
m.set_headers(std::move(headers));
}
template<typename Iterator>
void
extract_method(basic_request_parser_t<Iterator> const& parser, request_t& r)
{
std::string method{parser.method().first, parser.method().last};
r.set_method(std::move(method));
}
template<typename Iterator>
void
extract_target(basic_request_parser_t<Iterator> const& parser, request_t& r)
{
std::string target{parser.target().first, parser.target().last};
r.set_target(std::move(target));
auto target_uri = code::uri::try_parse(parser.target().first, parser.target().last);
if (target_uri) {
r.set_target_uri(std::move(*target_uri));
}
}
template<typename Iterator>
void
extract_status(basic_response_parser_t<Iterator> const& parser, response_t& r)
{
auto digit1 = parser.status().first;
auto digit2 = digit1 + 1;
auto digit3 = digit1 + 2;
unsigned long long cdig1 = (*digit1 - '0');
unsigned long long cdig2 = (*digit2 - '0');
unsigned long long cdig3 = (*digit3 - '0');
r.set_status((cdig1 * 100) + (cdig2 * 10) + cdig3);
}
template<typename Iterator>
void
extract_reason(basic_response_parser_t<Iterator> const& parser, response_t& r)
{
std::string reason{parser.reason().first, parser.reason().last};
r.set_status({r.status().code(), std::move(reason)});
}
template<typename Iterator>
void
extract_message(basic_parser_t<Iterator> const& parser, message_t& m)
{
extract_version(parser, m);
extract_headers(parser, m);
}
template<typename Iterator>
void
extract_message(basic_request_parser_t<Iterator> const& parser, request_t& r)
{
extract_message(parser, static_cast<message_t&>(r));
extract_method(parser, r);
extract_target(parser, r);
}
template<typename Iterator>
void
extract_message(basic_response_parser_t<Iterator> const& parser, response_t& r)
{
extract_message(parser, static_cast<message_t&>(r));
extract_status(parser, r);
extract_reason(parser, r);
}
template<typename InputIterator>
InputIterator
parse_request(request_t& r, InputIterator first, InputIterator last)
{
basic_request_parser_t<InputIterator> parser;
return parse_request(r, parser, first, last);
}
template<typename InputIterator>
InputIterator
parse_request(request_t& r,
InputIterator first,
InputIterator last,
std::error_code& ec)
{
basic_request_parser_t<InputIterator> parser;
return parse_request(r, parser, first, last, ec);
}
template<typename InputIterator>
InputIterator
parse_request(request_t& r,
basic_request_parser_t<InputIterator>& parser,
InputIterator first,
InputIterator last)
{
first = parser.parse(first, last);
extract_message(parser, r);
return first;
}
template<typename InputIterator>
InputIterator
parse_request(request_t& r,
basic_request_parser_t<InputIterator>& parser,
InputIterator first,
InputIterator last,
std::error_code& ec)
{
first = parser.parse(first, last, ec);
if (ec)
return first;
extract_message(parser, r);
return first;
}
template<typename InputIterator>
InputIterator
parse_response(response_t& r, InputIterator first, InputIterator last)
{
basic_response_parser_t<InputIterator> parser;
return parse_response(r, parser, first, last);
}
template<typename InputIterator>
InputIterator
parse_response(response_t& r,
InputIterator first,
InputIterator last,
std::error_code& ec)
{
basic_response_parser_t<InputIterator> parser;
return parse_response(r, parser, first, last, ec);
}
template<typename InputIterator>
InputIterator
parse_response(response_t& r,
basic_response_parser_t<InputIterator>& parser,
InputIterator first,
InputIterator last)
{
first = parser.parse(first, last);
extract_message(parser, r);
return first;
}
template<typename InputIterator>
InputIterator
parse_response(response_t& r,
basic_response_parser_t<InputIterator>& parser,
InputIterator first,
InputIterator last,
std::error_code& ec)
{
first = parser.parse(first, last, ec);
if (ec) {
return first;
}
extract_message(parser, r);
return first;
}
} // namespace seafire::protocol

View File

@ -0,0 +1,66 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import intf_libs =+ libasio%lib{asio}
import intf_libs =+ libcode-uri%lib{code-uri}
import intf_libs =+ seafire-common%lib{seafire-common}
./: lib{seafire-protocol}: libul{seafire-protocol}
libul{seafire-protocol}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{seafire-protocol}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{seafire-protocol}: bin.whole = false
}
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{seafire-protocol}:
{
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{seafire-protocol}: bin.lib.version = "-$version.project_id"
else
lib{seafire-protocol}: bin.lib.version = "-$version.major.$version.minor"
# Install into the seafire/protocol/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/seafire/protocol/
install.subdirs = true
}

View File

@ -0,0 +1,217 @@
#include <seafire/protocol/connection.hxx>
#include <seafire/protocol/read.hxx>
#include <seafire/protocol/read-content.hxx>
#include <seafire/protocol/write.hxx>
#include <seafire/protocol/rfc7230/content-length.hxx>
namespace seafire::protocol
{
/// Construct a new HTTP connection.
///
/// \param s The I/O stream.
/// \param max The maximum size of the connection buffer.
///
connection_t::
connection_t(common::io::stream_t& s, std::size_t max)
: stream_{s},
buffer_{max}
{}
/// Destroy this connection.
///
connection_t::
~connection_t() noexcept = default;
/// Access the underlying I/O stream.
///
common::io::stream_t&
connection_t::
get_stream()
{
return stream_;
}
asio::any_io_executor const&
connection_t::
get_executor()
{
return get_stream().get_executor();
}
void
connection_t::
cancel()
{
get_stream().cancel();
}
/// Perform a blocking read of a request.
///
/// \param r The target request object.
/// \param content The target request content buffer.
/// \throws std::system_error Thrown on error.
///
void
connection_t::
read(request_t& r, asio::streambuf& content)
{
protocol::read(get_stream(), buffer_, r);
read_content(get_stream(), buffer_, content, r);
}
/// Perform a blocking read of a request.
///
/// \param r The target request object.
/// \param content The target request content buffer.
///
void
connection_t::
read(request_t& r,
asio::streambuf& content,
std::error_code& ec)
{
protocol::read(get_stream(), buffer_, r, ec);
if (ec) {
return;
}
read_content(get_stream(), buffer_, content, r, ec);
}
/// Initiate an non-blocking read of a request.
///
/// \param r The target request object.
/// \param content The target request content buffer.
/// \param handler The handler to invoke on completion.
///
void
connection_t::
async_read(request_t& r,
asio::streambuf& content,
read_handler_t handler)
{
auto bound = [this, &r, &content, handler](std::error_code const& ec)
{
if (ec) {
handler(ec);
return;
}
async_read_content(get_stream(), buffer_, content, r, handler);
};
protocol::async_read(get_stream(), buffer_, r, bound);
}
/// Perform a blocking read of a response.
///
/// \param r The target response object.
/// \param content The target response content buffer.
/// \throws std::system_error Thrown on error.
///
void
connection_t::
read(response_t& r, asio::streambuf& content)
{
protocol::read(get_stream(), buffer_, r);
read_content(get_stream(), buffer_, content, r);
}
/// Perform a blocking read of a response.
///
/// \param r The target response object.
/// \param content The target response content buffer.
///
void
connection_t::
read(response_t& r,
asio::streambuf& content,
std::error_code& ec)
{
protocol::read(get_stream(), buffer_, r, ec);
if (ec) {
return;
}
read_content(get_stream(), buffer_, content, r, ec);
}
/// Initiate an non-blocking read of a response.
///
/// \param r The target response object.
/// \param content The target response content buffer.
/// \param handler The handler to invoke on completion.
///
void
connection_t::
async_read(response_t& r,
asio::streambuf& content,
read_handler_t handler)
{
auto bound = [this, &r, &content, handler](std::error_code const& ec)
{
if (ec) {
handler(ec);
return;
}
async_read_content(get_stream(), buffer_, content, r, handler);
};
protocol::async_read(get_stream(), buffer_, r, bound);
}
/// Initiate a blocking write of a request message.
///
void
connection_t::
write(request_t const& r, common::io::const_buffers_t const& content)
{
protocol::write(get_stream(), r, content);
}
/// Initiate a blocking write of a request message.
///
void
connection_t::
write(request_t const& r, common::io::const_buffers_t const& content, std::error_code& ec)
{
protocol::write(get_stream(), r, content, ec);
}
/// Initiate a non-blocking write of a request message.
///
void
connection_t::
async_write(request_t const& r, common::io::const_buffers_t const& content, write_handler_t handler)
{
protocol::async_write(get_stream(), r, content, handler);
}
void
connection_t::
write(response_t const& r, common::io::const_buffers_t const& content)
{
protocol::write(get_stream(), r, content);
}
void
connection_t::
write(response_t const& r, common::io::const_buffers_t const& content, std::error_code& ec)
{
protocol::write(get_stream(), r, content, ec);
}
void
connection_t::
async_write(response_t const& r, common::io::const_buffers_t const& content, write_handler_t handler)
{
protocol::async_write(get_stream(), r, content, handler);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,93 @@
#ifndef seafire__protocol__connection_hxx_
#define seafire__protocol__connection_hxx_
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace seafire::protocol
{
/// Implements an HTTP/1.0/1.1 connection.
///
class connection_t
{
public:
/// Asynchronous read handler type.
///
using read_handler_t = std::function<void(std::error_code)>;
/// Asynchronous write handler type.
///
using write_handler_t = std::function<void(std::error_code)>;
connection_t(common::io::stream_t&, std::size_t);
connection_t(connection_t const&) = delete;
connection_t(connection_t&&) = delete;
~connection_t() noexcept;
common::io::stream_t&
get_stream();
asio::any_io_executor const&
get_executor();
void
cancel();
void
read(request_t&, asio::streambuf&);
void
read(request_t&, asio::streambuf&, std::error_code&);
void
async_read(request_t&, asio::streambuf&, read_handler_t);
void
read(response_t&, asio::streambuf&);
void
read(response_t&, asio::streambuf&, std::error_code&);
void
async_read(response_t&, asio::streambuf&, read_handler_t);
void
write(request_t const&, common::io::const_buffers_t const&);
void
write(request_t const&, common::io::const_buffers_t const&, std::error_code&);
void
async_write(request_t const&, common::io::const_buffers_t const&, write_handler_t);
void
write(response_t const&, common::io::const_buffers_t const&);
void
write(response_t const&, common::io::const_buffers_t const&, std::error_code&);
void
async_write(response_t const&, common::io::const_buffers_t const&, write_handler_t);
connection_t& operator=(connection_t const&) = delete;
connection_t& operator=(connection_t&&) = delete;
private:
common::io::stream_t& stream_;
asio::streambuf buffer_;
};
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,42 @@
#include <seafire/protocol/date-time.hxx>
#include <iomanip>
#include <locale>
namespace seafire::protocol
{
std::string
format_http_date(std::chrono::system_clock::time_point const& time_point)
{
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
std::time_t now_c = std::chrono::system_clock::to_time_t(time_point);
std::stringstream str;
str.imbue(std::locale{});
str << std::put_time(std::gmtime(&now_c), time_format);
return str.str();
}
std::optional<std::chrono::system_clock::time_point>
try_parse_http_date(std::string const& text)
{
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
std::tm tm{};
std::istringstream str{text};
str.imbue(std::locale{});
str >> std::get_time(&tm, time_format);
if (str.fail())
return {};
std::time_t time = std::mktime(&tm);
return std::chrono::system_clock::from_time_t(time);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,19 @@
#ifndef seafire__protocol__date_time_hxx_
#define seafire__protocol__date_time_hxx_
#include <chrono>
#include <optional>
#include <string>
namespace seafire::protocol
{
std::string
format_http_date(std::chrono::system_clock::time_point const&);
std::optional<std::chrono::system_clock::time_point>
try_parse_http_date(std::string const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,88 @@
#include <seafire/protocol/error.hxx>
namespace seafire::protocol
{
std::error_category const&
get_parse_error_category()
{
class category_t
: public std::error_category
{
public:
char const*
name() const noexcept override
{
return "code.seafire.parser";
}
std::string
message(int ec) const override
{
switch (static_cast<parse_error_t>(ec)) {
case parse_error_t::incomplete_message: return "incomplete message";
case parse_error_t::bad_version: return "bad version";
case parse_error_t::bad_header_field: return "bad header field";
case parse_error_t::bad_header_value: return "bad header value";
case parse_error_t::bad_terminator: return "bad terminator";
case parse_error_t::bad_method: return "bad method";
case parse_error_t::bad_target: return "bad target";
case parse_error_t::bad_status: return "bad status";
case parse_error_t::bad_reason: return "bad reason";
}
return "code.seafire.parser error";
}
};
static category_t const category;
return category;
}
std::error_code
make_error_code(parse_error_t error)
{
return {static_cast<int>(error), get_parse_error_category()};
}
std::error_category const&
get_protocol_error_category()
{
class category_t
: public std::error_category
{
public:
char const*
name() const noexcept override
{
return "code.seafire.protocol";
}
std::string
message(int ec) const override
{
switch (static_cast<protocol_error_t>(ec)) {
case protocol_error_t::invalid_content_length: return "invalid content length";
case protocol_error_t::request_too_large: return "request too large";
case protocol_error_t::length_required: return "length required";
case protocol_error_t::invalid_header_value: return "invalid header value";
}
return "code.seafire.protocol error";
}
};
static category_t const category;
return category;
}
std::error_code
make_error_code(protocol_error_t error)
{
return {static_cast<int>(error), get_protocol_error_category()};
}
} // namespace seafire::protocol

View File

@ -0,0 +1,79 @@
#ifndef seafire__protocol__error_hxx_
#define seafire__protocol__error_hxx_
#include <string>
#include <system_error>
namespace seafire::protocol
{
enum class parse_error_t
{
incomplete_message = 1,
// Common.
bad_version,
bad_header_field,
bad_header_value,
bad_terminator,
// Request-specific.
bad_method,
bad_target,
// Response-specific.
bad_status,
bad_reason
};
std::error_category const&
get_parse_error_category();
std::error_code
make_error_code(parse_error_t);
enum class protocol_error_t
{
// indicates an invalid content-length.
//
invalid_content_length = 1,
// indicates a too big request body.
//
request_too_large,
// content-length missing.
//
length_required,
// For now.
//
invalid_header_value
};
std::error_category const&
get_protocol_error_category();
std::error_code
make_error_code(protocol_error_t);
} // namespace seafire::protocol
namespace std
{
template<>
struct is_error_code_enum<seafire::protocol::parse_error_t>
: true_type
{};
template<>
struct is_error_code_enum<seafire::protocol::protocol_error_t>
: true_type
{};
} // namespace std
#endif

View File

@ -0,0 +1,49 @@
#ifndef seafire__protocol__grammar_hxx_
#define seafire__protocol__grammar_hxx_
namespace seafire::protocol::grammar
{
bool
is_cr(char c);
bool
is_lf(char c);
bool
is_cr_or_lf(char c);
bool
is_space(char c);
bool
is_digit(char c);
bool
is_vchar(char c);
bool
is_tchar(char c);
bool
is_control_char(char c);
bool
is_obs_text(char c);
bool
is_hex(char c);
bool
is_target_char(char c);
template<typename InputIterator>
void
skip_space(InputIterator& it, InputIterator const& end);
} // namespace seafire::protocol::grammar
#include <seafire/protocol/grammar.ixx>
#include <seafire/protocol/grammar.txx>
#endif

View File

@ -0,0 +1,183 @@
namespace seafire::protocol::grammar
{
/// Check if \a c is carriage-return.
///
inline
bool
is_cr(char c)
{
return c == '\r';
}
/// Check if \a c is line-feed.
///
inline
bool
is_lf(char c)
{
return c == '\n';
}
/// Check if \a c is carriage-return or line-feed.
///
inline
bool
is_cr_or_lf(char c)
{
return c == '\r' || c == '\n';
}
/// Check if \a c is spage or tab.
///
inline
bool
is_space(char c)
{
return c == ' ' || c == '\t';
}
/// Check if \a c is a decimal digit.
///
inline
bool
is_digit(char c)
{
return 0 <= 'c' && c <= '9';
}
/// Check if \a c is a vchar.
///
inline
bool
is_vchar(char c)
{
auto u = static_cast< unsigned char >(c);
if (u == 0x7f || u >= 0x80) {
return false;
}
return true;
}
/// Check if \a c is a tchar.
///
inline
bool
is_tchar(char c)
{
if ('a' <= c && c <= 'z') {
return true;
}
if ('A' <= c && c <= 'Z') {
return true;
}
if ('0' <= c && c <= '9') {
return true;
}
switch (c) {
case '!':
case '#':
case '$':
case '%':
case '&':
case '\'':
case '*':
case '+':
case '-':
case '.':
case '^':
case '_':
case '`':
case '|':
case '~':
return true;
}
return false;
}
/// Check if \a c is a control char.
///
inline
bool
is_control_char(char c)
{
return c <= 31 || c == 127;
}
/// Check if \a c is obs-text.
///
inline
bool
is_obs_text(char c)
{
auto u = static_cast< unsigned char >(c);
return u >= 0x80 && u <= 0xFF;
}
/// Check if \a c is a hexadecimal digit.
///
inline
bool
is_hex(char c)
{
if ('0' <= c && c <= '9') {
return true;
}
if ('a' <= c && c <= 'f') {
return true;
}
if ('A' <= c && c <= 'F') {
return true;
}
return false;
}
/// Check if \a c is a target-char.
///
inline
bool
is_target_char(char c)
{
switch (c) {
case '!':
case '$':
case '%':
case '&':
case '(':
case ')':
case '*':
case '+':
case ',':
case '-':
case '.':
case '/':
case ':':
case ';':
case '=':
case '?':
case '@':
case '\'':
case '_':
case '~':
return true;
}
if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
return true;
if ('0' <= c && c <= '9')
return true;
return false;
}
} // namespace seafire::protocol::grammar

View File

@ -0,0 +1,15 @@
namespace seafire::protocol::grammar
{
/// Skip space.
///
template<typename InputIterator>
void
skip_space(InputIterator& it, InputIterator const& end)
{
while (it != end && is_space(*it)) {
++it;
}
}
} // namespace seafire::protocol::grammar

View File

@ -0,0 +1,139 @@
#include <seafire/protocol/header-collection.hxx>
#include <locale>
namespace seafire::protocol
{
/// Set a header.
///
/// Calling set
void
header_collection_t::
set(std::string key, std::string value)
{
normalize(key);
kv_store_.erase(key);
kv_store_.emplace(std::move(key), std::move(value));
}
/// Append a header value.
///
void
header_collection_t::
append(std::string key, std::string value)
{
normalize(key);
kv_store_.emplace(std::move(key), std::move(value));
}
/// Erase a header.
///
void
header_collection_t::
erase(std::string key)
{
normalize(key);
kv_store_.erase(key);
}
/// Check if this collection contains a header.
///
bool
header_collection_t::
contains(std::string key) const
{
normalize(key);
return kv_store_.find(key) != kv_store_.end();
}
/// Get a header.
///
std::vector<std::string>
header_collection_t::
get(std::string key) const
{
normalize(key);
std::vector<std::string> strings;
auto lower = kv_store_.lower_bound(key);
auto upper = kv_store_.upper_bound(key);
while (lower != upper) {
strings.push_back(lower->second);
++lower;
}
return strings;
}
/// Get a header.
///
std::optional<std::string>
header_collection_t::
get_one(std::string key) const
{
normalize(key);
auto lower = kv_store_.lower_bound(key);
auto upper = kv_store_.upper_bound(key);
if (lower != upper)
return lower->second;
return {};
}
header_collection_t::const_iterator
header_collection_t::
begin() const
{
return kv_store_.begin();
}
header_collection_t::const_iterator
header_collection_t::
cbegin() const
{
return kv_store_.cbegin();
}
header_collection_t::const_iterator
header_collection_t::
end() const
{
return kv_store_.end();
}
header_collection_t::const_iterator
header_collection_t::
cend() const
{
return kv_store_.cend();
}
/// Normalize header name.
///
void
header_collection_t::
normalize(std::string& name)
{
std::locale loc{ "C" };
for (auto& c : name)
c = std::tolower(c, loc);
}
/// Write headers to an output stream.
///
std::ostream&
operator<<(std::ostream& o, header_collection_t const& h)
{
for (auto const& j : h)
o << j.first << " = " << j.second << '\n';
return o;
}
} // namespace seafire::protocol

View File

@ -0,0 +1,65 @@
#ifndef seafire__protocol__header_collection_hxx_
#define seafire__protocol__header_collection_hxx_
#include <map>
#include <optional>
#include <ostream>
#include <string>
#include <vector>
namespace seafire::protocol
{
/// Represents a collection of HTTP headers.
///
class header_collection_t
{
public:
/// Iterator type.
///
using const_iterator = std::multimap<std::string, std::string>::const_iterator;
void
set(std::string, std::string);
void
append(std::string, std::string);
void
erase(std::string);
bool
contains(std::string) const;
std::vector<std::string>
get(std::string) const;
std::optional<std::string>
get_one(std::string) const;
const_iterator
begin() const;
const_iterator
cbegin() const;
const_iterator
end() const;
const_iterator
cend() const;
static
void
normalize(std::string&);
private:
std::multimap<std::string, std::string> kv_store_;
};
std::ostream&
operator<<(std::ostream&, header_collection_t const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,66 @@
#include <seafire/protocol/match.hxx>
namespace seafire::protocol
{
match_request_t::
match_request_t(request_t& m)
: message_{m}
{}
std::pair<char const*, bool>
match_request_t::
operator()(char const* begin, char const* end, std::error_code& ec)
{
if (begin == end)
return std::make_pair(begin, false);
parser_type p;
auto const last = p.parse(begin, end, ec);
if (ec == parse_error_t::incomplete_message) {
// reset ec, so that the match will be attempted again.
//
ec = std::error_code{};
return std::make_pair(begin, false);
}
if (ec)
return std::make_pair(begin, true);
extract_message(p, message_);
return std::make_pair(last, true);
}
match_response_t::
match_response_t(response_t& m)
: message_{m}
{}
std::pair<char const*, bool>
match_response_t::
operator()(char const* begin, char const* end, std::error_code& ec)
{
if (begin == end)
return std::make_pair(begin, false);
parser_type p;
auto const last = p.parse(begin, end, ec);
if (ec == parse_error_t::incomplete_message) {
// reset ec, so that the match will be attempted again.
//
ec = std::error_code{};
return std::make_pair(begin, false);
}
if (ec)
return std::make_pair(begin, true);
extract_message(p, message_);
return std::make_pair(last, true);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,53 @@
#ifndef seafire__protocol__match_hxx_
#define seafire__protocol__match_hxx_
#include <seafire/protocol/basic-parser.hxx>
#include <seafire/protocol/error.hxx>
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <system_error>
#include <utility>
namespace seafire::protocol
{
/// Implements an input match condition for matching a request.
///
class match_request_t
{
public:
using parser_type = basic_request_parser_t<char const*>;
explicit
match_request_t(request_t& m);
std::pair<char const*, bool>
operator()(char const*, char const*, std::error_code&);
private:
request_t& message_;
};
/// Implements an input match condition for matching a response.
///
class match_response_t
{
public:
using parser_type = basic_response_parser_t<char const*>;
explicit
match_response_t(response_t& m);
std::pair<char const*, bool>
operator()(char const*, char const*, std::error_code&);
private:
response_t& message_;
};
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,140 @@
#include <seafire/protocol/media-type.hxx>
#include <sstream>
namespace seafire::protocol
{
/// Construct a new empty media type.
///
media_type_t::
media_type_t() = default;
/// Construct a new media type.
///
media_type_t::
media_type_t(std::string type, std::string subtype)
: type_{std::move(type)}, subtype_{std::move(subtype)}
{}
/// Construct a new media type.
///
media_type_t::
media_type_t(std::string type,
std::string subtype,
params_t params)
: type_{std::move(type) },
subtype_{std::move(subtype)},
params_{std::move(params)}
{}
/// Get the type of this media type.
///
std::string const&
media_type_t::
type() const
{
return type_;
}
/// Get the subtype of this media type.
///
std::string const&
media_type_t::
subtype() const
{
return subtype_;
}
/// Get the parameters of this media type.
///
media_type_t::params_t const&
media_type_t::
params() const
{
return params_;
}
/// Attempt to parse a media type.
///
std::optional< media_type_t>
media_type_t::
try_parse(std::string const& str, std::error_code& ec)
{
auto begin = str.begin();
return try_parse(begin, str.end(), ec);
}
/// Compare two media types for equality.
///
/// Parameters are ignored.
///
/// \relatesalso media_type_t
///
bool
operator==(media_type_t const& lhs, media_type_t const& rhs)
{
if (lhs.type() != rhs.type()) {
if ("*" != lhs.type() && "*" != rhs.type()) {
return false;
}
}
if (lhs.subtype() != rhs.subtype()) {
if ("*" != lhs.subtype() && "*" != rhs.subtype()) {
return false;
}
}
return true;
}
/// Compare two media types for inequality.
///
/// Parameters are ignored.
///
/// \relatesalso media_type_t
///
bool
operator!=(media_type_t const& lhs, media_type_t const& rhs)
{
return !(lhs == rhs);
}
/// Write media type to an output stream.
///
/// \relatesalso media_type_t
///
std::ostream&
to_stream(std::ostream& o, media_type_t const& m)
{
o << m.type() << '/' << m.subtype();
for (auto const& j : m.params()) {
o << "; " << j.first << '=' << j.second;
}
return o;
}
/// Convert a media type to a string.
///
/// \relatesalso media_type_t
///
std::string
to_string(media_type_t const& m)
{
std::stringstream str;
to_stream(str, m);
return str.str();
}
/// Write a media type to an output stream.
///
/// \relatesalso media_type_t
///
std::ostream&
operator<<(std::ostream& o, media_type_t const& m)
{
return to_stream(o, m);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,77 @@
#ifndef seafire__protocol__media_type_hxx_
#define seafire__protocol__media_type_hxx_
#include <seafire/protocol/grammar.hxx>
#include <map>
#include <optional>
#include <string>
#include <system_error>
namespace seafire::protocol
{
/// Implements a media type according to RFC 2046.
///
class media_type_t
{
public:
/// Media type parameters type.
///
using params_t = std::map<std::string, std::string>;
media_type_t();
media_type_t(std::string, std::string);
media_type_t(std::string,
std::string,
params_t);
std::string const&
type() const;
std::string const&
subtype() const;
params_t const&
params() const;
template<typename InputIterator>
static
std::optional<media_type_t>
try_parse(InputIterator&,
InputIterator const&,
std::error_code&);
static
std::optional<media_type_t>
try_parse(std::string const&, std::error_code&);
private:
std::string type_;
std::string subtype_;
params_t params_;
};
bool
operator==(media_type_t const&, media_type_t const&);
bool
operator!=(media_type_t const&, media_type_t const&);
std::ostream&
to_stream(std::ostream&, media_type_t const&);
std::string
to_string(media_type_t const&);
std::ostream&
operator<<(std::ostream&, media_type_t const&);
} // namespace seafire::protocol
#include <seafire/protocol/media-type.txx>
#endif

View File

@ -0,0 +1,125 @@
namespace seafire::protocol
{
template<typename InputIterator>
std::optional<media_type_t>
media_type_t::
try_parse(InputIterator& begin,
InputIterator const& end,
std::error_code& ec)
{
std::optional<media_type_t> const failure;
auto skip_whitespace = [&]
{
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
};
auto try_parse_token = [&]() -> std::optional<std::string>
{
if (begin == end)
return std::nullopt;
if (!grammar::is_tchar(*begin))
return std::nullopt;
std::string token;
token += *begin;
++begin;
while (begin != end) {
if (!grammar::is_tchar(*begin))
break;
token += *begin;
++begin;
}
return std::move(token);
};
auto try_parse_parameter = [&]() -> std::optional<std::pair<std::string, std::string>>
{
if (begin == end || *begin != ';')
return std::nullopt;
++begin; // skips ';'
skip_whitespace();
auto name = try_parse_token();
if (!name || begin == end || *begin != '=')
return std::nullopt;
++begin; // skips '='
// Quoted-string value?
if (begin != end && *begin == '"') {
// fixme: support quoted string values
//
return std::nullopt;
}
auto value = try_parse_token();
if (!value)
return std::nullopt;
// fixme: normalize parameter name.
//
return std::pair<std::string, std::string >{*name, *value};
};
auto try_parse_parameters = [&]() -> std::map<std::string, std::string>
{
std::map< std::string, std::string > params;
while (begin != end) {
auto param = try_parse_parameter();
if (!param) {
return params;
}
params.insert(*param);
skip_whitespace();
}
return params;
};
skip_whitespace();
auto type = try_parse_token();
if (!type) {
return std::nullopt;
}
if (begin == end || *begin != '/') {
return std::nullopt;
}
++begin; // skips '/'
auto subtype = try_parse_token();
if (!subtype) {
return std::nullopt;
}
skip_whitespace();
return {{
std::move(*type),
std::move(*subtype),
try_parse_parameters()
}};
}
} // namespace seafire::protocol

View File

@ -0,0 +1,89 @@
#include <seafire/protocol/message.hxx>
namespace seafire::protocol
{
/// Construct a new message.
///
message_t::
message_t() = default;
/// Construct a new message.
///
message_t::
message_t(version_t version)
: version_{version}
{}
/// Get the message version.
///
version_t const&
message_t::
version() const
{
return version_;
}
/// Set the message version.
///
void
message_t::
set_version(version_t v)
{
version_ = std::move(v);
}
/// Access the message headers.
///
header_collection_t const&
message_t::
headers() const
{
return headers_;
}
/// Set/replace message header.
///
void
message_t::
set_header(std::string name, std::string value)
{
headers_.set(std::move(name), std::move(value));
}
/// Append message header.
///
void
message_t::
append_header(std::string name, std::string value)
{
headers_.append(std::move(name), std::move(value));
}
/// Erase message header.
///
void
message_t::
erase_header(std::string name)
{
headers_.erase(name);
}
/// Set/replace message headers.
///
void
message_t::
set_headers(header_collection_t headers)
{
headers_ = std::move(headers);
}
/// Write headers to output stream.
///
std::ostream&
operator<<(std::ostream& o, message_t const& m)
{
return o << m.headers();
}
} // namespace seafire::protocol

View File

@ -0,0 +1,86 @@
#ifndef seafire__protocol__message_hxx_
#define seafire__protocol__message_hxx_
#include <seafire/protocol/header-collection.hxx>
#include <seafire/protocol/protocol-version.hxx>
#include <seafire/protocol/traits.hxx>
#include <optional>
#include <ostream>
namespace seafire::protocol
{
/// Common base class for HTTP messages.
///
class message_t
{
public:
message_t();
explicit
message_t(version_t);
version_t const&
version() const;
void
set_version(version_t);
header_collection_t const&
headers() const;
void
set_headers(header_collection_t);
void
set_header(std::string, std::string);
void
append_header(std::string, std::string);
void
erase_header(std::string);
private:
version_t version_;
header_collection_t headers_;
};
template<typename Header>
bool
has(message_t const& m);
template<typename Header>
bool
has_quick(message_t const& m);
template<typename Header>
std::optional<traits::alias_type_t<Header>>
get(message_t const& m);
template<typename Header>
std::optional<traits::alias_type_t<Header>>
get(message_t const& m, std::error_code& ec);
template<typename Header, typename... Args>
void
set(message_t& m, Args&&... args);
template<typename Header, typename... Args>
void
set_if_not_set(message_t& m, Args&&... args);
template<typename Header>
void
erase(message_t& m);
std::ostream&
operator<<(std::ostream& o, message_t const& m);
} // namespace seafire::protocol
#include <seafire/protocol/message.txx>
#endif

View File

@ -0,0 +1,66 @@
namespace seafire::protocol
{
template<typename H>
bool
has(message_t const& m)
{
// FIXME: Parse header as well, to make sure it is valid.
return m.headers().contains(H::name);
}
template<typename H>
bool
has_quick(message_t const& m)
{
return m.headers().contains(H::name);
}
template<typename H>
std::optional<traits::alias_type_t<H>>
get(message_t const& m)
{
std::error_code ignored_ec;
return get<H>(m, ignored_ec);
}
template<typename H>
std::optional<traits::alias_type_t<H>>
get(message_t const& m, std::error_code& ec)
{
return H::try_parse(m.headers().get(H::name), ec);
}
template<typename H, typename... Args>
void
set(message_t& m, Args&&... args)
{
using type = traits::alias_type_t<H>;
if constexpr (traits::has_overridden_to_string_v<H>) {
m.set_header(H::name, H::to_string(type{std::forward<Args>(args)...}));
}
else {
using std::to_string;
m.set_header(H::name, to_string(type{std::forward<Args>(args)...}));
}
}
template<typename H, typename... Args>
void
set_if_not_set(message_t& m, Args&&... args)
{
if (has_quick<H>())
return;
set<H>(m, std::forward<Args>(args)...);
}
template<typename H>
void
erase(message_t& m)
{
m.erase_header(H::name);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,71 @@
#include <seafire/protocol/protocol-version.hxx>
#include <sstream>
namespace seafire::protocol
{
bool
version_t::
operator==(version_t const& other) const noexcept
{
return major == other.major && minor == other.minor;
}
bool
version_t::
operator!=(version_t const& other) const noexcept
{
return !(*this == other);
}
std::ostream&
to_stream(std::ostream& o, version_t const& v)
{
o << "HTTP/" << v.major << '.' << v.minor;
return o;
}
std::string
to_string(version_t const& v)
{
std::stringstream str;
to_stream(str, v);
return str.str();
}
std::ostream&
operator<<(std::ostream& o, version_t const& v)
{
return to_stream(o, v);
}
void
to_buffers(common::io::const_buffers_t& buffers, version_t const& v)
{
static char constexpr http10[]{'H', 'T', 'T', 'P', '/', '1', '.', '0'};
static char constexpr http11[]{'H', 'T', 'T', 'P', '/', '1', '.', '1'};
static char constexpr http[]{'H', 'T', 'T', 'P', '/'};
static char constexpr digits[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
static char constexpr dot[]{'.'};
using common::io::buffer;
if (v == http_1_0) {
buffers.emplace_back(buffer(http10));
return;
}
if (v == http_1_1) {
buffers.emplace_back(buffer(http11));
return;
}
buffers.emplace_back(buffer(http));
buffers.emplace_back(buffer(&digits[v.major % 10], 1));
buffers.emplace_back(buffer(dot));
buffers.emplace_back(buffer(&digits[v.minor % 10], 1));
}
} // namespace seafire::protocol

View File

@ -0,0 +1,66 @@
#ifndef seafire__protocol__protocol_version_hxx_
#define seafire__protocol__protocol_version_hxx_
#include <seafire/common/io/buffer.hxx>
#include <asio.hpp>
#include <cstdint>
#include <ostream>
#include <string>
#include <vector>
namespace seafire::protocol
{
/// Represents an HTTP version.
///
struct version_t
{
/// Construct a new protocol version.
///
constexpr
version_t() noexcept
: major{0}, minor{0}
{}
/// Construct a new protocol version.
///
/// \param major The major version number.
/// \param minor The minor version number.
///
constexpr
version_t(std::uint16_t major, std::uint16_t minor) noexcept
: major{major}, minor{minor}
{}
bool
operator==(version_t const&) const noexcept;
bool
operator!=(version_t const&) const noexcept;
std::uint16_t major;
std::uint16_t minor;
};
inline constexpr version_t const http_1_0{1, 0};
inline constexpr version_t const http_1_1{1, 1};
std::ostream&
to_stream(std::ostream&, version_t const&);
std::string
to_string(version_t const&);
std::ostream&
operator<<(std::ostream&, version_t const&);
void
to_buffers(common::io::const_buffers_t&, version_t const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,225 @@
#include <seafire/protocol/read-content.hxx>
#include <seafire/protocol/rfc7230/content-length.hxx>
namespace seafire::protocol
{
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
std::size_t content_length)
{
std::error_code ec;
read_content(s, i, c, content_length, ec);
if (ec) {
throw std::system_error{ec};
}
}
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
std::size_t content_length,
std::error_code& ec)
{
if (auto pending = i.size(); pending > 0) {
auto copied = buffer_copy(
c.prepare(pending),
i.data(),
std::min<std::size_t>(pending, content_length)
);
i.consume(copied);
c.commit(copied);
content_length -= copied;
}
auto n = s.read(c.prepare(content_length), ec);
c.commit(n);
}
void
async_read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
std::size_t content_length,
std::function<void(std::error_code)> h)
{
if (auto pending = i.size(); pending > 0) {
auto copied = buffer_copy(
c.prepare(pending),
i.data(),
std::min<std::size_t>(pending, content_length)
);
i.consume(copied);
c.commit(copied);
content_length -= copied;
}
auto bound = [&c, h](std::error_code const& ec, std::size_t n)
{
c.commit(n);
h(ec);
};
s.async_read(c.prepare(content_length), bound);
}
/// Read content from stream.
///
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
request_t const& r)
{
std::error_code ec;
read_content(s, i, c, r, ec);
if (ec) {
throw std::system_error{ec};
}
}
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
request_t const& r,
std::error_code& ec)
{
// fixme: support transfer encoding.
//
auto content_length = get<rfc7230::content_length_t>(r, ec);
if (ec) {
return;
}
if (content_length) {
read_content(s, i, c, *content_length, ec);
return;
}
// we just assume the message does not contain a payload.
//
}
void
async_read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
request_t const& r,
std::function<void(std::error_code)> handler)
{
std::error_code ec;
auto content_length = get<rfc7230::content_length_t>(r, ec);
if (ec) {
asio::post(
s.get_executor(),
[handler, ec]()
{
handler(ec);
}
);
return;
}
if (content_length) {
async_read_content(s, i, c, *content_length, handler);
return;
}
// we assume the request does not include a payload.
//
asio::post(
s.get_executor(),
[handler]
{
handler({});
}
);
}
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
response_t const& r)
{
std::error_code ec;
read_content(s, i, c, r, ec);
if (ec) {
throw std::system_error{ec};
}
}
void
read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
response_t const& r,
std::error_code& ec)
{
// fixme: support transfer encoding.
//
auto content_length = get<rfc7230::content_length_t>(r, ec);
if (ec) {
return;
}
if (content_length) {
read_content(s, i, c, *content_length, ec);
return;
}
// fixme: read until eof.
//
// fixme: return error until eof is implemented.
//
}
void
async_read_content(common::io::stream_t& s,
asio::streambuf& i,
asio::streambuf& c,
response_t const& r,
std::function<void(std::error_code)> handler)
{
// fixme: support transfer encoding.
//
std::error_code ec;
auto content_length = get<rfc7230::content_length_t>(r, ec);
if (ec) {
asio::post(
s.get_executor(),
[handler, ec]
{
handler(ec);
}
);
}
if (content_length) {
async_read_content(s, i, c, *content_length, handler);
return;
}
// fixme: read until eof.
//
// fixme: return error until eof is implemented.
//
}
} // namespace seafire::protocol

View File

@ -0,0 +1,74 @@
#ifndef seafire__protocol__read_content_hxx_
#define seafire__protocol__read_content_hxx_
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <seafire/common/io/stream.hxx>
namespace seafire::protocol
{
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
std::size_t);
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
std::size_t,
std::error_code&);
void
async_read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
std::size_t,
std::function<void(std::error_code)>);
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
request_t const&);
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
request_t const&,
std::error_code&);
void
async_read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
request_t const&,
std::function<void(std::error_code)>);
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
response_t const&);
void
read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
response_t const&,
std::error_code&);
void
async_read_content(common::io::stream_t&,
asio::streambuf&,
asio::streambuf&,
response_t const&,
std::function<void(std::error_code)>);
} // namespace seafire::protocol
#endif

171
seafire/protocol/read.cxx Normal file
View File

@ -0,0 +1,171 @@
#include <seafire/protocol/read.hxx>
#include <seafire/protocol/match.hxx>
#include <seafire/protocol/rfc7230/content-length.hxx>
#include <seafire/common/io/read-until.hxx>
#include <asio.hpp>
namespace seafire::protocol
{
/// Read a request message from buffer \a b, reading more content from
/// stream \a s as required.
///
/// The read request message is placed in \a r.
///
/// If the buffer \a b contains a full request message, no I/O operations
/// will be performed.
///
/// \throws std::system_error Thrown on error.
/// \relatesalso request_t
///
void
read(common::io::stream_t& s,
asio::streambuf& b,
request_t& r)
{
std::error_code ec;
read(s, b, r, ec);
if (ec) {
throw std::system_error{ec};
}
}
/// Read a request message from buffer \a b, reading more content from
/// stream \a s as required.
///
/// The read request message is placed in \a r.
///
/// If the buffer \a b contains a full request message, no I/O operations
/// will be performed.
///
/// Errors are reported through \a ec.
///
/// \relatesalso request_t
///
void
read(common::io::stream_t& s,
asio::streambuf& b,
request_t& r,
std::error_code& ec)
{
auto bytes_consumed = common::io::read_until(s, b, match_request_t{r}, ec);
if (ec) {
return;
}
b.consume(bytes_consumed);
}
/// Initialize an asynchronous read of a request message from buffer \a b,
/// reading more content from \a s as required.
///
/// The read request message is placed in \a r.
///
/// If the buffer \a b contains a full request message, no I/O operations
/// will be performed.
///
/// \a r must be valid until the completion-handler \a h is called.
///
void
async_read(common::io::stream_t& s,
asio::streambuf& b,
request_t& r,
std::function<void(std::error_code)> h)
{
auto bound = [&b, h](std::error_code const& ec, std::size_t n)
{
if (!ec) {
b.consume(n);
}
h(ec);
};
common::io::async_read_until(s, b, match_request_t{r}, bound);
}
/// Read a response message from buffer \a b, reading more content from
/// stream \a s as required.
///
/// The read response message is placed in \a r.
///
/// If the buffer \a b contains a full response message, no I/O operations
/// will be performed.
///
/// \throws std::system_error Thrown on error.
/// \relatesalso response_t
///
void
read(common::io::stream_t& s,
asio::streambuf& b,
response_t& r)
{
std::error_code ec;
read(s, b, r, ec);
if (ec) {
throw std::system_error{ec};
}
}
/// Read a response message from buffer \a b, reading more content from
/// stream \a s as required.
///
/// The read response message is placed in \a r.
///
/// If the buffer \a b contains a full response message, no I/O operations
/// will be performed.
///
/// Errors are reported through \a ec.
///
/// \relatesalso response_t
///
void
read(common::io::stream_t& s,
asio::streambuf& b,
response_t& r,
std::error_code& ec)
{
auto bytes_consumed = common::io::read_until(s, b, match_response_t{r}, ec);
if (ec) {
return;
}
b.consume(bytes_consumed);
}
/// Initialize an asynchronous read of a response message from buffer \a b,
/// reading more content from \a s as required.
///
/// The read response message is placed in \a r.
///
/// If the buffer \a b contains a full response message, no I/O operations
/// will be performed.
///
/// \a r must be valid until the completion-handler \a h is called.
///
void
async_read(common::io::stream_t& s,
asio::streambuf& b,
response_t& r,
std::function<void(std::error_code)> h)
{
auto bound = [&b, h](std::error_code const& ec, std::size_t n)
{
if (!ec) {
b.consume(n);
}
h(ec);
};
common::io::async_read_until(s, b, match_response_t{r}, bound);
}
} // namespace seafire::protocol

54
seafire/protocol/read.hxx Normal file
View File

@ -0,0 +1,54 @@
#ifndef seafire__protocol__read_hxx_
#define seafire__protocol__read_hxx_
#include <seafire/protocol/message.hxx>
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <cstddef>
#include <system_error>
namespace seafire::protocol
{
void
read(common::io::stream_t&,
asio::streambuf&,
request_t&);
void
read(common::io::stream_t&,
asio::streambuf&,
request_t&,
std::error_code&);
void
async_read(common::io::stream_t&,
asio::streambuf&,
request_t&,
std::function<void(std::error_code)>);
void
read(common::io::stream_t&,
asio::streambuf&,
response_t&);
void
read(common::io::stream_t&,
asio::streambuf&,
response_t&,
std::error_code&);
void
async_read(common::io::stream_t&,
asio::streambuf&,
response_t&,
std::function<void(std::error_code)>);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,56 @@
#include <seafire/protocol/reason.hxx>
namespace seafire::protocol
{
char const*
get_reason(unsigned short code)
{
switch (code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified"; // rfc7232
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized"; // rc7235
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required"; // rc7235
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed"; // rfc7232
case 413: return "Payload Too Large";
case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 417: return "Expectation Failed";
case 426: return "Upgrade Required";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
// Unofficial.
case 420: return "Enhance Your Calm";
}
return "Unknown";
}
} // namespace seafire::protocol

View File

@ -0,0 +1,12 @@
#ifndef seafire__protocol__reason_hxx_
#define seafire__protocol__reason_hxx_
namespace seafire::protocol
{
char const*
get_reason(unsigned short);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,149 @@
#include <seafire/protocol/request.hxx>
#include <asio.hpp>
namespace seafire::protocol
{
/// Construct a new request message.
///
request_t::
request_t()
{}
/// Construct a new request message.
///
/// \param method The method of the request message.
/// \param target The target of the request message.
/// \param version The version of the request message.
///
request_t::
request_t(std::string method,
std::string target,
version_t version)
{
set_method(std::move(method));
set_target(std::move(target));
set_version(version);
}
/// Access the method of the request message.
///
std::string const&
request_t::
method() const
{
return method_;
}
/// Set the method of the request message.
///
void
request_t::
set_method(std::string method)
{
method_ = std::move(method);
}
/// Access the target of the request message.
///
std::string const&
request_t::
target() const
{
return target_;
}
/// Set the target of the request message.
///
void
request_t::
set_target(std::string target)
{
target_ = std::move(target);
}
/// Access the target of the request as a URI.
///
code::uri::uri_t const&
request_t::
target_uri() const
{
return target_uri_;
}
/// Set the target URI of the request message.
///
void
request_t::
set_target_uri(code::uri::uri_t target_uri)
{
target_uri_ = std::move(target_uri);
}
/// Convert an HTTP request message to byte buffers.
///
/// The buffers are invalidated when the request message is destroyed
/// or any of its properties are modified.
///
/// The buffers are added to the \a buffers vector.
///
/// \relatesalso request_t
///
void
to_buffers(common::io::const_buffers_t& buffers, request_t const& r)
{
static char const colon_space[]{ ':', ' ' };
static char const crlf[]{ '\r', '\n' };
static char const space[]{ ' ' };
using common::io::buffer;
buffers.emplace_back(buffer(r.method()));
buffers.emplace_back(buffer(space));
buffers.emplace_back(buffer(r.target()));
buffers.emplace_back(buffer(space));
to_buffers(buffers, r.version());
buffers.emplace_back(buffer(crlf));
for (auto const& header : r.headers()) {
buffers.emplace_back(buffer(header.first));
buffers.emplace_back(buffer(colon_space));
buffers.emplace_back(buffer(header.second));
buffers.emplace_back(buffer(crlf));
}
buffers.emplace_back(buffer(crlf));
}
/// Convert an HTTP request message to byte buffers.
///
/// The buffers are invalidated when the request message is destroyed
/// or any of its properties are modified.
///
/// The buffers are returned as an array of buffers.
///
/// \relatesalso request_t
///
common::io::const_buffers_t
to_buffers(request_t const& r)
{
common::io::const_buffers_t buffers;
to_buffers(buffers, r);
return buffers;
}
/// Write a request message to an output stream.
///
/// \relatesalso request_t
///
std::ostream&
operator<<(std::ostream& o, request_t const& r)
{
o << r.method() << ' ' << r.target() << ' ' << r.version() << '\n';
o << static_cast<message_t const&>(r);
return o;
}
} // namespace seafire::protocol

View File

@ -0,0 +1,62 @@
#ifndef seafire__protocol__request_hxx_
#define seafire__protocol__request_hxx_
#include <seafire/protocol/message.hxx>
#include <seafire/common/io/buffer.hxx>
#include <code/uri/uri.hxx>
#include <ostream>
#include <string>
#include <vector>
namespace seafire::protocol
{
class request_t
: public message_t
{
public:
request_t();
request_t(std::string, std::string, version_t);
std::string const&
method() const;
void
set_method(std::string);
std::string const&
target() const;
void
set_target(std::string);
code::uri::uri_t const&
target_uri() const;
void
set_target_uri(code::uri::uri_t);
private:
std::string method_;
std::string target_;
code::uri::uri_t target_uri_;
std::string query_;
};
void
to_buffers(common::io::const_buffers_t&, request_t const&);
common::io::const_buffers_t
to_buffers(request_t const&);
std::ostream&
operator<<(std::ostream&, request_t const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,101 @@
#include <seafire/protocol/response.hxx>
#include <asio.hpp>
namespace seafire::protocol
{
/// Construct a new response message.
///
response_t::
response_t() = default;
/// Construct a new response message.
///
/// \param version The HTTP version of this response message.
/// \param status The HTTP status of this response message.
///
response_t::
response_t(version_t version, status_code_t status)
: message_t{version},
status_{status}
{}
/// Access the message status.
///
status_code_t const&
response_t::
status() const
{
return status_;
}
/// Set the message status.
///
void
response_t::
set_status(status_code_t status)
{
status_ = std::move(status);
}
/// Convert an HTTP response message to byte buffers.
///
/// The buffers are invalidated when the response message is destroyed
/// or any of its properties are modified.
///
/// The buffers are added to the \a buffers vector.
///
/// \relatesalso response_t
///
void
to_buffers(common::io::const_buffers_t& buffers, response_t const& r)
{
static char const colon_space[]{':', ' '};
static char const crlf[]{'\r', '\n'};
static char const digits[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
static char const space[]{' '};
using common::io::buffer;
to_buffers(buffers, r.version());
buffers.emplace_back(buffer(space));
auto code = r.status();
buffers.emplace_back(buffer(&digits[code / 100], 1));
buffers.emplace_back(buffer(&digits[code % 100 / 10], 1));
buffers.emplace_back(buffer(&digits[code % 10], 1));
buffers.emplace_back(buffer(" ", 1));
buffers.emplace_back(buffer(r.status().reason()));
buffers.emplace_back(buffer(crlf));
for (auto const& header : r.headers()) {
buffers.emplace_back(buffer(header.first));
buffers.emplace_back(buffer(colon_space));
buffers.emplace_back(buffer(header.second));
buffers.emplace_back(buffer(crlf));
}
buffers.emplace_back(buffer(crlf));
}
/// Convert an HTTP response message to byte buffers.
///
/// The buffers are invalidated when the response message is destroyed
/// or any of its properties are modified.
///
/// The buffers are returned as an array of buffers.
///
/// \relatesalso response_t
///
common::io::const_buffers_t
to_buffers(response_t const& r)
{
common::io::const_buffers_t buffers;
to_buffers(buffers, r);
return buffers;
}
} // namespace seafire::protocol

View File

@ -0,0 +1,44 @@
#ifndef seafire__protocol__response_hxx_
#define seafire__protocol__response_hxx_
#include <seafire/protocol/message.hxx>
#include <seafire/protocol/status-code.hxx>
#include <seafire/common/io/buffer.hxx>
#include <string>
#include <vector>
namespace seafire::protocol
{
/// Represents an HTTP/1.1 response message.
///
class response_t
: public message_t
{
public:
response_t();
response_t(version_t, status_code_t);
status_code_t const&
status() const;
void
set_status(status_code_t status);
private:
status_code_t status_;
};
void
to_buffers(common::io::const_buffers_t&, response_t const&);
common::io::const_buffers_t
to_buffers(response_t const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,88 @@
#include <seafire/protocol/rfc7230/connection.hxx>
namespace seafire::protocol::rfc7230
{
connection_t::
connection_t(std::set<token_t> tokens)
: tokens_{std::move(tokens)}
{}
connection_t::
connection_t(std::initializer_list<token_t> tokens)
: tokens_{tokens.begin(), tokens.end()}
{}
std::set<token_t> const&
connection_t::
tokens() const
{
return tokens_;
}
bool
connection_t::
close() const
{
return tokens().find("close") != tokens().end();
}
bool
connection_t::
keep_alive() const
{
return tokens().find("keep-alive") != tokens().end();
}
bool
connection_t::
upgrade() const
{
return tokens().find("upgrade") != tokens().end();
}
std::optional<connection_t>
connection_t::
try_parse(std::vector<std::string> const& strings, std::error_code&)
{
std::set<token_t> tokens;
for (auto const& j : strings) {
auto begin = j.begin();
auto end = j.end();
auto t = try_parse_tokens(begin, end);
if (!t) {
return std::nullopt;
}
if (begin != end) {
return std::nullopt;
}
for (auto const& k : *t) {
tokens.emplace(k);
}
}
return std::set<token_t>{tokens.begin(), tokens.end()};
}
std::string
to_string(connection_t const& c)
{
std::string joined;
for (auto const& j : c.tokens()) {
if (!joined.empty()) {
joined.append(", ");
}
joined.append(j.str());
}
return joined;
}
} // namespace seafire::protocol::rfc7230

View File

@ -0,0 +1,51 @@
#ifndef seafire__protocol__rfc7230__connection_hxx_
#define seafire__protocol__rfc7230__connection_hxx_
#include <seafire/protocol/token.hxx>
#include <initializer_list>
#include <set>
#include <string>
#include <system_error>
namespace seafire::protocol::rfc7230
{
/// Represents the HTTP `connection` header.
///
class connection_t
{
public:
static constexpr char const name[] = "connection";
connection_t(std::set<token_t>);
connection_t(std::initializer_list<token_t>);
std::set<token_t> const&
tokens() const;
bool
close() const;
bool
keep_alive() const;
bool
upgrade() const;
static
std::optional<connection_t>
try_parse(std::vector<std::string> const&, std::error_code&);
private:
std::set<token_t> tokens_;
};
std::string
to_string(connection_t const&);
} // namespace seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,28 @@
#include <seafire/protocol/rfc7230/content-length.hxx>
#include <seafire/protocol/error.hxx>
namespace seafire::protocol::rfc7230
{
std::optional<std::size_t>
content_length_t::
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (strings.size() == 1) {
try {
return std::stoull(strings[0]);
}
catch (...) {
ec = protocol_error_t::invalid_content_length;
return std::nullopt;
}
}
else if (strings.size() > 1) {
ec = protocol_error_t::invalid_content_length;
}
return std::nullopt;
}
} // namespace seafire::protocol::rfc7230

View File

@ -0,0 +1,29 @@
#ifndef seafire__protocol__rfc7230__content_length_hxx_
#define seafire__protocol__rfc7230__content_length_hxx_
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace seafire::protocol::rfc7230
{
// fixme: make class
struct content_length_t
{
using alias_type = std::uint64_t;
static constexpr char const name[] = "content-length";
static
std::optional<std::size_t>
try_parse(std::vector<std::string> const&, std::error_code&);
};
} // namespace seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,95 @@
#include <seafire/protocol/rfc7230/host.hxx>
#include <code/uri/grammar.hxx>
namespace seafire::protocol::rfc7230
{
host_t::
host_t(std::string hostname)
: hostname_{std::move(hostname)}
{}
host_t::
host_t(std::string hostname, std::optional<std::string> port)
: hostname_{std::move(hostname)}, port_{std::move(port)}
{}
std::string const&
host_t::
hostname() const
{
return hostname_;
}
std::optional<std::string> const&
host_t::
port() const
{
return port_;
}
std::optional<host_t>
host_t::
try_parse(std::vector<std::string> const& strings, std::error_code&)
{
std::string host_part;
std::optional<std::string> opt_port_part;
if (auto it = strings.rbegin(); it != strings.rend()) {
auto first = it->begin();
auto last = it->end();
auto try_parse_host = [&](auto init)
{
auto c = init;
while (c != last && code::uri::grammar::is_host(*c)) {
host_part += *c++;
}
return c;
};
auto try_parse_port = [&](auto init)
{
auto c = init;
if (c != last && *c == ':') {
++c; // skips ':'
opt_port_part = std::string{};
while (c != last && code::uri::grammar::is_digit(*c)) {
*opt_port_part += *c++;
}
return c;
}
return init;
};
first = try_parse_host(first);
first = try_parse_port(first);
if (first != last) {
return std::nullopt;
}
}
return {{host_part, opt_port_part}};
}
std::string
to_string(host_t const& host)
{
std::string str{host.hostname()};
if (host.port())
str += ':' + *host.port();
return str;
}
} // namespace seafire::protocol::rfc7230

View File

@ -0,0 +1,43 @@
#ifndef seafire__protocol__rfc7230__host_hxx_
#define seafire__protocol__rfc7230__host_hxx_
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace seafire::protocol::rfc7230
{
class host_t
{
public:
static constexpr char const name[] = "host";
explicit
host_t(std::string);
host_t(std::string, std::optional<std::string>);
std::string const&
hostname() const;
std::optional<std::string> const&
port() const;
static
std::optional<host_t>
try_parse(std::vector<std::string> const&, std::error_code&);
private:
std::string hostname_;
std::optional<std::string> port_;
};
std::string
to_string(host_t const&);
} // namespace seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,16 @@
#include <seafire/protocol/rfc7231/accept.hxx>
namespace seafire::protocol::rfc7231
{
std::optional<media_range_t>
accept_t::
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (strings.empty())
return std::nullopt;
return media_range_t::try_parse(strings, ec);
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,27 @@
#ifndef seafire__protocol__rc7231__accept_hxx_
#define seafire__protocol__rc7231__accept_hxx_
#include <seafire/protocol/rfc7231/media-range.hxx>
#include <optional>
#include <string>
#include <system_error>
namespace seafire::protocol::rfc7231
{
struct accept_t
{
using alias_type = media_range_t;
static constexpr char const name[] = "accept";
static
std::optional<media_range_t>
try_parse(std::vector< std::string > const& strings, std::error_code& ec);
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,36 @@
#ifndef seafire__protocol__rc7231__allow_hxx_
#define seafire__protocol__rc7231__allow_hxx_
#include <seafire/protocol/token.hxx>
#include <sstream>
#include <string>
namespace seafire::protocol::rfc7231
{
struct allow_t {
using alias_type = tokens_t;
static constexpr char const name[] = "allow";
static
std::string
to_string(tokens_t const& tokens)
{
std::ostringstream str;
if (auto it = tokens.begin(); it != tokens.end()) {
str << *it;
while (++it != tokens.end())
str << ", " << *it;
}
return str.str();
}
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,17 @@
#include <seafire/protocol/rfc7231/content-type.hxx>
namespace seafire::protocol::rfc7231
{
std::optional<media_type_t>
content_type_t::
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (auto it = strings.rbegin(); it != strings.rend()) {
return media_type_t::try_parse(*it, ec);
}
return {};
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,28 @@
#ifndef seafire__protocol__rfc7231__content_type_hxx_
#define seafire__protocol__rfc7231__content_type_hxx_
#include <seafire/protocol/media-type.hxx>
#include <optional>
#include <string>
#include <vector>
namespace seafire::protocol::rfc7231
{
// fixme: make class
struct content_type_t
{
using alias_type = media_type_t;
static constexpr char const name[] = "content-type";
static
std::optional<media_type_t>
try_parse(std::vector<std::string> const&, std::error_code&);
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,43 @@
#ifndef seafire__protocol__rfc7231__date_hxx_
#define seafire__protocol__rfc7231__date_hxx_
#include <seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <vector>
namespace seafire::protocol::rfc7231
{
/// fixme: make class
struct date_t
{
using alias_type = std::chrono::system_clock::time_point;
static constexpr char const name[] = "date";
static
std::optional<std::chrono::system_clock::time_point>
try_parse(std::vector<std::string> const& strings, std::error_code&)
{
if (strings.empty()) {
return std::nullopt;
}
return try_parse_http_date(strings.front());
}
static
std::string
to_string(std::chrono::system_clock::time_point const& time)
{
return format_http_date(time);
}
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,28 @@
#ifndef seafire__protocol__rc7231__location_hxx_
#define seafire__protocol__rc7231__location_hxx_
#include <code/uri/uri.hxx>
#include <string>
#include <system_error>
namespace seafire::protocol::rfc7231
{
struct location_t
{
using alias_type = code::uri::uri_t;
static constexpr const char name[] = "location";
static
std::string
to_string(code::uri::uri_t const& location)
{
return code::uri::to_string(location);
}
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,112 @@
#include <seafire/protocol/rfc7231/media-range.hxx>
namespace seafire::protocol::rfc7231
{
media_range_t::
media_range_t()
{}
media_range_t::
media_range_t(media_type_t type)
: types_{std::move(type)}
{}
media_range_t::
media_range_t(std::vector<media_type_t> types)
: types_{std::move(types)}
{
sort_internals();
}
media_range_t::const_iterator
media_range_t::
begin() const
{
return get().begin();
}
media_range_t::const_iterator
media_range_t::
cbegin() const
{
return get().cbegin();
}
media_range_t::const_iterator
media_range_t::
end() const
{
return get().end();
}
media_range_t::const_iterator
media_range_t::
cend() const
{
return get().cend();
}
std::vector<media_type_t> const&
media_range_t::
get() const
{
return types_;
}
std::optional<media_range_t>
media_range_t::
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (auto it = strings.rbegin(); it != strings.rend()) {
auto begin = it->begin();
return try_parse(begin, it->end(), ec);
}
return std::nullopt;
}
void
media_range_t::
sort_internals()
{
struct {
int
specificity(media_type_t const& type)
{
if (type.type() == "*")
return 0;
if (type.subtype() == "*")
return 1;
if (type.params().size() < 1)
return 2;
return 3;
}
bool
operator()(media_type_t const& a, media_type_t const& b)
{
auto const sa = specificity(a);
auto const sb = specificity(b);
if (sa == sb) {
if (a.type() < b.type())
return true;
if (a.subtype() < b.subtype())
return true;
return false;
}
return sb < sa;
}
} precedence;
std::sort(types_.begin(), types_.end(), precedence);
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,60 @@
#ifndef seafire__protocol__rc7231__media_range_hxx_
#define seafire__protocol__rc7231__media_range_hxx_
#include <seafire/protocol/media-type.hxx>
#include <algorithm>
#include <system_error>
#include <vector>
namespace seafire::protocol::rfc7231
{
class media_range_t {
public:
using const_iterator = typename std::vector<media_type_t>::const_iterator;
media_range_t();
media_range_t(media_type_t type);
media_range_t(std::vector<media_type_t> types);
const_iterator
begin() const;
const_iterator
cbegin() const;
const_iterator
end() const;
const_iterator
cend() const;
std::vector<media_type_t> const&
get() const;
template<typename InputIterator>
static
std::optional<media_range_t>
try_parse(InputIterator& begin,
InputIterator const& end,
std::error_code& ec);
static
std::optional<media_range_t>
try_parse(std::vector<std::string> const& strings, std::error_code& ec);
private:
void
sort_internals();
std::vector<media_type_t> types_;
};
} // namespace seafire::protocol::rfc7231
#include <seafire/protocol/rfc7231/media-range.txx>
#endif

View File

@ -0,0 +1,37 @@
namespace seafire::protocol::rfc7231
{
template<typename InputIterator>
std::optional<media_range_t>
media_range_t::
try_parse(InputIterator& begin,
InputIterator const& end,
std::error_code& ec)
{
auto skip_whitespace = [&] {
while (begin != end && grammar::is_space(*begin))
++begin;
};
std::vector<media_type_t> types;
while (begin != end) {
skip_whitespace();
auto type = media_type_t::try_parse(begin, end, ec);
if (ec || !type)
return std::nullopt;
types.emplace_back(std::move(*type));
skip_whitespace();
if (begin != end && *begin == ',')
++begin;
}
return types;
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,96 @@
#include <seafire/protocol/rfc7231/product.hxx>
namespace seafire::protocol::rfc7231
{
product_t::
product_t(token_t name)
: name_{std::move(name)}
{}
product_t::
product_t(token_t name, token_t version)
: name_{std::move(name)},
version_{std::move(version)}
{}
token_t const&
product_t::
name() const
{
return name_;
}
std::optional<token_t> const&
product_t::
version() const
{
return version_;
}
void
to_stream(std::ostream& o, product_t const& p)
{
if (p.name().empty())
return;
o << p.name();
if (p.version() && !p.version()->empty())
o << "/" << *p.version();
}
std::string
to_string(product_t const& p)
{
std::ostringstream str_stream;
to_stream(str_stream, p);
return str_stream.str();
}
void
to_stream(std::ostream& o, products_t const& products)
{
for (auto const& j : products) {
to_stream(o, j);
o << " ";
}
}
std::string
to_string(products_t const& products)
{
std::ostringstream str_stream;
to_stream(str_stream, products);
return str_stream.str();
}
std::optional<product_t>
try_parse_product(std::string const& str)
{
auto begin = str.begin();
return try_parse_product(begin, str.end());
}
std::optional<product_t>
try_parse_product(std::string const& str, std::error_code& ec)
{
auto begin = str.begin();
return try_parse_product(begin, str.end(), ec);
}
std::optional<products_t>
try_parse_products(std::string const& str)
{
auto begin = str.begin();
return try_parse_products(begin, str.end());
}
std::optional<products_t>
try_parse_products(std::string const& str, std::error_code& ec)
{
auto begin = str.begin();
return try_parse_products(begin, str.end(), ec);
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,77 @@
#ifndef seafire__protocol__rfc7231__product_hxx_
#define seafire__protocol__rfc7231__product_hxx_
#include <seafire/protocol/grammar.hxx>
#include <seafire/protocol/token.hxx>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
namespace seafire::protocol::rfc7231
{
class product_t {
public:
product_t(token_t);
product_t(token_t, token_t);
token_t const&
name() const;
std::optional<token_t> const&
version() const;
private:
token_t name_;
std::optional<token_t> version_;
};
using products_t = std::vector<product_t>;
void
to_stream(std::ostream&, product_t const&);
std::string
to_string(product_t const&);
void
to_stream(std::ostream&, products_t const&);
std::string
to_string(products_t const&);
template<typename InputIterator>
std::optional<product_t>
try_parse_product(InputIterator&, InputIterator);
template<typename InputIterator>
std::optional<product_t>
try_parse_product(InputIterator&, InputIterator, std::error_code& ec);
std::optional<product_t>
try_parse_product(std::string const&);
template<typename InputIterator>
std::optional<products_t>
try_parse_products(InputIterator&, InputIterator);
template<typename InputIterator>
std::optional<products_t>
try_parse_products(InputIterator&, InputIterator, std::error_code&);
std::optional<products_t>
try_parse_products(std::string const&);
std::optional<products_t>
try_parse_products(std::string const&, std::error_code&);
} // namespace seafire::protocol::rfc7231
#include <seafire/protocol/rfc7231/product.txx>
#endif

View File

@ -0,0 +1,115 @@
namespace seafire::protocol::rfc7231
{
template<typename InputIterator>
std::optional<product_t>
try_parse_product(InputIterator& first, InputIterator last)
{
auto name = try_parse_token(first, last);
if (!name) {
return std::nullopt;
}
if (first != last && *first == '/') {
++first; // skips '/'
auto version = try_parse_token(first, last);
if (!version) {
return std::nullopt;
}
return {{*name, *version}};
}
return {{*name}};
}
template<typename InputIterator>
std::optional<product_t>
try_parse_product(InputIterator& first, InputIterator last, std::error_code& ec)
{
ec = {};
auto name = try_parse_token(first, last, ec);
if (ec) {
return std::nullopt;
}
if (!name) {
return std::nullopt;
}
if (first != last && *first == '/') {
++first; // skips '/'
auto version = try_parse_token(first, last, ec);
if (ec) {
return std::nullopt;
}
if (!version) {
return std::nullopt;
}
return {{*name, *version}};
}
return {{*name}};
}
template<typename InputIterator>
std::optional<products_t>
try_parse_products(InputIterator& first, InputIterator last)
{
products_t products;
while (first != last) {
auto p = try_parse_product(first, last);
if (!p) {
return std::nullopt;
}
products.emplace_back(std::move(*p));
while (first != last && grammar::is_space(*first)) {
++first; // skips whitespace
}
}
return products;
}
template<typename InputIterator>
std::optional<products_t>
try_parse_products(InputIterator& first, InputIterator last, std::error_code& ec)
{
ec = {};
products_t products;
while (first != last) {
auto p = try_parse_product(first, last, ec);
if (ec) {
return std::nullopt;
}
if (!p) {
return std::nullopt;
}
products.emplace_back(std::move(*p));
while (first != last && grammar::is_space(*first)) {
++first; // skips whitespace
}
}
return products;
}
} // namespace seafire::protocol::rfc7231

View File

@ -0,0 +1,42 @@
#ifndef seafire__protocol__rfc7231__server_hxx_
#define seafire__protocol__rfc7231__server_hxx_
#include <seafire/protocol/rfc7231/product.hxx>
#include <optional>
#include <string>
namespace seafire::protocol::rfc7231
{
struct server_t
{
using alias_type = products_t;
static constexpr const char* name = "server";
static
std::optional<products_t>
try_parse(std::vector<std::string> const& strings)
{
if (auto it = strings.rbegin(); it != strings.rend())
return try_parse_products(*it);
return {};
}
static
std::optional<products_t>
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (auto it = strings.rbegin(); it != strings.rend())
return try_parse_products(*it, ec);
return {};
}
};
} // namespace seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,76 @@
#include <seafire/protocol/rfc7232/entity-tag.hxx>
namespace seafire::protocol::rfc7232
{
entity_tag_t::
entity_tag_t(std::string tag, tag_type type)
: tag_{ std::move(tag) }, type_{ type }
{
if (auto p = this->tag().find('"'); p != std::string::npos)
throw std::invalid_argument{ "ETag may not contain \"" };
}
std::string const&
entity_tag_t::
tag() const
{
return tag_;
}
entity_tag_t::tag_type
entity_tag_t::
type() const
{
return type_;
}
std::string
to_string(entity_tag_t const& et)
{
if (is_strong(et))
return "\"" + et.tag() + "\"";
return "W/\"" + et.tag() + '"';
}
bool
is_strong(entity_tag_t const& et)
{
return et.type() == entity_tag_t::strong;
}
bool
is_weak(entity_tag_t const& et)
{
return et.type() == entity_tag_t::weak;
}
bool
operator==(entity_tag_t const& lhs, entity_tag_t const& rhs)
{
return strong_compare(lhs, rhs);
}
bool
operator!=(entity_tag_t const& lhs, entity_tag_t const& rhs)
{
return !(lhs == rhs);
}
bool
strong_compare(entity_tag_t const& lhs, entity_tag_t const& rhs)
{
if (is_weak(lhs) || is_weak(rhs))
return false;
return lhs.tag() == rhs.tag();
}
bool
weak_compare(entity_tag_t const& lhs, entity_tag_t const& rhs)
{
return lhs.tag() == rhs.tag();
}
} // namespace seafire::protocol::rfc7232

View File

@ -0,0 +1,72 @@
#ifndef seafire__protocol__entity_tag_hxx_
#define seafire__protocol__entity_tag_hxx_
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
namespace seafire::protocol::rfc7232
{
class entity_tag_t
{
public:
enum class tag_type { strong, weak };
static constexpr tag_type strong = tag_type::strong;
static constexpr tag_type weak = tag_type::weak;
entity_tag_t(std::string tag, tag_type type);
std::string const&
tag() const;
tag_type
type() const;
template<typename InputIterator>
static
std::optional<entity_tag_t>
try_parse(InputIterator& it,
InputIterator const& end,
std::error_code& ec);
template<typename InputIterator>
static
std::optional<entity_tag_t>
try_parse(InputIterator&& it,
InputIterator const& end,
std::error_code& ec);
private:
std::string tag_;
tag_type type_;
};
std::string
to_string(entity_tag_t const& et);
bool
is_strong(entity_tag_t const& et);
bool
is_weak(entity_tag_t const& et);
bool
operator==(entity_tag_t const& lhs, entity_tag_t const& rhs);
bool
operator!=(entity_tag_t const& lhs, entity_tag_t const& rhs);
bool
strong_compare(entity_tag_t const& lhs, entity_tag_t const& rhs);
bool
weak_compare(entity_tag_t const& lhs, entity_tag_t const& rhs);
} // namespace seafire::protocol::rfc7232
#include <seafire/protocol/rfc7232/entity-tag.txx>
#endif

View File

@ -0,0 +1,55 @@
namespace seafire::protocol::rfc7232
{
template<typename InputIterator>
std::optional<entity_tag_t>
entity_tag_t::
try_parse(InputIterator& it,
InputIterator const& end,
std::error_code& ec)
{
bool weak = false;
if (it == end)
return {};
if (*it == 'W') {
++it; // consume 'W'
if (it == end || *it != '/')
return {};
++it; // consume '/'
weak = true;
}
if (it == end || *it != '"')
return {};
++it; // consume '"'
std::string opaque;
while (it != end && *it != '"') // TODO: Validate *it
opaque += *it++;
if (it == end || *it != '"')
return {};
++it; // consume '"'
return entity_tag_t{opaque, weak ? entity_tag_t::weak : entity_tag_t::strong};
}
template<typename InputIterator>
std::optional<entity_tag_t>
entity_tag_t::
try_parse(InputIterator&& it,
InputIterator const& end,
std::error_code& ec)
{
return try_parse(it, end, ec);
}
} // namespace seafire::protocol::rfc7232

View File

@ -0,0 +1,18 @@
#ifndef seafire__protocol__rfc7232__etag_hxx_
#define seafire__protocol__rfc7232__etag_hxx_
#include <seafire/protocol/rfc7232/entity-tag.hxx>
namespace seafire::protocol::rfc7232
{
struct etag_t {
using alias_type = entity_tag_t;
static constexpr char const name[] = "etag";
};
} // namespace seafire::protocol::rcf7232
#endif

View File

@ -0,0 +1,55 @@
#ifndef seafire__protocol__rfc7232__if_match_hxx_
#define seafire__protocol__rfc7232__if_match_hxx_
#include <seafire/protocol/error.hxx>
#include <seafire/protocol/grammar.hxx>
#include <seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <sstream>
#include <string>
#include <system_error>
#include <variant>
#include <vector>
namespace seafire::protocol::rfc7232
{
class if_match_t
{
public:
static constexpr char const name[] = "if-match";
struct anything_t {};
static constexpr anything_t anything{};
if_match_t(anything_t);
if_match_t(std::vector<entity_tag_t>);
bool
is_anything() const;
std::vector<entity_tag_t> const&
tags() const;
static
std::optional<if_match_t>
try_parse(std::vector<std::string> const&,
std::error_code&);
private:
std::variant<
anything_t, std::vector<entity_tag_t>
> value_;
};
std::string
to_string(if_match_t const&);
} // namespace seafire::protocol::rfc7232
#include <seafire/protocol/rfc7232/if-match.ixx>
#endif

View File

@ -0,0 +1,115 @@
namespace seafire::protocol::rfc7232
{
inline
if_match_t::
if_match_t(anything_t)
: value_{anything}
{}
inline
if_match_t::
if_match_t(std::vector<entity_tag_t> tags)
: value_{std::move(tags)}
{}
inline
bool
if_match_t::
is_anything() const
{
return std::holds_alternative<anything_t>(value_);
}
inline
std::vector<entity_tag_t> const&
if_match_t::
tags() const
{
if (std::holds_alternative<std::vector<entity_tag_t>>(value_))
return std::get<std::vector<entity_tag_t>>(value_);
throw std::invalid_argument{"invalid if-match"};
}
inline
std::optional<if_match_t>
if_match_t::
try_parse(std::vector<std::string> const& strings,
std::error_code& ec)
{
std::vector<entity_tag_t> tags;
for (auto const& j : strings) {
auto it = j.begin();
while (it != j.end()) {
grammar::skip_space(it, j.end());
if (it != j.end() && *it == '*') {
++it; // consume '*'
grammar::skip_space(it, j.end());
if (it != j.end() || tags.size() > 0) {
ec = make_error_code(protocol_error_t::invalid_header_value);
return std::nullopt;
}
return anything;
}
auto tag = entity_tag_t::try_parse(it, j.end(), ec);
if (ec)
return std::nullopt;
if (!tag) {
ec = make_error_code(protocol_error_t::invalid_header_value);
return std::nullopt;
}
tags.emplace_back(std::move(*tag));
grammar::skip_space(it, j.end());
if (it != j.end()) {
if (*it != ',') {
ec = make_error_code(protocol_error_t::invalid_header_value);
return std::nullopt;
}
++it;
}
}
}
if (tags.empty())
return {};
return tags;
}
inline
std::string
to_string(if_match_t const& if_match)
{
if (if_match.is_anything())
return "*";
std::ostringstream str;
auto it = if_match.tags().begin();
if (it != if_match.tags().end())
str << to_string(*it++);
while (it != if_match.tags().end()) {
str << ", ";
str << to_string(*it++);
}
return str.str();
}
} // namespace seafire::protocol::rfc7232

View File

@ -0,0 +1,38 @@
#ifndef seafire__protocol__rfc7232__if_modified_since_hxx_
#define seafire__protocol__rfc7232__if_modified_since_hxx_
#include <seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <vector>
namespace seafire::protocol::rfc7232 {
struct if_modified_since_t
{
using alias_type = std::chrono::system_clock::time_point;
static constexpr const char name[] = "if-modified-since";
static std::string
to_string(std::chrono::system_clock::time_point const& point_in_time)
{
return format_http_date(point_in_time);
}
static
std::optional<std::chrono::system_clock::time_point>
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (strings.empty())
return {};
return try_parse_http_date(strings.front());
}
};
} // namespace seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,34 @@
#ifndef seafire__protocol__rfc7232__if_none_match_hxx_
#define seafire__protocol__rfc7232__if_none_match_hxx_
#include <seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <system_error>
namespace seafire::protocol::rfc7232
{
struct if_none_match_t
{
static constexpr char const name[] = "if-none-match";
static std::optional<if_none_match_t>
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
// FIXME implement.
return {};
}
};
inline
std::string
to_string(if_none_match_t const& if_none_match)
{
// FIXME implement.
return {};
}
} // namespace seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,38 @@
#ifndef seafire__protocol__rfc7232__if_unmodified_since_hxx_
#define seafire__protocol__rfc7232__if_unmodified_since_hxx_
#include <seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace seafire::protocol::rfc7232
{
struct if_unmodified_since_t
{
using alias_type = std::chrono::system_clock::time_point;
static constexpr const char name[] = "if-unmodified-since";
static std::string
to_string(std::chrono::system_clock::time_point const& point_in_time)
{
return format_http_date(point_in_time);
}
static std::optional<std::chrono::system_clock::time_point>
try_parse(std::vector<std::string> const& strings, std::error_code& ec)
{
if (strings.empty())
return {};
return try_parse_http_date(strings.front());
}
};
} // namespace seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,39 @@
#ifndef seafire__protocol__rc7232__last_modified_hxx_
#define seafire__protocol__rc7232__last_modified_hxx_
#include <seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace seafire::protocol::rfc7232
{
struct last_modified_t
{
using alias_type = std::chrono::system_clock::time_point;
static constexpr const char name[] = "last-modified";
static std::string
to_string(std::chrono::system_clock::time_point const& point_in_time)
{
return format_http_date(point_in_time);
}
static std::optional<std::chrono::system_clock::time_point>
try_parse(std::vector<std::string> const& strings)
{
if (strings.empty())
return std::nullopt;
return try_parse_http_date(strings.front());
}
};
} // namespace seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,157 @@
#include <seafire/protocol/status-code.hxx>
#include <stdexcept>
namespace seafire::protocol
{
status_code_t::
status_code_t() = default;
/// Construct a new status code.
///
/// Valid codes are 100 to 999 (inclusive).
///
/// The reason string will be set if the status code is standard.
///
/// \throws std::invalid_argument Thrown if \a code is invalid.
///
status_code_t::
status_code_t(unsigned short code)
: code_{code}, reason_{get_reason(code)}
{
if (code < 100 || 999 < code) {
throw std::invalid_argument{"invalid status code"};
}
}
/// Construct a new status code with a custom reason.
///
/// Valid codes are 100 to 999 (inclusive).
///
/// \throws std::invalid_argument Thrown if \a code is invalid.
///
status_code_t::
status_code_t(unsigned short code, std::string reason)
: code_{ code }, reason_{ std::move(reason) }
{
if (code < 100 || 999 < code) {
throw std::invalid_argument{"invalid status code"};
}
}
/// Access the code of this status code.
///
unsigned short
status_code_t::
code() const
{
return code_;
}
/// Access the reason string of this status code.
///
std::string const&
status_code_t::
reason() const
{
return reason_;
}
/// Convert this status code to an unsigned short value.
///
status_code_t::
operator unsigned short()
{
return code_;
}
/// Compare two status codes for equality.
///
bool
operator==(status_code_t const& lhs, status_code_t const& rhs)
{
return lhs.code() == rhs.code();
}
/// Compare two status codes for equality.
///
bool
operator==(status_code_t const& lhs, unsigned short rhs)
{
return lhs.code() == rhs;
}
/// Compare two status codes for equality.
///
bool
operator==(unsigned short lhs, status_code_t const& rhs)
{
return lhs == rhs.code();
}
/// Compare two status codes for inequality.
///
bool
operator!=(status_code_t const& lhs, status_code_t const& rhs)
{
return !(lhs == rhs);
}
/// Compare two status codes for inequality.
///
bool
operator!=(status_code_t const& lhs, unsigned short rhs)
{
return !(lhs == rhs);
}
/// Compare two status codes for inequality.
///
bool
operator!=(unsigned short lhs, status_code_t const& rhs)
{
return !(lhs == rhs);
}
/// Check if the given status code, \a sc, is informational.
///
bool
is_informational(status_code_t const& sc)
{
return (sc.code() / 100) == 1;
}
/// Check if the given status code, \a sc, indicates success.
///
bool
is_success(status_code_t const& sc)
{
return (sc.code() / 100) == 2;
}
/// Check if the given status code, \a sc, is a redirection.
///
bool
is_redirection(status_code_t const& sc)
{
return (sc.code() / 100) == 3;
}
/// Check if the given status code, \a sc, indicates a client error.
///
bool
is_client_error(status_code_t const& sc)
{
return (sc.code() / 100) == 4;
}
/// Check if the given status code, \a sc, indicates a server error.
///
bool
is_server_error(status_code_t const& sc)
{
return (sc.code() / 100) == 5;
}
} // namespace seafire::protocol

View File

@ -0,0 +1,71 @@
#ifndef seafire__protocol__status_code_hxx_
#define seafire__protocol__status_code_hxx_
#include <seafire/protocol/reason.hxx>
#include <string>
namespace seafire::protocol
{
/// Represents an HTTP status code.
///
class status_code_t
{
public:
status_code_t();
status_code_t(unsigned short code);
status_code_t(unsigned short code, std::string reason);
unsigned short
code() const;
std::string const&
reason() const;
operator unsigned short();
private:
unsigned short code_{0};
std::string reason_;
};
bool
operator==(status_code_t const&, status_code_t const&);
bool
operator==(status_code_t const&, unsigned short);
bool
operator==(unsigned short, status_code_t const&);
bool
operator!=(status_code_t const&, status_code_t const&);
bool
operator!=(status_code_t const&, unsigned short);
bool
operator!=(unsigned short, status_code_t const&);
bool
is_informational(status_code_t const&);
bool
is_success(status_code_t const&);
bool
is_redirection(status_code_t const&);
bool
is_client_error(status_code_t const&);
bool
is_server_error(status_code_t const&);
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,80 @@
#include <seafire/protocol/token.hxx>
namespace seafire::protocol
{
/// Construct a new empty token.
///
token_t::
token_t() = default;
/// Construct a new token from a string.
///
/// \param str The token string.
/// \throws std::invalid_argument Thrown if \a str is invalid.
///
token_t::
token_t(std::string str)
: str_{
validate_token(str)
? std::move(str)
: throw std::invalid_argument{"invalid token"}
}
{}
/// Construct a new token from a string.
///
/// \param str The token string.
/// \throws std::invalid_argument Thrown if \a str is invalid.
///
token_t::
token_t(char const* str)
: str_{
str
? (
validate_token(str)
? str
: throw std::invalid_argument{"invalid token"}
)
: throw std::invalid_argument{"invalid token"}
}
{}
bool
token_t::
empty() const
{
return str().empty();
}
std::string const&
token_t::
str() const
{
return str_;
}
bool
token_t::
validate_token(std::string const& str)
{
// we allow empty tokens for the sake of simplicity.
//
if (str.empty())
return true;
for (auto const& c : str) {
if (!grammar::is_tchar(c))
return false;
}
return true;
}
std::ostream&
operator<<(std::ostream& o, token_t const& t)
{
return o << t.str();
}
} // namespace seafire::protocol

170
seafire/protocol/token.hxx Normal file
View File

@ -0,0 +1,170 @@
#ifndef seafire__protocol__token_hxx_
#define seafire__protocol__token_hxx_
#include <seafire/protocol/grammar.hxx>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
namespace seafire::protocol
{
class token_t
{
public:
token_t();
token_t(std::string);
token_t(char const*);
bool
empty() const;
std::string const&
str() const;
auto
operator<=>(token_t const&) const = default;
static
bool
validate_token(std::string const&);
private:
std::string str_;
};
std::ostream&
operator<<(std::ostream&, token_t const&);
/// Vector of tokens.
///
using tokens_t = std::vector<token_t>;
/// Attempt to parse a token.
///
template<typename Iterator>
std::optional<token_t>
try_parse_token(Iterator& begin, Iterator end)
{
std::string token;
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
while (begin != end && grammar::is_tchar(*begin)) {
token += *begin++;
}
if (token.empty()) {
return std::nullopt;
}
return token;
}
template<typename Iterator>
std::optional<token_t>
try_parse_token(Iterator& begin, Iterator end, std::error_code& ec)
{
ec = {};
std::string token;
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
while (begin != end && grammar::is_tchar(*begin)) {
token += *begin++;
}
if (token.empty()) {
return std::nullopt;
}
return token;
}
/// Attempt to parse tokens.
///
template<typename Iterator>
std::optional<tokens_t>
try_parse_tokens(Iterator& begin, Iterator end)
{
tokens_t tokens;
while (begin != end) {
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
auto token = try_parse_token(begin, end);
if (!token) {
return std::nullopt;
}
tokens.emplace_back(std::move(*token));
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
if (begin == end || *begin != ',') {
break;
}
++begin; // skips ','
}
return tokens;
}
/// Attempt to parse tokens.
///
template<typename Iterator>
std::optional<tokens_t>
try_parse_tokens(Iterator& begin, Iterator end, std::error_code& ec)
{
tokens_t tokens;
while (begin != end) {
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
auto token = try_parse_token(begin, end, ec);
if (ec) {
return std::nullopt;
}
if (!token) {
return std::nullopt;
}
tokens.emplace_back(std::move(*token));
while (begin != end && grammar::is_space(*begin)) {
++begin;
}
if (begin == end || *begin != ',') {
break;
}
++begin; // skips ','
}
return tokens;
}
} // namespace seafire::protocol
#endif

View File

@ -0,0 +1,70 @@
#ifndef seafire__protocol__traits_hxx_
#define seafire__protocol__traits_hxx_
#include <type_traits>
namespace seafire::protocol::traits
{
/// Check if T has an alias type defined.
///
template<typename, typename = std::void_t<>>
struct has_alias_type
: std::false_type
{};
template<typename T>
struct has_alias_type<
T,
std::void_t<
decltype(std::declval<typename T::alias_type>())
>
> : std::true_type
{};
/// Helper variable for has_alias_type.
///
template<typename T>
inline constexpr bool has_alias_type_v = has_alias_type<T>::value;
/// Access the alias type of T, if available, otherwise T.
///
template<typename T, bool = has_alias_type_v<T>>
struct alias_type
{
using type = T;
};
template<typename T>
struct alias_type<T, true>
{
using type = typename T::alias_type;
};
/// Helper for alias_type.
///
template<typename T>
using alias_type_t = typename alias_type<T>::type;
template<typename, typename = std::void_t<>>
struct has_overridden_to_string
: std::false_type
{};
template<typename T>
struct has_overridden_to_string<
T,
std::void_t<
decltype(T::to_string(std::declval<alias_type_t<T>>()))
>
> : std::true_type
{};
template<typename T>
inline constexpr bool has_overridden_to_string_v{
has_overridden_to_string<T>::value
};
} // seafire::protocol::traits
#endif

View File

@ -0,0 +1,37 @@
#ifndef seafire__protocol__version_hxx_
#define seafire__protocol__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 SEAFIRE_PROTOCOL_VERSION $seafire_protocol.version.project_number$ULL
#define SEAFIRE_PROTOCOL_VERSION_STR "$seafire_protocol.version.project$"
#define SEAFIRE_PROTOCOL_VERSION_ID "$seafire_protocol.version.project_id$"
#define SEAFIRE_PROTOCOL_VERSION_FULL "$seafire_protocol.version$"
#define SEAFIRE_PROTOCOL_VERSION_MAJOR $seafire_protocol.version.major$
#define SEAFIRE_PROTOCOL_VERSION_MINOR $seafire_protocol.version.minor$
#define SEAFIRE_PROTOCOL_VERSION_PATCH $seafire_protocol.version.patch$
#define SEAFIRE_PROTOCOL_PRE_RELEASE $seafire_protocol.version.pre_release$
#define SEAFIRE_PROTOCOL_SNAPSHOT_SN $seafire_protocol.version.snapshot_sn$ULL
#define SEAFIRE_PROTOCOL_SNAPSHOT_ID "$seafire_protocol.version.snapshot_id$"
#endif

174
seafire/protocol/write.cxx Normal file
View File

@ -0,0 +1,174 @@
#include <seafire/protocol/write.hxx>
namespace seafire::protocol
{
void
write(common::io::stream_t& s,
request_t const& r)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
s.write(payload);
}
void
write(common::io::stream_t& s,
request_t const& r,
std::error_code& ec)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
s.write(payload, ec);
}
void
write(common::io::stream_t& s,
request_t const& r,
common::io::const_buffers_t const& content)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.write(payload);
}
void
write(common::io::stream_t& s,
request_t const& r,
common::io::const_buffers_t const& content,
std::error_code& ec)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.write(payload, ec);
}
void
async_write(common::io::stream_t& s,
request_t const& r,
std::function<void(std::error_code)> handler)
{
auto bound = [handler](std::error_code const& ec, std::size_t)
{
handler(ec);
};
s.async_write(to_buffers(r), bound);
}
void
async_write(common::io::stream_t& s,
request_t const& r,
common::io::const_buffers_t const& content,
std::function<void(std::error_code)> handler)
{
auto bound = [handler](std::error_code const& ec, std::size_t)
{
handler(ec);
};
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.async_write(payload, bound);
}
void
write(common::io::stream_t& s,
response_t const& r)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
s.write(payload);
}
void
write(common::io::stream_t& s,
response_t const& r,
std::error_code& ec)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
s.write(payload, ec);
}
void
write(common::io::stream_t& s,
response_t const& r,
common::io::const_buffers_t const& content)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.write(payload);
}
void
write(common::io::stream_t& s,
response_t const& r,
common::io::const_buffers_t const& content,
std::error_code& ec)
{
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.write(payload, ec);
}
void
async_write(common::io::stream_t& s,
response_t const& r,
std::function<void(std::error_code)> handler)
{
auto bound = [handler](std::error_code const& ec, std::size_t)
{
handler(ec);
};
s.async_write(to_buffers(r), bound);
}
void
async_write(common::io::stream_t& s,
response_t const& r,
common::io::const_buffers_t const& content,
std::function<void(std::error_code)> handler)
{
auto bound = [handler](std::error_code const& ec, std::size_t)
{
handler(ec);
};
common::io::const_buffers_t payload;
to_buffers(payload, r);
for (auto const& j : content) {
payload.emplace_back(j);
}
s.async_write(payload, bound);
}
} // namespace seafire::protocol

View File

@ -0,0 +1,82 @@
#ifndef seafire__protocol__write_hxx_
#define seafire__protocol__write_hxx_
#include <seafire/protocol/request.hxx>
#include <seafire/protocol/response.hxx>
#include <seafire/common/io/buffer.hxx>
#include <seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace seafire::protocol
{
void
write(common::io::stream_t&,
request_t const&);
void
write(common::io::stream_t&,
request_t const&,
std::error_code&);
void
write(common::io::stream_t&,
request_t const&,
std::vector<common::io::const_buffer_t> const&);
void
write(common::io::stream_t&,
request_t const&,
std::vector<common::io::const_buffer_t> const&,
std::error_code&);
void
async_write(common::io::stream_t&,
request_t const&,
std::function<void(std::error_code)>);
void
async_write(common::io::stream_t&,
request_t const&,
std::vector<common::io::const_buffer_t> const&,
std::function<void(std::error_code)>);
void
write(common::io::stream_t&,
response_t const&);
void
write(common::io::stream_t&,
response_t const&,
std::error_code&);
void
write(common::io::stream_t&,
response_t const&,
std::vector<common::io::const_buffer_t> const&);
void
write(common::io::stream_t&,
response_t const&,
std::vector<common::io::const_buffer_t> const&,
std::error_code&);
void
async_write(common::io::stream_t&,
response_t const&,
std::function<void(std::error_code)>);
void
async_write(common::io::stream_t&,
response_t const&,
std::vector<common::io::const_buffer_t> const&,
std::function<void(std::error_code)>);
} // namespace seafire::protocol
#endif

8
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View 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
View File

@ -0,0 +1 @@
./: {*/ -build/}