Hello libcode-seafire-protocol

This commit is contained in:
G.H.O.S.T 2024-12-24 22:23:04 +01:00
commit 1b57787177
Signed by: G.H.O.S.T
GPG Key ID: 3BD93EABD1407B82
91 changed files with 6375 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

View File

@ -0,0 +1,24 @@
name: on-push
on: [push]
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
volumes:
- /build
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Authenticate
run: |
git config unset http.https://code.helloryan.se/.extraheader
echo "${{ secrets.NETRC }}" >> ~/.netrc
- name: Initialize
run: |
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
bdep init -A /build
- name: Build
run: b
- name: Test
run: b test

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
.bdep/
Doxyfile
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# libcode-seafire-protocol
![Build status](https://code.helloryan.se/code/libcode-seafire-protocol/actions/workflows/on-push.yaml/badge.svg)
## Requirements
None, other than a modern C++-compiler.
## Building
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
build instructions.
## Contact
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
## Contributing
Please send an e-mail to ryan@helloryan.se to request an account and
write-access to the libcode-seafire-protocol repository.

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 = libcode-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 code/seafire/protocol/
}
export $out_root/code/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 @@
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

9
code/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 code__seafire__protocol__basic_parser_hxx_
#define code__seafire__protocol__basic_parser_hxx_
#include <code/seafire/protocol/error.hxx>
#include <code/seafire/protocol/grammar.hxx>
#include <code/seafire/protocol/message.hxx>
#include <code/seafire/protocol/protocol-version.hxx>
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/uri/uri.hxx>
#include <stdexcept>
#include <vector>
namespace code::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 code::seafire::protocol
#include <code/seafire/protocol/basic-parser.txx>
#endif

View File

