Line data Source code
1 : #include "../../util/logger.hpp"
2 : #include "../../util/compression.hpp"
3 : #include <algorithm>
4 : #include <cstdint>
5 : #include <limits>
6 : #include <utility>
7 :
8 : #include "request.hpp"
9 : #include "trusted_proxies.hpp"
10 : #include "body_decoders.hpp"
11 : #include "routing/route.hpp"
12 : #include "virtual_host.hpp"
13 :
14 : namespace thinger::http{
15 :
16 : using nlohmann::json;
17 : using std::string;
18 :
19 90034 : request::request(
20 : std::unique_ptr<detail::request_source> source,
21 : std::shared_ptr<http_stream> http_stream,
22 90034 : std::shared_ptr<http_request> http_request) :
23 90034 : http_stream_{std::move(http_stream)},
24 90034 : source_{std::move(source)},
25 180068 : http_request_{std::move(http_request)}
26 : {
27 90034 : if (http_request_) {
28 90034 : body_reader_.set_framing(http_request_->is_chunked_transfer(), http_request_->pending_body_size());
29 : }
30 90034 : body_reader_.set_limit(default_max_body_size);
31 90034 : if (source_) {
32 : // (capturing only `this` fits in the function, without allocating; the source's
33 : // coroutine is the read itself)
34 27586 : body_reader_.set_source([this](uint8_t* buffer, size_t size) {
35 43903 : return source_->read_body_some(buffer, size);
36 : });
37 : }
38 90034 : }
39 :
40 90034 : request::~request()= default;
41 :
42 2846 : const string& request::operator[](const std::string& param) const{
43 2846 : return get_uri_parameter(param);
44 : }
45 :
46 21 : bool request::has(const std::string& param) const {
47 21 : return params_.contains(param);
48 : }
49 :
50 2 : bool request::erase(const std::string& param){
51 2 : return params_.erase(param)>0;
52 : }
53 :
54 176752 : std::shared_ptr<http_request> request::get_http_request(){
55 176752 : return http_request_;
56 : }
57 :
58 276 : std::shared_ptr<const http_request> request::get_http_request() const{
59 276 : return http_request_;
60 : }
61 :
62 61525 : std::shared_ptr<http_stream> request::get_http_stream() const {
63 61525 : return http_stream_;
64 : }
65 :
66 28 : void request::set_auth_user(const std::string& auth_user){
67 28 : auth_user_ = auth_user;
68 28 : }
69 :
70 28 : const std::string& request::get_auth_user() const{
71 28 : return auth_user_;
72 : }
73 :
74 29370 : void request::set_matched_route(const route* route) {
75 29370 : matched_route_ = route;
76 29370 : }
77 :
78 209 : const route* request::get_matched_route() const {
79 209 : return matched_route_;
80 : }
81 :
82 5995 : void request::set_uri_parameter(const std::string& param, const std::string& value){
83 : // erase all existing entries with the specified key
84 5995 : params_.erase(param);
85 :
86 : // insert the new key-value pair
87 5995 : params_.insert(std::make_pair(param, value));
88 5995 : }
89 :
90 4 : void request::add_uri_parameter(const std::string& param, const std::string& value){
91 4 : params_.insert({param, value});
92 4 : }
93 :
94 2846 : const std::string& request::get_uri_parameter(const std::string& param) const{
95 2846 : auto it = params_.find(param);
96 2846 : if(it!=params_.end()){
97 2846 : return it->second;
98 : }
99 0 : LOG_WARNING("cannot find required parameter: {}", param);
100 0 : static std::string empty_string;
101 0 : return empty_string;
102 : }
103 :
104 0 : const std::multimap<std::string, std::string>& request::get_uri_parameters() const{
105 0 : return params_;
106 : }
107 :
108 2 : void request::set_auth_groups(const std::set<std::string> &groups) {
109 2 : groups_ = groups;
110 2 : }
111 :
112 2 : const std::set<std::string> & request::get_auth_groups() const {
113 2 : return groups_;
114 : }
115 :
116 2 : std::string request::debug_parameters() const {
117 2 : std::stringstream str;
118 6 : for(const auto& param: params_){
119 4 : str << "(" << param.first << ":" << param.second << ") ";
120 : }
121 4 : return str.str();
122 2 : }
123 :
124 2280 : std::string request::get_peer_ip() const{
125 2280 : if (!peer_ip_.empty()) return peer_ip_;
126 2238 : return source_ ? source_->info().peer_ip : "";
127 : }
128 :
129 243 : connection_info request::connection() const {
130 243 : return source_ ? source_->info() : connection_info{};
131 : }
132 :
133 51 : std::optional<asio::tls_session> request::tls() const {
134 51 : return source_ ? source_->tls() : std::nullopt;
135 : }
136 :
137 27301 : bool request::misdirected() const {
138 27301 : if (!source_ || !http_request_ || !http_request_->is_ssl()) return false;
139 : // The name of the virtual host (see http_application::resolve_host())
140 3860 : const auto& host = http_request_->get_header(header::host);
141 3860 : return source_->misdirected(virtual_host::host_name(host.empty() ? http_request_->get_host() : host));
142 : }
143 :
144 204 : std::string request::get_request_ip() const{
145 204 : auto peer = get_peer_ip();
146 204 : if (!trusted_proxies_ || trusted_proxies_->empty() || !http_request_) return peer;
147 81 : return trusted_proxies_->resolve(peer, "http", *http_request_).ip;
148 204 : }
149 :
150 111 : std::string request::get_request_scheme() const{
151 111 : std::string scheme = http_request_ && http_request_->is_ssl() ? "https" : "http";
152 111 : if (!trusted_proxies_ || trusted_proxies_->empty() || !http_request_) return scheme;
153 81 : return trusted_proxies_->resolve(get_peer_ip(), scheme, *http_request_).scheme;
154 111 : }
155 :
156 2 : bool request::keep_alive() const{
157 2 : return http_request_ && http_request_->keep_alive();
158 : }
159 :
160 357 : cancel_reason request::cancelled() const {
161 357 : return http_stream_ ? http_stream_->cancelled() : cancel_reason::none;
162 : }
163 :
164 657 : thinger::awaitable<cancel_reason> request::client_gone() {
165 : if (http_stream_) co_return co_await http_stream_->wait_cancelled();
166 : // Never set: the wait ends when it is cancelled
167 : asio::async_event never(co_await boost::asio::this_coro::executor);
168 : co_await never.wait();
169 : co_return cancel_reason::none;
170 1314 : }
171 :
172 51 : boost::asio::cancellation_slot request::cancellation_slot() {
173 51 : return http_stream_ ? http_stream_->observer_slot() : boost::asio::cancellation_slot{};
174 : }
175 :
176 : // Convenience methods implementation
177 :
178 428 : std::string request::query(const std::string& key) const {
179 428 : if (http_request_ && http_request_->has_uri_parameter(key)) {
180 160 : return http_request_->get_uri_parameter(key);
181 : }
182 536 : return "";
183 : }
184 :
185 130 : std::string request::query(const std::string& key, const std::string& default_value) const {
186 130 : if (http_request_ && http_request_->has_uri_parameter(key)) {
187 108 : return http_request_->get_uri_parameter(key);
188 : }
189 22 : return default_value;
190 : }
191 :
192 464 : std::string request::body() const {
193 464 : return http_request_ ? http_request_->get_body() : "";
194 : }
195 :
196 15 : nlohmann::json request::json() const {
197 15 : if (!http_request_) {
198 0 : return nlohmann::json{};
199 : }
200 15 : const auto& content = http_request_->get_body();
201 15 : if (content.empty()) {
202 2 : return nlohmann::json{};
203 : }
204 13 : auto j = nlohmann::json::parse(content, nullptr, false);
205 13 : if (j.is_discarded()) {
206 2 : return nlohmann::json{};
207 : }
208 11 : return j;
209 13 : }
210 :
211 875 : std::string request::header(const std::string& key) const {
212 875 : return http_request_ ? http_request_->get_header(key) : "";
213 : }
214 :
215 1022 : const body_settings& request::get_body_settings() const {
216 1022 : return body_settings_ ? *body_settings_ : body_settings::defaults();
217 : }
218 :
219 : // --- Body reading ---
220 :
221 4478 : void request::set_read_ahead(const uint8_t* data, size_t size) {
222 4478 : body_reader_.set_read_ahead(data, size);
223 4478 : }
224 :
225 8 : size_t request::content_length() const {
226 8 : return http_request_ ? http_request_->get_content_length() : 0;
227 : }
228 :
229 171 : bool request::is_chunked() const {
230 171 : return http_request_ && http_request_->is_chunked_transfer();
231 : }
232 :
233 17654 : std::vector<uint8_t> request::take_read_ahead() {
234 17654 : return body_reader_.take_read_ahead();
235 : }
236 :
237 43843 : void request::note_body_read() {
238 43843 : if (body_reader_.has_pending() || body_reader_.error() != body_error::none) return;
239 : // What is left in the read-ahead follows the request (a pipelined request)
240 2052 : if (http_stream_) http_stream_->end_input(body_reader_.buffered() > 0);
241 : }
242 :
243 336 : thinger::awaitable<size_t> request::read(uint8_t* buffer, size_t size) {
244 : size_t bytes = co_await body_reader_.read(buffer, size);
245 : note_body_read();
246 : co_return bytes;
247 672 : }
248 :
249 41748 : thinger::awaitable<size_t> request::read_some(uint8_t* buffer, size_t max_size) {
250 : size_t bytes = co_await body_reader_.read_some(buffer, max_size);
251 : note_body_read();
252 : co_return bytes;
253 83496 : }
254 :
255 518 : void request::set_read_deadline(std::chrono::steady_clock::time_point deadline) {
256 518 : if (source_) source_->set_read_deadline(deadline);
257 518 : }
258 :
259 : // Decode the body in place according to its Content-Encoding (gzip, x-gzip, deflate, or
260 : // several of them; a body with any other coding is left to the handler, with its
261 : // Content-Encoding), up to `max_size` bytes once decoded
262 1037 : static body_error decode_content_encoding(http_request& http_request, size_t max_size) {
263 1037 : if (!http_request.has_header("Content-Encoding")) return body_error::none;
264 :
265 : // (the values of every Content-Encoding header make one list)
266 80 : std::string encoding;
267 160 : for (const auto& value : http_request.get_headers_with_key("Content-Encoding")) {
268 80 : if (!encoding.empty()) encoding += ", ";
269 80 : encoding += value;
270 80 : }
271 80 : switch (::thinger::util::decode_content(encoding, http_request.get_body(), max_size)) {
272 44 : case ::thinger::util::content_decoding::decoded:
273 44 : http_request.remove_headers("Content-Encoding");
274 44 : return body_error::none;
275 6 : case ::thinger::util::content_decoding::unsupported:
276 6 : return body_error::none;
277 15 : case ::thinger::util::content_decoding::too_large:
278 15 : LOG_ERROR("Decompressed {} request body exceeds the maximum body size", encoding);
279 15 : return body_error::too_large;
280 15 : case ::thinger::util::content_decoding::invalid:
281 15 : break;
282 : }
283 15 : LOG_ERROR("Failed to decompress {} request body", encoding);
284 15 : return body_error::bad_encoding;
285 80 : }
286 :
287 2063 : thinger::awaitable<bool> request::read_body() {
288 : if (!http_request_) co_return false;
289 :
290 : // Nothing left to read (already read, or no body)
291 : if (!body_reader_.has_pending()) co_return body_reader_.error() == body_error::none;
292 :
293 : // Held in memory: at most what a size_t counts, whatever the limit (a 32-bit build)
294 : auto limit = static_cast<size_t>(std::min<std::uint64_t>(body_reader_.limit(), std::numeric_limits<size_t>::max()));
295 : bool read = co_await body_reader_.read_all(http_request_->get_body(), limit);
296 : note_body_read();
297 : if (!read) co_return false;
298 :
299 : auto error = decode_content_encoding(*http_request_, limit);
300 : if (error != body_error::none) {
301 : body_reader_.set_error(error);
302 : co_return false;
303 : }
304 : co_return true;
305 4126 : }
306 :
307 76115 : bool request::has_pending_body() const {
308 76115 : return http_request_ && body_reader_.has_pending();
309 : }
310 :
311 25374 : body_error request::get_body_error() const {
312 25374 : return body_reader_.error();
313 : }
314 :
315 102 : thinger::awaitable<multipart_form> request::read_multipart(multipart_options options) {
316 : co_return co_await http::read_multipart(*this, std::move(options));
317 204 : }
318 :
319 179 : thinger::awaitable<bool> request::discard_body(std::uint64_t max_size) {
320 : bool discarded = co_await body_reader_.discard(max_size);
321 : note_body_read();
322 : co_return discarded;
323 358 : }
324 :
325 : }
|