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

namespace ircd::http
{
	using reason_codes = std::unordered_map<http::code, string_view>;
	using expectation_failure = spirit::qi::expectation_failure<const char *>;

	[[clang::internal_linkage, clang::always_destroy]]
	extern const reason_codes reason;

	[[noreturn]]
	static void throw_error(const expectation_failure &, const bool & = false);
}

BOOST_FUSION_ADAPT_STRUCT
(
    ircd::http::query,
    ( decltype(ircd::http::query::first),   first  )
    ( decltype(ircd::http::query::second),  second )
)

BOOST_FUSION_ADAPT_STRUCT
(
    ircd::http::header,
    ( decltype(ircd::http::header::first),   first  )
    ( decltype(ircd::http::header::second),  second )
)

BOOST_FUSION_ADAPT_STRUCT
(
    ircd::http::line::response,
    ( decltype(ircd::http::line::response::version),  version )
    ( decltype(ircd::http::line::response::status),   status  )
    ( decltype(ircd::http::line::response::reason),   reason  )
)

BOOST_FUSION_ADAPT_STRUCT
(
    ircd::http::line::request,
    ( decltype(ircd::http::line::request::method),    method   )
    ( decltype(ircd::http::line::request::path),      path     )
    ( decltype(ircd::http::line::request::query),     query    )
    ( decltype(ircd::http::line::request::fragment),  fragment )
    ( decltype(ircd::http::line::request::version),   version  )
)

decltype(ircd::http::reason)
ircd::http::reason
{
	{ code::CONTINUE,                            "Continue"                                        },
	{ code::SWITCHING_PROTOCOLS,                 "Switching Protocols"                             },
	{ code::PROCESSING,                          "Processing"                                      },
	{ code::EARLY_HINTS,                         "Early Hints"                                     },

	{ code::OK,                                  "OK"                                              },
	{ code::CREATED,                             "Created"                                         },
	{ code::ACCEPTED,                            "Accepted"                                        },
	{ code::NON_AUTHORITATIVE_INFORMATION,       "Non-Authoritative Information"                   },
	{ code::NO_CONTENT,                          "No Content"                                      },
	{ code::PARTIAL_CONTENT,                     "Partial Content"                                 },

	{ code::MULTIPLE_CHOICES,                    "Multiple Choices"                                },
	{ code::MOVED_PERMANENTLY,                   "Moved Permanently"                               },
	{ code::FOUND,                               "Found"                                           },
	{ code::SEE_OTHER,                           "See Other"                                       },
	{ code::NOT_MODIFIED,                        "Not Modified"                                    },
	{ code::USE_PROXY,                           "Use Proxy"                                       },
	{ code::SWITCH_PROXY,                        "Switch Proxy"                                    },
	{ code::TEMPORARY_REDIRECT,                  "Temporary Redirect"                              },
	{ code::PERMANENT_REDIRECT,                  "Permanent Redirect"                              },

	{ code::BAD_REQUEST,                         "Bad Request"                                     },
	{ code::UNAUTHORIZED,                        "Unauthorized"                                    },
	{ code::FORBIDDEN,                           "Forbidden"                                       },
	{ code::NOT_FOUND,                           "Not Found"                                       },
	{ code::METHOD_NOT_ALLOWED,                  "Method Not Allowed"                              },
	{ code::NOT_ACCEPTABLE,                      "Not Acceptable"                                  },
	{ code::REQUEST_TIMEOUT,                     "Request Time-out"                                },
	{ code::CONFLICT,                            "Conflict"                                        },
	{ code::GONE,                                "Gone"                                            },
	{ code::LENGTH_REQUIRED,                     "Length Required"                                 },
	{ code::PAYLOAD_TOO_LARGE,                   "Payload Too Large"                               },
	{ code::REQUEST_URI_TOO_LONG,                "Request URI Too Long"                            },
	{ code::UNSUPPORTED_MEDIA_TYPE,              "Unsupported Media Type"                          },
	{ code::RANGE_NOT_SATISFIABLE,               "Range Not Satisfiable"                           },
	{ code::EXPECTATION_FAILED,                  "Expectation Failed"                              },
	{ code::IM_A_TEAPOT,                         "Negative, I Am A Meat Popsicle"                  },
	{ code::UNPROCESSABLE_ENTITY,                "Unprocessable Entity"                            },
	{ code::PRECONDITION_REQUIRED,               "Precondition Required"                           },
	{ code::TOO_MANY_REQUESTS,                   "Too Many Requests"                               },
	{ code::REQUEST_HEADER_FIELDS_TOO_LARGE,     "Request Header Fields Too Large"                 },

	{ code::INTERNAL_SERVER_ERROR,               "Internal Server Error"                           },
	{ code::NOT_IMPLEMENTED,                     "Not Implemented"                                 },
	{ code::BAD_GATEWAY,                         "Bad Gateway"                                     },
	{ code::SERVICE_UNAVAILABLE,                 "Service Unavailable"                             },
	{ code::GATEWAY_TIMEOUT,                     "Gateway Timeout"                                 },
	{ code::HTTP_VERSION_NOT_SUPPORTED,          "HTTP Version Not Supported"                      },
	{ code::INSUFFICIENT_STORAGE,                "Insufficient Storage"                            },

	{ code::CLOUDFLARE_REFUSED,                  "Cloudflare Customer Connection Refused"          },
	{ code::CLOUDFLARE_TIMEDOUT,                 "Cloudflare Customer Connection Timed-out"        },
	{ code::CLOUDFLARE_UNREACHABLE,              "Cloudflare Customer Unreachable"                 },
	{ code::CLOUDFLARE_REQUEST_TIMEOUT,          "Cloudflare Customer Request Time-out"            },
};

