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