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