namespace ircd::http::parser
{
	using namespace ircd::spirit;

	template<class R = unused_type,
	         class... S>
	struct [[clang::internal_linkage]] rule
	:qi::rule<const char *, R, S...>
	{
		using qi::rule<const char *, R, S...>::rule;
	};

	const expr NUL                     { lit('\0')                                          ,"nul" };

	// insignificant whitespaces
	const expr SP                      { lit('\x20')                                      ,"space" };
	const expr HT                      { lit('\x09')                             ,"horizontal tab" };
	const expr ws                      { SP | HT                                     ,"whitespace" };

	const expr CR                      { lit('\x0D')                            ,"carriage return" };
	const expr LF                      { lit('\x0A')                                  ,"line feed" };
	const expr CRLF                    { CR >> LF                    ,"carriage return, line feed" };

	const expr illegal                 { NUL | CR | LF                                  ,"illegal" };
	const expr colon                   { lit(':')                                         ,"colon" };
	const expr slash                   { lit('/')                               ,"forward solidus" };
	const expr question                { lit('?')                                 ,"question mark" };
	const expr pound                   { lit('#')                                    ,"pound sign" };
	const expr equal                   { lit('=')                                    ,"equal sign" };
	const expr ampersand               { lit('&')                                     ,"ampersand" };

	const rule<string_view> token      { raw[+(char_ - (illegal | ws))]                   ,"token" };
	const rule<string_view> str        { raw[+(char_ - illegal)]                         ,"string" };
	const rule<string_view> line       { *ws >> -str >> CRLF                               ,"line" };

	const rule<string_view> status     { raw[repeat(3)[char_('0','9')]]                  ,"status" };
	const rule<string_view> reason     { str                                             ,"reason" };

	const rule<string_view> head_key   { raw[+(char_ - (illegal | ws | colon))]        ,"head key" };
	const rule<string_view> head_val   { -str                                        ,"head value" };
	const rule<http::header> header    { head_key >> *ws >> colon >> *ws >> head_val     ,"header" };
	const rule<> headers               { header % (*ws >> CRLF)                         ,"headers" };

	const expr query_terminator        { equal | question | ampersand | pound  ,"query terminator" };
	const expr query_illegal           { illegal | ws | query_terminator          ,"query illegal" };
	const rule<string_view> query_key  { raw[+(char_ - query_illegal)]                ,"query key" };
	const rule<string_view> query_val  { raw[*(char_ - query_illegal)]              ,"query value" };

	const rule<string_view> method     { token                                           ,"method" };
	const rule<string_view> path       { raw[-slash >> *(char_ - query_illegal)]           ,"path" };
	const rule<string_view> fragment   { pound >> -token                               ,"fragment" };
	const rule<string_view> version    { token                                          ,"version" };

	const rule<size_t> content_size    { ulong_                                  ,"content length" };
	const rule<uint32_t> chunk_size
	{
		qi::uint_parser<uint32_t, 16, 1, 8>{}
		,"chunk size"
	};

