/src/wt/src/web/WebRequest.C
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2008 Emweb bv, Herent, Belgium. |
3 | | * |
4 | | * See the LICENSE file for terms of use. |
5 | | */ |
6 | | |
7 | | #include "Wt/WException.h" |
8 | | #include "Wt/WLocale.h" |
9 | | #include "Wt/WLogger.h" |
10 | | #include "Wt/WDateTime.h" |
11 | | #include "Wt/Utils.h" |
12 | | |
13 | | #include "WebRequest.h" |
14 | | #include "WebUtils.h" |
15 | | #include "Configuration.h" |
16 | | |
17 | | #include "Wt/Auth/AuthUtils.h" |
18 | | |
19 | | #include <cstdlib> |
20 | | |
21 | | #include <boost/algorithm/string.hpp> |
22 | | |
23 | | #ifndef WT_NO_SPIRIT |
24 | | |
25 | | #include <boost/version.hpp> |
26 | | |
27 | | #if BOOST_VERSION < 103600 |
28 | | #include <boost/spirit.hpp> |
29 | | #else |
30 | | #include <boost/spirit/include/classic_core.hpp> |
31 | | #include <boost/spirit/include/classic_attribute.hpp> |
32 | | #endif |
33 | | |
34 | | #endif // WT_NO_SPIRIT |
35 | | |
36 | | using std::atoi; |
37 | | |
38 | | namespace { |
39 | | |
40 | 0 | inline std::string str(const char *s) { |
41 | 0 | return s ? std::string(s) : std::string(); |
42 | 0 | } |
43 | | |
44 | 0 | bool isPrivateIP(const std::string &s) { |
45 | 0 | return boost::starts_with(s, "127.") || |
46 | 0 | boost::starts_with(s, "10.") || |
47 | 0 | boost::starts_with(s, "192.168.") || |
48 | 0 | (s.size() >= 7 && |
49 | 0 | boost::starts_with(s, "172.") && |
50 | 0 | s[6] == '.' && |
51 | 0 | ((s[4] == '1' && |
52 | 0 | s[5] >= '6' && |
53 | 0 | s[5] <= '9') || |
54 | 0 | (s[4] == '2' && |
55 | 0 | s[5] >= '0' && |
56 | 0 | s[5] <= '9') || |
57 | 0 | (s[4] == '3' && |
58 | 0 | s[5] >= '0' && |
59 | 0 | s[5] <= '1'))); |
60 | 0 | } |
61 | | |
62 | | } |
63 | | |
64 | | namespace Wt { |
65 | | |
66 | | LOGGER("WebRequest"); |
67 | | |
68 | | Http::ParameterValues WebRequest::emptyValues_; |
69 | | |
70 | | struct WebRequest::AsyncEmulation { |
71 | | bool done; |
72 | | |
73 | | AsyncEmulation() |
74 | 0 | : done(false) |
75 | 0 | { } |
76 | | }; |
77 | | |
78 | | WebRequest::WebRequest() |
79 | 0 | : entryPoint_(nullptr), |
80 | 0 | extraStartIndex_(0), |
81 | 0 | async_(nullptr), |
82 | 0 | responseType_(ResponseType::Page), |
83 | 0 | webSocketRequest_(false) |
84 | 0 | { |
85 | 0 | #ifndef BENCH |
86 | 0 | start_ = std::chrono::high_resolution_clock::now(); |
87 | 0 | #endif |
88 | 0 | } |
89 | | |
90 | | WebRequest::~WebRequest() |
91 | 0 | { |
92 | 0 | delete async_; |
93 | 0 | log(); |
94 | 0 | } |
95 | | |
96 | | void WebRequest::log() |
97 | 0 | { |
98 | 0 | #ifndef BENCH |
99 | 0 | if (start_.time_since_epoch().count() > 0) { |
100 | 0 | auto end = std::chrono::high_resolution_clock::now(); |
101 | 0 | double microseconds |
102 | 0 | = std::chrono::duration_cast<std::chrono::microseconds>(end - start_) |
103 | 0 | .count(); |
104 | 0 | LOG_INFO("took " << (microseconds / 1000) << " ms"); |
105 | |
|
106 | 0 | start_ = std::chrono::high_resolution_clock::time_point(); |
107 | 0 | } |
108 | 0 | #endif |
109 | 0 | } |
110 | | |
111 | | void WebRequest::reset() |
112 | 0 | { |
113 | 0 | #ifndef BENCH |
114 | 0 | start_ = std::chrono::high_resolution_clock::now(); |
115 | 0 | #endif |
116 | |
|
117 | 0 | entryPoint_ = 0; |
118 | 0 | delete async_; |
119 | 0 | async_ = 0; |
120 | 0 | webSocketRequest_ = false; |
121 | |
|
122 | 0 | parameters_.clear(); |
123 | 0 | files_.clear(); |
124 | |
|
125 | 0 | urlParams_.clear(); |
126 | 0 | } |
127 | | |
128 | | void WebRequest::readWebSocketMessage(WT_MAYBE_UNUSED const ReadCallback& callback) |
129 | 0 | { |
130 | 0 | throw WException("should not get here"); |
131 | 0 | } |
132 | | |
133 | | bool WebRequest::webSocketMessagePending() const |
134 | 0 | { |
135 | 0 | throw WException("should not get here"); |
136 | 0 | } |
137 | | |
138 | | void WebRequest::setTransferWebSocketResourceSocketCallBack(WebSocketResourceTransferCallback cb) |
139 | 0 | { |
140 | 0 | wsResourceTransferCb_ = cb; |
141 | 0 | } |
142 | | |
143 | | bool WebRequest::hasTransferWebSocketResourceSocketCallBack() |
144 | 0 | { |
145 | 0 | return wsResourceTransferCb_ != nullptr; |
146 | 0 | } |
147 | | |
148 | | void WebRequest::transferWebSocketResourceSocket(const std::shared_ptr<WebSocketConnection> &socket) |
149 | 0 | { |
150 | 0 | Wt::Http::Request request(*this, nullptr); |
151 | 0 | wsResourceTransferCb_(request, socket); |
152 | 0 | } |
153 | | |
154 | | bool WebRequest::detectDisconnect(WT_MAYBE_UNUSED const DisconnectCallback& callback) |
155 | 0 | { |
156 | 0 | return false; /* Not implemented */ |
157 | 0 | } |
158 | | |
159 | | boost::string_view WebRequest::entryPointPath() const |
160 | 0 | { |
161 | 0 | return boost::string_view(pathInfo()).substr(0, extraStartIndex_); |
162 | 0 | } |
163 | | |
164 | | std::string WebRequest::fullEntryPointPath() const |
165 | 0 | { |
166 | 0 | return scriptName() + entryPointPath().to_string(); |
167 | 0 | } |
168 | | |
169 | | boost::string_view WebRequest::extraPathInfo() const |
170 | 0 | { |
171 | 0 | return boost::string_view(pathInfo()).substr(extraStartIndex_); |
172 | 0 | } |
173 | | |
174 | | const char *WebRequest::userAgent() const |
175 | 0 | { |
176 | 0 | return headerValue("User-Agent"); |
177 | 0 | } |
178 | | |
179 | | const char *WebRequest::referer() const |
180 | 0 | { |
181 | 0 | return headerValue("Referer"); |
182 | 0 | } |
183 | | |
184 | | const char *WebRequest::contentType() const |
185 | 0 | { |
186 | 0 | return envValue("CONTENT_TYPE"); |
187 | 0 | } |
188 | | |
189 | | ::int64_t WebRequest::contentLength() const |
190 | 0 | { |
191 | 0 | const char *lenstr = envValue("CONTENT_LENGTH"); |
192 | |
|
193 | 0 | if (!lenstr || strlen(lenstr) == 0) |
194 | 0 | return 0; |
195 | 0 | else { |
196 | 0 | try { |
197 | 0 | ::int64_t len = Utils::stoll(std::string(lenstr)); |
198 | 0 | if (len < 0) { |
199 | 0 | LOG_ERROR("Bad content-length: " << lenstr); |
200 | 0 | throw WException("Bad content-length"); |
201 | 0 | } else { |
202 | 0 | return len; |
203 | 0 | } |
204 | 0 | } catch (std::exception& e) { |
205 | 0 | LOG_ERROR("Bad content-length: " << lenstr); |
206 | 0 | throw WException("Bad content-length"); |
207 | 0 | } |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | | const std::string *WebRequest::getParameter(const std::string& name) const |
212 | 0 | { |
213 | 0 | const Http::ParameterValues& values = getParameterValues(name); |
214 | |
|
215 | 0 | return !values.empty() ? &values[0] : 0; |
216 | 0 | } |
217 | | |
218 | | const Http::ParameterValues& |
219 | | WebRequest::getParameterValues(const std::string& name) const |
220 | 0 | { |
221 | 0 | Http::ParameterMap::const_iterator i = parameters_.find(name); |
222 | 0 | if (i != parameters_.end()) |
223 | 0 | return i->second; |
224 | 0 | else |
225 | 0 | return emptyValues_; |
226 | 0 | } |
227 | | |
228 | | #ifndef WT_NO_SPIRIT |
229 | | namespace { |
230 | | #if BOOST_VERSION < 103600 |
231 | | using namespace boost::spirit; |
232 | | #else |
233 | | using namespace boost::spirit::classic; |
234 | | #endif |
235 | | |
236 | | using namespace boost; |
237 | | |
238 | | /* |
239 | | * My first spirit parser -- spirit is nifty ! |
240 | | * |
241 | | * Parses things like: |
242 | | * nl-be,en-us;q=0.7,en;q=0.3 |
243 | | * ISO-8859-1,utf-8;q=0.7,*;q=0.7 |
244 | | * |
245 | | * And store the values with indicated qualities. |
246 | | */ |
247 | | class ValueListParser : public grammar<ValueListParser> |
248 | | { |
249 | | public: |
250 | | struct Value { |
251 | | std::string value; |
252 | | double quality; |
253 | | |
254 | 0 | Value(std::string v, double q) : value(v), quality(q) { } |
255 | | }; |
256 | | |
257 | | ValueListParser(std::vector<Value>& values) |
258 | 0 | : values_(values) |
259 | 0 | { } |
260 | | |
261 | | private: |
262 | | std::vector<Value>& values_; |
263 | | |
264 | 0 | void setQuality(double v) const { |
265 | 0 | values_.back().quality = v; |
266 | 0 | } |
267 | | |
268 | 0 | void addValue(char const* str, char const* end) const { |
269 | 0 | values_.push_back(Value(std::string(str, end), 1.)); |
270 | 0 | } |
271 | | |
272 | | typedef ValueListParser self_t; |
273 | | |
274 | | public: |
275 | | template <typename ScannerT> |
276 | | struct definition |
277 | | { |
278 | | definition(ValueListParser const& self) |
279 | 0 | { |
280 | 0 | option |
281 | 0 | = ((ch_p('q') | ch_p('Q')) |
282 | 0 | >> '=' |
283 | 0 | >> ureal_p |
284 | 0 | [ |
285 | 0 | std::bind(&self_t::setQuality, self, std::placeholders::_1) |
286 | 0 | ] |
287 | 0 | ) |
288 | 0 | | (+alpha_p >> '=' >> +alnum_p) |
289 | 0 | ; |
290 | |
|
291 | 0 | value |
292 | 0 | = lexeme_d[(alpha_p >> +(alnum_p | '-')) | '*'] |
293 | 0 | [ |
294 | 0 | std::bind(&self_t::addValue, self, std::placeholders::_1, std::placeholders:: _2) |
295 | 0 | ] |
296 | 0 | >> !( ';' >> option ) |
297 | 0 | ; |
298 | |
|
299 | 0 | valuelist |
300 | 0 | = !(value >> *(',' >> value )) >> end_p |
301 | 0 | ; |
302 | 0 | } |
303 | | |
304 | | rule<ScannerT> option, value, valuelist; |
305 | | |
306 | 0 | rule<ScannerT> const& start() const { return valuelist; } |
307 | | }; |
308 | | }; |
309 | | } |
310 | | |
311 | | std::string WebRequest::parsePreferredAcceptValue(const char *str) const |
312 | 0 | { |
313 | 0 | if (!str) |
314 | 0 | return std::string(); |
315 | | |
316 | 0 | std::vector<ValueListParser::Value> values; |
317 | |
|
318 | 0 | ValueListParser valueListParser(values); |
319 | |
|
320 | 0 | parse_info<> info = parse(str, valueListParser, space_p); |
321 | |
|
322 | 0 | if (info.full) { |
323 | 0 | unsigned best = 0; |
324 | 0 | for (unsigned i = 1; i < values.size(); ++i) { |
325 | 0 | if (values[i].quality > values[best].quality) |
326 | 0 | best = i; |
327 | 0 | } |
328 | |
|
329 | 0 | if (best < values.size()) |
330 | 0 | return values[best].value; |
331 | 0 | else |
332 | 0 | return std::string(); |
333 | 0 | } else { |
334 | 0 | LOG_ERROR("Could not parse 'Accept-Language: " << str |
335 | 0 | << "', stopped at: '" << info.stop << '\''); |
336 | 0 | return std::string(); |
337 | 0 | } |
338 | 0 | } |
339 | | #else |
340 | | std::string WebRequest::parsePreferredAcceptValue(const char *str) const |
341 | | { |
342 | | return std::string(); |
343 | | } |
344 | | #endif // WT_NO_SPIRIT |
345 | | |
346 | | WLocale WebRequest::parseLocale() const |
347 | 0 | { |
348 | 0 | return WLocale(parsePreferredAcceptValue(headerValue("Accept-Language"))); |
349 | 0 | } |
350 | | |
351 | | void WebRequest::setAsyncCallback(const WriteCallback& cb) |
352 | 0 | { |
353 | 0 | asyncCallback_ = cb; |
354 | 0 | } |
355 | | |
356 | | const WebRequest::WriteCallback& WebRequest::getAsyncCallback() |
357 | 0 | { |
358 | 0 | return asyncCallback_; |
359 | 0 | } |
360 | | |
361 | | void WebRequest::emulateAsync(ResponseState state) |
362 | 0 | { |
363 | 0 | if (async_) { |
364 | 0 | if (state == ResponseState::ResponseDone) |
365 | 0 | async_->done = true; |
366 | |
|
367 | 0 | return; |
368 | 0 | } |
369 | | |
370 | 0 | if (state == ResponseState::ResponseFlush) { |
371 | 0 | async_ = new AsyncEmulation(); |
372 | | |
373 | | /* |
374 | | * Invoke callback immediately and keep doing so until we get a |
375 | | * flush(ResponseDone). If we do not have a callback then the application |
376 | | * is waiting for some event to finish te request (e.g. a resource |
377 | | * continuation) and thus we exit now and wait for more flush()ing. |
378 | | */ |
379 | 0 | while (!async_->done) { |
380 | 0 | if (!asyncCallback_) { |
381 | 0 | delete async_; |
382 | 0 | async_ = nullptr; |
383 | 0 | return; |
384 | 0 | } |
385 | | |
386 | 0 | WriteCallback fn = asyncCallback_; |
387 | 0 | asyncCallback_ = WriteCallback(); |
388 | 0 | fn(WebWriteEvent::Completed); |
389 | 0 | } |
390 | 0 | } |
391 | | |
392 | 0 | delete this; |
393 | 0 | } |
394 | | |
395 | | void WebRequest::setResponseType(ResponseType responseType) |
396 | 0 | { |
397 | 0 | responseType_ = responseType; |
398 | 0 | } |
399 | | |
400 | | const std::vector<std::pair<std::string, std::string> > &WebRequest::urlParams() const |
401 | 0 | { |
402 | 0 | return urlParams_; |
403 | 0 | } |
404 | | |
405 | | std::string WebRequest::clientAddress(const Configuration &conf) const |
406 | 0 | { |
407 | 0 | std::string remoteAddr = str(envValue("REMOTE_ADDR")); |
408 | 0 | if (conf.behindReverseProxy()) { |
409 | | // Old, deprecated behavior |
410 | 0 | std::string clientIp = str(headerValue("Client-IP")); |
411 | |
|
412 | 0 | std::vector<std::string> ips; |
413 | 0 | if (!clientIp.empty()) |
414 | 0 | boost::split(ips, clientIp, boost::is_any_of(",")); |
415 | |
|
416 | 0 | std::string forwardedFor = str(headerValue("X-Forwarded-For")); |
417 | |
|
418 | 0 | std::vector<std::string> forwardedIps; |
419 | 0 | if (!forwardedFor.empty()) |
420 | 0 | boost::split(forwardedIps, forwardedFor, boost::is_any_of(",")); |
421 | |
|
422 | 0 | Utils::insert(ips, forwardedIps); |
423 | |
|
424 | 0 | for (auto &ip : ips) { |
425 | 0 | boost::trim(ip); |
426 | |
|
427 | 0 | if (!ip.empty() |
428 | 0 | && !isPrivateIP(ip)) { |
429 | 0 | return ip; |
430 | 0 | } |
431 | 0 | } |
432 | | |
433 | 0 | return remoteAddr; |
434 | 0 | } else { |
435 | 0 | if (conf.isTrustedProxy(remoteAddr)) { |
436 | 0 | std::string forwardedFor = str(headerValue(conf.originalIPHeader().c_str())); |
437 | 0 | boost::trim(forwardedFor); |
438 | 0 | std::vector<std::string> forwardedIps; |
439 | 0 | boost::split(forwardedIps, forwardedFor, boost::is_any_of(",")); |
440 | 0 | for (auto it = forwardedIps.rbegin(); |
441 | 0 | it != forwardedIps.rend(); ++it) { |
442 | 0 | boost::trim(*it); |
443 | 0 | if (!it->empty()) { |
444 | 0 | if (!conf.isTrustedProxy(*it)) { |
445 | 0 | return *it; |
446 | 0 | } else { |
447 | | /* |
448 | | * When the left-most address in a forwardedHeader is contained |
449 | | * within a trustedProxy subnet, it should be returned as the clientAddress |
450 | | */ |
451 | 0 | remoteAddr = *it; |
452 | 0 | } |
453 | 0 | } |
454 | 0 | } |
455 | 0 | } |
456 | 0 | return remoteAddr; |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | std::string WebRequest::hostName(const Configuration &conf) const |
461 | 0 | { |
462 | 0 | std::string host = str(headerValue("Host")); |
463 | |
|
464 | 0 | if (conf.behindReverseProxy() || |
465 | 0 | conf.isTrustedProxy(remoteAddr())) { |
466 | 0 | std::string forwardedHost = str(headerValue("X-Forwarded-Host")); |
467 | |
|
468 | 0 | if (!forwardedHost.empty()) { |
469 | 0 | std::string::size_type i = forwardedHost.rfind(','); |
470 | 0 | if (i == std::string::npos) { |
471 | 0 | host = forwardedHost; |
472 | 0 | } else { |
473 | 0 | host = forwardedHost.substr(i + 1); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | } |
477 | |
|
478 | 0 | return host; |
479 | 0 | } |
480 | | |
481 | | std::string WebRequest::urlScheme(const Configuration &conf) const |
482 | 0 | { |
483 | 0 | if (conf.behindReverseProxy() || |
484 | 0 | conf.isTrustedProxy(remoteAddr())) { |
485 | 0 | std::string forwardedProto = str(headerValue("X-Forwarded-Proto")); |
486 | 0 | if (!forwardedProto.empty()) { |
487 | 0 | std::string::size_type i = forwardedProto.rfind(','); |
488 | 0 | if (i == std::string::npos) |
489 | 0 | return forwardedProto; |
490 | 0 | else |
491 | 0 | return forwardedProto.substr(i+1); |
492 | 0 | } |
493 | 0 | } |
494 | | |
495 | 0 | return urlScheme(); |
496 | 0 | } |
497 | | |
498 | | void WebRequest::addNonce() |
499 | 0 | { |
500 | 0 | nonce_ = Utils::base64Encode(Auth::Utils::createSalt(18), false); |
501 | 0 | addHeader("Content-Security-Policy", "script-src 'nonce-"+nonce()+"' 'strict-dynamic' 'unsafe-eval'"); |
502 | 0 | } |
503 | | |
504 | | } |