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