	const rule<string_view> chunk_extensions
	{
		';' >> raw[str]             //TODO: extensions
		,"chunk extensions"
	};

	const rule<http::query> query
	{
		query_key >> -(equal >> query_val)
		,"query"
	};

	const rule<string_view> query_string
	{
		question >> -raw[(query_key >> -(equal >> query_val)) % ampersand]
		,"query string"
	};

	const rule<line::request> request_line
	{
		method >> +SP >> path >> -query_string >> -fragment >> +SP >> version
		,"request line"
	};

	const rule<line::response> response_line
	{
		version >> +SP >> status >> -(+SP >> -reason)
		,"response line"
	};

	const rule<> request
	{
		request_line >> *ws >> CRLF >> -headers >> CRLF
		,"request"
	};

	const rule<> response
	{
		response_line >> *ws >> CRLF >> -headers >> CRLF
		,"response"
	};

	static size_t content_length(const string_view &val);
}

/// Compose a request. This prints an HTTP head into the buffer. No real IO is
/// done here. After composing into the buffer, the user can then drive the
/// socket by sending the header and the content as specified.
///
/// If termination is false, no extra CRLF is printed to the buffer allowing
/// additional headers not specified to be appended later.
ircd::http::request::request(window_buffer &out,
                             const string_view &host,
                             const string_view &method,
                             const string_view &uri,
                             const size_t &content_length,
                             const string_view &content_type,
                             const vector_view<const header> &headers,
                             const bool &termination)
{
	writeline(out, [&method, &uri](const mutable_buffer &out) -> size_t
	{
		assert(!method.empty());
		assert(!uri.empty());
		return fmt::sprintf
		{
			out, "%s %s HTTP/1.1", method, uri
		};
	});

	if(!has(headers, "host"))
		writeline(out, [&host](const mutable_buffer &out) -> size_t
		{
			assert(!host.empty());
			return fmt::sprintf
			{
				out, "Host: %s", host
			};
		});

	if(content_length && !has(headers, "content-type"))
		writeline(out, [&content_type](const mutable_buffer &out) -> size_t
		{
			return fmt::sprintf
			{
				out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8"
			};
		});

	if(!has(headers, "content-length"))
		writeline(out, [&content_length](const mutable_buffer &out) -> size_t
		{
			return fmt::sprintf
			{
				out, "Content-Length: %zu", content_length
			};
		});

	// Note: pass your own user-agent with an empty value to mute this header.
	if(!has(headers, "user-agent") && info::user_agent)
		writeline(out, [](const mutable_buffer &out) -> size_t
		{
			return fmt::sprintf
			{
				out, "User-Agent: %s", info::user_agent
			};
		});

	write(out, headers);

	if(termination)
		writeline(out);
}

namespace ircd::http
{
	static void assign(request::head &, const header &);
}

ircd::http::request::head::head(parse::capstan &pc,
                                const headers::closure &closure)
:line::request{pc}
,uri
{
	fragment? string_view { begin(path), end(fragment) }:
	query?    string_view { begin(path), end(query)    }:
	          string_view { begin(path), end(path)     }
}
,headers{[this, &pc, &closure]
{
	const bool version_compatible
	{
		this->version == "HTTP/1.0" ||
		this->version == "HTTP/1.1"
	};

	if(unlikely(!version_compatible))
		throw error
		{
			HTTP_VERSION_NOT_SUPPORTED, fmt::snstringf
			{
				128, "Sorry, no speak '%s'. Try HTTP/1.1 here.",
				this->version
			}
		};

	return http::headers
	{
		pc, [this, &closure](const auto &header)
		{
			assign(*this, header);
			if(likely(closure))
				closure(header);
		}
	};
}()}
{
}

ircd::http::request::head::operator
string_view()
const
{
	const string_view request_line
	{
		static_cast<const line::request &>(*this)
	};

	if(request_line.empty() || headers.empty())
		return request_line;

	return string_view
	{
		request_line.begin(), headers.end()
	};
}

