Hello libcode-seafire-protocol
This commit is contained in:
commit
1b57787177
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
indent_size = 4
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto
|
24
.gitea/workflows/on-push.yaml
Normal file
24
.gitea/workflows/on-push.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
name: on-push
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: linux
|
||||
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
|
||||
volumes:
|
||||
- /build
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Authenticate
|
||||
run: |
|
||||
git config unset http.https://code.helloryan.se/.extraheader
|
||||
echo "${{ secrets.NETRC }}" >> ~/.netrc
|
||||
- name: Initialize
|
||||
run: |
|
||||
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
|
||||
bdep init -A /build
|
||||
- name: Build
|
||||
run: b
|
||||
- name: Test
|
||||
run: b test
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
.bdep/
|
||||
Doxyfile
|
||||
|
||||
# Local default options files.
|
||||
#
|
||||
.build2/local/
|
||||
|
||||
# Compiler/linker output.
|
||||
#
|
||||
*.d
|
||||
*.t
|
||||
*.i
|
||||
*.i.*
|
||||
*.ii
|
||||
*.ii.*
|
||||
*.o
|
||||
*.obj
|
||||
*.gcm
|
||||
*.pcm
|
||||
*.ifc
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.a
|
||||
*.lib
|
||||
*.exp
|
||||
*.pdb
|
||||
*.ilk
|
||||
*.exe
|
||||
*.exe.dlls/
|
||||
*.exe.manifest
|
||||
*.pc
|
31
LICENSE
Normal file
31
LICENSE
Normal file
@ -0,0 +1,31 @@
|
||||
Copyright © 2024 Ryan. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. All advertising materials mentioning features or use of this software must
|
||||
display the following acknowledgement:
|
||||
|
||||
This product includes software developed by Ryan, http://helloryan.se/.
|
||||
|
||||
4. Neither the name(s) of the copyright holder(s) nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
||||
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# libcode-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
4
build/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/config.build
|
||||
/root/
|
||||
/bootstrap/
|
||||
build/
|
7
build/bootstrap.build
Normal file
7
build/bootstrap.build
Normal file
@ -0,0 +1,7 @@
|
||||
project = libcode-seafire-protocol
|
||||
|
||||
using version
|
||||
using config
|
||||
using test
|
||||
using install
|
||||
using dist
|
6
build/export.build
Normal file
6
build/export.build
Normal file
@ -0,0 +1,6 @@
|
||||
$out_root/
|
||||
{
|
||||
include code/seafire/protocol/
|
||||
}
|
||||
|
||||
export $out_root/code/seafire/protocol/$import.target
|
16
build/root.build
Normal file
16
build/root.build
Normal file
@ -0,0 +1,16 @@
|
||||
# Uncomment to suppress warnings coming from external libraries.
|
||||
#
|
||||
#cxx.internal.scope = current
|
||||
|
||||
cxx.std = latest
|
||||
|
||||
using cxx
|
||||
|
||||
hxx{*}: extension = hxx
|
||||
ixx{*}: extension = ixx
|
||||
txx{*}: extension = txx
|
||||
cxx{*}: extension = cxx
|
||||
|
||||
# The test target for cross-testing (running tests under Wine, etc).
|
||||
#
|
||||
test.target = $cxx.target
|
5
buildfile
Normal file
5
buildfile
Normal file
@ -0,0 +1,5 @@
|
||||
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
|
||||
|
||||
# Don't install tests.
|
||||
#
|
||||
tests/: install = false
|
9
code/seafire/protocol/.gitignore
vendored
Normal file
9
code/seafire/protocol/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Generated version header.
|
||||
#
|
||||
version.hxx
|
||||
|
||||
# Unit test executables and Testscript output directories
|
||||
# (can be symlinks).
|
||||
#
|
||||
*.test
|
||||
test-*.test
|
302
code/seafire/protocol/basic-parser.hxx
Normal file
302
code/seafire/protocol/basic-parser.hxx
Normal 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
|
546
code/seafire/protocol/basic-parser.txx
Normal file
546
code/seafire/protocol/basic-parser.txx
Normal 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
|
66
code/seafire/protocol/buildfile
Normal file
66
code/seafire/protocol/buildfile
Normal 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
|
||||
}
|
217
code/seafire/protocol/connection.cxx
Normal file
217
code/seafire/protocol/connection.cxx
Normal 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
|
93
code/seafire/protocol/connection.hxx
Normal file
93
code/seafire/protocol/connection.hxx
Normal 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
|
42
code/seafire/protocol/date-time.cxx
Normal file
42
code/seafire/protocol/date-time.cxx
Normal 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
|
19
code/seafire/protocol/date-time.hxx
Normal file
19
code/seafire/protocol/date-time.hxx
Normal 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
|
88
code/seafire/protocol/error.cxx
Normal file
88
code/seafire/protocol/error.cxx
Normal 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
|
79
code/seafire/protocol/error.hxx
Normal file
79
code/seafire/protocol/error.hxx
Normal 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
|
49
code/seafire/protocol/grammar.hxx
Normal file
49
code/seafire/protocol/grammar.hxx
Normal 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
|
183
code/seafire/protocol/grammar.ixx
Normal file
183
code/seafire/protocol/grammar.ixx
Normal 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
|
15
code/seafire/protocol/grammar.txx
Normal file
15
code/seafire/protocol/grammar.txx
Normal 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
|
139
code/seafire/protocol/header-collection.cxx
Normal file
139
code/seafire/protocol/header-collection.cxx
Normal 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
|
65
code/seafire/protocol/header-collection.hxx
Normal file
65
code/seafire/protocol/header-collection.hxx
Normal 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
|
66
code/seafire/protocol/match.cxx
Normal file
66
code/seafire/protocol/match.cxx
Normal 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
|
53
code/seafire/protocol/match.hxx
Normal file
53
code/seafire/protocol/match.hxx
Normal 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
|
140
code/seafire/protocol/media-type.cxx
Normal file
140
code/seafire/protocol/media-type.cxx
Normal 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
|
77
code/seafire/protocol/media-type.hxx
Normal file
77
code/seafire/protocol/media-type.hxx
Normal 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
|
125
code/seafire/protocol/media-type.txx
Normal file
125
code/seafire/protocol/media-type.txx
Normal 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
|
89
code/seafire/protocol/message.cxx
Normal file
89
code/seafire/protocol/message.cxx
Normal 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
|
86
code/seafire/protocol/message.hxx
Normal file
86
code/seafire/protocol/message.hxx
Normal 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
|
66
code/seafire/protocol/message.txx
Normal file
66
code/seafire/protocol/message.txx
Normal 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
|
71
code/seafire/protocol/protocol-version.cxx
Normal file
71
code/seafire/protocol/protocol-version.cxx
Normal 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
|
66
code/seafire/protocol/protocol-version.hxx
Normal file
66
code/seafire/protocol/protocol-version.hxx
Normal 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
|
225
code/seafire/protocol/read-content.cxx
Normal file
225
code/seafire/protocol/read-content.cxx
Normal 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
|
74
code/seafire/protocol/read-content.hxx
Normal file
74
code/seafire/protocol/read-content.hxx
Normal 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
|
171
code/seafire/protocol/read.cxx
Normal file
171
code/seafire/protocol/read.cxx
Normal 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
|
54
code/seafire/protocol/read.hxx
Normal file
54
code/seafire/protocol/read.hxx
Normal 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
|
56
code/seafire/protocol/reason.cxx
Normal file
56
code/seafire/protocol/reason.cxx
Normal 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
|
12
code/seafire/protocol/reason.hxx
Normal file
12
code/seafire/protocol/reason.hxx
Normal 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
|
149
code/seafire/protocol/request.cxx
Normal file
149
code/seafire/protocol/request.cxx
Normal 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
|
62
code/seafire/protocol/request.hxx
Normal file
62
code/seafire/protocol/request.hxx
Normal 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
|
101
code/seafire/protocol/response.cxx
Normal file
101
code/seafire/protocol/response.cxx
Normal 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
|
44
code/seafire/protocol/response.hxx
Normal file
44
code/seafire/protocol/response.hxx
Normal 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
|
88
code/seafire/protocol/rfc7230/connection.cxx
Normal file
88
code/seafire/protocol/rfc7230/connection.cxx
Normal 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
|
51
code/seafire/protocol/rfc7230/connection.hxx
Normal file
51
code/seafire/protocol/rfc7230/connection.hxx
Normal 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
|
28
code/seafire/protocol/rfc7230/content-length.cxx
Normal file
28
code/seafire/protocol/rfc7230/content-length.cxx
Normal 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
|
29
code/seafire/protocol/rfc7230/content-length.hxx
Normal file
29
code/seafire/protocol/rfc7230/content-length.hxx
Normal 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
|
95
code/seafire/protocol/rfc7230/host.cxx
Normal file
95
code/seafire/protocol/rfc7230/host.cxx
Normal 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
|
43
code/seafire/protocol/rfc7230/host.hxx
Normal file
43
code/seafire/protocol/rfc7230/host.hxx
Normal 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
|
16
code/seafire/protocol/rfc7231/accept.cxx
Normal file
16
code/seafire/protocol/rfc7231/accept.cxx
Normal 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
|
27
code/seafire/protocol/rfc7231/accept.hxx
Normal file
27
code/seafire/protocol/rfc7231/accept.hxx
Normal 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
|
36
code/seafire/protocol/rfc7231/allow.hxx
Normal file
36
code/seafire/protocol/rfc7231/allow.hxx
Normal 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
|
17
code/seafire/protocol/rfc7231/content-type.cxx
Normal file
17
code/seafire/protocol/rfc7231/content-type.cxx
Normal 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
|
28
code/seafire/protocol/rfc7231/content-type.hxx
Normal file
28
code/seafire/protocol/rfc7231/content-type.hxx
Normal 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
|
43
code/seafire/protocol/rfc7231/date.hxx
Normal file
43
code/seafire/protocol/rfc7231/date.hxx
Normal 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
|
28
code/seafire/protocol/rfc7231/location.hxx
Normal file
28
code/seafire/protocol/rfc7231/location.hxx
Normal 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
|
112
code/seafire/protocol/rfc7231/media-range.cxx
Normal file
112
code/seafire/protocol/rfc7231/media-range.cxx
Normal 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
|
60
code/seafire/protocol/rfc7231/media-range.hxx
Normal file
60
code/seafire/protocol/rfc7231/media-range.hxx
Normal 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
|
37
code/seafire/protocol/rfc7231/media-range.txx
Normal file
37
code/seafire/protocol/rfc7231/media-range.txx
Normal 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
|
96
code/seafire/protocol/rfc7231/product.cxx
Normal file
96
code/seafire/protocol/rfc7231/product.cxx
Normal 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
|
77
code/seafire/protocol/rfc7231/product.hxx
Normal file
77
code/seafire/protocol/rfc7231/product.hxx
Normal 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
|
115
code/seafire/protocol/rfc7231/product.txx
Normal file
115
code/seafire/protocol/rfc7231/product.txx
Normal 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
|
42
code/seafire/protocol/rfc7231/server.hxx
Normal file
42
code/seafire/protocol/rfc7231/server.hxx
Normal 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
|
76
code/seafire/protocol/rfc7232/entity-tag.cxx
Normal file
76
code/seafire/protocol/rfc7232/entity-tag.cxx
Normal 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
|
72
code/seafire/protocol/rfc7232/entity-tag.hxx
Normal file
72
code/seafire/protocol/rfc7232/entity-tag.hxx
Normal 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
|
55
code/seafire/protocol/rfc7232/entity-tag.txx
Normal file
55
code/seafire/protocol/rfc7232/entity-tag.txx
Normal 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
|
18
code/seafire/protocol/rfc7232/etag.hxx
Normal file
18
code/seafire/protocol/rfc7232/etag.hxx
Normal 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
|
55
code/seafire/protocol/rfc7232/if-match.hxx
Normal file
55
code/seafire/protocol/rfc7232/if-match.hxx
Normal 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
|
115
code/seafire/protocol/rfc7232/if-match.ixx
Normal file
115
code/seafire/protocol/rfc7232/if-match.ixx
Normal 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
|
38
code/seafire/protocol/rfc7232/if-modified-since.hxx
Normal file
38
code/seafire/protocol/rfc7232/if-modified-since.hxx
Normal 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
|
34
code/seafire/protocol/rfc7232/if-none-match.hxx
Normal file
34
code/seafire/protocol/rfc7232/if-none-match.hxx
Normal 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
|
38
code/seafire/protocol/rfc7232/if-unmodified-since.hxx
Normal file
38
code/seafire/protocol/rfc7232/if-unmodified-since.hxx
Normal 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
|
39
code/seafire/protocol/rfc7232/last-modified.hxx
Normal file
39
code/seafire/protocol/rfc7232/last-modified.hxx
Normal 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
|
157
code/seafire/protocol/status-code.cxx
Normal file
157
code/seafire/protocol/status-code.cxx
Normal 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
|
71
code/seafire/protocol/status-code.hxx
Normal file
71
code/seafire/protocol/status-code.hxx
Normal 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
|
80
code/seafire/protocol/token.cxx
Normal file
80
code/seafire/protocol/token.cxx
Normal 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
|
170
code/seafire/protocol/token.hxx
Normal file
170
code/seafire/protocol/token.hxx
Normal 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
|
70
code/seafire/protocol/traits.hxx
Normal file
70
code/seafire/protocol/traits.hxx
Normal 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
|
37
code/seafire/protocol/version.hxx.in
Normal file
37
code/seafire/protocol/version.hxx.in
Normal 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
|
174
code/seafire/protocol/write.cxx
Normal file
174
code/seafire/protocol/write.cxx
Normal 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
|
82
code/seafire/protocol/write.hxx
Normal file
82
code/seafire/protocol/write.hxx
Normal 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
14
manifest
Normal 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
15
repositories.manifest
Normal 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
8
tests/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Test executables.
|
||||
#
|
||||
driver
|
||||
|
||||
# Testscript output directories (can be symlinks).
|
||||
#
|
||||
test
|
||||
test-*
|
4
tests/build/.gitignore
vendored
Normal file
4
tests/build/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/config.build
|
||||
/root/
|
||||
/bootstrap/
|
||||
build/
|
5
tests/build/bootstrap.build
Normal file
5
tests/build/bootstrap.build
Normal file
@ -0,0 +1,5 @@
|
||||
project = # Unnamed tests subproject.
|
||||
|
||||
using config
|
||||
using test
|
||||
using dist
|
16
tests/build/root.build
Normal file
16
tests/build/root.build
Normal file
@ -0,0 +1,16 @@
|
||||
cxx.std = latest
|
||||
|
||||
using cxx
|
||||
|
||||
hxx{*}: extension = hxx
|
||||
ixx{*}: extension = ixx
|
||||
txx{*}: extension = txx
|
||||
cxx{*}: extension = cxx
|
||||
|
||||
# Every exe{} in this subproject is by default a test.
|
||||
#
|
||||
exe{*}: test = true
|
||||
|
||||
# The test target for cross-testing (running tests under Wine, etc).
|
||||
#
|
||||
test.target = $cxx.target
|
1
tests/buildfile
Normal file
1
tests/buildfile
Normal file
@ -0,0 +1 @@
|
||||
./: {*/ -build/}
|
Loading…
x
Reference in New Issue
Block a user