Line data Source code
1 : #ifndef THINGER_HTTP_HEADERS_HPP
2 : #define THINGER_HTTP_HEADERS_HPP
3 :
4 : #include <algorithm>
5 : #include <boost/logic/tribool.hpp>
6 : #include <boost/algorithm/string.hpp>
7 : #include <boost/lexical_cast.hpp>
8 : #include "http_frame.hpp"
9 : #include "../../util/ascii.hpp"
10 :
11 : namespace thinger::http{
12 :
13 : namespace misc_strings {
14 : static const std::string name_value_separator{": "};
15 : static const std::string crlf = "\r\n";
16 : static const std::string lf = "\n";
17 : static const std::string space = " ";
18 : static const std::string http_1_1 = "HTTP/1.1";
19 : static const std::string close = "close";
20 : } // namespace misc_strings
21 :
22 : namespace header{
23 : // TODO replace any header constant in code by those strings
24 : const std::string content_type = "Content-Type";
25 : const std::string cache_control = "Cache-Control";
26 : const std::string connection = "Connection";
27 : const std::string upgrade = "Upgrade";
28 : const std::string accept = "Accept";
29 : const std::string content_length = "Content-Length";
30 : const std::string transfer_encoding = "Transfer-Encoding";
31 : const std::string user_agent = "User-Agent";
32 : const std::string authorization = "Authorization";
33 : const std::string set_cookie = "Set-Cookie";
34 : const std::string cookie = "Cookie";
35 : const std::string location = "Location";
36 : const std::string host = "Host";
37 : const std::string referer = "Referer";
38 : const std::string x_frame_options = "X-Frame-Options";
39 : }
40 :
41 : namespace connection{
42 : const std::string keep_alive = "keep-alive";
43 : const std::string close = "close";
44 : const std::string upgrade = "upgrade";
45 : }
46 :
47 : namespace accept{
48 : const std::string event_stream = "text/event-stream";
49 : }
50 :
51 : namespace content_type{
52 : const std::string application_form_urlencoded = "application/x-www-form-urlencoded";
53 : const std::string text_html = "text/html";
54 : }
55 :
56 : class headers : public http_frame{
57 :
58 : public:
59 : using http_header = std::pair<std::string, std::string>;
60 :
61 : // constructors
62 566203 : headers() = default;
63 568800 : ~headers() override = default;
64 : headers(const headers&) = default;
65 2597 : headers(headers&&) = default;
66 : headers& operator=(const headers&) = default;
67 : headers& operator=(headers&&) = default;
68 :
69 : // The head of a message
70 23679 : frame_kind kind() const override { return frame_kind::head; }
71 :
72 : // setters. A header whose name is not a token, or whose value has CR, LF, NUL or another
73 : // control character but HTAB (RFC 9110, section 5), is refused, without throwing: it is not
74 : // stored (a refused set_header leaves the message as it was), and has_refused_header() is
75 : // set. So a response is sent without it, and a client request with one fails, unsent. A
76 : // header with an empty name is ignored. Writing through get_headers() or process_header()
77 : // is not checked: they are for the parsers and the library.
78 : void add_header(std::string key, std::string value);
79 : void set_header(std::string key, std::string value);
80 9 : void set(std::string key, std::string value) { set_header(std::move(key), std::move(value)); }
81 : // Leave a single `key` header with this value, whatever how many there were: the first
82 : // one keeps its place and the spelling of its name. set_header() changes only the first.
83 : void replace_headers(std::string key, std::string value);
84 : void set_proxy(std::string key, std::string value);
85 : void add_proxy(std::string key, std::string value);
86 : // Whether a setter refused a header (see add_header)
87 9890 : bool has_refused_header() const { return refused_header_; }
88 : bool upgrade() const;
89 : bool stream() const;
90 : bool has_header(std::string_view key) const;
91 : bool remove_header(std::string_view key);
92 : // Remove every `key` header, returning how many there were
93 : size_t remove_headers(std::string_view key);
94 : void set_http_version_major(uint8_t http_version_major);
95 : void set_http_version_minor(uint8_t http_version_minor);
96 : void set_keep_alive(bool keep_alive);
97 :
98 : // getters
99 : std::vector<http_header>& get_headers();
100 : const std::vector<http_header>& get_headers() const;
101 : static std::string get_parameter(const std::string& key, std::string_view name) ;
102 : std::vector<std::string> get_headers_with_key(std::string_view key) const;
103 : const std::string& get_header(std::string_view key) const;
104 :
105 : const std::string& get_authorization() const;
106 : const std::string& get_cookie() const;
107 : const std::string& get_user_agent() const;
108 : const std::string& get_content_type() const;
109 : bool is_content_type(const std::string& value) const;
110 : bool empty_headers() const;
111 : size_t get_content_length() const;
112 : // Whether a Content-Length header was not a valid number, or repeated with different values
113 18 : bool has_invalid_content_length() const { return invalid_content_length_; }
114 : // Whether there is a Content-Length / Transfer-Encoding header (tracked as headers are added
115 : // and removed, so checking it for every parsed header is not a scan of all the headers)
116 31584 : bool has_content_length_header() const { return content_length_header_; }
117 22438 : bool has_transfer_encoding_header() const { return transfer_encoding_header_; }
118 : // Whether the body is chunked: "Transfer-Encoding: chunked" (see has_valid_framing)
119 126004 : bool is_chunked_transfer() const { return chunked_transfer_; }
120 : // Whether the body framing of a received message is unambiguous (RFC 9112, section 6.3):
121 : // a valid Content-Length, or "Transfer-Encoding: chunked" alone (the only coding, in a
122 : // single header: no other is supported), but not both, and Transfer-Encoding only from
123 : // HTTP/1.1 on (section 6.1: in an HTTP/1.0 message, its framing is faulty). Otherwise
124 : // where the message ends, and so where the next one starts, is unknown: it must be
125 : // rejected, and the connection closed.
126 59407 : bool has_valid_framing() const {
127 59407 : bool before_http_1_1 = http_version_major_ < 1 || (http_version_major_ == 1 && http_version_minor_ < 1);
128 59087 : return !invalid_content_length_ && !invalid_transfer_encoding_
129 58854 : && !(chunked_transfer_ && content_length_header_)
130 118494 : && !(before_http_1_1 && transfer_encoding_header_);
131 : }
132 : int get_http_version_major() const;
133 : int get_http_version_minor() const;
134 : bool keep_alive() const;
135 : // Header names (and tokens) compare case-insensitively
136 3798410 : static bool is_header(std::string_view key, std::string_view header) {
137 3798410 : return ::thinger::util::ascii::iequals(key, header);
138 : }
139 :
140 : // debug
141 : void debug_headers(std::ostream& os) const;
142 : void log(const char* scope, const body_logging& options = {}) const override;
143 :
144 : // Whether the value of the header `name` is a credential, never logged (Authorization,
145 : // Proxy-Authorization, Cookie, Set-Cookie)
146 : static bool is_sensitive_header(std::string_view name);
147 :
148 : // process header intended to be override to process headers
149 : virtual void process_header(std::string key, std::string value);
150 :
151 : protected:
152 : // Log each header (debug): its name, and its value if `options` is enabled, but those of
153 : // the sensitive headers
154 : void log_headers(const body_logging& options) const;
155 :
156 : // Log the size of a body (trace), and its start if `options` is enabled
157 : static void log_body(std::string_view body, size_t size, const body_logging& options);
158 :
159 : // Keep track of the body framing (Content-Length, Transfer-Encoding) of a received header
160 : void process_framing_header(std::string_view key, std::string_view value);
161 :
162 : // Add, or replace the first `key` header, without checking it: for the parsers, and the
163 : // values the library builds itself (constants, digits)
164 : void store_header(std::string&& key, std::string&& value);
165 : void replace_header(std::string&& key, std::string&& value);
166 :
167 : // Whether a header set by a setter is valid; if not, it is logged and has_refused_header()
168 : // is set
169 : bool accept_header(std::string_view key, std::string_view value);
170 :
171 : // Keep track of the framing headers present, after adding or removing a `key` header
172 : void on_header_added(std::string_view key);
173 : void on_header_removed(std::string_view key);
174 :
175 : std::vector<http_header> headers_;
176 : std::vector<http_header> proxy_headers_;
177 : boost::tribool keep_alive_ = boost::indeterminate;
178 : bool upgrade_ = false;
179 : bool stream_ = false;
180 : size_t content_length_ = 0;
181 : bool invalid_content_length_ = false;
182 : bool content_length_header_ = false;
183 : bool transfer_encoding_header_ = false;
184 : bool chunked_transfer_ = false;
185 : bool invalid_transfer_encoding_ = false;
186 : bool refused_header_ = false;
187 : uint8_t http_version_major_ = 1;
188 : uint8_t http_version_minor_ = 1;
189 : };
190 :
191 : }
192 :
193 : #endif
|