void
ircd::http::assign(request::head &head,
                   const header &header)
{
	char buf[64];
	const auto &[key_, val] {header};
	assert(size(key_) <= sizeof(buf));
	const auto &key
	{
		tolower(buf, key_)
	};

	if(key == "content-length"_sv)
		head.content_length = parser::content_length(val);

	else if(key == "host"_sv)
		head.host = val;

	else if(key == "expect"_sv)
		head.expect = val;

	else if(key == "te"_sv)
		head.te = val;

	else if(key == "authorization"_sv)
		head.authorization = val;

	else if(key == "connection"_sv)
		head.connection = val;

	else if(key == "content-type"_sv)
		head.content_type = val;

	else if(key == "user-agent"_sv)
		head.user_agent = val;

	else if(key == "upgrade"_sv)
		head.upgrade = val;

	else if(key == "range"_sv)
		head.range = val;

	else if(key == "if-range"_sv)
		head.if_range = val;

	else if(key == "forwarded"_sv)
		head.forwarded[0] = val;

	else if(key == "x-forwarded-for"_sv)
		head.forwarded_for[0] = val;

	else if(key == "x-forwarded-host"_sv)
		head.forwarded_host[0] = val;
}

ircd::http::response::response(window_buffer &out,
                               const code &code,
                               const size_t &content_length,
                               const string_view &content_type,
                               const http::headers &headers_s,
                               const vector_view<const header> &headers_v,
                               const bool &termination)
{
	const auto has_header{[&headers_s, &headers_v]
	(const string_view &key) -> bool
	{
		return has(headers_v, key) || has(headers_s, key);
	}};

	writeline(out, [&code](const mutable_buffer &out) -> size_t
	{
		return fmt::sprintf
		{
			out, "HTTP/1.1 %u %s", uint(code), status(code)
		};
	});

	const bool write_server_header
	{
		code >= 200 && code < 300
		&& !has_header("server")
	};

	if(write_server_header)
		writeline(out, [&code](const mutable_buffer &out) -> size_t
		{
			size_t ret{0};
			ret += copy(out, "Server: "_sv);
			ret += copy(out + ret, ircd::info::server_agent);
			return ret;
		});

	const bool write_date_header
	{
		code < 400
		&& !has_header("date")
	};

	if(write_date_header)
		writeline(out, [](const mutable_buffer &out) -> size_t
		{
			char date_buf[96];
			return fmt::sprintf
			{
				out, "Date: %s", timef(date_buf, ircd::localtime)
			};
		});

	const bool write_content_type_header
	{
		code != NO_CONTENT
		&& content_type && content_length
		&& !has_header("content-type")
	};

	if(write_content_type_header)
		writeline(out, [&content_type](const mutable_buffer &out) -> size_t
		{
			return fmt::sprintf
			{
				out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8"
			};
		});

	const bool write_content_length_header
	{
		code != NO_CONTENT
		&& content_length != size_t(-1) // chunked encoding indication
		&& !has_header("content-length")
	};

	if(write_content_length_header)
		writeline(out, [&content_length](const mutable_buffer &out) -> size_t
		{
			return fmt::sprintf
			{
				out, "Content-Length: %zu", content_length
			};
		});

	const bool write_transfer_encoding_chunked
	{
		content_length == size_t(-1)
		&& !has_header("transfer-encoding")
	};

	if(write_transfer_encoding_chunked)
		writeline(out, [&content_length](const mutable_buffer &out) -> size_t
		{
			return copy(out, "Transfer-Encoding: chunked"_sv);
		});

	if(!headers_s.empty())
		out([&headers_s](const mutable_buffer &out)
		{
			assert(endswith(headers_s, "\r\n"));
			return copy(out, headers_s);
		});

	if(!headers_v.empty())
		write(out, headers_v);

	if(termination)
		writeline(out);
}

namespace ircd::http
{
	static void assign(response::head &, const header &);
}

ircd::http::response::head::head(parse::capstan &pc)
try
:line::response{pc}
,headers
{
	pc, [this](const auto &header)
	{
		assign(*this, header);
	}
}
{
}
catch(const expectation_failure &e)
{
	throw_error(e, true);
}

ircd::http::response::head::head(parse::capstan &pc,
                                 const headers::closure &closure)
try
:line::response{pc}
,headers
{
	pc, [this, &closure](const auto &header)
	{
		assign(*this, header);
		closure(header);
	}
}
{
}
catch(const expectation_failure &e)
{
	throw_error(e, true);
}

