Coverage Report

Created: 2026-06-10 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/web/WebController.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
7
#include <fstream>
8
9
#include <boost/algorithm/string.hpp>
10
11
#ifdef WT_THREADED
12
#include <chrono>
13
#endif // WT_THREADED
14
15
#include "Wt/Utils.h"
16
#include "Wt/WApplication.h"
17
#include "Wt/WEvent.h"
18
#include "Wt/WRandom.h"
19
#include "Wt/WResource.h"
20
#include "Wt/WServer.h"
21
#include "Wt/WSocketNotifier.h"
22
#include "Wt/WWebSocketResource.h"
23
24
#include "Configuration.h"
25
#include "CgiParser.h"
26
#include "WebController.h"
27
#include "WebRequest.h"
28
#include "WebSession.h"
29
#include "TimeUtil.h"
30
#include "WebUtils.h"
31
32
#ifdef HAVE_GRAPHICSMAGICK
33
#include <magick/api.h>
34
#endif
35
36
#include <boost/utility/string_view.hpp>
37
38
#include <algorithm>
39
#include <csignal>
40
41
0
#define WT_REDIRECT_SECRET_HEADER "X-Wt-Redirect-Secret"
42
43
namespace {
44
  std::string str(const std::string *strPtr)
45
0
  {
46
0
    if (strPtr) {
47
0
      return *strPtr;
48
0
    } else {
49
0
      return std::string();
50
0
    }
51
0
  }
52
}
53
54
namespace Wt {
55
56
LOGGER("WebController");
57
58
WebController::WebController(WServer& server,
59
                             const std::string& singleSessionId,
60
                             bool autoExpire)
61
0
  : conf_(server.configuration()),
62
0
    singleSessionId_(singleSessionId),
63
0
    autoExpire_(autoExpire),
64
0
    plainHtmlSessions_(0),
65
0
    ajaxSessions_(0),
66
0
    zombieSessions_(0),
67
0
    running_(false),
68
#ifdef WT_THREADED
69
0
    socketNotifier_(this),
70
#endif // WT_THREADED
71
0
    server_(server)
72
0
{
73
0
#ifndef WT_DEBUG_JS
74
0
  WObject::seedId(WRandom::get());
75
#else
76
  WObject::seedId(0);
77
#endif
78
79
0
  redirectSecret_ = WRandom::generateId(32);
80
81
#ifdef HAVE_GRAPHICSMAGICK
82
  InitializeMagick(0);
83
#endif
84
85
0
  start();
86
0
}
87
88
WebController::~WebController()
89
0
{
90
#ifdef HAVE_GRAPHICSMAGICK
91
  DestroyMagick();
92
#endif
93
0
}
94
95
void WebController::start()
96
0
{
97
0
  running_ = true;
98
0
}
99
100
void WebController::shutdown()
101
0
{
102
0
  {
103
0
    std::vector<std::shared_ptr<WebSession>> sessionList;
104
105
0
    {
106
0
#ifdef WT_THREADED
107
0
      std::unique_lock<std::recursive_mutex> lock(mutex_);
108
0
#endif // WT_THREADED
109
110
0
      running_ = false;
111
112
0
      LOG_INFO_S(&server_, "shutdown: stopping " << sessions_.size()
113
0
                 << " sessions.");
114
115
0
      for (SessionMap::iterator i = sessions_.begin(); i != sessions_.end();
116
0
           ++i)
117
0
        sessionList.push_back(i->second);
118
119
0
      sessions_.clear();
120
121
0
      ajaxSessions_ = 0;
122
0
      plainHtmlSessions_ = 0;
123
0
    }
124
125
0
    for (unsigned i = 0; i < sessionList.size(); ++i) {
126
0
      std::shared_ptr<WebSession> session = sessionList[i];
127
0
      WebSession::Handler handler(session,
128
0
                                  WebSession::Handler::LockOption::TakeLock);
129
0
      session->expire();
130
0
    }
131
0
  }
132
133
0
#ifdef WT_THREADED
134
0
  while (zombieSessions_ > 0) {
135
0
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
136
0
  }
137
0
#endif
138
0
}
139
140
void WebController::sessionDeleted()
141
0
{
142
0
#ifdef WT_THREADED
143
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
144
0
#endif // WT_THREADED
145
0
  --zombieSessions_;
146
0
}
147
148
Configuration& WebController::configuration()
149
0
{
150
0
  return conf_;
151
0
}
152
153
const Configuration& WebController::configuration() const
154
0
{
155
0
  return conf_;
156
0
}
157
158
int WebController::sessionCount() const
159
0
{
160
0
#ifdef WT_THREADED
161
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
162
0
#endif
163
0
  return sessions_.size();
164
0
}
165
166
std::vector<std::string> WebController::sessions(bool onlyRendered)
167
0
{
168
0
#ifdef WT_THREADED
169
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
170
0
#endif
171
0
  std::vector<std::string> sessionIds;
172
0
  for (SessionMap::const_iterator i = sessions_.begin(); i != sessions_.end(); ++i) {
173
0
    if (!onlyRendered || i->second->app() != nullptr)
174
0
      sessionIds.push_back(i->first);
175
0
  }
176
0
  return sessionIds;
177
0
}
178
179
bool WebController::expireSessions()
180
0
{
181
0
  std::vector<std::shared_ptr<WebSession>> toExpire;
182
183
0
  bool result;
184
0
  {
185
0
    Time now;
186
187
0
#ifdef WT_THREADED
188
0
    std::unique_lock<std::recursive_mutex> lock(mutex_);
189
0
#endif // WT_THREADED
190
191
0
    for (SessionMap::iterator i = sessions_.begin(); i != sessions_.end(); ++i) {
192
0
      std::shared_ptr<WebSession> session = i->second;
193
194
0
      int diff = session->expireTime() - now;
195
196
0
      if (diff < 1000 && configuration().sessionTimeout() != -1) {
197
0
        toExpire.push_back(session);
198
        // Note: the session is not yet removed from sessions_ map since
199
        // we want to grab the UpdateLock to do this and grabbing it here
200
        // might cause a deadlock.
201
0
      }
202
0
    }
203
204
0
    result = !sessions_.empty();
205
0
  }
206
207
0
  for (unsigned i = 0; i < toExpire.size(); ++i) {
208
0
    std::shared_ptr<WebSession> session = toExpire[i];
209
210
0
    LOG_INFO_S(session, "timeout: expiring");
211
0
    WebSession::Handler handler(session,
212
0
                                WebSession::Handler::LockOption::TakeLock);
213
214
0
#ifdef WT_THREADED
215
0
    std::unique_lock<std::recursive_mutex> lock(mutex_);
216
0
#endif // WT_THREADED
217
218
    // Another thread might have already removed it
219
0
    if (sessions_.find(session->sessionId()) == sessions_.end())
220
0
      continue;
221
222
0
    if (session->env().ajax())
223
0
      --ajaxSessions_;
224
0
    else
225
0
      --plainHtmlSessions_;
226
227
0
    ++zombieSessions_;
228
229
0
    sessions_.erase(session->sessionId());
230
231
0
    session->expire();
232
0
  }
233
234
0
  return result;
235
0
}
236
237
void WebController::addSession(const std::shared_ptr<WebSession>& session)
238
0
{
239
0
#ifdef WT_THREADED
240
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
241
0
#endif // WT_THREADED
242
243
0
  sessions_[session->sessionId()] = session;
244
0
}
245
246
void WebController::removeSession(const std::string& sessionId)
247
0
{
248
0
#ifdef WT_THREADED
249
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
250
0
#endif // WT_THREADED
251
252
0
  LOG_INFO("Removing session " << sessionId);
253
254
0
  SessionMap::iterator i = sessions_.find(sessionId);
255
0
  if (i != sessions_.end()) {
256
0
    ++zombieSessions_;
257
0
    if (i->second->env().ajax())
258
0
      --ajaxSessions_;
259
0
    else
260
0
      --plainHtmlSessions_;
261
0
    sessions_.erase(i);
262
0
  }
263
264
0
  if (server_.dedicatedSessionProcess() && sessions_.size() == 0) {
265
0
    server_.scheduleStop();
266
0
  }
267
0
}
268
269
std::string WebController::appSessionCookie(const std::string& url)
270
0
{
271
0
  return Utils::urlEncode(url);
272
0
}
273
274
std::string WebController::sessionFromCookie(const char * const cookies,
275
                                             const std::string& scriptName,
276
                                             const int sessionIdLength)
277
0
{
278
0
  if (!cookies)
279
0
    return std::string();
280
281
0
  std::string cookieName = appSessionCookie(scriptName);
282
283
  // is_whitespace returns whether a character is whitespace according to RFC 5234 WSP
284
0
  auto is_whitespace = [](char c) { return c == ' ' || c == '\t'; };
285
0
  auto is_alphanumeric = [](char c) { return (c >= 'A' && c <= 'Z') ||
286
0
                                             (c >= 'a' && c <= 'z') ||
287
0
                                             (c >= '0' && c <= '9'); };
288
289
0
  const char *start = cookies;
290
0
  const char * const end = cookies + strlen(cookies);
291
0
  start = std::find_if_not(start, end, is_whitespace); // Skip leading whitespace
292
0
  while (start < end) {
293
0
    const char *const nextEquals = std::find(start, end, '=');
294
0
    if (nextEquals == end)
295
0
      return std::string{}; // Cookie header has no equals anymore
296
0
    const char *const nextSemicolon = std::find(nextEquals+1, end, ';');
297
0
    if (nextSemicolon != end &&
298
0
        *(nextSemicolon + 1) != ' ')
299
0
      return std::string{}; // Malformed cookie header, no space after semicolon
300
301
0
    assert(nextEquals < nextSemicolon); // Should be guaranteed because nextSemicolon search starts at nextEquals+1
302
0
    assert(nextSemicolon <= end); // Should be guaranteed because nextSemicolon search ends at 'end'
303
0
    assert(start <= nextEquals); // Should be guaranteed because nextEquals search starts at start
304
305
    // othercookie=value; cookiename=cookievalue; lastCookie = value
306
    // ^- cookies         ^- start  ^           ^- nextSemicolon    ^- end
307
    //                              \- nextEquals
308
    // or (last cookie)
309
    // othercookie=value; cookiename=cookievalue
310
    // ^- cookies         ^- start  ^           ^- nextSemicolon = end
311
    //                              \- nextEquals
312
313
0
    if (std::distance(start, nextEquals) == (long)cookieName.size() &&
314
0
        std::equal(start, nextEquals, cookieName.c_str())) {
315
0
      const char * cookieValueStart = nextEquals+1;
316
0
      assert(cookieValueStart <= end); // Because of nextEquals == end check earlier
317
      // Leave out trailing whitespace
318
0
      const char * cookieValueEnd = nextSemicolon == end ? std::find_if(cookieValueStart, end, is_whitespace) : nextSemicolon;
319
320
      // Handle cookie value in double quotes
321
0
      if (*cookieValueStart == '"') {
322
0
        ++cookieValueStart;
323
0
        assert(cookieValueEnd - 1 >= cookies); // Should be guaranteed because cookieValueStart >= nextEquals + 1
324
0
        if (*(cookieValueEnd - 1) != '"')
325
0
          return std::string{}; // Malformed cookie header, unbalanced double quote
326
0
        --cookieValueEnd;
327
0
      }
328
329
      // cookiename=cookievalue;
330
      //            ^          ^- cookieValueEnd
331
      //            \- cookieValueStart
332
      // or (double quotes)
333
      // cookiename="cookievalue";
334
      //             ^          ^- cookieValueEnd
335
      //             \- cookieValueStart
336
337
0
      if (sessionIdLength != std::distance(cookieValueStart, cookieValueEnd))
338
0
        return std::string{}; // Session ID cookie length incorrect!
339
0
      if (!std::all_of(cookieValueStart, cookieValueEnd, is_alphanumeric))
340
0
        return std::string{}; // Session IDs should be alphanumeric!
341
0
      return std::string(cookieValueStart, sessionIdLength);
342
0
    }
343
344
0
    start = nextSemicolon + 2; // Skip over '; '
345
0
  }
346
0
  return std::string{};
347
0
}
348
349
#ifdef WT_THREADED
350
WebController::SocketNotifierMap&
351
WebController::socketNotifiers(WSocketNotifier::Type type)
352
0
{
353
0
  switch (type) {
354
0
  case WSocketNotifier::Type::Read:
355
0
    return socketNotifiersRead_;
356
0
  case WSocketNotifier::Type::Write:
357
0
    return socketNotifiersWrite_;
358
0
  case WSocketNotifier::Type::Exception:
359
0
  default: // to avoid return warning
360
0
    return socketNotifiersExcept_;
361
0
  }
362
0
}
363
#endif // WT_THREADED
364
365
void WebController::socketSelected(int descriptor, WSocketNotifier::Type type)
366
0
{
367
0
#ifdef WT_THREADED
368
  /*
369
   * Find notifier, extract session Id
370
   */
371
0
  std::string sessionId;
372
0
  {
373
0
    std::unique_lock<std::recursive_mutex> lock(notifierMutex_);
374
375
0
    SocketNotifierMap &notifiers = socketNotifiers(type);
376
0
    SocketNotifierMap::iterator k = notifiers.find(descriptor);
377
378
0
    if (k == notifiers.end()) {
379
0
      LOG_ERROR_S(&server_, "socketSelected(): socket notifier should have been "
380
0
                  "cancelled?");
381
382
0
      return;
383
0
    } else {
384
0
      sessionId = k->second->sessionId();
385
0
    }
386
0
  }
387
388
0
  server_.post(sessionId, std::bind(&WebController::socketNotify,
389
0
                                      this, descriptor, type));
390
0
#endif // WT_THREADED
391
0
}
392
393
#ifdef WT_THREADED
394
void WebController::socketNotify(int descriptor, WSocketNotifier::Type type)
395
0
{
396
0
  WSocketNotifier *notifier = nullptr;
397
0
  {
398
0
    std::unique_lock<std::recursive_mutex> lock(notifierMutex_);
399
0
    SocketNotifierMap &notifiers = socketNotifiers(type);
400
0
    SocketNotifierMap::iterator k = notifiers.find(descriptor);
401
0
    if (k != notifiers.end()) {
402
0
      notifier = k->second;
403
0
      notifiers.erase(k);
404
0
    }
405
0
  }
406
407
0
  if (notifier)
408
0
    notifier->notify();
409
0
}
410
#endif // WT_THREADED
411
412
void WebController::addSocketNotifier(WSocketNotifier *notifier)
413
0
{
414
0
#ifdef WT_THREADED
415
0
  {
416
0
    std::unique_lock<std::recursive_mutex> lock(notifierMutex_);
417
0
    socketNotifiers(notifier->type())[notifier->socket()] = notifier;
418
0
  }
419
420
0
  switch (notifier->type()) {
421
0
  case WSocketNotifier::Type::Read:
422
0
    socketNotifier_.addReadSocket(notifier->socket());
423
0
    break;
424
0
  case WSocketNotifier::Type::Write:
425
0
    socketNotifier_.addWriteSocket(notifier->socket());
426
0
    break;
427
0
  case WSocketNotifier::Type::Exception:
428
0
    socketNotifier_.addExceptSocket(notifier->socket());
429
0
    break;
430
0
  }
431
0
#endif // WT_THREADED
432
0
}
433
434
void WebController::removeSocketNotifier(WSocketNotifier *notifier)
435
0
{
436
0
#ifdef WT_THREADED
437
0
  switch (notifier->type()) {
438
0
  case WSocketNotifier::Type::Read:
439
0
    socketNotifier_.removeReadSocket(notifier->socket());
440
0
    break;
441
0
  case WSocketNotifier::Type::Write:
442
0
    socketNotifier_.removeWriteSocket(notifier->socket());
443
0
    break;
444
0
  case WSocketNotifier::Type::Exception:
445
0
    socketNotifier_.removeExceptSocket(notifier->socket());
446
0
    break;
447
0
  }
448
449
0
  std::unique_lock<std::recursive_mutex> lock(notifierMutex_);
450
451
0
  SocketNotifierMap &notifiers = socketNotifiers(notifier->type());
452
0
  SocketNotifierMap::iterator i = notifiers.find(notifier->socket());
453
0
  if (i != notifiers.end())
454
0
    notifiers.erase(i);
455
0
#endif // WT_THREADED
456
0
}
457
458
bool WebController::requestDataReceived(WebRequest *request,
459
                                        std::uintmax_t current,
460
                                        std::uintmax_t total)
461
0
{
462
0
#ifdef WT_THREADED
463
0
  std::unique_lock<std::mutex> lock(uploadProgressUrlsMutex_);
464
0
#endif // WT_THREADED
465
466
0
  if (!running_)
467
0
    return false;
468
469
0
  if (uploadProgressUrls_.find(request->queryString())
470
0
      != uploadProgressUrls_.end()) {
471
0
#ifdef WT_THREADED
472
0
    lock.unlock();
473
0
#endif // WT_THREADED
474
475
0
    CgiParser cgi(conf_.maxRequestSize(), conf_.maxFormDataSize());
476
477
0
    try {
478
0
      cgi.parse(*request, CgiParser::ReadHeadersOnly);
479
0
    } catch (std::exception& e) {
480
0
      LOG_ERROR_S(&server_, "could not parse request: " << e.what());
481
0
      return false;
482
0
    }
483
484
0
    const std::string *wtdE = request->getParameter("wtd");
485
0
    if (!wtdE)
486
0
      return false;
487
488
0
    std::string sessionId = *wtdE;
489
490
0
    UpdateResourceProgressParams params {
491
0
      str(request->getParameter("request")),
492
0
      str(request->getParameter("resource")),
493
0
      request->postDataExceeded(),
494
0
      request->extraPathInfo().to_string(),
495
0
      current,
496
0
      total
497
0
    };
498
0
    auto event = std::make_shared<ApplicationEvent>(sessionId,
499
0
                                                    std::bind(&WebController::updateResourceProgress,
500
0
                                                              this, params));
501
502
0
    if (handleApplicationEvent(event))
503
0
      return !request->postDataExceeded();
504
0
    else
505
0
      return false;
506
0
  }
507
508
0
  return true;
509
0
}
510
511
void WebController::updateResourceProgress(const UpdateResourceProgressParams &params)
512
0
{
513
0
  WApplication *app = WApplication::instance();
514
515
0
  WResource *resource = nullptr;
516
0
  if (!params.requestParam.empty() &&
517
0
      !params.pathInfo.empty()) {
518
0
    resource = app->decodeExposedResource("/path/" + params.pathInfo);
519
0
  }
520
521
0
  if (!resource) {
522
0
    resource = app->decodeExposedResource(params.resourceParam);
523
0
  }
524
525
0
  if (resource) {
526
0
    ::int64_t dataExceeded = params.postDataExceeded;
527
0
    if (dataExceeded)
528
0
      resource->dataExceeded().emit(dataExceeded);
529
0
    else
530
0
      resource->dataReceived().emit(params.current, params.total);
531
0
  }
532
0
}
533
534
bool WebController::handleApplicationEvent(const std::shared_ptr<ApplicationEvent>& event)
535
0
{
536
  /*
537
   * This should always be run from within a virgin thread of the
538
   * thread-pool
539
   */
540
0
  assert(!WebSession::Handler::instance());
541
542
  /*
543
   * Find session (and guard it against deletion)
544
   */
545
0
  std::shared_ptr<WebSession> session;
546
0
  {
547
0
#ifdef WT_THREADED
548
0
    std::unique_lock<std::recursive_mutex> lock(mutex_);
549
0
#endif // WT_THREADED
550
551
0
    SessionMap::iterator i = sessions_.find(event->sessionId);
552
553
0
    if (i != sessions_.end() && !i->second->dead())
554
0
      session = i->second;
555
0
  }
556
557
0
  if (!session) {
558
0
    if (event->fallbackFunction)
559
0
      event->fallbackFunction();
560
0
    return false;
561
0
  } else
562
0
    session->queueEvent(event);
563
564
  /*
565
   * Try to take the session lock now to propagate the event to the
566
   * application.
567
   */
568
0
  {
569
0
    WebSession::Handler handler(session, WebSession::Handler::LockOption::TryLock);
570
0
  }
571
572
0
  return true;
573
0
}
574
575
void WebController::addUploadProgressUrl(const std::string& url)
576
0
{
577
0
#ifdef WT_THREADED
578
0
  std::unique_lock<std::mutex> lock(uploadProgressUrlsMutex_);
579
0
#endif // WT_THREADED
580
581
0
  uploadProgressUrls_.insert(url.substr(url.find("?") + 1));
582
0
}
583
584
void WebController::removeUploadProgressUrl(const std::string& url)
585
0
{
586
0
#ifdef WT_THREADED
587
0
  std::unique_lock<std::mutex> lock(uploadProgressUrlsMutex_);
588
0
#endif // WT_THREADED
589
590
0
  uploadProgressUrls_.erase(url.substr(url.find("?") + 1));
591
0
}
592
593
std::string WebController::computeRedirectHash(const std::string& secret,
594
                                               const std::string& url)
595
0
{
596
0
  return Utils::base64Encode(Utils::md5(secret + url));
597
0
}
598
599
std::string WebController::redirectSecret(const Wt::WebRequest &request) const
600
0
{
601
0
#ifndef WT_TARGET_JAVA
602
0
  if (configuration().behindReverseProxy() ||
603
0
      configuration().isTrustedProxy(request.remoteAddr())) {
604
0
    const auto secretHeader = request.headerValue(WT_REDIRECT_SECRET_HEADER);
605
0
    if (secretHeader && secretHeader[0] != '\0') {
606
0
      return secretHeader;
607
0
    }
608
0
  }
609
0
#endif // WT_TARGET_JAVA
610
611
0
  return redirectSecret_;
612
0
}
613
614
void WebController::handleRequest(WebRequest *request)
615
0
{
616
0
  if (!running_) {
617
0
    request->setStatus(500);
618
0
    request->flush();
619
0
    return;
620
0
  }
621
622
0
  if (configuration().useScriptNonce()) {
623
0
    request->addNonce();
624
0
  }
625
626
0
  if (!request->entryPoint_) {
627
0
    EntryPointMatch match = getEntryPoint(request);
628
0
    request->entryPoint_ = match.entryPoint;
629
0
    request->extraStartIndex_ = match.extraStartIndex;
630
0
    if (!request->entryPoint_) {
631
0
      request->setStatus(404);
632
0
      request->flush();
633
0
      return;
634
0
    }
635
0
    request->urlParams_ = std::move(match.urlParams);
636
0
  }
637
638
0
  CgiParser cgi(conf_.maxRequestSize(), conf_.maxFormDataSize());
639
640
0
  try {
641
0
    cgi.parse(*request, conf_.needReadBodyBeforeResponse()
642
0
              ? CgiParser::ReadBodyAnyway
643
0
              : CgiParser::ReadDefault);
644
0
  } catch (std::exception& e) {
645
0
    LOG_ERROR_S(&server_, "could not parse request: " << e.what());
646
647
0
    request->setContentType("text/html");
648
0
    request->out()
649
0
      << "<title>Error occurred.</title>"
650
0
      << "<h2>Error occurred.</h2>"
651
0
         "Error parsing CGI request: " << e.what() << std::endl;
652
653
0
    request->flush(WebResponse::ResponseState::ResponseDone);
654
0
    return;
655
0
  }
656
657
0
  if (request->entryPoint_->type() == EntryPointType::StaticResource) {
658
    // Requests to WWebSocketResources need some spacial handling:
659
    // after the handshake is done, the socket is transferred to the
660
    // WWebSocketConnection for futher communication.
661
0
    if (request->isWebSocketRequest()) {
662
      // Retrieve the static resource
663
0
      WebSocketHandlerResource* wsResource = nullptr;
664
0
      if (request->entryPoint_->resource()) {
665
0
        wsResource = dynamic_cast<WebSocketHandlerResource*>(request->entryPoint_->resource());
666
0
      }
667
0
      if(!wsResource) {
668
        // No static resource found
669
0
        LOG_ERROR("handleRequest: resource '" << request->pathInfo() << "' is not a WWebSocketResource");
670
0
        request->setStatus(400);
671
0
        request->setContentType("text/html");
672
0
        request->out()
673
0
          << "<title>Error occurred.</title>"
674
0
          << "<html><body><h1>Not a websocket</h1></body></html>"
675
0
          << std::endl;
676
0
        request->flush(WebResponse::ResponseState::ResponseDone);
677
0
        return;
678
0
      } else if (!request->supportsTransferWebSocketResourceSocket()) {
679
        // The HTTP frontend doesn't allow WWebSocketResources
680
0
        LOG_ERROR("handleRequest: websocket resources not supported by HTTP frontend");
681
0
        request->setStatus(500);
682
0
        request->setContentType("text/html");
683
0
        request->out()
684
0
          << "<title>Error occurred.</title>"
685
0
          << "<html><body><h1>WebSocket not supported</h1></body></html>"
686
0
          << std::endl;
687
0
        request->flush(WebResponse::ResponseState::ResponseDone);
688
0
        return;
689
0
      }
690
      // Regular handling, perform the actual handshake.
691
0
      request->entryPoint_->resource()->handle(request, (WebResponse *)request);
692
693
      // If the handshake is successful, transfer the socket.
694
0
      if (request->status() == 101) {
695
0
        request->setTransferWebSocketResourceSocketCallBack(std::bind(&WebSocketHandlerResource::moveSocket, wsResource, std::placeholders::_1, std::placeholders::_2));
696
0
      }
697
0
    } else {
698
      // A regular static resource
699
0
      request->entryPoint_->resource()->handle(request, (WebResponse *)request);
700
0
    }
701
0
    return;
702
0
  }
703
704
0
  const std::string *requestE = request->getParameter("request");
705
0
  if (requestE && *requestE == "redirect") {
706
0
    handleRedirect(request);
707
0
    return;
708
0
  }
709
710
0
  std::string sessionId;
711
712
0
  const std::string *wtdE = request->getParameter("wtd");
713
714
0
  const char* userAgent = request->headerValue("User-Agent");
715
716
  // Block bot agents that request "follow-up" requests
717
  // These are requests from the framework, with session tracking or session signals.
718
0
  if (userAgent && conf_.agentIsBot(userAgent)) {
719
0
    const std::string *signalE = request->getParameter("signal");
720
0
    if (wtdE || signalE) {
721
0
      LOG_INFO_S(&server_, "A request from a bot agent with a wtd or signal attached, was blocked. "
722
0
                           "Bots are not allowed to do subsequent requests since their session has been killed.");
723
0
      request->setStatus(403);
724
0
      request->flush(WebResponse::ResponseState::ResponseDone);
725
0
      return;
726
0
    }
727
0
  }
728
729
0
  if (conf_.sessionTracking() == Configuration::CookiesURL
730
0
      && !conf_.reloadIsNewSession())
731
0
    sessionId = sessionFromCookie(request->headerValue("Cookie"),
732
0
                                  request->scriptName(),
733
0
                                  conf_.fullSessionIdLength());
734
735
0
  std::string multiSessionCookie;
736
0
  if (conf_.sessionTracking() == Configuration::Combined)
737
0
    multiSessionCookie = sessionFromCookie(request->headerValue("Cookie"),
738
0
                                           "ms" + request->scriptName(),
739
0
                                           conf_.sessionIdLength());
740
741
0
  if (sessionId.empty() && wtdE) {
742
0
    sessionId = *wtdE;
743
0
  }
744
745
0
  std::shared_ptr<WebSession> session;
746
0
  {
747
0
#ifdef WT_THREADED
748
0
    std::unique_lock<std::recursive_mutex> lock(mutex_);
749
0
#endif // WT_THREADED
750
751
0
    if (!singleSessionId_.empty() && sessionId != singleSessionId_) {
752
0
      if (conf_.persistentSessions()) {
753
        // This may be because of a race condition in the filesystem:
754
        // the session file is renamed in generateNewSessionId() but
755
        // still a request for an old session may have arrived here
756
        // while this was happening.
757
        //
758
        // If it is from the old app, We should be sent a reload signal,
759
        // this is what will be done by a new session (which does not create
760
        // an application).
761
        //
762
        // If it is another request to take over the persistent session,
763
        // it should be handled by the persistent session. We can distinguish
764
        // using the type of the request
765
0
        LOG_INFO_S(&server_,
766
0
                   "persistent session requested Id: " << sessionId << ", "
767
0
                   << "persistent Id: " << singleSessionId_);
768
769
0
        if (sessions_.empty() || strcmp(request->requestMethod(), "GET") == 0)
770
0
          sessionId = singleSessionId_;
771
0
      } else
772
0
        sessionId = singleSessionId_;
773
0
    }
774
775
0
    SessionMap::iterator i = sessions_.find(sessionId);
776
777
0
    Configuration::SessionTracking sessionTracking = configuration().sessionTracking();
778
779
0
    if (i == sessions_.end() || i->second->dead() ||
780
0
        (sessionTracking == Configuration::Combined &&
781
0
         (multiSessionCookie.empty() || multiSessionCookie != i->second->multiSessionId()))) {
782
0
      try {
783
0
        if (sessionTracking == Configuration::Combined &&
784
0
            i != sessions_.end() && !i->second->dead()) {
785
0
          if (!request->headerValue("Cookie")) {
786
0
            LOG_ERROR_S(&server_, "Valid session id: " << sessionId << ", but "
787
0
                        "no cookie received (expecting multi session cookie)");
788
0
            request->setStatus(403);
789
0
            request->flush(WebResponse::ResponseState::ResponseDone);
790
0
            return;
791
0
          }
792
0
        }
793
794
0
        if (request->isWebSocketRequest()) {
795
0
          LOG_INFO_S(&server_, "WebSocket request for non-existing session rejected. "
796
0
                               "This is likely because of a browser with an old session "
797
0
                               "trying to reconnect (e.g. when the server was restarted)");
798
0
          request->setStatus(403);
799
0
          request->flush(WebResponse::ResponseState::ResponseDone);
800
0
          return;
801
0
        }
802
803
0
        if (singleSessionId_.empty()) {
804
0
          do {
805
0
            sessionId = conf_.generateSessionId();
806
0
            if (!conf_.registerSessionId(std::string(), sessionId))
807
0
              sessionId.clear();
808
0
          } while (sessionId.empty());
809
0
        }
810
811
0
        std::string favicon = request->entryPoint_->favicon();
812
0
        if (favicon.empty())
813
0
          conf_.readConfigurationProperty("favicon", favicon);
814
815
0
        session.reset(new WebSession(this, sessionId,
816
0
                                     request->entryPoint_->type(),
817
0
                                     favicon, request));
818
819
0
        if (sessionTracking == Configuration::Combined) {
820
0
          if (multiSessionCookie.empty())
821
0
            multiSessionCookie = conf_.generateSessionId();
822
0
          session->setMultiSessionId(multiSessionCookie);
823
0
        }
824
825
0
        if (sessionTracking == Configuration::CookiesURL)
826
0
    request->addHeader("Set-Cookie",
827
0
           appSessionCookie(request->scriptName())
828
0
           + "=" + sessionId + "; Version=1;"
829
0
           + " Path=" + session->env().deploymentPath()
830
0
           + "; httponly;" + (session->env().urlScheme() == "https" ? " secure;" : "")
831
0
                             + " SameSite=Strict;");
832
833
0
        sessions_[sessionId] = session;
834
0
        ++plainHtmlSessions_;
835
0
#ifdef WT_TEST_VISIBILITY
836
0
        addedSessionId_.emit(sessionId);
837
0
#endif // WT_TEST_VISIBILITY
838
839
0
        if (server_.dedicatedSessionProcess()) {
840
0
          server_.updateProcessSessionId(sessionId);
841
0
        }
842
0
      } catch (std::exception& e) {
843
0
        LOG_ERROR_S(&server_, "could not create new session: " << e.what());
844
0
        request->flush(WebResponse::ResponseState::ResponseDone);
845
0
        return;
846
0
      }
847
0
    } else {
848
0
      session = i->second;
849
0
    }
850
0
  }
851
852
0
  bool handled = false;
853
0
  {
854
0
    WebSession::Handler handler(session, *request, *(WebResponse *)request);
855
856
0
    if (!session->dead()) {
857
0
      handled = true;
858
0
      session->handleRequest(handler);
859
0
    }
860
0
  }
861
862
0
  if (session->dead())
863
0
    removeSession(sessionId);
864
865
0
  session.reset();
866
867
0
  if (autoExpire_)
868
0
    expireSessions();
869
870
0
  if (!handled)
871
0
    handleRequest(request);
872
0
}
873
874
void WebController::handleRedirect(Wt::WebRequest *request)
875
0
{
876
0
  const std::string *urlE = request->getParameter("url");
877
0
  const std::string *hashE = request->getParameter("hash");
878
879
0
  if (urlE && hashE) {
880
0
    if (*hashE != computeRedirectHash(redirectSecret(*request), *urlE))
881
0
      hashE = nullptr;
882
0
  }
883
884
0
  if (urlE && hashE) {
885
0
    request->setRedirect(*urlE);
886
0
  } else {
887
0
    request->setContentType("text/html");
888
0
    request->out()
889
0
            << "<title>Error occurred.</title>"
890
0
            << "<h2>Error occurred.</h2><p>Invalid redirect.</p>" << std::endl;
891
0
  }
892
893
0
  request->flush(WebResponse::ResponseState::ResponseDone);
894
0
}
895
896
std::unique_ptr<WApplication> WebController
897
::doCreateApplication(WebSession *session)
898
0
{
899
0
  std::shared_ptr<const EntryPoint> ep
900
0
    = WebSession::Handler::instance()->request()->entryPoint_;
901
902
0
  return ep->appCallback()(session->env());
903
0
}
904
905
EntryPointMatch WebController::getEntryPoint(WebRequest *request)
906
0
{
907
0
  const std::string& scriptName = request->scriptName();
908
0
  const std::string& pathInfo = request->pathInfo();
909
910
0
  return conf_.matchEntryPoint(scriptName, pathInfo, true);
911
0
}
912
913
std::string
914
WebController::generateNewSessionId(const std::shared_ptr<WebSession>& session)
915
0
{
916
0
#ifdef WT_THREADED
917
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
918
0
#endif // WT_THREADED
919
920
0
  std::string newSessionId;
921
0
  do {
922
0
    newSessionId = conf_.generateSessionId();
923
0
    if (!conf_.registerSessionId(session->sessionId(), newSessionId))
924
0
      newSessionId.clear();
925
0
  } while (newSessionId.empty());
926
927
0
  sessions_[newSessionId] = session;
928
929
0
  SessionMap::iterator i = sessions_.find(session->sessionId());
930
0
  sessions_.erase(i);
931
932
0
  if (!singleSessionId_.empty())
933
0
    singleSessionId_ = newSessionId;
934
935
0
  return newSessionId;
936
0
}
937
938
void WebController::newAjaxSession()
939
0
{
940
0
#ifdef WT_THREADED
941
0
  std::unique_lock<std::recursive_mutex> lock(mutex_);
942
0
#endif // WT_THREADED
943
944
0
  --plainHtmlSessions_;
945
0
  ++ajaxSessions_;
946
0
}
947
948
bool WebController::limitPlainHtmlSessions()
949
0
{
950
0
  if (conf_.maxPlainSessionsRatio() > 0) {
951
0
#ifdef WT_THREADED
952
0
    std::unique_lock<std::recursive_mutex> lock(mutex_);
953
0
#endif // WT_THREADED
954
955
0
    if (plainHtmlSessions_ + ajaxSessions_ > conf_.minSessionsForDoS())
956
0
      return plainHtmlSessions_ > conf_.maxPlainSessionsRatio()
957
0
        * (ajaxSessions_ + plainHtmlSessions_);
958
0
    else
959
0
      return false;
960
0
  } else
961
0
    return false;
962
0
}
963
964
}