Coverage Report

Created: 2026-03-31 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}