blob: 8f2478fcd2984310d0c30d81f5595519f41b20fe [file] [log] [blame]
Ed Tanous9bd21fc2018-04-26 16:08:56 -07001#pragma once
Tanousf00032d2018-11-05 01:18:10 -03002
Ed Tanous11ba3972022-07-11 09:50:41 -07003#include <boost/algorithm/string/classification.hpp>
Nan Zhoud5c80ad2022-07-11 01:16:31 +00004#include <boost/algorithm/string/constants.hpp>
Nan Zhoud5c80ad2022-07-11 01:16:31 +00005#include <boost/iterator/iterator_facade.hpp>
6#include <boost/type_index/type_index_facade.hpp>
7
8#include <cctype>
9#include <iomanip>
10#include <ostream>
Ed Tanous99351cd2022-08-07 16:42:51 -070011#include <span>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000012#include <string>
13#include <string_view>
14#include <vector>
15
16// IWYU pragma: no_include <ctype.h>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050017
Ed Tanous1abe55e2018-09-05 08:30:59 -070018namespace http_helpers
19{
George Liu647b3cd2021-07-05 12:43:56 +080020
Ed Tanous99351cd2022-08-07 16:42:51 -070021enum class ContentType
George Liu647b3cd2021-07-05 12:43:56 +080022{
Ed Tanous99351cd2022-08-07 16:42:51 -070023 NoMatch,
Ed Tanous4a0e1a02022-09-21 15:28:04 -070024 ANY, // Accepts: */*
Ed Tanous99351cd2022-08-07 16:42:51 -070025 CBOR,
26 HTML,
27 JSON,
28 OctetStream,
29};
30
31struct ContentTypePair
32{
33 std::string_view contentTypeString;
34 ContentType contentTypeEnum;
35};
36
37constexpr std::array<ContentTypePair, 4> contentTypes{{
38 {"application/cbor", ContentType::CBOR},
39 {"application/json", ContentType::JSON},
40 {"application/octet-stream", ContentType::OctetStream},
41 {"text/html", ContentType::HTML},
42}};
43
Ed Tanoused194be2022-08-07 16:50:11 -070044inline ContentType
45 getPreferedContentType(std::string_view header,
46 std::span<const ContentType> preferedOrder)
Ed Tanous99351cd2022-08-07 16:42:51 -070047{
Ed Tanous99351cd2022-08-07 16:42:51 -070048 size_t lastIndex = 0;
49 while (lastIndex < header.size() + 1)
Ed Tanous1abe55e2018-09-05 08:30:59 -070050 {
Ed Tanousf8fe53e2022-06-30 15:55:45 -070051 size_t index = header.find(',', lastIndex);
Ed Tanous99351cd2022-08-07 16:42:51 -070052 if (index == std::string_view::npos)
Ed Tanous1abe55e2018-09-05 08:30:59 -070053 {
Ed Tanous99351cd2022-08-07 16:42:51 -070054 index = header.size();
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 }
Ed Tanous99351cd2022-08-07 16:42:51 -070056 std::string_view encoding = header.substr(lastIndex, index);
Ed Tanous6b5e77d2018-11-16 14:52:56 -080057
Ed Tanous99351cd2022-08-07 16:42:51 -070058 if (!header.empty())
59 {
60 header.remove_prefix(1);
61 }
62 lastIndex = index + 1;
Gunnar Millsa3526fe2022-02-02 21:56:44 +000063 // ignore any q-factor weighting (;q=)
64 std::size_t separator = encoding.find(";q=");
65
66 if (separator != std::string_view::npos)
67 {
68 encoding = encoding.substr(0, separator);
69 }
Ed Tanous99351cd2022-08-07 16:42:51 -070070 // If the client allows any encoding, given them the first one on the
71 // servers list
72 if (encoding == "*/*")
George Liu647b3cd2021-07-05 12:43:56 +080073 {
Ed Tanous4a0e1a02022-09-21 15:28:04 -070074 return ContentType::ANY;
George Liu647b3cd2021-07-05 12:43:56 +080075 }
Ed Tanous99351cd2022-08-07 16:42:51 -070076 const auto* knownContentType =
77 std::find_if(contentTypes.begin(), contentTypes.end(),
78 [encoding](const ContentTypePair& pair) {
79 return pair.contentTypeString == encoding;
80 });
81
82 if (knownContentType == contentTypes.end())
83 {
84 // not able to find content type in list
85 continue;
86 }
87
88 // Not one of the types requested
89 if (std::find(preferedOrder.begin(), preferedOrder.end(),
90 knownContentType->contentTypeEnum) == preferedOrder.end())
91 {
92 continue;
93 }
94 return knownContentType->contentTypeEnum;
George Liu647b3cd2021-07-05 12:43:56 +080095 }
Ed Tanous99351cd2022-08-07 16:42:51 -070096 return ContentType::NoMatch;
97}
98
Ed Tanous4a0e1a02022-09-21 15:28:04 -070099inline bool isContentTypeAllowed(std::string_view header, ContentType type,
100 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -0700101{
102 auto types = std::to_array({type});
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700103 ContentType allowed = getPreferedContentType(header, types);
104 if (allowed == ContentType::ANY)
105 {
106 return allowWildcard;
107 }
108
109 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +0800110}
111
Ed Tanous26ccae32023-02-16 10:28:44 -0800112inline std::string urlEncode(std::string_view value)
Ed Tanous6b5e77d2018-11-16 14:52:56 -0800113{
114 std::ostringstream escaped;
115 escaped.fill('0');
116 escaped << std::hex;
117
118 for (const char c : value)
119 {
120 // Keep alphanumeric and other accepted characters intact
Ed Tanouse662eae2022-01-25 10:39:19 -0800121 if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~')
Ed Tanous6b5e77d2018-11-16 14:52:56 -0800122 {
123 escaped << c;
124 continue;
125 }
126
127 // Any other characters are percent-encoded
128 escaped << std::uppercase;
129 escaped << '%' << std::setw(2)
130 << static_cast<int>(static_cast<unsigned char>(c));
131 escaped << std::nouppercase;
132 }
133
134 return escaped.str();
135}
Ed Tanous23a21a12020-07-25 04:45:05 +0000136} // namespace http_helpers