2018-02-04 03:22:01 +01:00
|
|
|
// Matrix Construct
|
|
|
|
//
|
|
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice is present in all copies. The
|
|
|
|
// full license for this software is available in the LICENSE file.
|
2016-11-29 16:23:38 +01:00
|
|
|
|
|
|
|
#pragma once
|
|
|
|
#define HAVE_IRCD_HTTP_H
|
|
|
|
|
2017-09-12 18:37:44 +02:00
|
|
|
/// HyperText TransPort: formal grammars & tools
|
2017-08-28 23:51:22 +02:00
|
|
|
namespace ircd::http
|
|
|
|
{
|
2018-03-11 18:23:06 +01:00
|
|
|
enum code :ushort;
|
2017-08-28 23:51:22 +02:00
|
|
|
struct error;
|
|
|
|
struct line;
|
|
|
|
struct query;
|
2017-12-24 22:25:09 +01:00
|
|
|
struct header;
|
2017-08-28 23:51:22 +02:00
|
|
|
struct headers;
|
|
|
|
struct request;
|
|
|
|
struct response;
|
|
|
|
|
2018-03-05 09:37:05 +01:00
|
|
|
string_view status(const code &);
|
|
|
|
code status(const string_view &);
|
2017-12-24 22:25:09 +01:00
|
|
|
|
2018-02-08 06:30:20 +01:00
|
|
|
void writeline(window_buffer &);
|
|
|
|
void writeline(window_buffer &, const window_buffer::closure &);
|
2018-03-19 04:22:22 +01:00
|
|
|
void write(window_buffer &, const header &);
|
|
|
|
void write(window_buffer &, const vector_view<const header> &);
|
2017-12-24 22:25:09 +01:00
|
|
|
size_t serialized(const vector_view<const header> &);
|
|
|
|
std::string strung(const vector_view<const header> &);
|
2018-03-22 07:20:33 +01:00
|
|
|
void writechunk(window_buffer &, const uint32_t &size);
|
|
|
|
const_buffer writechunk(const mutable_buffer &, const uint32_t &size);
|
2018-12-31 20:06:18 +01:00
|
|
|
bool has(const headers &, const string_view &key);
|
|
|
|
bool has(const vector_view<const header> &, const string_view &key);
|
2017-08-28 23:51:22 +02:00
|
|
|
}
|
2016-11-29 16:23:38 +01:00
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Root exception for HTTP.
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::error
|
2016-11-29 16:23:38 +01:00
|
|
|
:ircd::error
|
|
|
|
{
|
|
|
|
std::string content;
|
2017-12-24 22:25:09 +01:00
|
|
|
std::string headers;
|
2018-03-25 00:40:47 +01:00
|
|
|
http::code code {http::code(0)};
|
2016-11-29 16:23:38 +01:00
|
|
|
|
2018-03-25 00:40:47 +01:00
|
|
|
explicit operator bool() const { return code != http::code(0); }
|
|
|
|
bool operator!() const { return !bool(*this); }
|
|
|
|
|
|
|
|
error() = default;
|
2018-03-05 09:37:05 +01:00
|
|
|
error(const http::code &, std::string content = {}, std::string headers = {});
|
|
|
|
error(const http::code &, std::string content, const vector_view<const header> &);
|
2019-09-28 23:12:53 +02:00
|
|
|
template<class... args> error(const string_view &fmt, const http::code &, args&&...);
|
2020-02-28 21:12:51 +01:00
|
|
|
~error() noexcept;
|
2016-11-29 16:23:38 +01:00
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents a single \r\n delimited line used in HTTP.
|
|
|
|
///
|
|
|
|
/// This object is just a string_view of that line. The actual data backing
|
|
|
|
/// that view is the responsibility of the user. This object is constructed
|
|
|
|
/// with an ircd::parse::capstan argument which is used by the formal grammar
|
|
|
|
/// in the constructor.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::line
|
2016-11-29 16:23:38 +01:00
|
|
|
:string_view
|
|
|
|
{
|
|
|
|
struct request;
|
|
|
|
struct response;
|
|
|
|
|
2019-06-29 03:23:43 +02:00
|
|
|
static const string_view terminator; // "\r\n"
|
|
|
|
|
2016-11-29 16:23:38 +01:00
|
|
|
using string_view::string_view;
|
2017-03-13 23:24:42 +01:00
|
|
|
line(parse::capstan &);
|
2016-11-29 16:23:38 +01:00
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents a 'request line' or the first line a client sends to a server.
|
|
|
|
///
|
|
|
|
/// This is a dual-use class. For HTTP clients, one may simply connect the
|
|
|
|
/// members to the proper strings and then pass this structure to a function
|
|
|
|
/// making a client request. For HTTP servers, pass an http::line to the ctor
|
|
|
|
/// and the formal grammar will set the members appropriately. The actual data
|
|
|
|
/// behind these members is the responsibility of the user.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::line::request
|
2016-11-29 16:23:38 +01:00
|
|
|
{
|
|
|
|
string_view method;
|
2017-04-03 06:00:12 +02:00
|
|
|
string_view path;
|
|
|
|
string_view query;
|
|
|
|
string_view fragment;
|
2016-11-29 16:23:38 +01:00
|
|
|
string_view version;
|
|
|
|
|
2019-01-12 20:52:56 +01:00
|
|
|
operator string_view() const; // full view of line
|
|
|
|
|
2016-11-29 16:23:38 +01:00
|
|
|
request(const line &);
|
|
|
|
request() = default;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents a 'response line' or the first line a server sends to a client.
|
|
|
|
///
|
|
|
|
/// This is a dual-use class and symmetric to the http::line::request class.
|
|
|
|
/// Servers may set the members and then use this object to respond to a client
|
|
|
|
/// while clients should provide an http::line to the constructor which will
|
|
|
|
/// fill in the members.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::line::response
|
2016-11-29 16:23:38 +01:00
|
|
|
{
|
|
|
|
string_view version;
|
|
|
|
string_view status;
|
|
|
|
string_view reason;
|
|
|
|
|
|
|
|
response(const line &);
|
|
|
|
response() = default;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents a single key/value pair in a query string.
|
|
|
|
///
|
|
|
|
/// This is used by the ircd::http::query::string object when parsing query
|
|
|
|
/// strings.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::query
|
2017-04-03 06:00:12 +02:00
|
|
|
:std::pair<string_view, string_view>
|
|
|
|
{
|
|
|
|
struct string;
|
|
|
|
|
|
|
|
bool operator<(const string_view &s) const { return iless(first, s); }
|
|
|
|
bool operator==(const string_view &s) const { return iequals(first, s); }
|
|
|
|
|
|
|
|
using std::pair<string_view, string_view>::pair;
|
|
|
|
query() = default;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Tool for parsing an HTTP query string.
|
|
|
|
///
|
|
|
|
/// Query string is read as a complete string off the tape (into request.query)
|
|
|
|
/// and not parsed further. To make queries into that string use this class to
|
|
|
|
/// view it. Once this object is constructed by viewing the whole query string,
|
|
|
|
/// the member functions invoke the formal grammar to get individual key/value
|
|
|
|
/// pairs.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::query::string
|
2017-04-03 06:00:12 +02:00
|
|
|
:string_view
|
|
|
|
{
|
2018-08-31 03:58:59 +02:00
|
|
|
using closure = std::function<bool (const query &)>;
|
2017-04-03 06:00:12 +02:00
|
|
|
|
2018-08-31 03:58:59 +02:00
|
|
|
bool for_each(const closure &) const;
|
2019-03-07 02:17:59 +01:00
|
|
|
bool for_each(const string_view &key, const closure &) const;
|
|
|
|
|
|
|
|
string_view _get(const string_view &key, size_t idx = 0) const;
|
|
|
|
template<class T = string_view> T get(const string_view &key, const T &def = {}, const size_t &idx = 0) const;
|
2017-04-03 06:00:12 +02:00
|
|
|
string_view operator[](const string_view &key) const;
|
2019-03-07 02:17:59 +01:00
|
|
|
|
|
|
|
string_view at(const string_view &key, const size_t &idx = 0) const;
|
|
|
|
template<class T> T at(const string_view &key, const size_t &idx = 0) const;
|
|
|
|
|
2020-04-22 04:41:29 +02:00
|
|
|
vector_view<string_view> array(const mutable_buffer &, const string_view &key, string_view *const &, const size_t &) const;
|
|
|
|
template<size_t MAX> vector_view<string_view> array(const mutable_buffer &, const string_view &key, string_view (&)[MAX]) const;
|
|
|
|
|
2019-03-07 02:17:59 +01:00
|
|
|
size_t count(const string_view &key) const;
|
|
|
|
bool has(const string_view &key) const;
|
2017-04-03 06:00:12 +02:00
|
|
|
|
|
|
|
using string_view::string_view;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents an HTTP header key/value pair.
|
|
|
|
///
|
|
|
|
/// This is a dual-use class. Those sending headers will simply fill in the
|
|
|
|
/// components of the std::pair. Those receiving headers can pass the ctor an
|
|
|
|
/// ircd::http::line which will construct the pair using the formal grammars.
|
|
|
|
///
|
2017-12-24 22:25:09 +01:00
|
|
|
struct ircd::http::header
|
2016-11-29 16:23:38 +01:00
|
|
|
:std::pair<string_view, string_view>
|
|
|
|
{
|
|
|
|
bool operator<(const string_view &s) const { return iless(first, s); }
|
|
|
|
bool operator==(const string_view &s) const { return iequals(first, s); }
|
2018-12-31 20:06:18 +01:00
|
|
|
bool operator!=(const string_view &s) const { return !operator==(s); }
|
2016-11-29 16:23:38 +01:00
|
|
|
|
|
|
|
using std::pair<string_view, string_view>::pair;
|
|
|
|
header(const line &);
|
|
|
|
header() = default;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// This device allows parsing HTTP headers directly off the wire without state
|
|
|
|
///
|
|
|
|
/// The constructor of this object contains the grammar to read HTTP headers
|
|
|
|
/// from the capstan and then proffer them one by one to the provided closure,
|
|
|
|
/// that's all it does.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::headers
|
2017-10-12 03:00:33 +02:00
|
|
|
:string_view
|
2016-11-29 16:23:38 +01:00
|
|
|
{
|
2017-09-30 02:11:23 +02:00
|
|
|
using closure = std::function<void (const header &)>;
|
2018-12-31 20:06:18 +01:00
|
|
|
using closure_bool = std::function<bool (const header &)>;
|
2016-11-29 16:23:38 +01:00
|
|
|
|
2019-06-29 03:23:43 +02:00
|
|
|
static const string_view terminator; // "\r\n\r\n"
|
|
|
|
|
2018-12-31 20:06:18 +01:00
|
|
|
bool for_each(const closure_bool &) const;
|
|
|
|
string_view operator[](const string_view &key) const;
|
|
|
|
string_view at(const string_view &key) const;
|
|
|
|
bool has(const string_view &key) const;
|
|
|
|
|
|
|
|
using string_view::string_view;
|
|
|
|
headers(parse::capstan &, closure_bool);
|
2017-03-13 23:24:42 +01:00
|
|
|
headers(parse::capstan &, const closure & = {});
|
2018-12-31 20:06:18 +01:00
|
|
|
headers() = default;
|
|
|
|
|
|
|
|
friend bool has(const headers &, const string_view &key);
|
|
|
|
friend bool has(const vector_view<const header> &, const string_view &key);
|
2016-11-29 16:23:38 +01:00
|
|
|
};
|
|
|
|
|
2017-12-12 21:38:16 +01:00
|
|
|
/// HTTP request suite. Functionality to send and receive requests.
|
|
|
|
///
|
|
|
|
struct ircd::http::request
|
|
|
|
{
|
|
|
|
struct head;
|
|
|
|
|
2018-01-12 22:15:30 +01:00
|
|
|
// compose a request into buffer
|
2018-02-08 06:30:20 +01:00
|
|
|
request(window_buffer &,
|
2017-12-23 01:40:44 +01:00
|
|
|
const string_view &host,
|
2017-12-12 21:38:16 +01:00
|
|
|
const string_view &method = "GET",
|
2018-01-20 11:29:03 +01:00
|
|
|
const string_view &uri = "/",
|
2017-12-23 01:40:44 +01:00
|
|
|
const size_t &content_length = 0,
|
|
|
|
const string_view &content_type = {},
|
|
|
|
const vector_view<const header> & = {},
|
|
|
|
const bool &termination = true);
|
2017-12-12 21:38:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Represents an HTTP request head. This is only for receiving requests.
|
|
|
|
///
|
|
|
|
struct ircd::http::request::head
|
|
|
|
:line::request
|
|
|
|
{
|
|
|
|
string_view host;
|
|
|
|
string_view expect;
|
|
|
|
string_view te;
|
|
|
|
string_view authorization;
|
|
|
|
string_view connection;
|
2018-01-12 22:15:30 +01:00
|
|
|
string_view content_type;
|
|
|
|
string_view user_agent;
|
2019-05-31 06:48:40 +02:00
|
|
|
string_view upgrade;
|
2019-06-15 21:48:01 +02:00
|
|
|
string_view range;
|
|
|
|
string_view if_range;
|
2020-04-23 08:37:53 +02:00
|
|
|
string_view forwarded_for;
|
2017-12-12 21:38:16 +01:00
|
|
|
size_t content_length {0};
|
|
|
|
|
2018-01-21 09:38:47 +01:00
|
|
|
string_view uri; // full view of (path, query, fragmet)
|
|
|
|
string_view headers; // full view of all headers
|
2017-12-12 21:38:16 +01:00
|
|
|
|
2019-01-12 20:52:56 +01:00
|
|
|
// full view of all head (request line and headers)
|
|
|
|
operator string_view() const;
|
|
|
|
|
2017-12-12 21:38:16 +01:00
|
|
|
head(parse::capstan &pc, const headers::closure &c = {});
|
2018-01-12 22:15:30 +01:00
|
|
|
head() = default;
|
2017-12-12 21:38:16 +01:00
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// HTTP response suite. Functionality to send and receive responses.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::response
|
2016-11-29 16:23:38 +01:00
|
|
|
{
|
|
|
|
struct head;
|
2018-03-19 22:40:04 +01:00
|
|
|
struct chunk;
|
2017-03-13 22:07:58 +01:00
|
|
|
|
2018-01-12 22:15:30 +01:00
|
|
|
// compose a response into buffer
|
2018-02-08 06:30:20 +01:00
|
|
|
response(window_buffer &,
|
2018-05-21 02:37:50 +02:00
|
|
|
const code &,
|
2017-12-23 01:40:44 +01:00
|
|
|
const size_t &content_length = 0,
|
|
|
|
const string_view &content_type = {},
|
2018-12-31 20:45:04 +01:00
|
|
|
const http::headers &headers = {},
|
2017-12-23 01:40:44 +01:00
|
|
|
const vector_view<const header> & = {},
|
|
|
|
const bool &termination = true);
|
2017-03-13 22:07:58 +01:00
|
|
|
|
2017-09-28 03:32:31 +02:00
|
|
|
response() = default;
|
|
|
|
};
|
|
|
|
|
2017-10-16 06:35:25 +02:00
|
|
|
/// Represents an HTTP response head. This is for receiving responses only.
|
|
|
|
///
|
2017-08-28 23:51:22 +02:00
|
|
|
struct ircd::http::response::head
|
2017-03-13 22:07:58 +01:00
|
|
|
:line::response
|
2016-11-29 16:23:38 +01:00
|
|
|
{
|
2018-02-26 13:00:36 +01:00
|
|
|
string_view content_type;
|
2019-06-15 21:48:01 +02:00
|
|
|
size_t content_length {0};
|
|
|
|
string_view content_range;
|
|
|
|
string_view accept_range;
|
2017-09-30 02:11:23 +02:00
|
|
|
string_view transfer_encoding;
|
2018-01-24 18:15:16 +01:00
|
|
|
string_view server;
|
2020-03-12 00:32:55 +01:00
|
|
|
string_view location;
|
2017-03-13 22:07:58 +01:00
|
|
|
|
2017-10-12 03:00:33 +02:00
|
|
|
string_view headers;
|
|
|
|
|
2020-03-13 19:07:39 +01:00
|
|
|
head(parse::capstan &pc, const headers::closure &c);
|
|
|
|
head(parse::capstan &pc);
|
2018-01-14 03:03:26 +01:00
|
|
|
head() = default;
|
2016-11-29 16:23:38 +01:00
|
|
|
};
|
2018-03-16 19:11:02 +01:00
|
|
|
|
2018-03-19 22:40:04 +01:00
|
|
|
struct ircd::http::response::chunk
|
|
|
|
:line
|
|
|
|
{
|
2020-05-06 02:32:41 +02:00
|
|
|
uint32_t size {0};
|
2018-03-19 22:40:04 +01:00
|
|
|
|
|
|
|
chunk(parse::capstan &pc);
|
|
|
|
chunk() = default;
|
|
|
|
};
|
|
|
|
|
2018-05-21 02:37:50 +02:00
|
|
|
//
|
|
|
|
// Add more as you go...
|
|
|
|
//
|
|
|
|
enum ircd::http::code
|
|
|
|
:ushort
|
|
|
|
{
|
|
|
|
CONTINUE = 100,
|
|
|
|
SWITCHING_PROTOCOLS = 101,
|
2020-03-12 02:18:53 +01:00
|
|
|
PROCESSING = 102,
|
|
|
|
EARLY_HINTS = 103,
|
2018-05-21 02:37:50 +02:00
|
|
|
|
|
|
|
OK = 200,
|
|
|
|
CREATED = 201,
|
|
|
|
ACCEPTED = 202,
|
|
|
|
NON_AUTHORITATIVE_INFORMATION = 203,
|
|
|
|
NO_CONTENT = 204,
|
|
|
|
PARTIAL_CONTENT = 206,
|
|
|
|
|
|
|
|
MULTIPLE_CHOICES = 300,
|
|
|
|
MOVED_PERMANENTLY = 301,
|
|
|
|
FOUND = 302,
|
|
|
|
SEE_OTHER = 303,
|
|
|
|
NOT_MODIFIED = 304,
|
2019-02-19 20:34:48 +01:00
|
|
|
USE_PROXY = 305,
|
|
|
|
SWITCH_PROXY = 306,
|
|
|
|
TEMPORARY_REDIRECT = 307,
|
|
|
|
PERMANENT_REDIRECT = 308,
|
2018-05-21 02:37:50 +02:00
|
|
|
|
|
|
|
BAD_REQUEST = 400,
|
|
|
|
UNAUTHORIZED = 401,
|
|
|
|
FORBIDDEN = 403,
|
|
|
|
NOT_FOUND = 404,
|
|
|
|
METHOD_NOT_ALLOWED = 405,
|
|
|
|
NOT_ACCEPTABLE = 406,
|
|
|
|
REQUEST_TIMEOUT = 408,
|
|
|
|
CONFLICT = 409,
|
2019-09-09 03:29:19 +02:00
|
|
|
GONE = 410,
|
2018-05-21 02:37:50 +02:00
|
|
|
LENGTH_REQUIRED = 411,
|
|
|
|
PAYLOAD_TOO_LARGE = 413,
|
|
|
|
REQUEST_URI_TOO_LONG = 414,
|
|
|
|
UNSUPPORTED_MEDIA_TYPE = 415,
|
2019-06-15 21:50:53 +02:00
|
|
|
RANGE_NOT_SATISFIABLE = 416,
|
2018-05-21 02:37:50 +02:00
|
|
|
EXPECTATION_FAILED = 417,
|
|
|
|
IM_A_TEAPOT = 418,
|
|
|
|
UNPROCESSABLE_ENTITY = 422,
|
2019-03-08 01:01:19 +01:00
|
|
|
PRECONDITION_REQUIRED = 428,
|
2018-05-21 02:37:50 +02:00
|
|
|
TOO_MANY_REQUESTS = 429,
|
|
|
|
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
|
|
|
|
|
|
|
INTERNAL_SERVER_ERROR = 500,
|
|
|
|
NOT_IMPLEMENTED = 501,
|
|
|
|
BAD_GATEWAY = 502,
|
|
|
|
SERVICE_UNAVAILABLE = 503,
|
2018-05-22 00:56:13 +02:00
|
|
|
GATEWAY_TIMEOUT = 504,
|
2018-05-21 02:37:50 +02:00
|
|
|
HTTP_VERSION_NOT_SUPPORTED = 505,
|
|
|
|
INSUFFICIENT_STORAGE = 507,
|
2020-03-21 03:08:39 +01:00
|
|
|
|
|
|
|
CLOUDFLARE_REFUSED = 521,
|
|
|
|
CLOUDFLARE_TIMEDOUT = 522,
|
|
|
|
CLOUDFLARE_UNREACHABLE = 523,
|
|
|
|
CLOUDFLARE_REQUEST_TIMEOUT = 524,
|
2018-05-21 02:37:50 +02:00
|
|
|
};
|
|
|
|
|
2020-04-22 04:41:29 +02:00
|
|
|
template<size_t MAX>
|
|
|
|
inline ircd::vector_view<ircd::string_view>
|
|
|
|
ircd::http::query::string::array(const mutable_buffer &buf,
|
|
|
|
const string_view &key,
|
|
|
|
string_view (&out)[MAX])
|
|
|
|
const
|
|
|
|
{
|
|
|
|
return array(buf, key, out, MAX);
|
|
|
|
}
|
|
|
|
|
2018-03-16 19:11:02 +01:00
|
|
|
template<class T>
|
|
|
|
T
|
|
|
|
ircd::http::query::string::get(const string_view &key,
|
2019-03-07 02:17:59 +01:00
|
|
|
const T &def,
|
|
|
|
const size_t &idx)
|
2018-03-16 19:11:02 +01:00
|
|
|
const try
|
|
|
|
{
|
2019-03-07 02:17:59 +01:00
|
|
|
const auto val(_get(key, idx));
|
2018-03-16 19:11:02 +01:00
|
|
|
return val? lex_cast<T>(val) : def;
|
|
|
|
}
|
|
|
|
catch(const bad_lex_cast &)
|
|
|
|
{
|
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
T
|
2019-03-07 02:17:59 +01:00
|
|
|
ircd::http::query::string::at(const string_view &key,
|
|
|
|
const size_t &idx)
|
2018-03-16 19:11:02 +01:00
|
|
|
const
|
|
|
|
{
|
2019-03-07 02:17:59 +01:00
|
|
|
return lex_cast<T>(at(key, idx));
|
2018-03-16 19:11:02 +01:00
|
|
|
}
|
2019-09-28 23:12:53 +02:00
|
|
|
|
|
|
|
template<class... args>
|
|
|
|
ircd::http::error::error(const string_view &fmt,
|
|
|
|
const http::code &code,
|
|
|
|
args&&... a)
|
|
|
|
:error
|
|
|
|
{
|
|
|
|
code,
|
|
|
|
fmt::snstringf //TODO: XXX fmt::sstringf
|
|
|
|
{
|
|
|
|
3192, fmt, std::forward<args>(a)...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{}
|