void
ircd::http::assign(response::head &head,
                   const header &header)
{
	char buf[96];
	const auto &[key_, val] {header};
	assert(size(key_) <= sizeof(buf));
	if(unlikely(size(key_) > sizeof(buf)))
		throw error
		{
			REQUEST_HEADER_FIELDS_TOO_LARGE
		};

	const auto &key
	{
		tolower(buf, key_)
	};

	if(key == "content-length"_sv)
		head.content_length = parser::content_length(val);

	else if(key == "content-type"_sv)
		head.content_type = val;

	else if(key == "content-range"_sv)
		head.content_range = val;

	else if(key == "accept-range"_sv)
		head.content_range = val;

	else if(key == "transfer-encoding"_sv)
		head.transfer_encoding = val;

	else if(key == "server"_sv)
		head.server = val;

	else if(key == "location"_sv)
		head.location = val;
}

namespace ircd::http::parser
{
	extern const rule<size_t> parse_chunk_head;
}

decltype(ircd::http::parser::parse_chunk_head)
ircd::http::parser::parse_chunk_head
{
	eoi | (eps > (chunk_size >> -chunk_extensions))
	,"chunk head"
};

ircd::http::response::chunk::chunk(parse::capstan &pc)
try
:line{pc}
{
	const char *start(line::begin()), *const stop(line::end());
	const auto res
	{
		ircd::parse(start, stop, parser::parse_chunk_head, this->size)
	};

	assert(res == true);
}
catch(const expectation_failure &e)
{
	throw_error(e, true);
}

//
// headers
//

bool
ircd::http::has(const vector_view<const header> &headers,
                const string_view &key)
{
	return end(headers) != std::find_if(begin(headers), end(headers), [&key]
	(const header &header)
	{
		return header == key;
	});
}

bool
ircd::http::has(const headers &headers,
                const string_view &key)
{
	return headers.has(key);
}

//
// headers::headers
//

decltype(ircd::http::headers::terminator)
ircd::http::headers::terminator
{
	"\r\n\r\n"
};

ircd::http::headers::headers(parse::capstan &pc,
                             closure c)
:string_view{[&pc, &c]
() -> string_view
{
	header h{pc};
	const char *const &started{h.first.data()}, *stopped{started};
	for(; !h.first.empty(); stopped = pc.parsed, h = header{pc})
		if(c && !c(h))
			c = {};

	return { started, stopped };
}()}
{
}

bool
ircd::http::headers::has(const string_view &key)
const
{
	// has header if break early from for_each
	return !for_each([&key]
	(const header &header)
	{
		// true to continue; false to break (for_each protocol)
		return iequals(header.first, key)? false : true;
	});
}

ircd::string_view
ircd::http::headers::at(const string_view &key)
const
{
	const string_view ret
	{
		this->operator[](key)
	};

	if(unlikely(!ret))
		throw std::out_of_range{key};

	return ret;
}

ircd::string_view
ircd::http::headers::operator[](const string_view &key)
const
{
	string_view ret;
	for_each([&key, &ret](const auto &header)
	{
		if(iequals(header.first, key))
		{
			ret = header.second;
			return false;
		}
		else return true;
	});

	return ret;
}

bool
ircd::http::headers::for_each(const closure &closure)
const
{
	if(empty())
		return true;

	parse::buffer pb{const_buffer{*this}};
	parse::capstan pc{pb};
	header h{pc};
	for(; !h.first.empty(); h = header{pc})
		if(!closure(h))
			return false;

	return true;
}

//
// header
//

namespace ircd::http::parser
{
	extern const rule<http::header> parse_header;
	extern const rule<http::line::request> parse_request;
	extern const rule<http::line::response> parse_response;
}

decltype(ircd::http::parser::parse_header)
ircd::http::parser::parse_header
{
	eoi | expect[header]
	,"header"
};

decltype(ircd::http::parser::parse_request)
ircd::http::parser::parse_request
{
	expect[request_line]
	,"request head line"
};

decltype(ircd::http::parser::parse_response)
ircd::http::parser::parse_response
{
	expect[response_line]
	,"response head line"
};

ircd::http::header::header(const line &line)
try
{
	const char
	*start(line.data()),
	*const stop(line.data() + line.size());
	const auto ok
	{
		ircd::parse(start, stop, parser::parse_header, *this)
	};

	assert(ok);
	assert(start == stop);
}
catch(const expectation_failure &e)
{
	throw_error(e);
}

