0
\$\begingroup\$

This class keep tracks of the HTTP headers that are being read from the incoming request.

Few special cases.

As per RFC 9110 Section 5.3 some headers can only appear once and are automatically de-dupped (removing all but the first one of these headers).

Most (but not all) headers allow multiple values. The multiple values can be either in the same header (comma separated) or provided by multiple header lines. We combine all the values as appropriate. So for each header we map it to a std::vector<std::string> to store each of the values.

Also headers are supposed to be case insensitive. So to make things simpler on the server all headers are lowercased before use.

HeaderRequest.h

#ifndef THORSANVIL_NISSE_NISSEHTTP_HEADER_REQUEST_H
#define THORSANVIL_NISSE_NISSEHTTP_HEADER_REQUEST_H
#include "NisseHTTPConfig.h"
#include <map>
#include <vector>
#include <string>
#include <string_view>
namespace ThorsAnvil::Nisse::NisseHTTP
{
class HeaderRequest
{
 using HeaderMap = std::map<std::string, std::vector<std::string>>;
 using CIterator = HeaderMap::const_iterator;
 HeaderMap headers;
 public:
 CIterator begin() const {return std::begin(headers);}
 CIterator end() const {return std::end(headers);}
 bool hasHeader(std::string_view header) const {return hasHeader(std::string(header));}
 bool hasHeader(std::string const& header) const {return headers.find(header) != headers.end();}
 std::vector<std::string> const& getHeader(char const* header) const {return getHeader(std::string(header));}
 std::vector<std::string> const& getHeader(std::string_view header) const {return getHeader(std::string(header));}
 std::vector<std::string> const& getHeader(std::string const& header) const;
 bool operator==(HeaderRequest const& rhs) const {return headers == rhs.headers;}
 bool operator!=(HeaderRequest const& rhs) const {return !(*this == rhs);}
 void add(std::string_view header, std::string_view value);
 private:
 bool dedupHeader(std::string_view header);
 bool splitOnComma(std::string_view header);
 std::string_view getValue(std::string_view input);
};
}
#endif

HeaderRequest.cpp

#include "HeaderRequest.h"
#include <set>
#include <sstream>
using namespace ThorsAnvil::Nisse::NisseHTTP;
std::vector<std::string> const& HeaderRequest::getHeader(std::string const& header) const
{
 static const std::vector<std::string> empty;
 auto find = headers.find(header);
 if (find == headers.end()) {
 return empty;
 }
 return find->second;
}
void HeaderRequest::add(std::string_view header, std::string_view value)
{
 header = getValue(header);
 std::string key(header.size(), ' ');
 std::transform(std::begin(header), std::end(header), std::begin(key), [](unsigned char c){ return std::tolower(c);});
 if (key == "set-cookie")
 {
 // SPECIAL HANDLING for Cookies.
 // TODO
 return;
 }
 auto& headerValue = headers[key];
 if (dedupHeader(key))
 {
 if (headerValue.size() == 1) {
 return;
 }
 headerValue.emplace_back(getValue(value));
 return;
 }
 if (splitOnComma(key))
 {
 std::stringstream ss{std::string{value}};
 std::string splitValue;
 while (std::getline(ss, splitValue, ','))
 {
 headerValue.emplace_back(getValue(splitValue));
 }
 }
 else
 {
 headerValue.emplace_back(getValue(value));
 }
}
std::string_view HeaderRequest::getValue(std::string_view input)
{
 // Remove leading and trailing white space.
 input.remove_prefix(std::min(input.size(), input.find_first_not_of(" \r\t\v")));
 input.remove_suffix(input.size() - std::min(input.size(), input.find_last_not_of(" \r\t\v")) - 1);
 return input;
}
bool HeaderRequest::dedupHeader(std::string_view header)
{
 // See RFC 9110 Section 5.3 for more information.
 static const std::set<std::string_view> singleValueHeaders = {"age", "authorization", "content-length", "content-type", "etag", "expires", "from", "host", "if-modified-since", "if-unmodified-since", "last-modified", "location", "max-forwards", "proxy-authorization", "referer", "retry-after", "server", "user-agent"};
 return singleValueHeaders.find(header) != singleValueHeaders.end();
}
bool HeaderRequest::splitOnComma(std::string_view header)
{
 static const std::set<std::string_view> nosplit = {"accept-datetime", "access-control-request-method", "access-control-request-header", "content-md5", "cookie", "date", "expect", "if-match", "if-none-match", "if-range"};
 return nosplit.find(header) == nosplit.end();
}
asked Oct 20, 2024 at 18:06
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.