@ -0,0 +1,546 @@
namespace code::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 = 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 code::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 =+ libcode-seafire-common%lib{code-seafire-common}
./: lib{code-seafire-protocol}: libul{code-seafire-protocol}
libul{code-seafire-protocol}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{code-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{code-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{code-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{code-seafire-protocol}: bin.lib.version = "-$version.project_id"
else
lib{code-seafire-protocol}: bin.lib.version = "-$version.major.$version.minor"
# Install into the code/seafire/protocol/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/code/seafire/protocol/
install.subdirs = true
}

View File

@ -0,0 +1,217 @@
#include <code/seafire/protocol/connection.hxx>
#include <code/seafire/protocol/read.hxx>
#include <code/seafire/protocol/read-content.hxx>
#include <code/seafire/protocol/write.hxx>
#include <code/seafire/protocol/rfc7230/content-length.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,93 @@
#ifndef code__seafire__protocol__connection_hxx_
#define code__seafire__protocol__connection_hxx_
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,42 @@
#include <code/seafire/protocol/date-time.hxx>
#include <iomanip>
#include <locale>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,19 @@
#ifndef code__seafire__protocol__date_time_hxx_
#define code__seafire__protocol__date_time_hxx_
#include <chrono>
#include <optional>
#include <string>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,88 @@
#include <code/seafire/protocol/error.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,79 @@
#ifndef code__seafire__protocol__error_hxx_
#define code__seafire__protocol__error_hxx_
#include <string>
#include <system_error>
namespace code::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 code::seafire::protocol
namespace std
{
template<>
struct is_error_code_enum<code::seafire::protocol::parse_error_t>
: true_type
{};
template<>
struct is_error_code_enum<code::seafire::protocol::protocol_error_t>
: true_type
{};
} // namespace std
#endif

View File

@ -0,0 +1,49 @@
#ifndef code__seafire__protocol__grammar_hxx_
#define code__seafire__protocol__grammar_hxx_
namespace code::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 code::seafire::protocol::grammar
#include <code/seafire/protocol/grammar.ixx>
#include <code/seafire/protocol/grammar.txx>
#endif

View File

@ -0,0 +1,183 @@
namespace code::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 code::seafire::protocol::grammar

View File

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

View File

@ -0,0 +1,139 @@
#include <code/seafire/protocol/header-collection.hxx>
#include <locale>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,65 @@
#ifndef code__seafire__protocol__header_collection_hxx_
#define code__seafire__protocol__header_collection_hxx_
#include <map>
#include <optional>
#include <ostream>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,66 @@
#include <code/seafire/protocol/match.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,53 @@
#ifndef code__seafire__protocol__match_hxx_
#define code__seafire__protocol__match_hxx_
#include <code/seafire/protocol/basic-parser.hxx>
#include <code/seafire/protocol/error.hxx>
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <system_error>
#include <utility>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,140 @@
#include <code/seafire/protocol/media-type.hxx>
#include <sstream>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,77 @@
#ifndef code__seafire__protocol__media_type_hxx_
#define code__seafire__protocol__media_type_hxx_
#include <code/seafire/protocol/grammar.hxx>
#include <map>
#include <optional>
#include <string>
#include <system_error>
namespace code::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 code::seafire::protocol
#include <code/seafire/protocol/media-type.txx>
#endif

View File

@ -0,0 +1,125 @@
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,89 @@
#include <code/seafire/protocol/message.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,86 @@
#ifndef code__seafire__protocol__message_hxx_
#define code__seafire__protocol__message_hxx_
#include <code/seafire/protocol/header-collection.hxx>
#include <code/seafire/protocol/protocol-version.hxx>
#include <code/seafire/protocol/traits.hxx>
#include <optional>
#include <ostream>
namespace code::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 code::seafire::protocol
#include <code/seafire/protocol/message.txx>
#endif

View File

@ -0,0 +1,66 @@
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,71 @@
#include <code/seafire/protocol/protocol-version.hxx>
#include <sstream>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,66 @@
#ifndef code__seafire__protocol__protocol_version_hxx_
#define code__seafire__protocol__protocol_version_hxx_
#include <code/seafire/common/io/buffer.hxx>
#include <asio.hpp>
#include <cstdint>
#include <ostream>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,225 @@
#include <code/seafire/protocol/read-content.hxx>
#include <code/seafire/protocol/rfc7230/content-length.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,74 @@
#ifndef code__seafire__protocol__read_content_hxx_
#define code__seafire__protocol__read_content_hxx_
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/seafire/common/io/stream.hxx>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,171 @@
#include <code/seafire/protocol/read.hxx>
#include <code/seafire/protocol/match.hxx>
#include <code/seafire/protocol/rfc7230/content-length.hxx>
#include <code/seafire/common/io/read-until.hxx>
#include <asio.hpp>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,54 @@
#ifndef code__seafire__protocol__read_hxx_
#define code__seafire__protocol__read_hxx_
#include <code/seafire/protocol/message.hxx>
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <cstddef>
#include <system_error>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,56 @@
#include <code/seafire/protocol/reason.hxx>
namespace code::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 code::seafire::protocol

View File

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

View File

@ -0,0 +1,149 @@
#include <code/seafire/protocol/request.hxx>
#include <asio.hpp>
namespace code::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.
///
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(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 code::seafire::protocol

View File

@ -0,0 +1,62 @@
#ifndef code__seafire__protocol__request_hxx_
#define code__seafire__protocol__request_hxx_
#include <code/seafire/protocol/message.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <code/uri/uri.hxx>
#include <ostream>
#include <string>
#include <vector>
namespace code::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);
uri::uri_t const&
target_uri() const;
void
set_target_uri(uri::uri_t);
private:
std::string method_;
std::string target_;
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 code::seafire::protocol
#endif

View File

@ -0,0 +1,101 @@
#include <code/seafire/protocol/response.hxx>
#include <asio.hpp>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,44 @@
#ifndef code__seafire__protocol__response_hxx_
#define code__seafire__protocol__response_hxx_
#include <code/seafire/protocol/message.hxx>
#include <code/seafire/protocol/status-code.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,88 @@
#include <code/seafire/protocol/rfc7230/connection.hxx>
namespace code::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 code::seafire::protocol::rfc7230

View File

@ -0,0 +1,51 @@
#ifndef code__seafire__protocol__rfc7230__connection_hxx_
#define code__seafire__protocol__rfc7230__connection_hxx_
#include <code/seafire/protocol/token.hxx>
#include <initializer_list>
#include <set>
#include <string>
#include <system_error>
namespace code::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 code::seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,28 @@
#include <code/seafire/protocol/rfc7230/content-length.hxx>
#include <code/seafire/protocol/error.hxx>
namespace code::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 code::seafire::protocol::rfc7230

View File

@ -0,0 +1,29 @@
#ifndef code__seafire__protocol__rfc7230__content_length_hxx_
#define code__seafire__protocol__rfc7230__content_length_hxx_
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,95 @@
#include <code/seafire/protocol/rfc7230/host.hxx>
#include <code/uri/grammar.hxx>
namespace code::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 && 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 && 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 code::seafire::protocol::rfc7230

View File

@ -0,0 +1,43 @@
#ifndef code__seafire__protocol__rfc7230__host_hxx_
#define code__seafire__protocol__rfc7230__host_hxx_
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7230
#endif

View File

@ -0,0 +1,16 @@
#include <code/seafire/protocol/rfc7231/accept.hxx>
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,27 @@
#ifndef code__seafire__protocol__rc7231__accept_hxx_
#define code__seafire__protocol__rc7231__accept_hxx_
#include <code/seafire/protocol/rfc7231/media-range.hxx>
#include <optional>
#include <string>
#include <system_error>
namespace code::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 code::seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,36 @@
#ifndef code__seafire__protocol__rc7231__allow_hxx_
#define code__seafire__protocol__rc7231__allow_hxx_
#include <code/seafire/protocol/token.hxx>
#include <sstream>
#include <string>
namespace code::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 code::seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,17 @@
#include <code/seafire/protocol/rfc7231/content-type.hxx>
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,28 @@
#ifndef code__seafire__protocol__rfc7231__content_type_hxx_
#define code__seafire__protocol__rfc7231__content_type_hxx_
#include <code/seafire/protocol/media-type.hxx>
#include <optional>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,43 @@
#ifndef code__seafire__protocol__rfc7231__date_hxx_
#define code__seafire__protocol__rfc7231__date_hxx_
#include <code/seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7231
#endif

View File

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

View File

@ -0,0 +1,112 @@
#include <code/seafire/protocol/rfc7231/media-range.hxx>
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,60 @@
#ifndef code_seafire__protocol__rc7231__media_range_hxx_
#define code_seafire__protocol__rc7231__media_range_hxx_
#include <code/seafire/protocol/media-type.hxx>
#include <algorithm>
#include <system_error>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7231
#include <code/seafire/protocol/rfc7231/media-range.txx>
#endif

View File

@ -0,0 +1,37 @@
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,96 @@
#include <code/seafire/protocol/rfc7231/product.hxx>
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,77 @@
#ifndef code__seafire__protocol__rfc7231__product_hxx_
#define code__seafire__protocol__rfc7231__product_hxx_
#include <code/seafire/protocol/grammar.hxx>
#include <code/seafire/protocol/token.hxx>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7231
#include <code/seafire/protocol/rfc7231/product.txx>
#endif

View File

@ -0,0 +1,115 @@
namespace code::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 code::seafire::protocol::rfc7231

View File

@ -0,0 +1,42 @@
#ifndef code__seafire__protocol__rfc7231__server_hxx_
#define code__seafire__protocol__rfc7231__server_hxx_
#include <code/seafire/protocol/rfc7231/product.hxx>
#include <optional>
#include <string>
namespace code::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 code::seafire::protocol::rfc7231
#endif

View File

@ -0,0 +1,76 @@
#include <code/seafire/protocol/rfc7232/entity-tag.hxx>
namespace code::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 code::seafire::protocol::rfc7232

View File

@ -0,0 +1,72 @@
#ifndef code__seafire__protocol__entity_tag_hxx_
#define code__seafire__protocol__entity_tag_hxx_
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>
namespace code::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 code::seafire::protocol::rfc7232
#include <code/seafire/protocol/rfc7232/entity-tag.txx>
#endif

View File

@ -0,0 +1,55 @@
namespace code::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 code::seafire::protocol::rfc7232

View File

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

View File

@ -0,0 +1,55 @@
#ifndef libseafire__protocol__rfc7232__if_match_hxx_
#define libseafire__protocol__rfc7232__if_match_hxx_
#include <code/seafire/protocol/error.hxx>
#include <code/seafire/protocol/grammar.hxx>
#include <code/seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <sstream>
#include <string>
#include <system_error>
#include <variant>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7232
#include <code/seafire/protocol/rfc7232/if-match.ixx>
#endif

View File

@ -0,0 +1,115 @@
namespace code::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 code::seafire::protocol::rfc7232

View File

@ -0,0 +1,38 @@
#ifndef code__seafire__protocol__rfc7232__if_modified_since_hxx_
#define code__seafire__protocol__rfc7232__if_modified_since_hxx_
#include <code/seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,34 @@
#ifndef code__seafire__protocol__rfc7232__if_none_match_hxx_
#define code__seafire__protocol__rfc7232__if_none_match_hxx_
#include <code/seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <system_error>
namespace code::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 code::seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,38 @@
#ifndef code__seafire__protocol__rfc7232__if_unmodified_since_hxx_
#define code__seafire__protocol__rfc7232__if_unmodified_since_hxx_
#include <code/seafire/protocol/rfc7232/entity-tag.hxx>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,39 @@
#ifndef code__seafire__protocol__rc7232__last_modified_hxx_
#define code__seafire__protocol__rc7232__last_modified_hxx_
#include <code/seafire/protocol/date-time.hxx>
#include <chrono>
#include <optional>
#include <string>
#include <system_error>
#include <vector>
namespace code::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 code::seafire::protocol::rfc7232
#endif

View File

@ -0,0 +1,157 @@
#include <code/seafire/protocol/status-code.hxx>
#include <stdexcept>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,71 @@
#ifndef code__seafire__protocol__status_code_hxx_
#define code__seafire__protocol__status_code_hxx_
#include <code/seafire/protocol/reason.hxx>
#include <string>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,80 @@
#include <code/seafire/protocol/token.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,170 @@
#ifndef code__seafire__protocol__token_hxx_
#define code__seafire__protocol__token_hxx_
#include <code/seafire/protocol/grammar.hxx>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
namespace code::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 code::seafire::protocol
#endif

View File

@ -0,0 +1,70 @@
#ifndef code__seafire__protocol__traits_hxx_
#define code__seafire__protocol__traits_hxx_
#include <type_traits>
namespace code::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 code__seafire__protocol__version_hxx_
#define code__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 LIBCODE_SEAFIRE_PROTOCOL_VERSION $libcode_seafire_protocol.version.project_number$ULL
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_STR "$libcode_seafire_protocol.version.project$"
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_ID "$libcode_seafire_protocol.version.project_id$"
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_FULL "$libcode_seafire_protocol.version$"
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_MAJOR $libcode_seafire_protocol.version.major$
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_MINOR $libcode_seafire_protocol.version.minor$
#define LIBCODE_SEAFIRE_PROTOCOL_VERSION_PATCH $libcode_seafire_protocol.version.patch$
#define LIBCODE_SEAFIRE_PROTOCOL_PRE_RELEASE $libcode_seafire_protocol.version.pre_release$
#define LIBCODE_SEAFIRE_PROTOCOL_SNAPSHOT_SN $libcode_seafire_protocol.version.snapshot_sn$ULL
#define LIBCODE_SEAFIRE_PROTOCOL_SNAPSHOT_ID "$libcode_seafire_protocol.version.snapshot_id$"
#endif

View File

@ -0,0 +1,174 @@
#include <code/seafire/protocol/write.hxx>
namespace code::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 code::seafire::protocol

View File

@ -0,0 +1,82 @@
#ifndef code__seafire__protocol__write_hxx_
#define code__seafire__protocol__write_hxx_
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <asio.hpp>
#include <functional>
#include <system_error>
namespace code::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 code::seafire::protocol
#endif

14
manifest Normal file
View File

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

15
repositories.manifest Normal file
View File

@ -0,0 +1,15 @@
: 1
summary: libcode-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/code/libcode-seafire-common.git##HEAD

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/}