/src/wt/src/Wt/WEnvironment.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 | | #include "Wt/WEnvironment.h" |
7 | | |
8 | | #include "Wt/Utils.h" |
9 | | #include "Wt/WException.h" |
10 | | #include "Wt/WLogger.h" |
11 | | #include "Wt/WSslInfo.h" |
12 | | #include "Wt/Http/Request.h" |
13 | | #include "Wt/Json/Parser.h" |
14 | | #include "Wt/Json/Object.h" |
15 | | #include "Wt/Json/Array.h" |
16 | | #include "Wt/WString.h" |
17 | | |
18 | | #include "WebController.h" |
19 | | #include "WebRequest.h" |
20 | | #include "WebSession.h" |
21 | | #include "WebUtils.h" |
22 | | #include "Configuration.h" |
23 | | |
24 | | #include <boost/algorithm/string.hpp> |
25 | | |
26 | | #ifndef WT_TARGET_JAVA |
27 | | #ifdef WT_WITH_SSL |
28 | | #include <openssl/ssl.h> |
29 | | #include "SslUtils.h" |
30 | | #endif //WT_TARGET_JAVA |
31 | | #endif //WT_WITH_SSL |
32 | | |
33 | | namespace { |
34 | 0 | inline std::string str(const char *s) { |
35 | 0 | return s ? std::string(s) : std::string(); |
36 | 0 | } |
37 | | } |
38 | | |
39 | | namespace Wt { |
40 | | |
41 | | LOGGER("WEnvironment"); |
42 | | |
43 | | WEnvironment::WEnvironment() |
44 | 0 | : session_(nullptr), |
45 | 0 | doesAjax_(false), |
46 | 0 | doesCookies_(false), |
47 | 0 | internalPathUsingFragments_(false), |
48 | 0 | screenWidth_(-1), |
49 | 0 | screenHeight_(-1), |
50 | 0 | dpiScale_(1), |
51 | 0 | webGLsupported_(false), |
52 | 0 | isLikelyBotGetRequest_(false), |
53 | 0 | timeZoneOffset_(0) |
54 | 0 | { } |
55 | | |
56 | | WEnvironment::WEnvironment(WebSession *session) |
57 | 0 | : session_(session), |
58 | 0 | doesAjax_(false), |
59 | 0 | doesCookies_(false), |
60 | 0 | internalPathUsingFragments_(false), |
61 | 0 | screenWidth_(-1), |
62 | 0 | screenHeight_(-1), |
63 | 0 | dpiScale_(1), |
64 | 0 | webGLsupported_(false), |
65 | 0 | isLikelyBotGetRequest_(false), |
66 | 0 | timeZoneOffset_(0) |
67 | 0 | { } |
68 | | |
69 | | WEnvironment::~WEnvironment() |
70 | 0 | { } |
71 | | |
72 | | void WEnvironment::setInternalPath(const std::string& path) |
73 | 0 | { |
74 | 0 | if (path.empty()) |
75 | 0 | internalPath_ = path; |
76 | 0 | else |
77 | 0 | internalPath_ = Utils::prepend(path, '/'); |
78 | 0 | } |
79 | | |
80 | | const std::string& WEnvironment::deploymentPath() const |
81 | 0 | { |
82 | 0 | if (!publicDeploymentPath_.empty()) |
83 | 0 | return publicDeploymentPath_; |
84 | 0 | else |
85 | 0 | return session_->deploymentPath(); |
86 | 0 | } |
87 | | |
88 | | void WEnvironment::updateHostName(const WebRequest& request) |
89 | 0 | { |
90 | 0 | Configuration& conf = session_->controller()->configuration(); |
91 | 0 | std::string newHost = request.hostName(conf); |
92 | |
|
93 | 0 | if (!newHost.empty()) { |
94 | 0 | host_ = newHost; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | void WEnvironment::updateUrlScheme(const WebRequest& request) |
99 | 0 | { |
100 | 0 | Configuration& conf = session_->controller()->configuration(); |
101 | |
|
102 | 0 | urlScheme_ = request.urlScheme(conf); |
103 | 0 | } |
104 | | |
105 | | |
106 | | void WEnvironment::init(const WebRequest& request, const std::string& sessionId) |
107 | 0 | { |
108 | 0 | Configuration& conf = session_->controller()->configuration(); |
109 | |
|
110 | 0 | queryString_ = request.queryString(); |
111 | 0 | parameters_ = request.getParameterMap(); |
112 | 0 | host_ = str(request.headerValue("Host")); |
113 | 0 | referer_ = str(request.headerValue("Referer")); |
114 | 0 | accept_ = str(request.headerValue("Accept")); |
115 | 0 | serverSignature_ = str(request.envValue("SERVER_SIGNATURE")); |
116 | 0 | serverSoftware_ = str(request.envValue("SERVER_SOFTWARE")); |
117 | 0 | serverAdmin_ = str(request.envValue("SERVER_ADMIN")); |
118 | 0 | redirectSecret_ = session_->controller()->redirectSecret(request); |
119 | |
|
120 | 0 | #ifndef WT_TARGET_JAVA |
121 | 0 | sslInfo_ = request.sslInfo(conf); |
122 | 0 | #endif |
123 | |
|
124 | 0 | setUserAgent(str(request.headerValue("User-Agent"))); |
125 | 0 | updateUrlScheme(request); |
126 | |
|
127 | 0 | LOG_INFO("UserAgent: " << userAgent_); |
128 | | |
129 | | /* |
130 | | * If behind a reverse proxy, use external host, schema as communicated using 'X-Forwarded' |
131 | | * headers. |
132 | | */ |
133 | 0 | if (conf.behindReverseProxy() || |
134 | 0 | conf.isTrustedProxy(request.remoteAddr())) { |
135 | 0 | std::string forwardedHost = str(request.headerValue("X-Forwarded-Host")); |
136 | |
|
137 | 0 | if (!forwardedHost.empty()) { |
138 | 0 | std::string::size_type i = forwardedHost.rfind(','); |
139 | 0 | if (i == std::string::npos) |
140 | 0 | host_ = forwardedHost; |
141 | 0 | else |
142 | 0 | host_ = forwardedHost.substr(i+1); |
143 | 0 | } |
144 | 0 | } |
145 | | |
146 | | |
147 | |
|
148 | 0 | if (host_.empty()) { |
149 | | /* |
150 | | * HTTP 1.0 doesn't require it: guess from config |
151 | | */ |
152 | 0 | host_ = request.serverName(); |
153 | 0 | if (!request.serverPort().empty()) |
154 | 0 | host_ += ":" + request.serverPort(); |
155 | 0 | } |
156 | |
|
157 | 0 | clientAddress_ = request.clientAddress(conf); |
158 | |
|
159 | 0 | const char *cookie = request.headerValue("Cookie"); |
160 | 0 | doesCookies_ = cookie; |
161 | |
|
162 | 0 | if (cookie) |
163 | 0 | parseCookies(cookie, cookies_); |
164 | |
|
165 | 0 | locale_ = request.parseLocale(); |
166 | | |
167 | | // Incoming GET requests with `wtd` shouldn't exist in a new |
168 | | // application: see #13970 |
169 | | // Note: this can come from a reload, which would then initially |
170 | | // kill its session, but after navigation be handled correctly. |
171 | 0 | const std::string* wtdE = request.getParameter("wtd"); |
172 | 0 | const char* method = request.requestMethod(); |
173 | |
|
174 | 0 | isLikelyBotGetRequest_ = wtdE && *wtdE != sessionId && |
175 | 0 | method && std::string(method) == "GET" && |
176 | 0 | agentSupportsAjax(); |
177 | 0 | } |
178 | | |
179 | | |
180 | | void WEnvironment::enableAjax(const WebRequest& request) |
181 | 0 | { |
182 | 0 | doesAjax_ = true; |
183 | 0 | session_->controller()->newAjaxSession(); |
184 | |
|
185 | 0 | doesCookies_ = request.headerValue("Cookie") != nullptr; |
186 | |
|
187 | 0 | if (!request.getParameter("htmlHistory")) |
188 | 0 | internalPathUsingFragments_ = true; |
189 | |
|
190 | 0 | const std::string *scaleE = request.getParameter("scale"); |
191 | |
|
192 | 0 | try { |
193 | 0 | dpiScale_ = scaleE ? Utils::stod(*scaleE) : 1; |
194 | 0 | } catch (std::exception& e) { |
195 | 0 | dpiScale_ = 1; |
196 | 0 | } |
197 | |
|
198 | 0 | const std::string *webGLE = request.getParameter("webGL"); |
199 | |
|
200 | 0 | webGLsupported_ = webGLE ? (*webGLE == "true") : false; |
201 | |
|
202 | 0 | const std::string *tzE = request.getParameter("tz"); |
203 | |
|
204 | 0 | try { |
205 | 0 | timeZoneOffset_ = std::chrono::minutes(tzE ? Utils::stoi(*tzE) : 0); |
206 | 0 | } catch (std::exception& e) { |
207 | 0 | } |
208 | |
|
209 | 0 | const std::string *tzSE = request.getParameter("tzS"); |
210 | |
|
211 | 0 | timeZoneName_ = tzSE ? *tzSE : std::string(""); |
212 | |
|
213 | 0 | const std::string *hashE = request.getParameter("_"); |
214 | | |
215 | | // the internal path, when present as an anchor (#), is only |
216 | | // conveyed in the second request |
217 | 0 | if (hashE) |
218 | 0 | setInternalPath(*hashE); |
219 | |
|
220 | 0 | const std::string *deployPathE = request.getParameter("deployPath"); |
221 | 0 | if (deployPathE) { |
222 | 0 | publicDeploymentPath_ = *deployPathE; |
223 | 0 | std::size_t s = publicDeploymentPath_.find('/'); |
224 | 0 | if (s != 0) |
225 | 0 | publicDeploymentPath_.clear(); // looks invalid |
226 | 0 | } |
227 | |
|
228 | 0 | const std::string *scrWE = request.getParameter("scrW"); |
229 | 0 | if (scrWE) { |
230 | 0 | try { |
231 | 0 | screenWidth_ = Utils::stoi(*scrWE); |
232 | 0 | } catch (std::exception &e) { |
233 | 0 | } |
234 | 0 | } |
235 | 0 | const std::string *scrHE = request.getParameter("scrH"); |
236 | 0 | if (scrHE) { |
237 | 0 | try { |
238 | 0 | screenHeight_ = Utils::stoi(*scrHE); |
239 | 0 | } catch (std::exception &e) { |
240 | 0 | } |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | void WEnvironment::setUserAgent(const std::string& userAgent) |
245 | 0 | { |
246 | 0 | userAgent_ = userAgent; |
247 | |
|
248 | 0 | Configuration& conf = session_->controller()->configuration(); |
249 | |
|
250 | 0 | agent_ = UserAgent::Unknown; |
251 | | |
252 | | /* detecting MSIE is as messy as their browser */ |
253 | 0 | if (userAgent_.find("Trident/4.0") != std::string::npos) { |
254 | 0 | agent_ = UserAgent::IE8; return; |
255 | 0 | } if (userAgent_.find("Trident/5.0") != std::string::npos) { |
256 | 0 | agent_ = UserAgent::IE9; return; |
257 | 0 | } else if (userAgent_.find("Trident/6.0") != std::string::npos) { |
258 | 0 | agent_ = UserAgent::IE10; return; |
259 | 0 | } else if (userAgent_.find("Trident/") != std::string::npos) { |
260 | 0 | agent_ = UserAgent::IE11; return; |
261 | 0 | } else if (userAgent_.find("MSIE 2.") != std::string::npos |
262 | 0 | || userAgent_.find("MSIE 3.") != std::string::npos |
263 | 0 | || userAgent_.find("MSIE 4.") != std::string::npos |
264 | 0 | || userAgent_.find("MSIE 5.") != std::string::npos |
265 | 0 | || userAgent_.find("IEMobile") != std::string::npos) |
266 | 0 | agent_ = UserAgent::IEMobile; |
267 | 0 | else if (userAgent_.find("MSIE 6.") != std::string::npos) |
268 | 0 | agent_ = UserAgent::IE6; |
269 | 0 | else if (userAgent_.find("MSIE 7.") != std::string::npos) |
270 | 0 | agent_ = UserAgent::IE7; |
271 | 0 | else if (userAgent_.find("MSIE 8.") != std::string::npos) |
272 | 0 | agent_ = UserAgent::IE8; |
273 | 0 | else if (userAgent_.find("MSIE 9.") != std::string::npos) |
274 | 0 | agent_ = UserAgent::IE9; |
275 | 0 | else if (userAgent_.find("MSIE") != std::string::npos) |
276 | 0 | agent_ = UserAgent::IE10; |
277 | | |
278 | 0 | if (userAgent_.find("Opera") != std::string::npos) { |
279 | 0 | agent_ = UserAgent::Opera; |
280 | |
|
281 | 0 | std::size_t t = userAgent_.find("Version/"); |
282 | 0 | if (t != std::string::npos) { |
283 | 0 | std::string vs = userAgent_.substr(t + 8); |
284 | 0 | t = vs.find(' '); |
285 | 0 | if (t != std::string::npos) |
286 | 0 | vs = vs.substr(0, t); |
287 | 0 | try { |
288 | 0 | double v = Utils::stod(vs); |
289 | 0 | if (v >= 10) |
290 | 0 | agent_ = UserAgent::Opera10; |
291 | 0 | } catch (std::exception& e) { } |
292 | 0 | } |
293 | 0 | } |
294 | |
|
295 | 0 | if (userAgent_.find("Chrome") != std::string::npos) { |
296 | 0 | if (userAgent_.find("Android") != std::string::npos) |
297 | 0 | agent_ = UserAgent::MobileWebKitAndroid; |
298 | 0 | else if (userAgent_.find("Chrome/0.") != std::string::npos) |
299 | 0 | agent_ = UserAgent::Chrome0; |
300 | 0 | else if (userAgent_.find("Chrome/1.") != std::string::npos) |
301 | 0 | agent_ = UserAgent::Chrome1; |
302 | 0 | else if (userAgent_.find("Chrome/2.") != std::string::npos) |
303 | 0 | agent_ = UserAgent::Chrome2; |
304 | 0 | else if (userAgent_.find("Chrome/3.") != std::string::npos) |
305 | 0 | agent_ = UserAgent::Chrome3; |
306 | 0 | else if (userAgent_.find("Chrome/4.") != std::string::npos) |
307 | 0 | agent_ = UserAgent::Chrome4; |
308 | 0 | else |
309 | 0 | agent_ = UserAgent::Chrome5; |
310 | 0 | } else if (userAgent_.find("Safari") != std::string::npos) { |
311 | 0 | if (userAgent_.find("iPhone") != std::string::npos |
312 | 0 | || userAgent_.find("iPad") != std::string::npos) { |
313 | 0 | agent_ = UserAgent::MobileWebKitiPhone; |
314 | 0 | } else if (userAgent_.find("Android") != std::string::npos) { |
315 | 0 | agent_ = UserAgent::MobileWebKitAndroid; |
316 | 0 | } else if (userAgent_.find("Mobile") != std::string::npos) { |
317 | 0 | agent_ = UserAgent::MobileWebKit; |
318 | 0 | } else if (userAgent_.find("Version") == std::string::npos) { |
319 | 0 | if (userAgent_.find("Arora") != std::string::npos) |
320 | 0 | agent_ = UserAgent::Arora; |
321 | 0 | else |
322 | 0 | agent_ = UserAgent::Safari; |
323 | 0 | } else if (userAgent_.find("Version/3") != std::string::npos) |
324 | 0 | agent_ = UserAgent::Safari3; |
325 | 0 | else |
326 | 0 | agent_ = UserAgent::Safari4; |
327 | 0 | } else if (userAgent_.find("WebKit") != std::string::npos) { |
328 | 0 | if (userAgent_.find("iPhone") != std::string::npos) |
329 | 0 | agent_ = UserAgent::MobileWebKitiPhone; |
330 | 0 | else |
331 | 0 | agent_ = UserAgent::WebKit; |
332 | 0 | } else if (userAgent_.find("Konqueror") != std::string::npos) |
333 | 0 | agent_ = UserAgent::Konqueror; |
334 | 0 | else if (userAgent_.find("Gecko") != std::string::npos) |
335 | 0 | agent_ = UserAgent::Gecko; |
336 | |
|
337 | 0 | if (userAgent_.find("Firefox") != std::string::npos) { |
338 | 0 | if (userAgent_.find("Firefox/0.") != std::string::npos) |
339 | 0 | agent_ = UserAgent::Firefox; |
340 | 0 | else if (userAgent_.find("Firefox/1.") != std::string::npos) |
341 | 0 | agent_ = UserAgent::Firefox; |
342 | 0 | else if (userAgent_.find("Firefox/2.") != std::string::npos) |
343 | 0 | agent_ = UserAgent::Firefox; |
344 | 0 | else { |
345 | 0 | if (userAgent_.find("Firefox/3.0") != std::string::npos) |
346 | 0 | agent_ = UserAgent::Firefox3_0; |
347 | 0 | else if (userAgent_.find("Firefox/3.1") != std::string::npos) |
348 | 0 | agent_ = UserAgent::Firefox3_1; |
349 | 0 | else if (userAgent_.find("Firefox/3.1b") != std::string::npos) |
350 | 0 | agent_ = UserAgent::Firefox3_1b; |
351 | 0 | else if (userAgent_.find("Firefox/3.5") != std::string::npos) |
352 | 0 | agent_ = UserAgent::Firefox3_5; |
353 | 0 | else if (userAgent_.find("Firefox/3.6") != std::string::npos) |
354 | 0 | agent_ = UserAgent::Firefox3_6; |
355 | 0 | else if (userAgent_.find("Firefox/4.") != std::string::npos) |
356 | 0 | agent_ = UserAgent::Firefox4_0; |
357 | 0 | else |
358 | 0 | agent_ = UserAgent::Firefox5_0; |
359 | 0 | } |
360 | 0 | } |
361 | |
|
362 | 0 | if (userAgent_.find("Edge/") != std::string::npos) { |
363 | 0 | agent_ = UserAgent::Edge; |
364 | 0 | } |
365 | |
|
366 | 0 | if (conf.agentIsBot(userAgent_)) |
367 | 0 | agent_ = UserAgent::BotAgent; |
368 | 0 | } |
369 | | |
370 | | bool WEnvironment::agentSupportsAjax() const |
371 | 0 | { |
372 | 0 | Configuration& conf = session_->controller()->configuration(); |
373 | |
|
374 | 0 | return conf.agentSupportsAjax(userAgent_); |
375 | 0 | } |
376 | | |
377 | | bool WEnvironment::supportsCss3Animations() const |
378 | 0 | { |
379 | 0 | return ((agentIsGecko() && |
380 | 0 | static_cast<unsigned int>(agent_) >= |
381 | 0 | static_cast<unsigned int>(UserAgent::Firefox5_0)) || |
382 | 0 | (agentIsIE() && |
383 | 0 | static_cast<unsigned int>(agent_) >= |
384 | 0 | static_cast<unsigned int>(UserAgent::IE10)) || |
385 | 0 | agentIsWebKit()); |
386 | 0 | } |
387 | | |
388 | | std::string WEnvironment::libraryVersion() |
389 | 0 | { |
390 | 0 | return WT_VERSION_STR; |
391 | 0 | } |
392 | | |
393 | | #ifndef WT_TARGET_JAVA |
394 | | void WEnvironment::libraryVersion(int& series, int& major, int& minor) const |
395 | 0 | { |
396 | 0 | series = WT_SERIES; |
397 | 0 | major = WT_MAJOR; |
398 | 0 | minor = WT_MINOR; |
399 | 0 | } |
400 | | #endif //WT_TARGET_JAVA |
401 | | |
402 | | const Http::ParameterValues& |
403 | | WEnvironment::getParameterValues(const std::string& name) const |
404 | 0 | { |
405 | 0 | Http::ParameterMap::const_iterator i = parameters_.find(name); |
406 | |
|
407 | 0 | if (i != parameters_.end()) |
408 | 0 | return i->second; |
409 | 0 | else |
410 | 0 | return WebRequest::emptyValues_; |
411 | 0 | } |
412 | | |
413 | | const std::string *WEnvironment::getParameter(const std::string& name) const |
414 | 0 | { |
415 | 0 | const Http::ParameterValues& values = getParameterValues(name); |
416 | 0 | if (!Utils::isEmpty(values)) |
417 | 0 | return &values[0]; |
418 | 0 | else |
419 | 0 | return nullptr; |
420 | 0 | } |
421 | | |
422 | | const std::string *WEnvironment::getCookie(const std::string& cookieName) |
423 | | const |
424 | 0 | { |
425 | 0 | CookieMap::const_iterator i = cookies_.find(cookieName); |
426 | |
|
427 | 0 | if (i == cookies_.end()) |
428 | 0 | return nullptr; |
429 | 0 | else |
430 | 0 | return &i->second; |
431 | 0 | } |
432 | | |
433 | | const std::string WEnvironment::headerValue(const std::string& name) const |
434 | 0 | { |
435 | 0 | return session_->getCgiHeader(name); |
436 | 0 | } |
437 | | |
438 | | std::string WEnvironment::getCgiValue(const std::string& varName) const |
439 | 0 | { |
440 | 0 | if (varName == "QUERY_STRING") |
441 | 0 | return queryString_; |
442 | 0 | else |
443 | 0 | return session_->getCgiValue(varName); |
444 | 0 | } |
445 | | |
446 | | WServer *WEnvironment::server() const |
447 | 0 | { |
448 | 0 | #ifndef WT_TARGET_JAVA |
449 | 0 | return session_->controller()->server(); |
450 | | #else |
451 | | return session_->controller(); |
452 | | #endif // WT_TARGET_JAVA |
453 | 0 | } |
454 | | |
455 | | bool WEnvironment::isTest() const |
456 | 0 | { |
457 | 0 | return false; |
458 | 0 | } |
459 | | |
460 | | void WEnvironment::parseCookies(const std::string& cookie, |
461 | | std::map<std::string, std::string>& result) |
462 | 0 | { |
463 | | // Cookie parsing strategy: |
464 | | // - First, split the string on cookie separators (-> name-value pair). |
465 | | // ';' is cookie separator. ',' is not a cookie separator (as in PHP) |
466 | | // - Then, split the name-value pairs on the first '=' |
467 | | // - URL decoding/encoding |
468 | | // - Trim the name, trim the value |
469 | | // - If a name-value pair does not contain an '=', the name-value pair |
470 | | // was the name of the cookie and the value is empty |
471 | |
|
472 | 0 | std::vector<std::string> list; |
473 | 0 | boost::split(list, cookie, boost::is_any_of(";")); |
474 | 0 | for (unsigned int i = 0; i < list.size(); ++i) { |
475 | 0 | std::string::size_type e = list[i].find('='); |
476 | 0 | if (e == std::string::npos) |
477 | 0 | continue; |
478 | 0 | std::string cookieName = list[i].substr(0, e); |
479 | 0 | std::string cookieValue = |
480 | 0 | (e != std::string::npos && list[i].size() > e + 1) ? |
481 | 0 | list[i].substr(e + 1) : ""; |
482 | |
|
483 | 0 | boost::trim(cookieName); |
484 | 0 | boost::trim(cookieValue); |
485 | |
|
486 | 0 | cookieName = Wt::Utils::urlDecode(cookieName); |
487 | 0 | cookieValue = Wt::Utils::urlDecode(cookieValue); |
488 | 0 | if (cookieName != "") |
489 | 0 | result[cookieName] = cookieValue; |
490 | 0 | } |
491 | 0 | } |
492 | | Signal<WDialog *>& WEnvironment::dialogExecuted() const |
493 | 0 | { |
494 | 0 | throw WException("Internal error"); |
495 | 0 | } |
496 | | |
497 | | Signal<WPopupMenu *>& WEnvironment::popupExecuted() const |
498 | 0 | { |
499 | 0 | throw WException("Internal error"); |
500 | 0 | } |
501 | | |
502 | | } |