ircd::http::line::response::response(const line &line)
try
{
	const char
	*start(line.data()),
	*const stop(line.data() + line.size());
	const auto ok
	{
		ircd::parse(start, stop, parser::parse_response, *this)
	};

	assert(ok);
	assert(start == stop);
}
catch(const expectation_failure &e)
{
	throw_error(e, true);
}

ircd::http::line::request::request(const line &line)
try
{
	const char
	*start(line.data()),
	*const stop(line.data() + line.size());
	const auto ok
	{
		ircd::parse(start, stop, parser::parse_request, *this)
	};

	assert(ok);
	assert(start == stop);
}
catch(const expectation_failure &e)
{
	throw_error(e);
}

ircd::http::line::request::operator
string_view()
const
{
	if(method.empty())
		return string_view{};

	assert(!version.empty());
	assert(method.begin() < version.end());
	return string_view
	{
		method.begin(), version.end()
	};
}

//
// http::line
//

namespace ircd::http::parser
{
	extern const rule<string_view> parse_line;
}

decltype(ircd::http::parser::parse_line)
ircd::http::parser::parse_line
{
	expect[line]
	,"line"
};

decltype(ircd::http::line::terminator)
ircd::http::line::terminator
{
	"\r\n"
};

ircd::http::line::line(parse::capstan &pc)
{
	auto &ret(static_cast<string_view &>(*this));
	pc([&ret](const char *&start, const char *const &stop)
	{
		if(start == stop)
			return false;

		const bool ok
		{
			ircd::parse(start, stop, parser::parse_line, ret)
		};

		assert(ok);
		return ok;
	});
}

//
// query::string
//

bool
ircd::http::query::string::has(const string_view &key)
const
{
	bool ret{false};
	for_each(key, [&ret]
	(const auto &)
	noexcept
	{
		ret = true;
		return false;
	});

	return ret;
}

size_t
ircd::http::query::string::count(const string_view &key)
const
{
	size_t ret{0};
	for_each(key, [&ret]
	(const auto &)
	noexcept
	{
		++ret;
		return true;
	});

	return ret;
}

ircd::vector_view<ircd::string_view>
ircd::http::query::string::array(const mutable_buffer &buf,
                                 const string_view &key,
                                 string_view *const &out,
                                 const size_t &max)
const
{
	if(unlikely(!max))
		return {};

	size_t ret(0);
	window_buffer window(buf);
	for_each(key, [&out, &max, &ret, &window]
	(const auto &query)
	{
		window([&out, &max, &ret, &query]
		(const mutable_buffer &buf)
		{
			assert(ret < max);
			const auto &[_, val] {query};
			out[ret] = url::decode(buf, val);
			return out[ret];
		});

		return ++ret < max;
	});

	return vector_view<string_view>
	{
		out, ret
	};
}


ircd::string_view
ircd::http::query::string::at(const string_view &key,
                              const size_t &idx)
const
{
	const string_view &ret
	{
		_get(key, idx)
	};

	if(unlikely(!ret))
	{
		char buf[1024];
		const string_view msg
		{
			fmt::sprintf
			{
				buf, "Failed to find value for required query string key '%s' #%zu.",
				key,
				idx
			}
		};

		throw std::out_of_range
		{
			msg.c_str() // fmt::sprintf() will null terminate msg
		};
	}

	return ret;
}

ircd::string_view
ircd::http::query::string::operator[](const string_view &key)
const
{
	return _get(key, 0);
}

ircd::string_view
ircd::http::query::string::_get(const string_view &key,
                                size_t idx)
const
{
	string_view ret;
	for_each(key, [&idx, &ret]
	(const auto &query)
	noexcept
	{
		if(!idx--)
		{
			ret = query.second;
			return false;
		}
		else return true;
	});

	return ret;
}

bool
ircd::http::query::string::for_each(const string_view &key,
                                    const closure &closure)
const
{
	return for_each([&key, &closure]
	(const auto &query)
	{
		if(query.first != key)
			return true;

		return closure(query);
	});
}

bool
ircd::http::query::string::for_each(const closure &view)
const
{
	bool ret{true};
	const auto action{[&view, &ret]
	(const auto &attribute, const auto &context, auto &continue_)
	{
		ret = view(attribute);
		continue_ = ret;
	}};

	const parser::rule<> grammar
	{
		-parser::question >> (parser::query[action] % parser::ampersand)
		,"query strings"
	};

	const string_view &s(*this);
	const char *start(s.begin()), *const stop(s.end());
	ircd::parse(start, stop, grammar);
	return ret;
}

