* Copyright 2022 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Niels Sascha Reedijk, niels.reedijk@gmail.com
*/
#include <HttpFields.h>
#include <algorithm>
#include <ctype.h>
#include <utility>
#include "HttpPrivate.h"
using namespace BPrivate::Network;
\brief Validate whether the string is a valid HTTP header value
RFC 7230 section 3.2.6 determines that valid tokens for the header are:
HTAB ('\t'), SP (32), all visible ASCII characters (33-126), and all characters that
not control characters (in the case of a char, any value < 0)
\note When printing out the HTTP header, sometimes the string needs to be quoted and some
characters need to be escaped. This function is not checking for whether the string can
be transmitted as is.
\returns \c true if the string is valid, or \c false if it is not.
*/
static inline bool
validate_value_string(const std::string_view& string)
{
for (auto it = string.cbegin(); it < string.cend(); it++) {
if ((*it >= 0 && *it < 32) || *it == 127 || *it == '\t')
return false;
}
return true;
}
\brief Case insensitively compare two string_views.
Inspired by https://stackoverflow.com/a/4119881
*/
static inline bool
iequals(const std::string_view& a, const std::string_view& b)
{
return std::equal(a.begin(), a.end(), b.begin(), b.end(),
[](char a, char b) { return tolower(a) == tolower(b); });
}
\brief Trim whitespace from the beginning and end of a string_view
Inspired by:
https://terrislinenbach.medium.com/trimming-whitespace-from-a-string-view-6795e18b108f
*/
static inline std::string_view
trim(std::string_view in)
{
auto left = in.begin();
for (;; ++left) {
if (left == in.end())
return std::string_view();
if (!isspace(*left))
break;
}
auto right = in.end() - 1;
for (; right > left && isspace(*right); --right)
;
return std::string_view(left, std::distance(left, right) + 1);
}
BHttpFields::InvalidInput::InvalidInput(const char* origin, BString input)
:
BError(origin),
input(std::move(input))
{
}
const char*
BHttpFields::InvalidInput::Message() const noexcept
{
return "Invalid format or unsupported characters in input";
}
BString
BHttpFields::InvalidInput::DebugMessage() const
{
BString output = BError::DebugMessage();
output << "\t " << input << "\n";
return output;
}
BHttpFields::FieldName::FieldName() noexcept
:
fName(std::string_view())
{
}
BHttpFields::FieldName::FieldName(const std::string_view& name) noexcept
:
fName(name)
{
}
\brief Copy constructor;
*/
BHttpFields::FieldName::FieldName(const FieldName& other) noexcept = default;
\brief Move constructor
Moving leaves the other object in the empty state. It is implemented to satisfy the internal
requirements of BHttpFields and std::list<Field>. Once an object is moved from it must no
longer be used as an entry in a BHttpFields object.
*/
BHttpFields::FieldName::FieldName(FieldName&& other) noexcept
:
fName(std::move(other.fName))
{
other.fName = std::string_view();
}
\brief Copy assignment;
*/
BHttpFields::FieldName& BHttpFields::FieldName::operator=(
const BHttpFields::FieldName& other) noexcept = default;
\brief Move assignment
Moving leaves the other object in the empty state. It is implemented to satisfy the internal
requirements of BHttpFields and std::list<Field>. Once an object is moved from it must no
longer be used as an entry in a BHttpFields object.
*/
BHttpFields::FieldName&
BHttpFields::FieldName::operator=(BHttpFields::FieldName&& other) noexcept
{
fName = std::move(other.fName);
other.fName = std::string_view();
return *this;
}
bool
BHttpFields::FieldName::operator==(const BString& other) const noexcept
{
return iequals(fName, std::string_view(other.String()));
}
bool
BHttpFields::FieldName::operator==(const std::string_view& other) const noexcept
{
return iequals(fName, other);
}
bool
BHttpFields::FieldName::operator==(const BHttpFields::FieldName& other) const noexcept
{
return iequals(fName, other.fName);
}
BHttpFields::FieldName::operator std::string_view() const
{
return fName;
}
BHttpFields::Field::Field() noexcept
:
fName(std::string_view()),
fValue(std::string_view())
{
}
BHttpFields::Field::Field(const std::string_view& name, const std::string_view& value)
{
if (name.length() == 0 || !validate_http_token_string(name))
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(name.data(), name.size()));
if (value.length() == 0 || !validate_value_string(value))
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(value.data(), value.length()));
BString rawField(name.data(), name.size());
rawField << ": ";
rawField.Append(value.data(), value.size());
fName = std::string_view(rawField.String(), name.size());
fValue = std::string_view(rawField.String() + name.size() + 2, value.size());
fRawField = std::move(rawField);
}
BHttpFields::Field::Field(BString& field)
{
auto separatorIndex = field.FindFirst(':');
if (separatorIndex <= 0)
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, field);
auto name = std::string_view(field.String(), separatorIndex);
auto value = trim(std::string_view(field.String() + separatorIndex + 1));
if (name.length() == 0 || !validate_http_token_string(name))
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(name.data(), name.size()));
if (value.length() == 0 || !validate_value_string(value))
throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(value.data(), value.length()));
fRawField = std::move(field);
fName = name;
fValue = value;
}
BHttpFields::Field::Field(const BHttpFields::Field& other)
:
fName(std::string_view()),
fValue(std::string_view())
{
if (other.IsEmpty()) {
fRawField = BString();
fName = std::string_view();
fValue = std::string_view();
} else {
fRawField = other.fRawField;
auto nameSize = other.Name().fName.size();
auto valueOffset = other.fValue.data() - other.fRawField.value().String();
fName = std::string_view((*fRawField).String(), nameSize);
fValue = std::string_view((*fRawField).String() + valueOffset, other.fValue.size());
}
}
BHttpFields::Field::Field(BHttpFields::Field&& other) noexcept
:
fRawField(std::move(other.fRawField)),
fName(std::move(other.fName)),
fValue(std::move(other.fValue))
{
other.fName.fName = std::string_view();
other.fValue = std::string_view();
}
BHttpFields::Field&
BHttpFields::Field::operator=(const BHttpFields::Field& other)
{
if (other.IsEmpty()) {
fRawField = BString();
fName = std::string_view();
fValue = std::string_view();
} else {
fRawField = other.fRawField;
auto nameSize = other.Name().fName.size();
auto valueOffset = other.fValue.data() - other.fRawField.value().String();
fName = std::string_view((*fRawField).String(), nameSize);
fValue = std::string_view((*fRawField).String() + valueOffset, other.fValue.size());
}
return *this;
}
BHttpFields::Field&
BHttpFields::Field::operator=(BHttpFields::Field&& other) noexcept
{
fRawField = std::move(other.fRawField);
fName = std::move(other.fName);
other.fName.fName = std::string_view();
fValue = std::move(other.fValue);
fValue = std::string_view();
return *this;
}
const BHttpFields::FieldName&
BHttpFields::Field::Name() const noexcept
{
return fName;
}
std::string_view
BHttpFields::Field::Value() const noexcept
{
return fValue;
}
std::string_view
BHttpFields::Field::RawField() const noexcept
{
if (fRawField)
return std::string_view((*fRawField).String(), (*fRawField).Length());
else
return std::string_view();
}
bool
BHttpFields::Field::IsEmpty() const noexcept
{
return !fRawField.has_value();
}
BHttpFields::BHttpFields()
{
}
BHttpFields::BHttpFields(std::initializer_list<BHttpFields::Field> fields)
{
AddFields(fields);
}
BHttpFields::BHttpFields(const BHttpFields& other) = default;
BHttpFields::BHttpFields(BHttpFields&& other)
:
fFields(std::move(other.fFields))
{
other.fFields.clear();
}
BHttpFields::~BHttpFields() noexcept
{
}
BHttpFields& BHttpFields::operator=(const BHttpFields& other) = default;
BHttpFields&
BHttpFields::operator=(BHttpFields&& other) noexcept
{
fFields = std::move(other.fFields);
other.fFields.clear();
return *this;
}
const BHttpFields::Field&
BHttpFields::operator[](size_t index) const
{
if (index >= fFields.size())
throw BRuntimeError(__PRETTY_FUNCTION__, "Index out of bounds");
auto it = fFields.cbegin();
std::advance(it, index);
return *it;
}
void
BHttpFields::AddField(const std::string_view& name, const std::string_view& value)
{
fFields.emplace_back(name, value);
}
void
BHttpFields::AddField(BString& field)
{
fFields.emplace_back(field);
}
void
BHttpFields::AddFields(std::initializer_list<Field> fields)
{
for (auto& field: fields) {
if (!field.IsEmpty())
fFields.push_back(std::move(field));
}
}
void
BHttpFields::RemoveField(const std::string_view& name) noexcept
{
for (auto it = FindField(name); it != end(); it = FindField(name)) {
fFields.erase(it);
}
}
void
BHttpFields::RemoveField(ConstIterator it) noexcept
{
fFields.erase(it);
}
void
BHttpFields::MakeEmpty() noexcept
{
fFields.clear();
}
BHttpFields::ConstIterator
BHttpFields::FindField(const std::string_view& name) const noexcept
{
for (auto it = fFields.cbegin(); it != fFields.cend(); it++) {
if ((*it).Name() == name)
return it;
}
return fFields.cend();
}
size_t
BHttpFields::CountFields() const noexcept
{
return fFields.size();
}
size_t
BHttpFields::CountFields(const std::string_view& name) const noexcept
{
size_t count = 0;
for (auto it = fFields.cbegin(); it != fFields.cend(); it++) {
if ((*it).Name() == name)
count += 1;
}
return count;
}
BHttpFields::ConstIterator
BHttpFields::begin() const noexcept
{
return fFields.cbegin();
}
BHttpFields::ConstIterator
BHttpFields::end() const noexcept
{
return fFields.cend();
}