Coverage Report

Created: 2026-09-23 07:12

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