//
// parser util
//

size_t
ircd::http::parser::content_length(const string_view &str)
{
	const char
	*start(str.data()),
	*const stop(start + str.size());

	size_t ret;
	const bool parsed
	{
		ircd::parse(start, stop, parser::content_size, ret)
	};

	if(!parsed || ret >= 256_GiB)
		throw error
		{
			BAD_REQUEST, "Invalid content-length value"
		};

	return ret;
}

//
// util
//

ircd::const_buffer
ircd::http::writechunk(const mutable_buffer &buf,
                       const uint32_t &chunk_size)
{
	window_buffer window{buf};
	writechunk(window, chunk_size);
	return window.completed();
}

void
ircd::http::writechunk(window_buffer &buf,
                       const uint32_t &chunk_size)
{
	writeline(buf, [&chunk_size]
	(const mutable_buffer &out) -> size_t
	{
		assert(size(out) >= (8 + 1));
		return ::snprintf(data(out), size(out), "%08x", chunk_size);
	});
}

std::string
ircd::http::strung(const vector_view<const header> &headers)
{
	return ircd::string(serialized(headers), [&]
	(window_buffer out)
	{
		write(out, headers);
		return out.consumed();
	});
}

/// Indicates the buffer size required to write these headers. This size
/// may include room for a terminating null character which may be written
/// by write(headers). Only use write(headers) to know the actually written
/// string size (without null) not this.
size_t
ircd::http::serialized(const vector_view<const header> &headers)
{
	// Because the write(header) functions use fmt::sprintf we have to
	// indicate an extra space for a null string terminator to not overlof
	const size_t initial{!headers.empty()};
	return std::accumulate(std::begin(headers), std::end(headers), initial, []
	(auto &&ret, const auto &pair)
	{
		//            key                 :   SP  value                CRLF
		return ret += pair.first.size() + 1 + 1 + pair.second.size() + 2;
	});
}

void
ircd::http::write(window_buffer &out,
                  const vector_view<const header> &headers)
{
	for(const auto &header : headers)
		write(out, header);
}

void
ircd::http::write(window_buffer &out,
                  const header &header)
{
	if(header.second.empty())
		return;

	assert(!header.first.empty());
	if(unlikely(header.first.empty()))
		return;

	writeline(out, [&header](const mutable_buffer &out) -> size_t
	{
		return fmt::sprintf
		{
			out, "%s: %s", header.first, header.second
		};
	});
}

/// Close over the user's closure to append a newline.
void
ircd::http::writeline(window_buffer &write,
                      const window_buffer::closure &closure)
{
	// A new window_buffer is implicit constructed out of the mutable_buffer
	// otherwise presented to this closure as its write window.
	write([&closure](window_buffer write)
	{
		const auto newline{[](const mutable_buffer &out)
		{
			return copy(out, line::terminator);
		}};

		write(closure);
		write(newline);
		return write.consumed();
	});
}

void
ircd::http::writeline(window_buffer &write)
{
	writeline(write, []
	(const mutable_buffer &out)
	noexcept
	{
		return 0;
	});
}

/// Called to translate a grammar exception into an http::error within our
/// system. This will then usually propagate back to our client.
///
/// If we are a client to another server, set internal=true. Even though this
/// still generates an HTTP error, the code is 500 so if it propagates back to
/// a client it does not indicate to *that* client that *they* made a bad
/// request from a 400 back to them.
void
ircd::http::throw_error(const expectation_failure &e,
                        const bool &internal)
{
	const auto &code_
	{
		internal?
			code::INTERNAL_SERVER_ERROR:
			code::BAD_REQUEST
	};

	const char *const &fmtstr
	{
		internal?
			"I expected a valid HTTP %s. Server sent %zu invalid characters starting with `%s'.":
			"I require a valid HTTP %s. You sent %zu invalid characters starting with `%s'."
	};

	const auto &rule
	{
		ircd::string(e.what_)
	};

	throw error
	{
		code_, fmt::snstringf
		{
			512, fmtstr,
			between(rule, "<", ">"),
			size_t(e.last - e.first),
			string_view{e.first, e.last}
		}
	};
}

//
// error
//

ircd::http::error::error(const http::code &code,
                         std::string content,
                         const vector_view<const header> &headers)
:http::error
{
	code, std::move(content), strung(headers)
}
{
}

ircd::http::error::error(const http::code &code,
                         std::string content,
                         std::string headers)
:ircd::error
{
	generate_skip
}
,content
{
	std::move(content)
}
,headers
{
	std::move(headers)
}
,code{code}
{
	auto &buf(ircd::error::buf);
	const auto msg(status(code));
	::snprintf
	(
		buf, sizeof(buf),
		"%u %s",
		uint(code),
		msg? msg.c_str(): ""
	);
}

// Out-of-line placement.
ircd::http::error::~error()
noexcept
{
}

//
// status
//

namespace ircd::http::parser
{
	extern const rule<uint8_t> status_codepoint;
	extern const rule<enum http::category> status_category;
	extern const rule<enum http::code> status_code;
}

decltype(ircd::http::parser::status_codepoint)
ircd::http::parser::status_codepoint
{
	qi::uint_parser<uint8_t, 10, 1, 1>{}
	,"status codepoint"
};

decltype(ircd::http::parser::status_category)
ircd::http::parser::status_category
{
	&char_('1','9')
	>> status_codepoint
	>> omit[repeat(2)[char_('0','9')] >> (eoi | ws)]
	,"status category"
};

decltype(ircd::http::parser::status_code)
ircd::http::parser::status_code
{
	qi::uint_parser<uint16_t, 10, 3, 3>{}
	>> omit[eoi | ws]
	,"status code"
};

enum ircd::http::code
ircd::http::status(const string_view &str)
{
	short ret;
	const char *start(begin(str)), *const stop(end(str));
	const bool parsed
	{
		ircd::parse(start, stop, parser::status_code, ret)
	};

	if(!parsed)
		throw ircd::error
		{
			"Invalid HTTP status code"
		};

	assert(ret >= 0 && ret < 1000);
	return http::code(ret);
}

ircd::string_view
ircd::http::status(const enum code &code)
noexcept try
{
	return reason.at(code);
}
catch(const std::out_of_range &e)
{
	log::dwarning
	{
		"No reason string for HTTP status code %u", uint(code)
	};

	return ""_sv;
}

enum ircd::log::level
ircd::http::severity(const enum category &category)
noexcept
{
	switch(category)
	{
		case http::category::NONE:        return log::level::DERROR;
		case http::category::SUCCESS:     return log::level::DEBUG;
		case http::category::REDIRECT:    return log::level::DWARNING;
		case http::category::ERROR:       return log::level::DERROR;
		case http::category::SERVER:      return log::level::DERROR;
		case http::category::INTERNAL:    return log::level::ERROR;
		default:
		case http::category::UNKNOWN:     return log::level::DWARNING;
	}
}

enum ircd::http::category
ircd::http::category(const string_view &str)
noexcept
{
	enum category ret;
	const char *start(begin(str)), *const stop(end(str));
	const bool parsed
	{
		ircd::parse(start, stop, parser::status_category, ret)
	};

	if(!parsed || ret > category::UNKNOWN)
		ret = category::UNKNOWN;

	assert(uint8_t(ret) <= uint8_t(category::UNKNOWN));
	return ret;
}

enum ircd::http::category
ircd::http::category(const enum code &code)
noexcept
{
	if(ushort(code) == 0)
		return category::NONE;

	if(ushort(code) < 200)
		return category::INFO;

	if(ushort(code) < 300)
		return category::SUCCESS;

	if(ushort(code) < 400)
		return category::REDIRECT;

	if(ushort(code) < 500)
		return category::ERROR;

	if(ushort(code) == 500)
		return category::INTERNAL;

	if(ushort(code) < 600)
		return category::SERVER;

	return category::UNKNOWN;
}

ircd::string_view
ircd::http::category(const enum category &category)
noexcept
{
	switch(category)
	{
		case category::NONE:      return "NONE";
		case category::INFO:      return "INFO";
		case category::SUCCESS:   return "SUCCESS";
		case category::REDIRECT:  return "REDIRECT";
		case category::ERROR:     return "ERROR";
		case category::SERVER:    return "SERVER";
		case category::INTERNAL:  return "INTERNAL";
		case category::UNKNOWN:   break;
	}

	return "UNKNOWN";
}
