Coverage Report

Created: 2026-09-23 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/web/WebSession.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 "Wt/Utils.h"
8
#include "Wt/WApplication.h"
9
#include "Wt/WCombinedLocalizedStrings.h"
10
#include "Wt/WContainerWidget.h"
11
#include "Wt/WException.h"
12
#include "Wt/WFormWidget.h"
13
#ifndef WT_TARGET_JAVA
14
#include "Wt/WIOService.h"
15
#endif
16
#include "Wt/WResource.h"
17
#include "Wt/WServer.h"
18
#include "Wt/WTimerWidget.h"
19
#include "Wt/WUrlFavicon.h"
20
#ifndef WT_TARGET_JAVA
21
#include "Wt/WWebSocketResource.h"
22
#endif // WT_TARGET_JAVA
23
#include "Wt/Http/Request.h"
24
25
#include "CgiParser.h"
26
#include "Configuration.h"
27
#include "DomElement.h"
28
#include "WebController.h"
29
#include "WebRequest.h"
30
#include "WebSession.h"
31
#include "WebSocketMessage.h"
32
#include "WebUtils.h"
33
34
#include <boost/algorithm/string.hpp>
35
#ifndef _MSC_VER
36
#include <unistd.h>
37
#endif
38
39
#ifdef WT_WIN32
40
#include <process.h>
41
#endif
42
43
#ifdef WT_TARGET_JAVA
44
#define RETHROW(e) throw e
45
#else
46
0
#define RETHROW(e) throw
47
#endif
48
49
namespace {
50
  #ifdef WT_TARGET_JAVA
51
  static Wt::Http::UploadedFile* uf;
52
  #endif
53
54
0
  bool isAbsoluteUrl(const std::string& url) {
55
0
    return url.find(":") != std::string::npos;
56
0
  }
57
58
0
  std::string host(const std::string& url) {
59
0
    std::size_t pos = 0;
60
0
    for (unsigned i = 0; i < 3; ++i) {
61
0
      pos = url.find('/', pos);
62
0
      if (pos == std::string::npos)
63
0
        return url;
64
0
      else
65
0
        ++pos;
66
0
    }
67
0
    return url.substr(0, pos - 1);
68
0
  }
69
70
0
  inline std::string str(const char *v) {
71
0
    return v ? std::string(v) : std::string();
72
0
  }
73
74
0
  inline bool isEqual(const char *s1, const char *s2) {
75
#ifdef WT_TARGET_JAVA
76
    if (s1 == 0) {
77
      return s2 == 0;
78
    } else {
79
      return std::string(s1) == s2;
80
    }
81
#else
82
0
    return strcmp(s1, s2) == 0;
83
0
#endif
84
0
  }
85
}
86
87
namespace Wt {
88
89
LOGGER("Wt");
90
91
#ifdef WT_TARGET_JAVA
92
boost::thread_specific_ptr<WebSession::Handler> WebSession::threadHandler_;
93
#else // WT_TARGET_JAVA
94
#ifdef WT_THREADED
95
static thread_local WebSession::Handler * threadHandler_ = nullptr;
96
#else // !WT_THREADED
97
static WebSession::Handler * threadHandler_ = nullptr;
98
#endif // WT_THREADED
99
#endif // WT_TARGET_JAVA
100
101
WebSession::WebSession(WebController *controller,
102
                       const std::string& sessionId,
103
                       EntryPointType type,
104
                       const std::string& favicon,
105
                       const WebRequest *request,
106
                       WEnvironment *env)
107
0
  : type_(type),
108
0
    defaultFavicon_(std::make_unique<WUrlFavicon>(favicon)),
109
0
    state_(State::JustCreated),
110
0
    sessionId_(sessionId),
111
0
    sessionIdChanged_(false),
112
0
    sessionIdCookieChanged_(false),
113
0
    sessionIdInUrl_(false),
114
0
    controller_(controller),
115
0
    renderer_(*this),
116
0
    asyncResponse_(nullptr),
117
0
    webSocket_(nullptr),
118
0
    bootStyleResponse_(nullptr),
119
0
    canWriteWebSocket_(false),
120
0
    webSocketConnected_(false),
121
0
    pollRequestsIgnored_(0),
122
0
    progressiveBoot_(false),
123
0
    deferredRequest_(nullptr),
124
0
    deferredResponse_(nullptr),
125
0
    deferCount_(0),
126
#ifdef WT_TARGET_JAVA
127
    recursiveEvent_(mutex_.newCondition()),
128
    recursiveEventDone_(mutex_.newCondition()),
129
    newRecursiveEvent_(nullptr),
130
    updatesPendingEvent_(mutex_.newCondition()),
131
#else
132
0
    newRecursiveEvent_(nullptr),
133
#endif
134
0
    updatesPending_(false),
135
0
    triggerUpdate_(false),
136
0
    embeddedEnv_(this),
137
0
    app_(nullptr),
138
0
    debug_(controller_->configuration().debug()),
139
0
    recursiveEventHandler_(nullptr)
140
0
{
141
0
  env_ = env ? env : &embeddedEnv_;
142
143
  // Update the URL scheme so we can set the session cookie correctly (with secure for https)
144
0
  if (request)
145
0
    env_->updateUrlScheme(*request);
146
147
  /*
148
   * Obtain the applicationName_ as soon as possible for log().
149
   */
150
0
  if (request)
151
0
    applicationUrl_ = request->fullEntryPointPath();
152
0
  else
153
0
    applicationUrl_ = "/";
154
155
0
  deploymentPath_ = applicationUrl_;
156
157
0
  std::string::size_type slashpos = deploymentPath_.rfind('/');
158
0
  if (slashpos != std::string::npos) {
159
0
    basePath_ = deploymentPath_.substr(0, slashpos + 1);
160
0
    applicationName_ = deploymentPath_.substr(slashpos + 1);
161
0
  } else { // ?
162
0
    basePath_ = "";
163
0
    applicationName_ = applicationUrl_;
164
0
  }
165
166
0
#ifndef WT_TARGET_JAVA
167
0
  LOG_INFO("session created (#sessions = " <<
168
0
           (controller_->sessionCount() + 1) << ")");
169
170
0
  expire_ = Time() + 60*1000;
171
0
#endif // WT_TARGET_JAVA
172
173
0
  if (controller_->configuration().sessionIdCookie()) {
174
0
    sessionIdCookie_ = WRandom::generateId();
175
0
    sessionIdCookieChanged_ = true;
176
177
0
    Http::Cookie cookie("Wt" + sessionIdCookie_, "1");
178
0
    cookie.setSecure(env_->urlScheme() == "https");
179
0
#ifndef WT_TARGET_JAVA
180
0
    cookie.setSameSite(Http::Cookie::SameSite::Strict);
181
#else
182
    cookie.setHttpOnly(true);
183
#endif
184
0
    renderer().setCookie(cookie);
185
0
  }
186
0
}
187
188
void WebSession::setApplication(WApplication *app)
189
0
{
190
0
  app_ = app;
191
0
}
192
193
void WebSession::deferRendering()
194
0
{
195
0
  if (!deferredRequest_) {
196
0
    Handler *handler = WebSession::Handler::instance();
197
0
    deferredRequest_ = handler->request();
198
0
    deferredResponse_ = handler->response();
199
0
    handler->setRequest(nullptr, nullptr);
200
0
  }
201
202
0
  ++deferCount_;
203
0
}
204
205
void WebSession::resumeRendering()
206
0
{
207
0
  if (--deferCount_ == 0) {
208
0
    Handler *handler = WebSession::Handler::instance();
209
0
    handler->setRequest(deferredRequest_, deferredResponse_);
210
0
    deferredRequest_ = nullptr;
211
0
    deferredResponse_ = nullptr;
212
0
  }
213
0
}
214
215
void WebSession::setTriggerUpdate(bool update)
216
0
{
217
0
  triggerUpdate_ = update;
218
0
}
219
220
#ifndef WT_TARGET_JAVA
221
WLogger& WebSession::logInstance() const
222
0
{
223
0
    return controller_->server()->logger();
224
0
}
225
226
WLogEntry WebSession::log(const std::string& type) const
227
0
{
228
0
  if (controller_->server()->customLogger()) {
229
0
    return WLogEntry(*controller_->server()->customLogger(), type);
230
0
  }
231
232
0
  WLogEntry e = controller_->server()->logger().entry(type);
233
234
0
#ifndef WT_TARGET_JAVA
235
0
  if (state_ == State::BeingDestroyed) {
236
    /*
237
     * We cannot use the currentLocale here because the destructor may
238
     * be called due to a thread_local handler being destroyed
239
     * (see Issue #14667)
240
     */
241
0
    e << WLogger::defaultLocaleTimestamp;
242
0
  } else {
243
0
    e << WLogger::timestamp;
244
0
  }
245
246
0
  e << WLogger::sep << getpid() << WLogger::sep
247
0
    << '[' << deploymentPath_ << ' ' << sessionId()
248
0
    << ']' << WLogger::sep << '[' << type << ']' << WLogger::sep;
249
0
#endif // WT_TARGET_JAVA
250
251
0
  return e;
252
0
}
253
#endif // WT_TARGET_JAVA
254
255
WebSession::~WebSession()
256
0
{
257
  /*
258
   * From here on, we cannot create a shared_ptr to this session. Therefore,
259
   * app_ uses a weak_ptr to this session for which lock() returns an empty
260
   * shared pointer.
261
   */
262
0
  state_ = State::BeingDestroyed;
263
264
0
#ifndef WT_TARGET_JAVA
265
0
  Handler handler(this);
266
267
0
  if (app_)
268
0
    app_->notify
269
0
      (WEvent(WEvent::Impl
270
0
              (&handler, std::bind(&WApplication::finalize, app_))));
271
272
0
  delete app_;
273
0
  app_ = nullptr;
274
0
#endif // WT_TARGET_JAVA
275
276
0
  if (asyncResponse_) {
277
0
    asyncResponse_->flush();
278
0
    asyncResponse_ = nullptr;
279
0
  }
280
281
0
  if (webSocket_) {
282
0
    webSocket_->flush();
283
0
    webSocket_ = nullptr;
284
0
  }
285
286
0
  if (deferredResponse_) {
287
0
    deferredResponse_->flush();
288
0
    deferredResponse_ = nullptr;
289
0
  }
290
291
0
#ifdef WT_BOOST_THREADS
292
0
  updatesPendingEvent_.notify_one();
293
0
#endif // WT_BOOST_THREADS
294
295
0
  flushBootStyleResponse();
296
297
0
  controller_->configuration().registerSessionId(sessionId_, std::string());
298
0
  controller_->sessionDeleted();
299
300
0
#ifndef WT_TARGET_JAVA
301
0
  LOG_INFO("session destroyed (#sessions = " << controller_->sessionCount()
302
0
           << ")");
303
0
#endif // WT_TARGET_JAVA
304
305
0
}
306
307
#ifdef WT_TARGET_JAVA
308
void WebSession::destruct()
309
{
310
  if (asyncResponse_) {
311
    asyncResponse_->flush();
312
    asyncResponse_ = nullptr;
313
  }
314
315
  if (deferredResponse_) {
316
    deferredResponse_->flush();
317
    deferredResponse_ = nullptr;
318
  }
319
320
  mutex_.lock();
321
  updatesPendingEvent_.notify_one();
322
  mutex_.unlock();
323
324
  flushBootStyleResponse();
325
}
326
#endif // WT_TARGET_JAVA
327
328
WFavicon* WebSession::favicon() const
329
0
{
330
0
  return app_ ? app_->favicon() : defaultFavicon();
331
0
}
332
333
std::string WebSession::docType() const
334
0
{
335
0
  const bool xhtml = env_->contentType() == HtmlContentType::XHTML1;
336
337
0
  if (xhtml)
338
    /*
339
     * This would be what we want, but it is too strict (does not
340
     * validate iframe's and target attribute for links):
341
342
     "\"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN\" "
343
     "\"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\">"
344
     * so instead we use transitional xhtml -- it will fail to
345
     * validate properly when we have svg !
346
     */
347
0
    return "<!DOCTYPE html PUBLIC "
348
0
      "\"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
349
0
      "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
350
0
  else
351
0
    return
352
#ifdef HTML4_DOCTYPE
353
      "<!DOCTYPE html PUBLIC "
354
      "\"-//W3C//DTD HTML 4.01 Transitional//EN\" "
355
      "\"http://www.w3.org/TR/html4/loose.dtd\">";
356
#else
357
0
      "<!DOCTYPE html>"; // HTML5 hoeray
358
0
#endif
359
0
}
360
361
void WebSession::setLoaded()
362
0
{
363
0
  bool wasSuspended = state_ == State::Suspended;
364
0
  setState(State::Loaded, controller_->configuration().sessionTimeout());
365
366
0
  if (wasSuspended) {
367
0
    if (env_->ajax() && controller_->configuration().reloadIsNewSession()) {
368
0
      app_->doJavaScript(WT_CLASS ".history.removeSessionId()");
369
0
      sessionIdInUrl_ = false;
370
0
    }
371
0
    app_->unsuspended().emit();
372
0
  }
373
0
}
374
375
void WebSession::setExpectLoad()
376
0
{
377
0
  if (controller_->configuration().ajaxPuzzle())
378
0
    setState(State::ExpectLoad, controller_->configuration().bootstrapTimeout());
379
0
  else
380
0
    setLoaded();
381
0
}
382
383
void WebSession::setState(State state, int timeout)
384
0
{
385
0
#ifdef WT_THREADED
386
  // this assertion is not true for when we are working from an attached
387
  // thread: that thread does not have an associated handler, but its contract
388
  // dictates that it should work on behalf of a thread that has the lock.
389
  //assert(WebSession::Handler::instance()->haveLock());
390
0
#endif // WT_THREADED
391
392
0
  if (!dead()) {
393
0
    state_ = state;
394
395
0
    LOG_DEBUG("Setting to expire in " << timeout << "s");
396
397
0
#ifndef WT_TARGET_JAVA
398
0
    if (controller_->configuration().sessionTimeout() != -1)
399
0
      expire_ = Time() + timeout*1000;
400
0
#endif // WT_TARGET_JAVA
401
0
  }
402
0
}
403
404
std::string WebSession::sessionQuery() const
405
0
{
406
0
  std::string wtd = env_->treatLikeBot() ? "bot" : sessionId_;
407
0
  std::string result ="?wtd=" + DomElement::urlEncodeS(wtd);
408
0
  if (type() == EntryPointType::WidgetSet)
409
0
    result += "&wtt=widgetset";
410
0
  return result;
411
0
}
412
413
void WebSession::init(const WebRequest& request)
414
0
{
415
0
  env_->init(request, sessionId_);
416
417
0
  const std::string *hashE = request.getParameter("_");
418
419
0
  absoluteBaseUrl_ = env_->urlScheme() + "://" + env_->hostName() + basePath_;
420
421
0
  bool useAbsoluteUrls;
422
0
#ifndef WT_TARGET_JAVA
423
0
  useAbsoluteUrls
424
0
    = env_->server()->readConfigurationProperty("baseURL", absoluteBaseUrl_);
425
#else
426
  std::string* absoluteBaseUrl
427
    = app_->readConfigurationProperty("baseURL", absoluteBaseUrl_);
428
  if (absoluteBaseUrl != &absoluteBaseUrl_) {
429
    absoluteBaseUrl_ = *absoluteBaseUrl;
430
    useAbsoluteUrls = true;
431
  } else {
432
    useAbsoluteUrls = false;
433
  }
434
#endif
435
436
0
  if (useAbsoluteUrls) {
437
0
    std::string::size_type slashpos = absoluteBaseUrl_.rfind('/');
438
0
    if (slashpos != std::string::npos
439
0
        && slashpos != absoluteBaseUrl_.length() - 1)
440
0
      absoluteBaseUrl_ = absoluteBaseUrl_.substr(0, slashpos + 1);
441
442
0
    slashpos = absoluteBaseUrl_.find("://");
443
444
0
    if (slashpos != std::string::npos) {
445
0
      slashpos = absoluteBaseUrl_.find("/", slashpos + 3);
446
0
      if (slashpos != std::string::npos) {
447
0
        deploymentPath_ = absoluteBaseUrl_.substr(slashpos) + applicationName_;
448
0
      }
449
0
    }
450
0
  }
451
452
0
  bookmarkUrl_ = applicationName_;
453
454
0
  if (type() == EntryPointType::WidgetSet || useAbsoluteUrls) {
455
0
    applicationUrl_ = absoluteBaseUrl_ + applicationName_;
456
0
    bookmarkUrl_ = applicationUrl_;
457
0
  }
458
459
0
  auto extraPathInfo = request.extraPathInfo().to_string();
460
0
  std::string path = extraPathInfo;
461
0
  if (path.empty() && hashE)
462
0
    path = *hashE;
463
464
0
  env_->setInternalPath(path);
465
0
  pagePathInfo_ = std::move(extraPathInfo);
466
467
  // Cache document root
468
0
  docRoot_ = getCgiValue("DOCUMENT_ROOT");
469
0
}
470
471
bool WebSession::useUglyInternalPaths() const
472
0
{
473
0
#ifndef WT_TARGET_JAVA
474
  /*
475
   * We need ugly ?_= internal paths if the server does not route
476
   * /app/foo to an application deployed as /app/
477
   */
478
0
  if (applicationName_.empty() && controller_->server()) {
479
0
    Configuration& conf = controller_->configuration();
480
0
    return conf.useSlashExceptionForInternalPaths();
481
0
  } else
482
0
    return false;
483
#else
484
  return false;
485
#endif
486
0
}
487
488
std::string WebSession::bootstrapUrl(WT_MAYBE_UNUSED const WebResponse& response,
489
                                     BootstrapOption option) const
490
0
{
491
0
  switch (option) {
492
0
  case BootstrapOption::KeepInternalPath: {
493
0
    std::string url;
494
495
0
    std::string internalPath
496
0
      = app_ ? app_->internalPath() : env_->internalPath();
497
498
0
    if (useUglyInternalPaths()) {
499
0
      if (internalPath.length() > 1)
500
0
        url = "?_=" + DomElement::urlEncodeS(internalPath, "#/");
501
502
0
      if (isAbsoluteUrl(applicationUrl_))
503
0
        url = applicationUrl_ + url;
504
0
    } else {
505
0
      if (!isAbsoluteUrl(applicationUrl_)) {
506
        /*
507
         * Java application servers use ";jsessionid=..." which generates
508
         * URLs relative to the current directory, not current filename
509
         * (unlike '?=...')
510
         *
511
         * Therefore we start with the current 'filename', this does no harm
512
         * for C++ well behaving servers either.
513
         */
514
0
        if (internalPath.length() > 1) {
515
0
          std::string lastPart
516
0
            = internalPath.substr(internalPath.rfind('/') + 1);
517
518
0
          url = ""; /* lastPart; */
519
0
        } else
520
0
          url = applicationName_;
521
0
      } else {
522
0
        if (applicationName_.empty() && internalPath.length() > 1)
523
0
          internalPath = internalPath.substr(1);
524
525
0
        url = applicationUrl_ + internalPath;
526
0
      }
527
0
    }
528
529
0
    return appendSessionQuery(url);
530
0
  }
531
0
  case BootstrapOption::ClearInternalPath: {
532
0
    std::string url;
533
0
    if (applicationName_.empty()) {
534
0
      url = fixRelativeUrl(".");
535
0
      url = url.substr(0, url.length() - 1);
536
0
    } else
537
0
      url = fixRelativeUrl(applicationName_);
538
539
0
    return appendSessionQuery(url);
540
0
  }
541
0
  default:
542
0
    assert(false);
543
0
  }
544
545
0
  return std::string();
546
0
}
547
548
std::string WebSession::fixRelativeUrl(const std::string& url) const
549
0
{
550
0
  if (isAbsoluteUrl(url))
551
0
    return url;
552
553
0
  if (url.length() > 0 && url[0] == '#') {
554
0
    if (!isAbsoluteUrl(applicationUrl_))
555
0
      return url;
556
0
    else
557
      // we have <href base=...> which requires us to put the application
558
      // name before a named anchor
559
0
      return applicationName_ + url;
560
0
  }
561
562
0
  if (!isAbsoluteUrl(applicationUrl_)) {
563
0
    if (!url.empty() && url[0] == '/')
564
0
      return url;
565
0
    else if (!env_->publicDeploymentPath_.empty()) {
566
0
      std::string dp = env_->publicDeploymentPath_;
567
568
0
      if (url.empty())
569
0
        return dp;
570
0
      else if (url[0] == '?')
571
0
        return dp + url;
572
0
      else {
573
0
        std::size_t s = dp.rfind('/');
574
0
        std::string parentDir = dp.substr(0, s + 1);
575
0
        if (url[0] == '.' && (url.size() == 1 || url[1] == '?' || url[1] == '#' || url[1] == ';'))
576
0
          return parentDir + url.substr(1);
577
0
        else if (url.size() >= 2 && url[0] == '.' && url[1] == '/') {
578
          // Note: deployment path is guaranteed to start with /
579
          //       WEnvironment checks this!
580
0
          return parentDir + url.substr(2);
581
0
        } else
582
0
          return parentDir + url;
583
0
      }
584
0
    } else {
585
      /*
586
       * The public deployment path may lack if:
587
       *  - we are a widget set script, but then we should have absolute
588
       *    applicationUrl and internal paths are not really going to work
589
       *  - we are a plain HTML session. but then we are not hashing internal
590
       *    paths, so first condition should never be met
591
       */
592
0
      if (env_->internalPathUsingFragments())
593
0
        return url;
594
0
      else {
595
0
        std::string rel = "";
596
0
        std::string pi = pagePathInfo_;
597
598
0
        for (unsigned i = 0; i < pi.length(); ++i) {
599
0
          if (pi[i] == '/')
600
0
            rel += "../";
601
0
        }
602
603
0
        if (url.empty()) {
604
0
          if (applicationName_.empty() && rel.empty()) {
605
            // This is basically a link to the current url
606
0
            return "#";
607
0
          } else {
608
0
            return rel + applicationName_;
609
0
          }
610
0
        } else {
611
0
          return rel + url;
612
0
        }
613
0
      }
614
0
    }
615
0
  } else
616
0
    return makeAbsoluteUrl(url);
617
0
}
618
619
std::string WebSession::makeAbsoluteUrl(const std::string& url) const
620
0
{
621
0
  if (isAbsoluteUrl(url))
622
0
    return url;
623
0
  else {
624
0
    if (!url.empty() && url[0] == '.' &&
625
0
        (url.length() == 1 || url[1] != '.'))
626
0
      return absoluteBaseUrl_ + url.substr(1);
627
0
    else if (url.empty() || url[0] != '/')
628
0
      return absoluteBaseUrl_ + url;
629
0
    else
630
0
      return host(absoluteBaseUrl_) + url;
631
0
  }
632
0
}
633
634
std::string WebSession::mostRelativeUrl(const std::string& internalPath,
635
                                        bool excludeBot) const
636
0
{
637
0
  return appendSessionQuery(bookmarkUrl(internalPath), excludeBot);
638
0
}
639
640
std::string WebSession::appendSessionQuery(const std::string& url, bool force) const
641
0
{
642
0
  std::string result = url;
643
644
0
  if (env_->treatLikeBot() && !force) {
645
0
    return result;
646
0
  }
647
648
0
  std::size_t questionPos = result.find('?');
649
650
0
  if (questionPos == std::string::npos)
651
0
    result += sessionQuery();
652
0
  else if (questionPos == result.length() - 1)
653
0
    result += sessionQuery().substr(1);
654
0
  else
655
0
    result += '&' + sessionQuery().substr(1);
656
657
0
#ifndef WT_TARGET_JAVA
658
0
  return result;
659
#else
660
  if (boost::starts_with(result, "?"))
661
    result = applicationUrl_ + result;
662
663
  if (WebSession::Handler::instance()->response()) {
664
    try {
665
      return WebSession::Handler::instance()->response()->encodeURL(result);
666
    } catch (std::exception& e) {
667
      LOG_ERROR("appendSessionQuery(): could not encode URL using response");
668
    }
669
  }
670
  /*
671
   * This may happen if we are inside a WServer::posted() function,
672
   * or if the response is recycled by the servlet container.
673
   * Unfortunately, then we cannot use Servlet API to URL encode.
674
   */
675
  questionPos = result.find('?');
676
  return result.substr(0, questionPos) + ";jsessionid=" + sessionId()
677
    + result.substr(questionPos);
678
#endif // WT_TARGET_JAVA
679
0
}
680
681
std::string WebSession::bookmarkUrl() const
682
0
{
683
0
  if (app_)
684
0
    return bookmarkUrl(app_->internalPath());
685
0
  else
686
0
    return bookmarkUrl(env_->internalPath());
687
0
}
688
689
std::string WebSession::bookmarkUrl(const std::string& internalPath) const
690
0
{
691
0
  std::string result = bookmarkUrl_;
692
693
0
  return appendInternalPath(result, internalPath);
694
0
}
695
696
std::string WebSession::appendInternalPath(const std::string& baseUrl,
697
                                           const std::string& internalPath)
698
  const
699
0
{
700
0
  if (internalPath.empty() || internalPath == "/")
701
0
    if (baseUrl.empty())
702
0
      if (applicationName_.empty())
703
0
        return "";
704
0
      else
705
0
        return applicationName_;
706
0
    else
707
0
      return baseUrl;
708
0
  else {
709
0
    if (useUglyInternalPaths())
710
0
      return baseUrl + "?_=" + DomElement::urlEncodeS(internalPath, "#/");
711
0
    else {
712
0
      if (applicationName_.empty())
713
0
        return baseUrl + DomElement::urlEncodeS(internalPath.substr(1), "#/");
714
0
      else
715
0
        return baseUrl + DomElement::urlEncodeS(internalPath, "#/");
716
0
    }
717
0
  }
718
0
}
719
720
bool WebSession::start(WebResponse *response)
721
0
{
722
0
  try {
723
0
    app_ = controller_->doCreateApplication(this).release();
724
0
    if (app_) {
725
0
      if (!app_->internalPathValid_) {
726
0
        if (response->responseType() == WebResponse::ResponseType::Page) {
727
0
          response->setStatus(404);
728
0
        }
729
0
      }
730
0
    } else {
731
0
      throw WException("WebSession::start: ApplicationCreator returned a nullptr");
732
0
    }
733
0
  } catch (std::exception& e) {
734
0
    app_ = nullptr;
735
736
0
    kill();
737
0
    RETHROW(e);
738
0
  } catch (...) {
739
0
    app_ = nullptr;
740
741
0
    kill();
742
0
    throw;
743
0
  }
744
745
0
  return app_;
746
0
}
747
748
std::string WebSession::getCgiValue(const std::string& varName) const
749
0
{
750
0
  WebRequest *request = WebSession::Handler::instance()->request();
751
0
  if (request)
752
0
    return str(request->envValue(varName.c_str()));
753
0
  else if(varName == "DOCUMENT_ROOT")
754
0
        return docRoot_;
755
0
  else
756
0
    return std::string();
757
0
}
758
759
std::string WebSession::getCgiHeader(const std::string& headerName) const
760
0
{
761
0
  WebRequest *request = WebSession::Handler::instance()->request();
762
0
  if (request)
763
0
    return str(request->headerValue(headerName.c_str()));
764
0
  else
765
0
    return std::string();
766
0
}
767
768
void WebSession::kill()
769
0
{
770
0
  state_ = State::Dead;
771
772
  /*
773
   * Unlock the recursive eventloop that may be pending.
774
   */
775
0
  unlockRecursiveEventLoop();
776
0
}
777
778
void WebSession::checkTimers()
779
0
{
780
0
  WContainerWidget *timers = app_->timerRoot();
781
782
0
  const std::vector<WWidget *>& timerWidgets = timers->children();
783
784
0
  std::vector<WTimerWidget *> expired;
785
786
0
  for (unsigned i = 0; i < timerWidgets.size(); ++i) {
787
0
    WTimerWidget *wti = dynamic_cast<WTimerWidget *>(timerWidgets[i]);
788
789
0
    if (wti->timerExpired())
790
0
      expired.push_back(wti);
791
0
  }
792
793
0
  WMouseEvent dummy;
794
795
0
  for (unsigned i = 0; i < expired.size(); ++i)
796
0
    expired[i]->clicked().emit(dummy);
797
0
}
798
799
void WebSession::redirect(const std::string& url)
800
0
{
801
0
  redirect_ = url;
802
0
  if (redirect_.empty())
803
0
    redirect_ = "?";
804
0
}
805
806
std::string WebSession::getRedirect()
807
0
{
808
0
  std::string result = redirect_;
809
0
  redirect_.clear();
810
0
  return result;
811
0
}
812
813
WebSession::Handler::Handler()
814
0
  : nextSignal(-1),
815
0
    prevHandler_(nullptr),
816
0
    session_(nullptr),
817
0
    request_(nullptr),
818
0
    response_(nullptr),
819
0
    killed_(false)
820
0
{
821
0
  init();
822
0
}
823
824
WebSession::Handler::Handler(const std::shared_ptr<WebSession>& session,
825
                             LockOption lockOption)
826
0
  : nextSignal(-1),
827
#ifndef WT_TARGET_JAVA
828
0
    sessionPtr_(session),
829
#endif // WT_TARGET_JAVA
830
#ifdef WT_THREADED
831
0
    lock_(session->mutex_, std::defer_lock),
832
#endif // WT_THREADED
833
0
    prevHandler_(nullptr),
834
0
    session_(session.get()),
835
0
    request_(nullptr),
836
0
    response_(nullptr),
837
0
    killed_(false)
838
0
{
839
0
  switch (lockOption) {
840
0
  case LockOption::NoLock:
841
0
    break;
842
0
  case LockOption::TakeLock:
843
0
#ifdef WT_THREADED
844
0
    lockOwner_ = std::this_thread::get_id();
845
0
    lock_.lock();
846
0
#endif
847
#ifdef WT_TARGET_JAVA
848
    session->mutex().lock();
849
#endif
850
0
    break;
851
0
  case LockOption::TryLock:
852
0
#ifdef WT_THREADED
853
0
    if (lock_.try_lock())
854
0
      lockOwner_ = std::this_thread::get_id();
855
0
#endif
856
#ifdef WT_TARGET_JAVA
857
    session->mutex().try_lock();
858
#endif
859
0
    break;
860
0
  }
861
862
0
  init();
863
0
}
864
865
WebSession::Handler::Handler(WebSession *session)
866
0
  : nextSignal(-1),
867
#ifdef WT_THREADED
868
0
    lock_(session->mutex_),
869
#endif // WT_THREADED
870
0
    prevHandler_(nullptr),
871
0
    session_(session),
872
0
    request_(nullptr),
873
0
    response_(nullptr),
874
0
    killed_(false)
875
0
{
876
0
#ifdef WT_THREADED
877
0
  lockOwner_ = std::this_thread::get_id();
878
0
#endif
879
#ifdef WT_TARGET_JAVA
880
  session->mutex().lock();
881
#endif // WT_TARGET_JAVA
882
883
0
  init();
884
0
}
885
886
WebSession::Handler::Handler(const std::shared_ptr<WebSession>& session,
887
                             WebRequest& request, WebResponse& response)
888
0
  : nextSignal(-1),
889
#ifndef WT_TARGET_JAVA
890
0
    sessionPtr_(session),
891
#endif // WT_TARGET_JAVA
892
#ifdef WT_THREADED
893
0
    lock_(session->mutex_),
894
#endif // WT_THREADED
895
0
    prevHandler_(nullptr),
896
0
    session_(session.get()),
897
0
    request_(&request),
898
0
    response_(&response),
899
0
    killed_(false)
900
0
{
901
0
#ifdef WT_THREADED
902
0
  lockOwner_ = std::this_thread::get_id();
903
0
#endif
904
#ifdef WT_TARGET_JAVA
905
  session->mutex().lock();
906
#endif
907
908
0
  init();
909
0
}
910
911
WebSession::Handler *WebSession::Handler::instance()
912
1.12k
{
913
#ifdef WT_TARGET_JAVA
914
  return threadHandler_.get();
915
#else
916
1.12k
  return threadHandler_;
917
1.12k
#endif
918
1.12k
}
919
920
bool WebSession::Handler::haveLock() const
921
0
{
922
0
#ifdef WT_THREADED
923
0
  return lock_.owns_lock();
924
#else
925
#ifdef WT_TARGET_JAVA
926
  return session_->mutex().owns_lock();
927
#else
928
  return true;
929
#endif
930
#endif
931
0
}
932
933
void WebSession::Handler::unlock()
934
0
{
935
0
  if (haveLock()) {
936
0
#ifndef WT_TARGET_JAVA
937
0
    Utils::erase(session_->handlers_, this);
938
0
#ifdef WT_THREADED
939
0
    lock_.unlock();
940
0
#endif // WT_THREADED
941
0
#endif // WT_TARGET_JAVA
942
#ifdef WT_TARGET_JAVA
943
    session_->mutex().unlock();
944
#endif
945
0
  }
946
0
}
947
948
void WebSession::Handler::init()
949
0
{
950
0
  prevHandler_ = attachThreadToHandler(this);
951
952
0
#ifndef WT_TARGET_JAVA
953
0
  if (haveLock())
954
0
    session_->handlers_.push_back(this);
955
0
#endif
956
0
}
957
958
WebSession::Handler *
959
WebSession::Handler::attachThreadToHandler(Handler *handler)
960
0
{
961
0
  WebSession::Handler *result;
962
963
#ifdef WT_TARGET_JAVA
964
  result = threadHandler_.release();
965
  threadHandler_.reset(handler);
966
#else
967
0
  result = threadHandler_;
968
0
  threadHandler_ = handler;
969
0
#endif
970
971
0
  return result;
972
0
}
973
974
bool WebSession::attachThreadToLockedHandler()
975
0
{
976
0
#if !defined(WT_TARGET_JAVA)
977
  /*
978
   * We assume that another handler has already locked this session for us.
979
   * We just need to find it.
980
   */
981
0
  for (unsigned i = 0; i < handlers_.size(); ++i)
982
0
    if (handlers_[i]->haveLock()) {
983
0
      WebSession::Handler::attachThreadToHandler(handlers_[i]);
984
0
      return true;
985
0
    }
986
987
0
  return false;
988
#else
989
  Handler::attachThreadToHandler(new Handler(this, Handler::LockOption::NoLock));
990
  return true;
991
#endif
992
0
}
993
994
void WebSession
995
::Handler::attachThreadToSession(const std::shared_ptr<WebSession>& session)
996
0
{
997
0
  attachThreadToHandler(nullptr);
998
999
0
#ifdef WT_BOOST_THREADS
1000
0
  if (!session.get())
1001
0
    return;
1002
1003
  /*
1004
   * It may be that we still need to attach to a session while it is being
1005
   * destroyed ? I'm not sure why this is useful, but cannot see anything
1006
   * wrong about it either ?
1007
   */
1008
0
  if (session->dead())
1009
0
    LOG_WARN_S(session, "attaching to dead session?");
1010
1011
0
  if (!session.get()->attachThreadToLockedHandler()) {
1012
    /*
1013
     * We actually have two scenarios:
1014
     * - attachThread() once to have WApplication::instance() work. This will
1015
     *   give the warning, and will not work reliably !
1016
     * - attachThread() in the wtwithqt case should execute what we have above
1017
     */
1018
0
    LOG_WARN_S(session,
1019
0
               "attachThread(): no thread is holding this application's "
1020
0
               "lock ?");
1021
0
    WebSession::Handler::attachThreadToHandler
1022
0
      (new Handler(session, Handler::LockOption::NoLock));
1023
0
  }
1024
#else
1025
  LOG_ERROR_S(session, "attachThread(): needs Wt built with threading enabled");
1026
#endif
1027
0
}
1028
1029
std::shared_ptr<ApplicationEvent> WebSession::popQueuedEvent()
1030
0
{
1031
0
#ifdef WT_BOOST_THREADS
1032
0
#ifndef WT_TARGET_JAVA
1033
0
  std::unique_lock<std::mutex> lock(eventQueueMutex_);
1034
#else
1035
  eventQueueMutex_.lock();
1036
#endif // WT_TARGET_JAVA
1037
0
#endif // WT_BOOST_THREADS
1038
1039
0
  std::shared_ptr<ApplicationEvent> result;
1040
1041
0
  LOG_DEBUG("popQueuedEvent(): " << eventQueue_.size());
1042
1043
0
  if (!eventQueue_.empty()) {
1044
0
    result = eventQueue_.front();
1045
0
    eventQueue_.pop_front();
1046
0
  }
1047
1048
#ifdef WT_TARGET_JAVA
1049
  eventQueueMutex_.unlock();
1050
#endif // WT_TARGET_JAVA
1051
1052
0
  return result;
1053
0
}
1054
1055
void WebSession::queueEvent(const std::shared_ptr<ApplicationEvent>& event)
1056
0
{
1057
0
#ifdef WT_BOOST_THREADS
1058
0
#ifndef WT_TARGET_JAVA
1059
0
  std::unique_lock<std::mutex> lock(eventQueueMutex_);
1060
#else
1061
  eventQueueMutex_.lock();
1062
#endif // WT_TARGET_JAVA
1063
0
#endif // WT_BOOST_THREADS
1064
1065
0
  eventQueue_.push_back(event);
1066
1067
0
  LOG_DEBUG("queueEvent(): " << eventQueue_.size());
1068
1069
#ifdef WT_TARGET_JAVA
1070
  eventQueueMutex_.unlock();
1071
#endif // WT_TARGET_JAVA
1072
0
}
1073
1074
void WebSession::processQueuedEvents(WebSession::Handler& handler)
1075
0
{
1076
0
  for (;;) {
1077
0
    std::shared_ptr<ApplicationEvent> event = popQueuedEvent();
1078
1079
0
    if (event) {
1080
0
      if (!dead()) {
1081
0
        externalNotify(WEvent::Impl(&handler, event->function));
1082
1083
0
        if (app() && app()->hasQuit())
1084
0
          kill();
1085
1086
0
        if (dead())
1087
0
          controller()->removeSession(event->sessionId);
1088
0
      } else {
1089
0
        if (event->fallbackFunction)
1090
0
          WT_CALL_FUNCTION(event->fallbackFunction);
1091
0
      }
1092
0
    } else
1093
0
      break;
1094
0
  }
1095
0
}
1096
1097
#ifdef WT_TARGET_JAVA
1098
void WebSession::Handler::release()
1099
{
1100
  if (haveLock()) {
1101
    session_->processQueuedEvents(*this);
1102
    if (session_->triggerUpdate_)
1103
      session_->pushUpdates();
1104
    session_->mutex().unlock();
1105
  }
1106
1107
  attachThreadToHandler(prevHandler_);
1108
}
1109
#endif
1110
1111
WebSession::Handler::~Handler()
1112
0
{
1113
0
#ifndef WT_TARGET_JAVA
1114
0
  if (haveLock()) {
1115
    /* We should check that the session state is not dead ? */
1116
0
    session_->processQueuedEvents(*this);
1117
0
    if (session_->triggerUpdate_)
1118
0
      session_->pushUpdates();
1119
0
    else if (response_ && !session_->dead())
1120
0
      session()->render(*this);
1121
1122
0
    Utils::erase(session_->handlers_, this);
1123
0
  }
1124
1125
0
  if (session_->handlers_.empty())
1126
0
    session_->hibernate();
1127
1128
0
  attachThreadToHandler(prevHandler_);
1129
0
#endif // WT_TARGET_JAVA
1130
0
}
1131
1132
void WebSession::Handler::setRequest(WebRequest *request,
1133
                                     WebResponse *response)
1134
0
{
1135
0
  request_ = request;
1136
0
  response_ = response;
1137
0
}
1138
1139
void WebSession::Handler::flushResponse()
1140
0
{
1141
0
  if (response_) {
1142
0
    response_->flush();
1143
0
    setRequest(nullptr, nullptr);
1144
0
  }
1145
0
}
1146
1147
void WebSession::hibernate()
1148
0
{
1149
0
  if (app_ && app_->localizedStrings_)
1150
0
    app_->localizedStrings_->hibernate();
1151
0
}
1152
1153
EventSignalBase *WebSession::decodeSignal(const std::string& signalId,
1154
                                          bool checkExposed) const
1155
0
{
1156
0
  EventSignalBase *result = app_->decodeExposedSignal(signalId);
1157
1158
0
  if (result && checkExposed) {
1159
0
    WWidget *w = dynamic_cast<WWidget *>(result->owner());
1160
0
    if (w && !app_->isExposed(w))
1161
0
      result = nullptr;
1162
0
  }
1163
1164
0
  if (!result && checkExposed) {
1165
0
    if (app_->justRemovedSignals().find(signalId) ==
1166
0
        app_->justRemovedSignals().end())
1167
0
      LOG_ERROR("decodeSignal(): signal '" << signalId << "' not exposed");
1168
0
  }
1169
1170
0
  return result;
1171
0
}
1172
1173
EventSignalBase *WebSession::decodeSignal(const std::string& objectId,
1174
                                          const std::string& name,
1175
                                          bool checkExposed) const
1176
0
{
1177
0
  std::string signalId = app_->encodeSignal(objectId, name);
1178
1179
0
  return decodeSignal(signalId, checkExposed && name != "resized");
1180
0
}
1181
1182
WebSession *WebSession::instance()
1183
1.12k
{
1184
1.12k
  Handler *handler = WebSession::Handler::instance();
1185
1.12k
  return handler ? handler->session() : nullptr;
1186
1.12k
}
1187
1188
void WebSession::doRecursiveEventLoop()
1189
0
{
1190
0
  Handler *handler = WebSession::Handler::instance();
1191
1192
#ifndef WT_BOOST_THREADS
1193
  LOG_ERROR("cannot do recursive event loop without threads");
1194
#else
1195
1196
#ifdef WT_TARGET_JAVA
1197
  if (handler->request() && !WebController::isAsyncSupported())
1198
    throw WException("Recursive eventloop requires a Servlet 3.0 "
1199
                     "enabled servlet container and an application "
1200
                     "with async-supported enabled.");
1201
#endif
1202
1203
  /*
1204
   * Finish the request that is being handled
1205
   *
1206
   * It could be that handler does not have a request/response:
1207
   *  if it is actually a long polling server push request.
1208
   *  if we are somehow recursing recursive event loops: e.g.
1209
   *    processEvents() during exec()
1210
   *
1211
   * In that case, we do not need to finish it.
1212
   */
1213
0
  if (handler->request())
1214
0
    handler->session()->notifySignal(WEvent(WEvent::Impl(handler)));
1215
0
  else
1216
0
    if (app_->updatesEnabled())
1217
0
      app_->triggerUpdate();
1218
1219
0
  if (handler->response())
1220
0
    handler->session()->render(*handler);
1221
1222
0
  if (dead()) {
1223
0
    recursiveEventHandler_ = nullptr;
1224
0
    throw WException("doRecursiveEventLoop(): session was killed");
1225
0
  }
1226
1227
  /*
1228
   * Register that we are doing a recursive event loop, this is used in
1229
   * handleRequest() to let the recursive event loop do the actual
1230
   * notification.
1231
   */
1232
0
  Handler *prevRecursiveEventHandler = recursiveEventHandler_;
1233
0
  recursiveEventHandler_ = handler;
1234
0
  newRecursiveEvent_ = nullptr;
1235
1236
  /*
1237
   * Release session mutex lock, wait for recursive event, and retake
1238
   * the session mutex lock.
1239
   */
1240
0
#ifndef WT_TARGET_JAVA
1241
0
  if (webSocket_)
1242
0
    webSocket_->readWebSocketMessage
1243
0
      (std::bind(&WebSession::handleWebSocketMessage, shared_from_this(),
1244
0
                 std::placeholders::_1));
1245
1246
0
  if (controller_->server()->ioService().requestBlockedThread()) {
1247
0
    while (!newRecursiveEvent_)
1248
0
      try {
1249
0
        recursiveEvent_.wait(handler->lock());
1250
0
    } catch (...) {
1251
0
      controller_->server()->ioService().releaseBlockedThread();
1252
0
      throw;
1253
0
    }
1254
0
    controller_->server()->ioService().releaseBlockedThread();
1255
0
  } else {
1256
    // Allow at least one thread to serve requests in order to avoid a
1257
    // locked-up Wt. Even worse, Wt deadlocks if all threads are
1258
    // occupied in internal event loops and all those browser windows
1259
    // are closed (session time out does not work anymore)
1260
0
    throw WException("doRecursiveEventLoop(): all threads are busy. "
1261
0
                     "Avoid using recursive event loops.");
1262
0
  }
1263
#else
1264
  while (!newRecursiveEvent_)
1265
    recursiveEvent_.wait();
1266
#endif
1267
1268
0
  if (dead()) {
1269
0
    recursiveEventHandler_ = nullptr;
1270
0
    delete newRecursiveEvent_;
1271
0
    newRecursiveEvent_ = nullptr;
1272
0
    throw WException("doRecursiveEventLoop(): session was killed");
1273
0
  }
1274
1275
0
  setLoaded();
1276
1277
  /*
1278
   * We use recursiveEventHandler_ != 0 to postpone rendering: we only want
1279
   * the event handling part.
1280
   */
1281
0
  app_->notify(WEvent(*newRecursiveEvent_));
1282
0
  delete newRecursiveEvent_;
1283
0
  newRecursiveEvent_ = nullptr;
1284
0
  recursiveEventDone_.notify_one();
1285
1286
0
  recursiveEventHandler_ = prevRecursiveEventHandler;
1287
0
#endif // WT_BOOST_THREADS
1288
0
}
1289
1290
void WebSession::expire()
1291
0
{
1292
0
  kill();
1293
0
}
1294
1295
bool WebSession::unlockRecursiveEventLoop()
1296
0
{
1297
0
  if (!recursiveEventHandler_)
1298
0
    return false;
1299
1300
  /*
1301
   * Pass on the handler to the recursive event loop.
1302
   */
1303
0
  Handler *handler = WebSession::Handler::instance();
1304
1305
0
  recursiveEventHandler_->setRequest(handler->request(), handler->response());
1306
0
  handler->setRequest(nullptr, nullptr);
1307
1308
0
  newRecursiveEvent_ = new WEvent::Impl(recursiveEventHandler_);
1309
1310
0
#ifdef WT_BOOST_THREADS
1311
0
  recursiveEvent_.notify_one();
1312
0
#endif
1313
1314
0
  return true;
1315
0
}
1316
1317
void WebSession::handleRequest(Handler& handler)
1318
0
{
1319
0
  WebRequest& request = *handler.request();
1320
1321
0
  const std::string *wtdE = request.getParameter("wtd");
1322
1323
0
  Configuration& conf = controller_->configuration();
1324
1325
0
  const char *origin = request.headerValue("Origin");
1326
0
  if (request.isWebSocketRequest()) {
1327
0
    std::string trustedOrigin = env_->urlScheme() + "://" + env_->hostName();
1328
1329
0
#ifndef WT_TARGET_JAVA
1330
    // Fallback if the WebSocket was generated on the framework WebSocket.
1331
    // The origin would remain "http(s)", but the urlScheme would already be "ws(s)".
1332
0
    std::string wsTrustedOrigin = (env_->urlScheme() == "ws" ? "http://" : "https://") + env_->hostName();
1333
0
#endif // WT_TARGET_JAVA
1334
    // Allow new WebSocket connection:
1335
    // - Origin is OK if:
1336
    //  - It is the same as the current host
1337
    //  - or we are using WidgetSet mode and the origin is allowed
1338
    // - Wt session id matches
1339
0
    if (origin && (trustedOrigin == origin ||
1340
0
#ifndef WT_TARGET_JAVA
1341
0
                   wsTrustedOrigin == origin ||
1342
0
#endif // WT_TARGET_JAVA
1343
0
                   (type() == EntryPointType::WidgetSet && conf.isAllowedOrigin(origin))) &&
1344
0
        wtdE && *wtdE == sessionId_) {
1345
      // OK
1346
0
    } else {
1347
      // Not OK
1348
0
      if (origin) {
1349
0
        LOG_ERROR("WebSocket request refused: Origin '" << origin <<
1350
0
            "' not allowed (trusted origin is '" << trustedOrigin << "')");
1351
0
      } else {
1352
0
        LOG_ERROR("WebSocket request refused: missing Origin");
1353
0
      }
1354
0
      handler.response()->setStatus(403);
1355
0
      handler.flushResponse();
1356
0
      return;
1357
0
    }
1358
0
  } else if (origin) {
1359
    /*
1360
     * CORS (Cross-Origin Resource Sharing)
1361
     */
1362
    /*
1363
     * Do we allow this XMLHttpRequest or WebSocketRequest?
1364
     *
1365
     * Only if all of the conditions below are met:
1366
     *  - this is a WidgetSet sessions
1367
     *  - the Origin is allowed according to the configuration
1368
     *  - this is a new session or the session id matches
1369
     */
1370
0
    if (type() == EntryPointType::WidgetSet &&
1371
0
        ((wtdE && *wtdE == sessionId_) || state_ == State::JustCreated) &&
1372
0
        conf.isAllowedOrigin(origin)) {
1373
0
      if (isEqual(origin, "null"))
1374
0
        origin = "*";
1375
0
      handler.response()->addHeader("Access-Control-Allow-Origin", origin);
1376
0
      handler.response()->addHeader("Access-Control-Allow-Credentials", "true");
1377
0
      handler.response()->addHeader("Vary", "Origin");
1378
1379
0
      if (isEqual(request.requestMethod(), "OPTIONS")) {
1380
0
        WebResponse *response = handler.response();
1381
1382
0
        response->setStatus(200);
1383
0
        response->addHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
1384
0
        response->addHeader("Access-Control-Max-Age", "1728000");
1385
0
        const char *requestHeaders = request.headerValue("Access-Control-Request-Headers");
1386
0
        if (requestHeaders)
1387
0
          response->addHeader("Access-Control-Allow-Headers", requestHeaders);
1388
0
        handler.flushResponse();
1389
1390
0
        return;
1391
0
      }
1392
0
    }
1393
0
  }
1394
1395
0
  const std::string *requestE = request.getParameter("request");
1396
0
  bool requestForResource = resourceRequest(request);
1397
0
  bool requestForStyle = requestE && *requestE == "style";
1398
1399
0
  if (requestE && *requestE == "ws" && !request.isWebSocketRequest()) {
1400
0
    LOG_ERROR("invalid WebSocket request, ignoring");
1401
1402
0
    LOG_INFO("Connection: " << str(request.headerValue("Connection")));
1403
0
    LOG_INFO("Upgrade: " << str(request.headerValue("Upgrade")));
1404
0
    LOG_INFO("Sec-WebSocket-Version: "
1405
0
             << str(request.headerValue("Sec-WebSocket-Version")));
1406
1407
0
    handler.flushResponse();
1408
0
    return;
1409
0
  }
1410
1411
0
  if (request.isWebSocketRequest()) {
1412
#ifdef WT_TARGET_JAVA
1413
    if (conf.webSockets()) {
1414
      handler.response()->setStatus(500); // Internal Server Error
1415
      handler.flushResponse();
1416
      throw new WException("Server does not implement JSR-356 for WebSockets");
1417
    }
1418
#else
1419
0
    if (state_ != State::JustCreated && requestE && *requestE == "ws") {
1420
      // This is the framework-internal rendering websocket
1421
0
      handleWebSocketRequest(handler);
1422
0
      return;
1423
0
    } else if (!requestForResource) {
1424
      // Other websocket requests, not intended for WWebSocketResources,
1425
      // are not expected.
1426
0
      handler.flushResponse();
1427
0
      kill();
1428
0
      return;
1429
0
    }
1430
0
#endif // WT_TARGET_JAVA
1431
0
  }
1432
1433
1434
0
  handler.response()->setResponseType(WebResponse::ResponseType::Page);
1435
1436
  /*
1437
   * Only handle GET, POST and OPTIONS requests, unless a resource is
1438
   * listening.
1439
   */
1440
0
  if (!(requestForResource
1441
0
        || isEqual(request.requestMethod(), "POST")
1442
0
        || isEqual(request.requestMethod(), "GET"))) {
1443
0
    handler.response()->setStatus(400); // Bad Request
1444
0
    handler.flushResponse();
1445
0
    return;
1446
0
  }
1447
1448
  /*
1449
   * If ajax session is already established, reject GET with wtd parameter
1450
   * matching sessionId_, unless resource request or reloadIsNewSession() is false
1451
   */
1452
0
  if (env_->ajax()
1453
0
      && isEqual(request.requestMethod(), "GET")
1454
0
      && !requestForResource
1455
0
      && !requestForStyle
1456
0
      && conf.reloadIsNewSession()
1457
0
      && !suspended()
1458
0
      && wtdE && *wtdE == sessionId_) {
1459
0
    LOG_SECURE("Unexpected GET request with wtd of existing Ajax session");
1460
0
    serveError(403, handler, "Forbidden");
1461
0
    return;
1462
0
  }
1463
1464
  /*
1465
   * Under what circumstances do we allow a request which does not have
1466
   * a session ID (i.e. who as it only through a cookie?)
1467
   *  - when a new session is created
1468
   *  - when reloading the page
1469
   *
1470
   * in other cases: discard the request
1471
   */
1472
0
  if ((!wtdE || (*wtdE != sessionId_))
1473
0
      && state_ != State::JustCreated
1474
0
      && (requestE && (*requestE == "jsupdate" ||
1475
0
                       *requestE == "jserror" ||
1476
0
                       *requestE == "resource"))) {
1477
0
    LOG_DEBUG("CSRF: " << (wtdE ? *wtdE : "no wtd") << " != " << sessionId_ <<
1478
0
              ", requestE: " << (requestE ? *requestE : "none"));
1479
0
    LOG_SECURE("CSRF prevention kicked in.");
1480
0
    serveError(403, handler, "Forbidden");
1481
0
  } else
1482
0
    try {
1483
      /*
1484
       * If we have just created a new session, we need to take care:
1485
       * - requests from a dead session -> reload
1486
       * - otherwise: serve Boot.html, Hybrid.html or Plain.html
1487
       *
1488
       * Otherwise, we are Loaded: we need to react to:
1489
       * - when missing a request: rerender (Plain or Hybrid)
1490
       * - if request for 'script':
1491
       *   (if appropriate, upgrade to Ajax)
1492
       *     serve script
1493
       * - if signal ...
1494
       * - if resource ...
1495
       */
1496
0
      switch (state_) {
1497
0
      case State::JustCreated: {
1498
0
        if (conf.sessionTracking() == Configuration::Combined) {
1499
0
          renderer().updateMultiSessionCookie(request);
1500
0
        }
1501
1502
0
        switch (type_) {
1503
0
        case EntryPointType::Application: {
1504
0
          init(request); // env, url/internalpath
1505
1506
          // Handle requests from dead sessions:
1507
          //
1508
          // We need to send JS to reload the page when we get
1509
          // 'request' == 'updatejs' or 'request' == "script"
1510
          // We ignore 'request' == 'resource'
1511
          //
1512
          // In other cases we can simply start
1513
1514
0
          if (requestE) {
1515
0
            if (*requestE == "jsupdate" ||
1516
0
                *requestE == "jserror" ||
1517
0
                *requestE == "script") {
1518
0
              handler.response()->setResponseType
1519
0
                (WebResponse::ResponseType::Update);
1520
0
              LOG_INFO("signal from dead session, sending reload.");
1521
0
              renderer_.letReloadJS(*handler.response(), true);
1522
1523
0
              kill();
1524
0
              break;
1525
0
            } else if (*requestE != "page") {
1526
0
              LOG_INFO("Not serving this: request of type '" << *requestE << "' "
1527
0
                       "in a brand new session (probably coming from an old session)");
1528
0
              handler.response()->setContentType("text/html");
1529
0
              handler.response()->out()
1530
0
                << "<html><head></head><body></body></html>";
1531
1532
0
              kill();
1533
0
              break;
1534
0
            }
1535
0
          }
1536
1537
          /*
1538
           * We can simply bootstrap.
1539
           */
1540
0
          {
1541
0
            const std::string *internalPath = env_->getCookie("WtInternalPath");
1542
0
            if (internalPath)
1543
0
              env_->setInternalPath(*internalPath);
1544
0
          }
1545
1546
0
          bool forcePlain = env_->treatLikeBot()
1547
0
                            || !env_->agentSupportsAjax();
1548
1549
0
          progressiveBoot_ = !forcePlain
1550
0
                             && conf.progressiveBoot(env_->internalPath());
1551
1552
0
          if (forcePlain || progressiveBoot_) {
1553
            /*
1554
             * First start the application
1555
             */
1556
0
            if (!start(handler.response()))
1557
0
              throw WException("Could not start application.");
1558
1559
0
            app_->notify(WEvent(WEvent::Impl(&handler)));
1560
1561
0
            if (env_->agentIsSpiderBot()) { // Configured as bot (in wt_config.xml)
1562
0
              kill();
1563
0
            } else if (env_->isLikelyBotGetRequest() && conf.isInvalidWtdSuspicious()) { // Detected as bad (potential) bot request
1564
0
              LOG_SECURE("terminating session for suspicious initial GET request (containing session ID)");
1565
0
              kill();
1566
0
            } else if (controller_->limitPlainHtmlSessions()) {
1567
0
              LOG_SECURE("DoS: plain HTML sessions being limited");
1568
1569
0
              if (forcePlain) {
1570
0
                kill();
1571
0
              } else {// progressiveBoot_
1572
0
                setState(State::Loaded, conf.bootstrapTimeout());
1573
0
              }
1574
0
            } else {
1575
0
              setLoaded();
1576
0
            }
1577
0
          } else {
1578
            /*
1579
             * Delay application start
1580
             */
1581
0
            serveResponse(handler);
1582
0
            setState(State::Loaded, conf.bootstrapTimeout());
1583
0
          }
1584
0
          break; }
1585
0
        case EntryPointType::WidgetSet:
1586
0
          if (requestForResource || requestForStyle) {
1587
0
            const std::string *resourceE = request.getParameter("resource");
1588
0
            if (resourceE && *resourceE == "blank") {
1589
0
              handler.response()->setContentType("text/html");
1590
0
              handler.response()->out() <<
1591
0
                "<html><head><title>bhm</title></head>"
1592
0
                "<body> </body></html>";
1593
0
            } else {
1594
0
              LOG_INFO("not starting session for unexpected request type.");
1595
0
              handler.response()->setContentType("text/html");
1596
0
              handler.response()->out()
1597
0
                << "<html><head></head><body></body></html>";
1598
0
            }
1599
1600
0
            kill();
1601
0
          } else {
1602
0
            handler.response()->setResponseType(WebResponse::ResponseType::Script);
1603
0
            const std::string* wtt = request.getParameter("wtt");
1604
0
            if (wtt && *wtt == "widgetset") {
1605
0
              env_->enableAjax(request);
1606
0
              if (!start(handler.response())) {
1607
0
                throw WException("Could not start application.");
1608
0
              }
1609
1610
0
              app_->notify(WEvent(WEvent::Impl(&handler)));
1611
0
              setExpectLoad();
1612
0
            } else {
1613
0
              init(request); // env, url/internalpath, initial query parameters
1614
0
              serveResponse(handler);
1615
0
            }
1616
0
          }
1617
1618
0
          break;
1619
0
        default:
1620
0
          assert(false); // EntryPointType::StaticResource
1621
0
        }
1622
1623
0
        break;
1624
0
      }
1625
0
      case State::ExpectLoad:
1626
0
      case State::Loaded:
1627
0
      case State::Suspended: {
1628
0
        if (conf.sessionTracking() == Configuration::Combined) {
1629
0
          const std::string *signalE
1630
0
            = handler.request()->getParameter("signal");
1631
0
          bool isKeepAlive = requestE && signalE && *signalE == "keepAlive";
1632
0
          if (isKeepAlive || !env_->ajax()) {
1633
0
            renderer().updateMultiSessionCookie(request);
1634
0
          }
1635
0
        }
1636
1637
0
        if (requestE) {
1638
0
          if (*requestE == "jsupdate" ||
1639
0
              *requestE == "jserror")
1640
0
            handler.response()->setResponseType(WebResponse::ResponseType::Update);
1641
0
          else if (*requestE == "script") {
1642
0
            handler.response()->setResponseType(WebResponse::ResponseType::Script);
1643
0
            if (state_ == State::Loaded)
1644
0
              setExpectLoad();
1645
0
          } else if (*requestE == "style") {
1646
0
            flushBootStyleResponse();
1647
1648
0
            const std::string *page = request.getParameter("page");
1649
1650
            // See:
1651
            // http://www.blaze.io/mobile/ios5-top10-performance-changes/
1652
            // Mozilla/5.0 (iPad; CPU OS 5_1_1 like Mac OS X)
1653
            //  AppleWebKit/534.46 (KHTML, like Gecko)
1654
            //  Version/5.1 Mobile/9B206 Safari/7534.48.3
1655
0
            bool ios5 = env_->agentIsMobileWebKit()
1656
0
              && (env_->userAgent().find("OS 5_") != std::string::npos
1657
0
                  || env_->userAgent().find("OS 6_") != std::string::npos
1658
0
                  || env_->userAgent().find("OS 7_") != std::string::npos
1659
0
                  || env_->userAgent().find("OS 8_") != std::string::npos);
1660
1661
            // check js parameter
1662
0
            const std::string *jsE = request.getParameter("js");
1663
0
            bool nojs = jsE && *jsE == "no";
1664
1665
0
            bool bootStyle =
1666
0
              (app_ || (!ios5 && !nojs)) &&
1667
0
              page && *page == std::to_string(renderer_.pageId());
1668
1669
0
            if (!bootStyle) {
1670
0
              handler.response()->setContentType("text/css");
1671
0
              handler.flushResponse();
1672
0
            } else {
1673
0
#ifndef WT_TARGET_JAVA
1674
0
              if (!app_) {
1675
0
                bootStyleResponse_ = handler.response();
1676
0
                handler.setRequest(nullptr, nullptr);
1677
1678
0
                controller_->server()
1679
0
                  ->schedule(std::chrono::milliseconds{2000}, sessionId_,
1680
0
                             std::bind(&WebSession::flushBootStyleResponse,
1681
0
                                       this));
1682
0
              } else {
1683
0
                renderer_.serveLinkedCss(*handler.response());
1684
0
                handler.flushResponse();
1685
0
              }
1686
#else
1687
              /*
1688
               * In Servlet2, we canont defer responding the request. So we
1689
               * do a little spin lock here.
1690
               *
1691
               * There is no reason why the second request (script) does not
1692
               * arrive within seconds.
1693
               */
1694
              unsigned i = 0;
1695
              const unsigned MAX_TRIES = 1000;
1696
1697
              while (!app_ && i < MAX_TRIES) {
1698
                mutex_.unlock();
1699
                std::this_thread::sleep_for(std::chrono::milliseconds(5));
1700
                mutex_.lock();
1701
1702
                ++i;
1703
              }
1704
1705
              if (i < MAX_TRIES) {
1706
                renderer_.serveLinkedCss(*handler.response());
1707
              }
1708
1709
              handler.flushResponse();
1710
#endif // WT_TARGET_JAVA
1711
0
            }
1712
1713
0
            break;
1714
0
          }
1715
0
        }
1716
1717
0
        if (!app_) {
1718
0
          const std::string *resourceE = request.getParameter("resource");
1719
1720
0
          if (handler.response()->responseType() == WebResponse::ResponseType::Script) {
1721
0
            env_->enableAjax(request);
1722
1723
0
            if (!start(handler.response()))
1724
0
              throw WException("Could not start application.");
1725
0
          } else if (requestForResource && resourceE && *resourceE == "blank") {
1726
0
            handler.response()->setContentType("text/html");
1727
0
            handler.response()->out() <<
1728
0
              "<html><head><title>bhm</title></head>"
1729
0
              "<body> </body></html>";
1730
1731
0
            break;
1732
0
          } else {
1733
0
            const std::string *jsE = request.getParameter("js");
1734
1735
0
            if (jsE && *jsE == "no") {
1736
0
              if (!start(handler.response()))
1737
0
                throw WException("Could not start application.");
1738
1739
0
              if (controller_->limitPlainHtmlSessions()) {
1740
0
                LOG_SECURE("DoS: plain HTML sessions being limited");
1741
0
                kill();
1742
0
              }
1743
0
            } else {
1744
              // This could be because the session Id was not as
1745
              // expected. At least, it should be correct now.
1746
0
              if (!conf.reloadIsNewSession() && wtdE && *wtdE == sessionId_) {
1747
0
                serveResponse(handler);
1748
0
                setState(State::Loaded, conf.bootstrapTimeout());
1749
0
              } else {
1750
0
                handler.response()->setContentType("text/html");
1751
0
                handler.response()->out() <<
1752
0
                  "<html><body><h1>Refusing to respond.</h1></body></html>";
1753
0
              }
1754
1755
0
              break;
1756
0
            }
1757
0
          }
1758
0
        }
1759
1760
0
        bool doNotify = false;
1761
1762
0
        if (handler.request()) {
1763
0
          const std::string *signalE
1764
0
            = handler.request()->getParameter("signal");
1765
0
          bool isPoll = signalE && *signalE == "poll";
1766
1767
0
          if (requestForResource || isPoll || !unlockRecursiveEventLoop()) {
1768
0
            doNotify = true;
1769
1770
0
            if (env_->ajax()) {
1771
0
              if (state_ != State::ExpectLoad &&
1772
0
                  state_ != State::Suspended &&
1773
0
                  handler.response()->responseType() ==
1774
0
                  WebResponse::ResponseType::Update) {
1775
0
                setLoaded();
1776
0
              }
1777
0
            } else if (state_ != State::ExpectLoad &&
1778
0
                       !(state_ == State::Suspended && requestForResource) &&
1779
0
                       !controller_->limitPlainHtmlSessions()) {
1780
0
              setLoaded();
1781
0
            }
1782
0
          }
1783
0
        } else {
1784
0
#ifndef WT_TARGET_JAVA
1785
0
          doNotify = !app_->initialized_;
1786
#else
1787
          doNotify = false;
1788
#endif
1789
0
        }
1790
1791
0
        if (doNotify) {
1792
0
          app_->notify(WEvent(WEvent::Impl(&handler)));
1793
0
          if (handler.response() && !requestForResource) {
1794
            /*
1795
             * This may be when an error was thrown during event
1796
             * propagation: then we want to render the error message.
1797
             */
1798
0
            app_->notify(WEvent(WEvent::Impl(&handler, true)));
1799
0
          }
1800
0
        }
1801
1802
0
        break;
1803
0
      }
1804
0
      case State::BeingDestroyed:
1805
0
      case State::Dead:
1806
0
        LOG_INFO("request to dead session, ignoring");
1807
0
        break;
1808
0
      }
1809
0
    } catch (WException& e) {
1810
0
      LOG_ERROR("fatal error: " << e.what());
1811
1812
#ifdef WT_TARGET_JAVA
1813
      e.printStackTrace();
1814
#endif // WT_TARGET_JAVA
1815
1816
0
      kill();
1817
1818
0
      if (handler.response())
1819
0
        serveError(500, handler, "Internal Server Error");
1820
1821
0
    } catch (std::exception& e) {
1822
0
      LOG_ERROR("fatal error: " << e.what());
1823
1824
#ifdef WT_TARGET_JAVA
1825
      e.printStackTrace();
1826
#endif // WT_TARGET_JAVA
1827
1828
0
      kill();
1829
1830
0
      if (handler.response())
1831
0
        serveError(500, handler, "Internal Server Error");
1832
0
    } catch (...) {
1833
0
      LOG_ERROR("fatal error: caught unknown exception.");
1834
1835
0
      kill();
1836
1837
0
      if (handler.response())
1838
0
        serveError(500, handler, "Internal Server Error");
1839
0
    }
1840
1841
0
  if (handler.response())
1842
0
    handler.flushResponse();
1843
0
}
1844
1845
void WebSession::flushBootStyleResponse()
1846
0
{
1847
0
  if (bootStyleResponse_) {
1848
0
    bootStyleResponse_->flush();
1849
0
    bootStyleResponse_ = nullptr;
1850
0
  }
1851
0
}
1852
1853
#ifndef WT_TARGET_JAVA
1854
void WebSession::handleWebSocketRequest(Handler& handler)
1855
0
{
1856
0
  if (state_ != State::Loaded &&
1857
0
      state_ != State::ExpectLoad &&
1858
0
      state_ != State::Suspended) {
1859
0
    handler.flushResponse();
1860
0
    return;
1861
0
  }
1862
1863
  /*
1864
   * This triggers an orderly switch from Ajax to WebSocket:
1865
   *
1866
   *  First we ask for a 'connect', and in the mean time we do not yet
1867
   *  use the socket. On the JS side, the connect disables any pending
1868
   *  ajax long poll, and waits for the current pending response, if any.
1869
   *  only then, we confirm the connect, transferring the last ackId.
1870
   */
1871
0
  if (webSocket_) {
1872
0
    webSocket_->flush();
1873
0
    webSocket_ = nullptr;
1874
0
  }
1875
1876
0
  webSocket_ = handler.response();
1877
0
  canWriteWebSocket_ = false;
1878
0
  webSocketConnected_ = false;
1879
1880
0
  webSocket_->flush
1881
0
    (WebRequest::ResponseState::ResponseFlush,
1882
0
     std::bind(&WebSession::webSocketConnect,
1883
0
               std::weak_ptr<WebSession>(shared_from_this()),
1884
0
               std::placeholders::_1));
1885
1886
0
  handler.setRequest(nullptr, nullptr);
1887
0
}
1888
#endif // WT_TARGET_JAVA
1889
1890
#ifndef WT_TARGET_JAVA
1891
void WebSession::webSocketConnect(std::weak_ptr<WebSession> session,
1892
                                  WebWriteEvent event)
1893
0
{
1894
0
  LOG_DEBUG("webSocketConnect()");
1895
1896
0
  std::shared_ptr<WebSession> lock = session.lock();
1897
0
  if (lock) {
1898
0
    Handler handler(lock, Handler::LockOption::TakeLock);
1899
1900
0
    if (!lock->webSocket_)
1901
0
      return;
1902
1903
0
    switch (event) {
1904
0
    case WebWriteEvent::Completed:
1905
0
      lock->webSocket_->out() << "connect";
1906
1907
0
      lock->webSocket_->flush
1908
0
        (WebRequest::ResponseState::ResponseFlush,
1909
0
         std::bind(&WebSession::webSocketReady,
1910
0
                   std::weak_ptr<WebSession>(lock),
1911
0
                   std::placeholders::_1));
1912
1913
0
      lock->webSocket_->readWebSocketMessage
1914
0
        (std::bind(&WebSession::handleWebSocketMessage,
1915
0
                   std::weak_ptr<WebSession>(lock),
1916
0
                   std::placeholders::_1));
1917
1918
0
      break;
1919
0
    case WebWriteEvent::Error:
1920
0
      lock->webSocket_->flush();
1921
0
      lock->webSocket_ = nullptr;
1922
1923
0
      break;
1924
0
    }
1925
0
  }
1926
1927
0
}
1928
#endif // WT_TARGET_JAVA
1929
1930
#ifdef WT_TARGET_JAVA
1931
void WebSession::handleWebSocketMessage(Handler& handler)
1932
{
1933
  WebRequest *message = handler.request();
1934
  bool closing = message->contentLength() == 0;
1935
1936
  if (!closing) {
1937
    const std::string *connectedE = message->getParameter("connected");
1938
    if (connectedE) {
1939
      renderer_.ackUpdate(Utils::stoi(*connectedE));
1940
      webSocketConnected_ = true;
1941
      canWriteWebSocket_ = true;
1942
    }
1943
1944
    const std::string *wsRqIdE = message->getParameter("wsRqId");
1945
    if (wsRqIdE) {
1946
      int wsRqId = Utils::stoi(*wsRqIdE);
1947
      renderer_.addWsRequestId(wsRqId);
1948
    }
1949
1950
    const std::string *signalE = message->getParameter("signal");
1951
1952
    if (signalE && *signalE == "ping") {
1953
      LOG_DEBUG("ws: handle ping");
1954
      if (canWriteWebSocket_) {
1955
        webSocket_->out() << "{}";
1956
        webSocket_->flushBuffer();
1957
        return;
1958
      }
1959
    }
1960
1961
    const std::string *pageIdE = message->getParameter("pageId");
1962
1963
    if (pageIdE && *pageIdE != std::to_string(renderer_.pageId())) {
1964
      closing = true;
1965
    }
1966
1967
    if (!closing) {
1968
      handleRequest(handler);
1969
    } else
1970
      webSocket_->flush();
1971
    }
1972
}
1973
#endif // WT_TARGET_JAVA
1974
1975
#ifndef WT_TARGET_JAVA
1976
void WebSession::handleWebSocketMessage(std::weak_ptr<WebSession> session,
1977
                                        WebReadEvent event)
1978
0
{
1979
0
  LOG_DEBUG("handleWebSocketMessage: " << (int)event);
1980
0
  std::shared_ptr<WebSession> lock = session.lock();
1981
0
  if (lock) {
1982
0
    Handler handler(lock, Handler::LockOption::TakeLock);
1983
1984
0
    if (!lock->webSocket_)
1985
0
      return;
1986
1987
0
    switch (event) {
1988
0
    case WebReadEvent::Error:
1989
0
      {
1990
0
        if (lock->canWriteWebSocket_) {
1991
0
          lock->webSocket_->flush();
1992
0
          lock->webSocket_ = nullptr;
1993
0
        }
1994
1995
0
        return;
1996
0
      }
1997
1998
0
    case WebReadEvent::Ping:
1999
0
      {
2000
0
        WebSocketMessage *message = new WebSocketMessage(lock.get());
2001
2002
0
        if (lock->canWriteWebSocket_) {
2003
0
          lock->canWriteWebSocket_ = false;
2004
0
          lock->webSocket_->out() << "{}";
2005
0
          lock->webSocket_->flush
2006
0
            (WebRequest::ResponseState::ResponseFlush,
2007
0
             std::bind(&WebSession::webSocketReady, session,
2008
0
                       std::placeholders::_1));
2009
0
        }
2010
2011
0
        delete message;
2012
2013
0
        lock->webSocket_->readWebSocketMessage
2014
0
          (std::bind(&WebSession::handleWebSocketMessage, session,
2015
0
                     std::placeholders::_1));
2016
2017
0
        break;
2018
0
      }
2019
2020
0
    case WebReadEvent::Message:
2021
0
      {
2022
0
        WebSocketMessage *message = new WebSocketMessage(lock.get());
2023
2024
0
        bool closing = message->contentLength() == 0;
2025
2026
0
        if (!closing) {
2027
0
          CgiParser cgi(lock->controller_->configuration().maxRequestSize(),
2028
0
                        lock->controller_->configuration().maxFormDataSize());
2029
0
          try {
2030
0
            cgi.parse(*message, CgiParser::ReadDefault);
2031
0
          } catch (std::exception& e) {
2032
0
            LOG_ERROR("could not parse ws message: " << e.what());
2033
0
            closing = true;
2034
0
          }
2035
0
        }
2036
2037
0
        if (!closing) {
2038
0
          const std::string *connectedE = message->getParameter("connected");
2039
0
          if (connectedE) {
2040
0
            if (lock->asyncResponse_) {
2041
0
              lock->asyncResponse_->flush();
2042
0
              lock->asyncResponse_ = nullptr;
2043
0
            }
2044
2045
0
            lock->renderer_.ackUpdate(static_cast<unsigned int>(Utils::stoul(*connectedE)));
2046
0
            lock->webSocketConnected_ = true;
2047
0
          }
2048
2049
0
          const std::string *wsRqIdE = message->getParameter("wsRqId");
2050
0
          if (wsRqIdE) {
2051
0
            int wsRqId = Utils::stoi(*wsRqIdE);
2052
0
            lock->renderer_.addWsRequestId(wsRqId);
2053
0
          }
2054
2055
0
          const std::string *signalE = message->getParameter("signal");
2056
2057
0
          if (signalE && *signalE == "ping") {
2058
0
            LOG_DEBUG("ws: handle ping");
2059
0
            if (lock->canWriteWebSocket_) {
2060
0
              lock->canWriteWebSocket_ = false;
2061
0
              lock->webSocket_->out() << "{}";
2062
0
              lock->webSocket_->flush
2063
0
                (WebRequest::ResponseState::ResponseFlush,
2064
0
                 std::bind(&WebSession::webSocketReady, session,
2065
0
                           std::placeholders::_1));
2066
0
            }
2067
2068
0
            lock->webSocket_->readWebSocketMessage
2069
0
              (std::bind(&WebSession::handleWebSocketMessage, session,
2070
0
                         std::placeholders::_1));
2071
2072
0
            delete message;
2073
2074
0
            return;
2075
0
          }
2076
2077
0
          const std::string *pageIdE = message->getParameter("pageId");
2078
0
          if (pageIdE && *pageIdE != std::to_string(lock->renderer_.pageId()))
2079
0
            closing = true;
2080
0
        }
2081
2082
0
        if (!closing) {
2083
0
          handler.setRequest(message, (WebResponse *)(message));
2084
0
          lock->handleRequest(handler);
2085
0
        } else
2086
0
          delete message;
2087
2088
0
        if (lock->dead()) {
2089
0
          closing = true;
2090
0
          lock->controller_->removeSession(lock->sessionId());
2091
0
        }
2092
2093
0
        if (closing) {
2094
0
          if (lock->webSocket_ && lock->canWriteWebSocket_) {
2095
0
            lock->webSocket_->flush();
2096
0
            lock->webSocket_ = nullptr;
2097
0
          }
2098
0
        } else
2099
0
          if (lock->webSocket_)
2100
0
            lock->webSocket_->readWebSocketMessage
2101
0
              (std::bind(&WebSession::handleWebSocketMessage, session,
2102
0
                         std::placeholders::_1));
2103
0
      }
2104
0
    }
2105
0
  }
2106
0
}
2107
#endif // WT_TARGET_JAVA
2108
2109
std::string WebSession::ajaxCanonicalUrl(const WebResponse& request) const
2110
0
{
2111
0
  const std::string *hashE = nullptr;
2112
0
  if (applicationName_.empty())
2113
0
    hashE = request.getParameter("_");
2114
2115
0
  if (!pagePathInfo_.empty() || (hashE && hashE->length() > 1)) {
2116
0
    std::string url;
2117
0
    if (applicationName_.empty()) {
2118
0
      url = fixRelativeUrl("?");
2119
0
      url = url.substr(0, url.length() - 1);
2120
0
    } else
2121
0
      url = fixRelativeUrl(applicationName_);
2122
2123
0
    bool firstParameter = true;
2124
0
    for (Http::ParameterMap::const_iterator i
2125
0
           = request.getParameterMap().begin();
2126
0
         i != request.getParameterMap().end(); ++i) {
2127
0
      if (i->first != "_") {
2128
0
        url += (firstParameter ? '?' : '&')
2129
0
          + Utils::urlEncode(i->first) + '='
2130
0
          + Utils::urlEncode(i->second[0]);
2131
0
        firstParameter = false;
2132
0
      }
2133
0
    }
2134
2135
0
    url += '#' + (app_ ? app_->internalPath() : env_->internalPath());
2136
2137
0
    return url;
2138
0
  } else
2139
0
    return std::string();
2140
0
}
2141
2142
void WebSession::pushUpdates()
2143
0
{
2144
0
  LOG_DEBUG("pushUpdates()");
2145
2146
0
  triggerUpdate_ = false;
2147
2148
0
  if (!app_ || !renderer_.isDirty()) {
2149
0
    LOG_DEBUG("pushUpdates(): nothing to do");
2150
0
    return;
2151
0
  }
2152
2153
0
  updatesPending_ = true;
2154
2155
0
  if (asyncResponse_) {
2156
0
    asyncResponse_->setResponseType(WebResponse::ResponseType::Update);
2157
0
    app_->notify(WEvent(WEvent::Impl(asyncResponse_)));
2158
0
    updatesPending_ = false;
2159
0
    asyncResponse_->flush();
2160
0
    asyncResponse_ = nullptr;
2161
0
  } else if (webSocket_ && webSocketConnected_) {
2162
0
    if (webSocket_->webSocketMessagePending()) {
2163
0
      LOG_DEBUG("pushUpdates(): web socket message pending");
2164
0
      return;
2165
0
    }
2166
2167
0
    if (canWriteWebSocket_) {
2168
0
#ifndef WT_TARGET_JAVA
2169
0
      {
2170
0
        WebSocketMessage m(this);
2171
0
        m.setResponseType(WebResponse::ResponseType::Update);
2172
0
        app_->notify(WEvent(WEvent::Impl((WebResponse *)&m)));
2173
0
      }
2174
2175
0
      updatesPending_ = false;
2176
0
      canWriteWebSocket_ = false;
2177
0
      webSocket_->flush
2178
0
        (WebRequest::ResponseState::ResponseFlush,
2179
0
         std::bind(&WebSession::webSocketReady,
2180
0
                   std::weak_ptr<WebSession>(shared_from_this()),
2181
0
                   std::placeholders::_1));
2182
#else
2183
      webSocket_->setResponseType(WebRequest::ResponseType::Update);
2184
      app_->notify(WEvent(WEvent::Impl(webSocket_)));
2185
      updatesPending_ = false;
2186
      webSocket_->flushBuffer();
2187
#endif
2188
0
    }
2189
0
  }
2190
2191
0
  if (updatesPending_) {
2192
0
    LOG_DEBUG("pushUpdates(): cannot write now");
2193
0
#ifdef WT_BOOST_THREADS
2194
0
    updatesPendingEvent_.notify_one();
2195
0
#endif
2196
0
  }
2197
0
}
2198
2199
#ifndef WT_TARGET_JAVA
2200
void WebSession::webSocketReady(std::weak_ptr<WebSession> session,
2201
                                WebWriteEvent event)
2202
0
{
2203
0
  LOG_DEBUG("webSocketReady()");
2204
2205
0
  std::shared_ptr<WebSession> lock = session.lock();
2206
0
  if (lock) {
2207
0
    Handler handler(lock, Handler::LockOption::TakeLock);
2208
2209
0
    LOG_DEBUG("webSocketReady: webSocket_ = " << (long long)lock->webSocket_
2210
0
              << " updatesPending = " << lock->updatesPending_
2211
0
              << " event = " << (int)event);
2212
2213
0
    switch (event) {
2214
0
    case WebWriteEvent::Completed:
2215
0
      if (lock->webSocket_) {
2216
0
        lock->canWriteWebSocket_ = true;
2217
2218
0
        if (lock->updatesPending_)
2219
0
          lock->pushUpdates();
2220
0
      }
2221
2222
0
      break;
2223
0
    case WebWriteEvent::Error:
2224
0
      if (lock->webSocket_) {
2225
0
        lock->webSocket_->flush();
2226
0
        lock->webSocket_ = nullptr;
2227
0
        lock->canWriteWebSocket_ = false;
2228
0
      }
2229
2230
0
      break;
2231
0
    }
2232
0
  }
2233
0
}
2234
#endif // WT_TARGET_JAVA
2235
2236
const std::string *WebSession::getSignal(const WebRequest& request,
2237
                                         const std::string& se) const
2238
0
{
2239
0
  const std::string *signalE = request.getParameter(se + "signal");
2240
2241
0
  if (!signalE) {
2242
0
    const int signalLength = 7 + se.length();
2243
2244
0
    const Http::ParameterMap& entries = request.getParameterMap();
2245
2246
0
    for (Http::ParameterMap::const_iterator i = entries.begin();
2247
0
         i != entries.end(); ++i) {
2248
0
      if (i->first.length() > static_cast<unsigned>(signalLength)
2249
0
          && i->first.substr(0, signalLength) == se + "signal=") {
2250
0
        signalE = &i->second[0];
2251
2252
0
        std::string v = i->first.substr(signalLength);
2253
0
        if (v.length() >= 2) {
2254
0
          std::string e = v.substr(v.length() - 2);
2255
0
          if (e == ".x" || e == ".y")
2256
0
            v = v.substr(0, v.length() - 2);
2257
0
        }
2258
2259
0
        *(const_cast<std::string *>(signalE)) = v;
2260
0
        break;
2261
0
      }
2262
0
    }
2263
0
  }
2264
2265
0
  return signalE;
2266
0
}
2267
2268
void WebSession::externalNotify(const WEvent::Impl& event)
2269
0
{
2270
0
  if (recursiveEventHandler_ &&
2271
0
      !newRecursiveEvent_) {
2272
0
#ifdef WT_BOOST_THREADS
2273
0
    newRecursiveEvent_ = new WEvent::Impl(event);
2274
0
    recursiveEvent_.notify_one();
2275
0
    while (newRecursiveEvent_) {
2276
#ifdef WT_TARGET_JAVA
2277
      recursiveEventDone_.wait();
2278
#else
2279
0
      recursiveEventDone_.wait(event.handler->lock());
2280
0
#endif
2281
0
    }
2282
0
#endif
2283
0
  } else {
2284
0
    if (app_)
2285
0
      app_->notify(WEvent(event));
2286
0
    else
2287
0
      notify(WEvent(event));
2288
0
  }
2289
0
}
2290
2291
void WebSession::notify(const WEvent& event)
2292
0
{
2293
0
  if (event.impl_.response) {
2294
0
    try {
2295
0
      renderer_.serveResponse(*event.impl_.response);
2296
0
    } catch (std::exception& e) {
2297
0
      LOG_ERROR("Exception in WApplication::notify(): " << e.what());
2298
2299
#ifdef WT_TARGET_JAVA
2300
      e.printStackTrace();
2301
#endif // WT_TARGET_JAVA
2302
0
    } catch (...) {
2303
0
      LOG_ERROR("Exception in WApplication::notify()");
2304
0
    }
2305
0
    return;
2306
0
  }
2307
2308
0
  if (event.impl_.function) {
2309
0
    try {
2310
0
      WT_CALL_FUNCTION(event.impl_.function);
2311
2312
0
      if (event.impl_.handler->request())
2313
0
        render(*event.impl_.handler);
2314
0
    } catch (std::exception& e) {
2315
0
      LOG_ERROR("Exception in WApplication::notify(): " << e.what());
2316
2317
#ifdef WT_TARGET_JAVA
2318
      e.printStackTrace();
2319
#endif // WT_TARGET_JAVA
2320
0
    } catch (...) {
2321
0
      LOG_ERROR("Exception in WApplication::notify()");
2322
0
    }
2323
0
    return;
2324
0
  }
2325
2326
0
  Handler& handler = *event.impl_.handler;
2327
2328
0
#ifndef WT_TARGET_JAVA
2329
0
  if (!app_->initialized_) {
2330
0
    app_->initialized_ = true;
2331
0
    app_->initialize();
2332
0
    if (!app_->internalPathValid_) {
2333
0
      WebResponse *response = handler.response();
2334
0
      if (response && response->responseType() == WebResponse::ResponseType::Page)
2335
0
        response->setStatus(404);
2336
0
    }
2337
0
  }
2338
0
#endif
2339
2340
0
  if (!handler.response())
2341
0
    return;
2342
2343
0
  WebRequest& request = *handler.request();
2344
0
  WebResponse& response = *handler.response();
2345
2346
0
  if (WebSession::Handler::instance() != &handler)
2347
    // We will want to set these right before doing anything !
2348
0
    WebSession::Handler::instance()->setRequest(&request, &response);
2349
2350
0
  if (event.impl_.renderOnly) {
2351
0
    render(handler);
2352
0
    return;
2353
0
  }
2354
2355
0
  const std::string *requestE = request.getParameter("request");
2356
2357
  /*
2358
   * Capture JavaScript error server-side.
2359
   */
2360
0
  if (requestE && *requestE == "jserror") {
2361
0
    const std::string *err = request.getParameter("err");
2362
0
    if (err) {
2363
0
      app_->handleJavaScriptError(*err);
2364
0
    } else {
2365
      // Forming a custom request with missing err parameter should not crash the server,
2366
      // but our JavaScript should not produce these requests.
2367
0
      LOG_ERROR("malformed jserror request: missing err parameter");
2368
0
      app_->handleJavaScriptError("unknown error");
2369
0
    }
2370
0
    renderer_.setJSSynced(false);
2371
0
    render(handler);
2372
0
    return;
2373
0
  }
2374
2375
0
  const std::string *pageIdE = request.getParameter("pageId");
2376
0
  if (pageIdE && *pageIdE != std::to_string(renderer_.pageId())) {
2377
0
    handler.response()->setContentType("text/javascript; charset=UTF-8");
2378
0
    handler.response()->out() << "{}";
2379
0
    handler.flushResponse();
2380
0
    return;
2381
0
  }
2382
2383
0
  switch (state_) {
2384
0
  case State::JustCreated:
2385
0
    render(handler);
2386
2387
0
    break;
2388
0
  case State::ExpectLoad:
2389
0
  case State::Loaded:
2390
0
  case State::Suspended:
2391
    /*
2392
     * Excluding resources here ?
2393
     */
2394
0
    if ((!requestE || (*requestE != "resource"))
2395
0
        && handler.response()->responseType() == WebResponse::ResponseType::Page) {
2396
      /*
2397
       * Prevent a session fixation attack and a session stealing attack:
2398
       * - user agent has changed: close the session
2399
       * - remote IP address changes: only allowed if the session cookie
2400
       *   is not empty and matches.
2401
       * - prevent attack on ajax sessions:
2402
       *   - use random initial ackUpdateId to prevent attacks on ajax sessions
2403
       *     (in the case somehow the session Id got stolen): this ackUpdateId
2404
       *     is not exposed in a referer
2405
       *   - tie script with unique id to page to prevent attack on a ajax
2406
       *     session: this scriptId is not exposed in a referer
2407
       *
2408
       * Note: this may interfere with the use-case for the undocumented
2409
       *       persistent session configuration option
2410
       */
2411
0
      if (!env_->agentIsIE())
2412
0
        if (str(handler.request()->headerValue("User-Agent"))
2413
0
            != env_->userAgent()) {
2414
0
          LOG_SECURE("change of user-agent not allowed.");
2415
0
          LOG_INFO("old user agent: " << env_->userAgent());
2416
0
          LOG_INFO("new user agent: "
2417
0
                   << str(handler.request()->headerValue("User-Agent")));
2418
0
          serveError(403, handler, "Forbidden");
2419
0
          return;
2420
0
        }
2421
2422
0
      std::string ca = handler.request()->clientAddress(controller_->configuration());
2423
2424
0
      if (ca != env_->clientAddress()) {
2425
0
        bool isInvalid = sessionIdCookie_.empty();
2426
2427
0
        if (!isInvalid) {
2428
0
          std::string cookie = str(request.headerValue("Cookie"));
2429
0
          if (cookie.find("Wt" + sessionIdCookie_) == std::string::npos)
2430
0
            isInvalid = true;
2431
0
        }
2432
2433
0
        if (isInvalid) {
2434
0
          LOG_SECURE("change of IP address (" << env_->clientAddress()
2435
0
                     << " -> " << ca << ") not allowed.");
2436
0
          serveError(403, handler, "Forbidden");
2437
0
          return;
2438
0
        }
2439
0
      }
2440
0
    }
2441
2442
0
    if (sessionIdCookieChanged_) {
2443
0
      std::string cookie = str(request.headerValue("Cookie"));
2444
0
      if (cookie.find("Wt" + sessionIdCookie_) == std::string::npos) {
2445
0
        sessionIdCookie_.clear();
2446
0
        LOG_INFO("session id cookie not working");
2447
0
      }
2448
2449
0
      sessionIdCookieChanged_ = false;
2450
0
    }
2451
2452
0
    if (handler.response()->responseType() == WebResponse::ResponseType::Script) {
2453
0
      const std::string *sidE = request.getParameter("sid");
2454
0
      if (!sidE || *sidE != std::to_string(renderer_.scriptId())) {
2455
0
        throw WException("Script id mismatch");
2456
0
      }
2457
2458
0
      if (!env_->ajax()) {
2459
0
        env_->enableAjax(request);
2460
0
        app_->enableAjax();
2461
0
        if (env_->internalPath().length() > 1)
2462
0
          changeInternalPath(env_->internalPath(), handler.response());
2463
0
      } else {
2464
0
        const std::string *hashE = request.getParameter("_");
2465
0
        if (hashE)
2466
0
          changeInternalPath(*hashE, handler.response());
2467
0
      }
2468
2469
0
      render(handler);
2470
0
    } else {
2471
      // a normal request to a loaded application
2472
0
      try {
2473
0
        if (request.postDataExceeded())
2474
0
          app_->requestTooLarge().emit(request.postDataExceeded());
2475
0
      } catch (std::exception& e) {
2476
0
        LOG_ERROR("Exception in WApplication::requestTooLarge" << e.what());
2477
0
        RETHROW(e);
2478
0
      } catch (...) {
2479
0
        LOG_ERROR("Exception in WApplication::requestTooLarge");
2480
0
        throw;
2481
0
      }
2482
2483
0
      const std::string *hashE = request.getParameter("_");
2484
2485
0
      WResource *resource = nullptr;
2486
0
      if (!requestE) {
2487
0
        if (!request.extraPathInfo().empty())
2488
0
          resource = app_->decodeExposedResource
2489
0
            ("/path/" + Utils::prepend(request.extraPathInfo().to_string(), '/'));
2490
2491
0
        if (!resource && hashE)
2492
0
          resource = app_->decodeExposedResource
2493
0
            ("/path/" + *hashE);
2494
0
      }
2495
2496
0
      const std::string *resourceE = request.getParameter("resource");
2497
0
      const std::string *signalE = getSignal(request, "");
2498
0
      const std::string *verE = request.getParameter("ver");
2499
2500
0
      if (signalE)
2501
0
        progressiveBoot_ = false;
2502
2503
0
      if (resource || (requestE && *requestE == "resource" && resourceE)) {
2504
0
        if (resourceE && *resourceE == "blank") {
2505
0
          handler.response()->setContentType("text/html");
2506
0
          handler.response()->out() <<
2507
0
            "<html><head><title>bhm</title></head>"
2508
0
            "<body> </body></html>";
2509
0
          handler.flushResponse();
2510
0
        } else {
2511
0
          if (!resource) {
2512
0
            unsigned long ver = 0;
2513
0
            try {
2514
0
              if (verE)
2515
0
                ver = Utils::stoul(*verE);
2516
0
            } catch (std::exception& e) {
2517
0
              ver = 0;
2518
0
            }
2519
0
            resource = app_->decodeExposedResource(*resourceE, ver);
2520
0
          }
2521
2522
0
          if (resource) {
2523
0
            try {
2524
0
#ifndef WT_TARGET_JAVA
2525
              // Requests to WebSocketResources need some special handling:
2526
              // after the handshake is done, the socket is transferred to
2527
              // the WWebSocketConnection for further communication
2528
0
              WWebSocketResource* wsResource = nullptr;
2529
0
              if (request.isWebSocketRequest()) {
2530
0
                wsResource = app_->findMatchingWebSocketResource(resource);
2531
0
                if (!wsResource) {
2532
0
                  LOG_ERROR("websocket: resource '" << *resourceE
2533
0
                      << "' is not a WWebSocketResource");
2534
0
                  handler.response()->setStatus(400);
2535
0
                  handler.response()->setContentType("text/html");
2536
0
                  handler.response()->out() <<
2537
0
                    "<html><body><h1>Not a websocket</h1></body></html>";
2538
0
                  handler.flushResponse();
2539
0
                  return;
2540
0
                } else if (!response.supportsTransferWebSocketResourceSocket()) {
2541
                  // this http frontend type does not work with WebSocketResources
2542
0
                  LOG_ERROR("websocket: websocket resources not supported by HTTP frontend");
2543
0
                  handler.response()->setStatus(500);
2544
0
                  handler.response()->setContentType("text/html");
2545
0
                  handler.response()->out() <<
2546
0
                    "<html><body><h1>WebSockets not supported</h1></body></html>";
2547
0
                  handler.flushResponse();
2548
0
                  return;
2549
0
                }
2550
0
              }
2551
0
#endif // WT_TARGET_JAVA
2552
0
              resource->handle(&request, &response);
2553
0
              handler.setRequest(nullptr, nullptr);
2554
0
#ifndef WT_TARGET_JAVA
2555
0
              if (wsResource) {
2556
                // note: when first written, status was always 101
2557
0
                if (response.status() == 101) {
2558
                  // status 101 -> send response and transfer socket
2559
0
                  request.setTransferWebSocketResourceSocketCallBack(std::bind(&WebSocketHandlerResource::moveSocket, wsResource->handleResource(), std::placeholders::_1, std::placeholders::_2));
2560
0
                }
2561
0
              }
2562
0
#endif // WT_TARGET_JAVA
2563
0
            } catch (std::exception& e) {
2564
0
              LOG_ERROR("Exception while streaming resource" << e.what());
2565
0
              RETHROW(e);
2566
0
            } catch (...) {
2567
0
              LOG_ERROR("Exception while streaming resource");
2568
0
              throw;
2569
0
            }
2570
0
          } else {
2571
0
            LOG_ERROR("decodeResource(): resource '" << *resourceE
2572
0
                      << "' not exposed");
2573
0
            handler.response()->setStatus(404);
2574
0
            handler.response()->setContentType("text/html");
2575
0
            handler.response()->out() <<
2576
0
              "<html><body><h1>Page not found.</h1></body></html>";
2577
0
            handler.flushResponse();
2578
0
          }
2579
0
        }
2580
0
      } else {
2581
0
        env_->updateUrlScheme(request);
2582
2583
0
        if (signalE) {
2584
          /*
2585
           * Check the ackIdE. This is required for a request carrying a signal.
2586
           */
2587
0
          const std::string *ackIdE = request.getParameter("ackId");
2588
2589
0
          bool invalidAckId = env_->ajax()
2590
0
            && !request.isWebSocketMessage();
2591
2592
0
          WebRenderer::AckState ackState = WebRenderer::CorrectAck;
2593
0
          if (invalidAckId && ackIdE) {
2594
0
            try {
2595
0
              ackState = renderer_.ackUpdate(static_cast<unsigned int>(Utils::stoul(*ackIdE)));
2596
0
              if (ackState != WebRenderer::BadAck)
2597
0
                invalidAckId = false;
2598
0
            } catch (const std::exception& e) {
2599
0
            }
2600
0
          }
2601
2602
0
           if (invalidAckId) {
2603
0
            if (!ackIdE)
2604
0
              LOG_SECURE("missing ackId");
2605
0
            else
2606
0
              LOG_SECURE("invalid ackId");
2607
0
            serveError(403, handler, "Forbidden");
2608
0
            return;
2609
0
          }
2610
2611
0
          if (*signalE == "poll" &&
2612
0
              ackState != WebRenderer::CorrectAck &&
2613
0
              renderer_.jsSynced()) {
2614
0
            LOG_DEBUG("Ignoring poll with incorrect ack -- was rescheduled in browser?");
2615
0
            handler.flushResponse();
2616
0
            return;
2617
0
          }
2618
2619
          /*
2620
           * In case we are not using websocket but long polling, the client
2621
           * aborts the previous poll request to indicate a client-side event.
2622
           *
2623
           * So we also discard the previous asyncResponse_ server-side.
2624
           * We don't do this if we have a websocket request -- it might be
2625
           * a race between the websocket being established and a poll
2626
           * request.
2627
           */
2628
0
          if (asyncResponse_) {
2629
0
            asyncResponse_->flush();
2630
0
            asyncResponse_ = nullptr;
2631
0
          }
2632
2633
0
          if (*signalE == "poll") {
2634
0
#ifdef WT_BOOST_THREADS
2635
            /*
2636
             * If we cannot do async I/O, we cannot suspend the current
2637
             * request and return. Thus we need to block the thread, waiting
2638
             * for a push update. We wait at most twice as long as the client
2639
             * will renew this poll connection.
2640
             */
2641
0
            if (!WebController::isAsyncSupported() && renderer_.jsSynced()) {
2642
0
              updatesPendingEvent_.notify_one();
2643
0
              if (!updatesPending_) {
2644
0
#ifndef WT_TARGET_JAVA
2645
0
                updatesPendingEvent_.wait(handler.lock());
2646
#else
2647
                try {
2648
                  updatesPendingEvent_.timed_wait
2649
                    (controller_->configuration().serverPushTimeout() * 2);
2650
                } catch (InterruptedException& e) { }
2651
#endif // WT_TARGET_JAVA
2652
0
              }
2653
0
              if (!updatesPending_) {
2654
0
                handler.flushResponse();
2655
0
                return;
2656
0
              }
2657
0
            }
2658
0
#endif // WT_BOOST_THREADS
2659
2660
            // LOG_DEBUG("poll: " << updatesPending_ << ", " << (asyncResponse_ ? "async" : "no async"));
2661
0
            if (!updatesPending_ && renderer_.jsSynced()) {
2662
              /*
2663
               * If we are ignoring many poll requests (because we are
2664
               * assuming to have a websocket), we will need to assume
2665
               * the web socket isn't working properly.
2666
               */
2667
0
              if (!webSocket_ || (pollRequestsIgnored_ == 2)) {
2668
0
                if (webSocket_) {
2669
0
                  LOG_INFO("discarding broken websocket");
2670
0
                  webSocket_->flush();
2671
0
                  webSocket_ = nullptr;
2672
0
                }
2673
2674
0
                pollRequestsIgnored_ = 0;
2675
0
                asyncResponse_ = handler.response();
2676
0
                handler.setRequest(nullptr, nullptr);
2677
0
              } else {
2678
0
                ++pollRequestsIgnored_;
2679
0
                LOG_DEBUG("ignored poll request (#" << pollRequestsIgnored_
2680
0
                          << ")");
2681
0
              }
2682
0
            } else
2683
0
              pollRequestsIgnored_ = 0;
2684
0
          } else {
2685
0
#ifdef WT_BOOST_THREADS
2686
0
            if (!WebController::isAsyncSupported()) {
2687
0
              updatesPending_ = false;
2688
0
              updatesPendingEvent_.notify_one();
2689
0
            }
2690
0
#endif
2691
0
          }
2692
2693
0
          if (handler.request()) {
2694
0
            LOG_DEBUG("signal: " << *signalE);
2695
2696
            /*
2697
             * Special signal values:
2698
             * 'poll' : long poll
2699
             * 'none' : no event, but perhaps a synchronization
2700
             * 'load' : load invisible content
2701
             * 'keepAlive' : no event, keep alive
2702
             */
2703
2704
0
            try {
2705
0
              handler.nextSignal = -1;
2706
0
              notifySignal(event);
2707
0
            } catch (std::exception& e) {
2708
0
              LOG_ERROR("error during event handling: " << e.what());
2709
0
              RETHROW(e);
2710
0
            } catch (...) {
2711
0
              LOG_ERROR("error during event handling");
2712
0
              throw;
2713
0
            }
2714
0
          }
2715
0
        }
2716
2717
0
        if (handler.response()
2718
0
            && handler.response()->responseType() ==
2719
0
               WebResponse::ResponseType::Page
2720
0
            && (!env_->ajax() ||
2721
0
                suspended() ||
2722
0
                !controller_->configuration().reloadIsNewSession())) {
2723
0
          app_->domRoot()->setRendered(false);
2724
2725
0
          env_->parameters_ = handler.request()->getParameterMap();
2726
2727
0
          if (hashE)
2728
0
            changeInternalPath(*hashE, handler.response());
2729
0
          else if (!handler.request()->extraPathInfo().empty()) {
2730
0
            changeInternalPath(handler.request()->extraPathInfo().to_string(),
2731
0
                               handler.response());
2732
0
          } else
2733
0
            changeInternalPath("", handler.response());
2734
0
        }
2735
2736
0
        if (!signalE) {
2737
0
          if (type() == EntryPointType::WidgetSet) {
2738
0
            LOG_ERROR("bogus request: missing signal, discarding");
2739
0
            handler.flushResponse();
2740
0
            return;
2741
0
          }
2742
2743
0
          LOG_INFO("refreshing session");
2744
2745
0
          flushBootStyleResponse();
2746
2747
0
          if (handler.request()) {
2748
0
            env_->parameters_ = handler.request()->getParameterMap();
2749
0
            env_->updateHostName(*handler.request());
2750
0
          }
2751
0
            app_->refresh();
2752
0
        }
2753
2754
0
        if (handler.response() && !recursiveEventHandler_)
2755
0
          render(handler);
2756
0
      }
2757
0
    }
2758
0
  case State::BeingDestroyed:
2759
0
  case State::Dead:
2760
0
    break;
2761
0
  }
2762
0
}
2763
2764
void WebSession::changeInternalPath(const std::string& path,
2765
                                    WebResponse *response)
2766
0
{
2767
0
  if (!app_->internalPathIsChanged_)
2768
0
    if (!app_->changedInternalPath(path))
2769
0
      if (response->responseType() == WebResponse::ResponseType::Page)
2770
0
        response->setStatus(404);
2771
0
}
2772
2773
EventType WebSession::getEventType(const WEvent& event) const
2774
0
{
2775
0
  if (event.impl_.handler == nullptr)
2776
0
    return EventType::Other;
2777
2778
0
  Handler& handler = *event.impl_.handler;
2779
2780
0
#ifndef WT_TARGET_JAVA
2781
0
  if (event.impl_.function)
2782
0
    return EventType::Other;
2783
0
#endif // WT_TARGET_JAVA
2784
2785
0
  WebRequest& request = *handler.request();
2786
2787
0
  if (event.impl_.renderOnly || !handler.request())
2788
0
    return EventType::Other;
2789
2790
0
  const std::string *pageIdE = handler.request()->getParameter("pageId");
2791
0
  if (pageIdE && *pageIdE != std::to_string(renderer_.pageId()))
2792
0
    return EventType::Other;
2793
2794
0
  switch (state_) {
2795
0
  case State::ExpectLoad:
2796
0
  case State::Loaded:
2797
0
  case State::Suspended:
2798
0
    if (handler.response()->responseType() == WebResponse::ResponseType::Script) {
2799
0
      return EventType::Other;
2800
0
    } else if (resourceRequest(request)) {
2801
0
      return EventType::Resource;
2802
0
    } else {
2803
0
      const std::string *signalE = getSignal(request, "");
2804
2805
0
      if (signalE) {
2806
0
        if (*signalE == "none" || *signalE == "load" ||
2807
0
            *signalE == "hash" || *signalE == "poll" ||
2808
0
            *signalE == "keepAlive")
2809
0
          return EventType::Other;
2810
0
        else {
2811
0
          std::vector<SignalProcessAction> signalActions
2812
0
            = getSignalProcessingOrder(event);
2813
2814
0
          unsigned timerSignals = 0;
2815
2816
0
          for (unsigned i = 0; i < signalActions.size(); ++i) {
2817
0
            SignalProcessAction signalI = signalActions[i];
2818
2819
0
            if (!signalI.handleSignal) {
2820
0
              continue; // signal has already been trough this loop
2821
0
            }
2822
2823
0
            std::string se = signalI.number > 0
2824
0
              ? 'e' + std::to_string(signalI.number) : std::string();
2825
0
            const std::string *s = getSignal(request, se);
2826
2827
0
            if (!s)
2828
0
              break;
2829
0
            else if (*signalE == "user")
2830
0
              return EventType::User;
2831
0
            else {
2832
0
              EventSignalBase* esb = decodeSignal(*s, false);
2833
2834
0
              if (!esb)
2835
0
                continue;
2836
2837
0
              WTimerWidget* t = dynamic_cast<WTimerWidget*>(esb->owner());
2838
0
              if (t)
2839
0
                ++timerSignals;
2840
0
              else
2841
0
                return EventType::User;
2842
0
            }
2843
0
          }
2844
2845
0
          if (timerSignals)
2846
0
            return EventType::Timer;
2847
0
        }
2848
0
      } else
2849
0
        return EventType::Other;
2850
0
    }
2851
    /*
2852
     * If we don't recognise the event type it is defaulted to Other
2853
     */
2854
0
    WT_FALLTHROUGH
2855
0
  default:
2856
0
    return EventType::Other;
2857
0
  }
2858
0
}
2859
2860
bool WebSession::resourceRequest(const WebRequest& request) const
2861
0
{
2862
0
  if (state_ == State::ExpectLoad ||
2863
0
      state_ == State::Loaded ||
2864
0
      state_ == State::Suspended) {
2865
0
    const std::string *requestE = request.getParameter("request");
2866
0
    const std::string *resourceE = request.getParameter("resource");
2867
0
    if (requestE && *requestE == "resource" && resourceE) {
2868
0
      return true;
2869
0
    } else if (!requestE && app_) { // check if resource is deployed on internal path
2870
0
      if (!request.extraPathInfo().empty() &&
2871
0
          app_->decodeExposedResource("/path/" + Utils::prepend(request.extraPathInfo().to_string(), '/')) != nullptr)
2872
0
        return true;
2873
2874
0
      const std::string *hashE = request.getParameter("_");
2875
0
      if (hashE &&
2876
0
          app_->decodeExposedResource("/path/" + *hashE) != nullptr)
2877
0
        return true;
2878
0
    }
2879
0
  }
2880
2881
0
  return false;
2882
0
}
2883
2884
void WebSession::render(Handler& handler)
2885
0
{
2886
0
  LOG_DEBUG("render()");
2887
  /*
2888
   * In any case, render() will flush the response, even if an error
2889
   * occurred. Since we are already rendering the response, we can no longer
2890
   * show a nice error message.
2891
   */
2892
2893
0
  try {
2894
0
    if (!env_->ajax())
2895
0
      try {
2896
0
        checkTimers();
2897
0
      } catch (std::exception& e) {
2898
0
        LOG_ERROR("Exception while triggering timers" << e.what());
2899
0
        RETHROW(e);
2900
0
      } catch (...) {
2901
0
        LOG_ERROR("Exception while triggering timers");
2902
0
        throw;
2903
0
      }
2904
2905
0
    if (app_ && app_->hasQuit())
2906
0
      kill();
2907
2908
0
    if (handler.response()) { // a recursive eventloop may remove it in kill()
2909
0
      updatesPending_ = false;
2910
0
      serveResponse(handler);
2911
0
    }
2912
2913
0
  } catch (std::exception& e) {
2914
0
    handler.flushResponse();
2915
2916
0
    RETHROW(e);
2917
0
  } catch (...) {
2918
0
    handler.flushResponse();
2919
2920
0
    throw;
2921
0
  }
2922
0
}
2923
2924
void WebSession::serveError(int status, Handler& handler, const std::string& e)
2925
0
{
2926
0
  renderer_.serveError(status, *handler.response(), e);
2927
0
  handler.flushResponse();
2928
0
}
2929
2930
void WebSession::serveResponse(Handler& handler)
2931
0
{
2932
0
  if (handler.response()->responseType() == WebResponse::ResponseType::Page) {
2933
0
    pagePathInfo_ = handler.request()->extraPathInfo().to_string();
2934
0
    const std::string *wtdE = handler.request()->getParameter("wtd");
2935
0
    if (wtdE && *wtdE == sessionId_)
2936
0
      sessionIdInUrl_ = true;
2937
0
    else
2938
0
      sessionIdInUrl_ = false;
2939
0
  }
2940
2941
  /*
2942
   * If the request is a web socket message, then we should not actually
2943
   * render -- there may be more messages following.
2944
   */
2945
0
  if (!handler.request()->isWebSocketMessage()) {
2946
    /*
2947
     * In any case, flush the style request when we are serving a new
2948
     * page (without Ajax) or the main script (with Ajax).
2949
     */
2950
0
    if (handler.response()->responseType() == WebResponse::ResponseType::Script) {
2951
0
#ifndef WT_TARGET_JAVA
2952
0
      if (bootStyleResponse_) {
2953
0
        renderer_.serveLinkedCss(*bootStyleResponse_);
2954
0
        flushBootStyleResponse();
2955
0
      }
2956
#else
2957
      /*
2958
       * Preempt the thread waiting for the bootstyle to be ready.
2959
       * Otherwise there is a reflow as the page already renders
2960
       * without having the CSS (Duh?)
2961
       */
2962
      mutex_.unlock();
2963
      try {
2964
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
2965
      } catch (InterruptedException& e) { }
2966
      mutex_.lock();
2967
#endif
2968
0
    }
2969
2970
0
    renderer_.serveResponse(*handler.response());
2971
0
  }
2972
2973
0
  handler.flushResponse();
2974
0
}
2975
2976
void WebSession::propagateFormValues(const WEvent& e, const std::string& se, const SignalProcessAction& spa)
2977
0
{
2978
0
  const WebRequest& request = *e.impl_.handler->request();
2979
2980
0
  renderer_.updateFormObjectsList(app_);
2981
0
  WebRenderer::FormObjectsMap formObjects = renderer_.formObjects();
2982
2983
0
  const std::string *focus = request.getParameter(se + "focus");
2984
0
  if (focus) {
2985
0
    int selectionStart = -1, selectionEnd = -1;
2986
0
    try {
2987
0
      const std::string *selStart = request.getParameter(se + "selstart");
2988
0
      if (selStart)
2989
0
        selectionStart = Utils::stoi(*selStart);
2990
2991
0
      const std::string *selEnd = request.getParameter(se + "selend");
2992
0
      if (selEnd)
2993
0
        selectionEnd = Utils::stoi(*selEnd);
2994
0
    } catch (std::exception& ee) {
2995
0
      LOG_ERROR("Could not lexical cast selection range");
2996
0
    }
2997
2998
0
    app_->setFocus(*focus, selectionStart, selectionEnd);
2999
0
  } else
3000
0
    app_->setFocus(nullptr, -1, -1);
3001
3002
0
  for (WebRenderer::FormObjectsMap::const_iterator i = formObjects.begin();
3003
0
       i != formObjects.end(); ++i) {
3004
0
    std::string formName = i->first;
3005
0
    WObject *obj = i->second;
3006
3007
0
    if (!request.postDataExceeded()) {
3008
0
      WWidget *w = dynamic_cast<WWidget*>(obj);
3009
0
      WObject::FormData data = getFormData(request, formName, spa);
3010
      // FIXME: reenable isVisible() check once we've fixed all of the regressions
3011
0
      if (!spa.handleSignal ||
3012
0
          (w && (!w->isEnabled()/* || !w->isVisible()*/))) {
3013
        /* Do not update form data of a disabled or invisible widget or
3014
         * if it was already handled.
3015
         */
3016
0
        continue;
3017
0
      }
3018
0
      obj->setFormData(data);
3019
0
    } else
3020
0
      obj->setRequestTooLarge(request.postDataExceeded());
3021
0
  }
3022
0
}
3023
3024
const Http::ParameterValues& WebSession::getFormParamValues(const WebRequest& request,
3025
                                                            const std::string& name,
3026
                                                            const SignalProcessAction& spa)
3027
0
{
3028
0
  Configuration& conf = controller_->configuration();
3029
3030
0
  int signalNumber = spa.number;
3031
0
  const Http::ParameterValues* requestParam;
3032
3033
  /* If the signals are not processed in the order they are received,
3034
   * we also need to check if the form data was not changed in a
3035
   * previous signal that has not been processed yet. Since the cache
3036
   * is not yet updated.
3037
   */
3038
0
  do {
3039
0
    std::string se = signalNumber > 0 ? 'e' + std::to_string(signalNumber) : std::string();
3040
0
    requestParam = &(request.getParameterValues(se + name));
3041
0
    signalNumber--;
3042
0
  } while (conf.cacheFormData() &&
3043
0
           !spa.updateCache &&
3044
0
           Utils::isEmpty(*requestParam) &&
3045
0
           signalNumber >= 0);
3046
3047
0
  if (!Utils::isEmpty(*requestParam)) {
3048
0
    if ((*requestParam)[0] == "Wt-null") {
3049
0
      requestParam = &WebRequest::emptyValues_;
3050
0
    }
3051
3052
0
    if (conf.cacheFormData() && spa.updateCache) {
3053
0
      formDataCache_[name] = *requestParam;
3054
0
    }
3055
3056
0
    return *requestParam;
3057
0
  } else if (conf.cacheFormData()) {
3058
0
    auto it = formDataCache_.find(name);
3059
0
    if (it != formDataCache_.end()) {
3060
0
      return it->second;
3061
0
    }
3062
0
  }
3063
0
  return *requestParam;
3064
0
}
3065
3066
WObject::FormData WebSession::getFormData(const WebRequest& request,
3067
                                          const std::string& name,
3068
                                          const SignalProcessAction& spa)
3069
0
{
3070
0
  std::vector<Http::UploadedFile> files;
3071
0
  Utils::find(request.uploadedFiles(), name, files);
3072
3073
0
  const Http::ParameterValues& paramValues = getFormParamValues(request, name, spa);
3074
3075
0
  return WObject::FormData(paramValues, files);
3076
0
}
3077
3078
bool WebSession::inFormDataCache(const std::string& name) const
3079
0
{
3080
0
  return formDataCache_.find(name) != formDataCache_.end();
3081
0
}
3082
3083
void WebSession::pruneFormDataCache()
3084
0
{
3085
0
  for (auto it = formDataCache_.begin(); it != formDataCache_.end();)
3086
0
  {
3087
0
    if (renderer_.currentFormObjects_.find(it->first) == renderer_.currentFormObjects_.end())
3088
0
    {
3089
0
      Utils::eraseAndNext(formDataCache_, it);
3090
0
    } else {
3091
0
      ++it;
3092
0
    }
3093
0
  }
3094
0
}
3095
3096
std::vector<WebSession::SignalProcessAction>
3097
WebSession::getSignalProcessingOrder(const WEvent& e) const
3098
0
{
3099
  // Rush 'onChange' events. Reason: if a user edits a text area and
3100
  // a subsequent click on another element deletes the text area, we
3101
  // have seen situations (at least on firefox) where the clicked event
3102
  // is processed before the changed event, causing the changed event
3103
  // to fail because the event target was deleted.
3104
0
  WebSession::Handler& handler = *e.impl_.handler;
3105
0
  Configuration& conf = controller_->configuration();
3106
3107
0
  std::vector<SignalProcessAction> highPriority;
3108
0
  std::vector<SignalProcessAction> normalPriority;
3109
3110
0
  for (unsigned i = 0;; ++i) {
3111
0
    const WebRequest& request = *handler.request();
3112
3113
0
    std::string se = i > 0 ? 'e' + std::to_string(i) : std::string();
3114
0
    const std::string *signalE = getSignal(request, se);
3115
0
    if (!signalE)
3116
0
      break;
3117
0
    if (*signalE != "user" &&
3118
0
        *signalE != "hash" &&
3119
0
        *signalE != "none" &&
3120
0
        *signalE != "poll" &&
3121
0
        *signalE != "load" &&
3122
0
        *signalE != "keepAlive") {
3123
0
      EventSignalBase *signal = decodeSignal(*signalE, true);
3124
0
      if (!signal) {
3125
        // Signal was not exposed, do nothing
3126
0
      } else if (signal->name() == WFormWidget::CHANGE_SIGNAL) {
3127
        // compare by pointer in the condition above is ok
3128
0
        if (highPriority.size() != i && conf.cacheFormData()) {
3129
          // We need to process the signal but update the cache later.
3130
0
          highPriority.push_back(SignalProcessAction(i, false, true));
3131
0
          normalPriority.push_back(SignalProcessAction(i, true, false));
3132
0
        } else {
3133
0
          highPriority.push_back(SignalProcessAction(i, true, true));
3134
0
        }
3135
0
      } else {
3136
0
        normalPriority.push_back(SignalProcessAction(i, true, true));
3137
0
      }
3138
0
    } else {
3139
0
      normalPriority.push_back(SignalProcessAction(i, true, true));
3140
0
    }
3141
0
  }
3142
3143
0
  Utils::insert(highPriority, normalPriority);
3144
3145
0
  return highPriority;
3146
0
}
3147
3148
void WebSession::notifySignal(const WEvent& e)
3149
0
{
3150
0
  WebSession::Handler& handler = *e.impl_.handler;
3151
3152
  // Reorder signals, as browsers sometimes generate them in a strange order
3153
0
  if (handler.nextSignal == -1) {
3154
0
    handler.signalActions = getSignalProcessingOrder(e);
3155
0
    handler.nextSignal = 0;
3156
0
  }
3157
3158
0
  for (unsigned i = handler.nextSignal; i < handler.signalActions.size(); ++i) {
3159
0
    if (!handler.request())
3160
0
      return;
3161
3162
0
    const WebRequest& request = *handler.request();
3163
3164
0
    SignalProcessAction signalI = handler.signalActions[i];
3165
0
    std::string se = signalI.number > 0
3166
0
      ? 'e' + std::to_string(signalI.number) : std::string();
3167
0
    const std::string *signalE = getSignal(request, se);
3168
3169
0
    if (!signalE)
3170
0
      return;
3171
3172
0
    LOG_DEBUG("signal: " << *signalE);
3173
3174
0
    if (type() != EntryPointType::WidgetSet ||
3175
0
        (*signalE != "none" && *signalE != "load"))
3176
0
      renderer_.setRendered(true);
3177
3178
0
    if (*signalE == "none" || *signalE == "load") {
3179
0
      if (*signalE == "load") {
3180
0
        if (!renderer_.checkResponsePuzzle(request))
3181
0
          app_->quit();
3182
0
        else
3183
0
          setLoaded();
3184
0
      }
3185
3186
      // We will want invisible changes now too.
3187
0
      renderer_.setVisibleOnly(false);
3188
0
    } else if (*signalE == "keepAlive") {
3189
      // Do nothing
3190
0
    } else if (*signalE != "poll") {
3191
0
      propagateFormValues(e, se, signalI);
3192
3193
      // Save pending changes (e.g. from resource completion)
3194
      // This is needed because we will discard changes from learned
3195
      // signals (see below) and thus need to start with a clean slate
3196
0
      bool discardStateless = !request.isWebSocketMessage() && i == 0;
3197
0
      if (discardStateless)
3198
0
        renderer_.saveChanges();
3199
3200
0
      handler.nextSignal = i + 1;
3201
3202
0
      if (*signalE == "hash") {
3203
0
        const std::string *hashE = request.getParameter(se + "_");
3204
0
        if (hashE) {
3205
0
          changeInternalPath(*hashE, handler.response());
3206
0
          app_->doJavaScript(WT_CLASS ".scrollHistory();");
3207
0
        } else
3208
0
          changeInternalPath("", handler.response());
3209
0
      } else {
3210
0
        for (unsigned k = 0; k < 3; ++k) {
3211
0
          SignalKind kind = (SignalKind)k;
3212
3213
0
          if (kind == SignalKind::AutoLearnStateless && request.postDataExceeded())
3214
0
            break;
3215
3216
0
          EventSignalBase *s;
3217
0
          if (*signalE == "user") {
3218
0
            const std::string *idE = request.getParameter(se + "id");
3219
0
            const std::string *nameE = request.getParameter(se + "name");
3220
3221
0
            if (!idE || !nameE)
3222
0
              break;
3223
3224
0
            s = decodeSignal(*idE, *nameE, k == 0);
3225
0
          } else
3226
0
            s = decodeSignal(*signalE, k == 0);
3227
3228
0
          processSignal(s, se, request, kind);
3229
3230
0
          if (kind == SignalKind::LearnedStateless && discardStateless)
3231
0
            renderer_.discardChanges();
3232
0
        }
3233
0
      }
3234
0
    }
3235
0
  }
3236
3237
0
  app_->justRemovedSignals().clear();
3238
0
}
3239
3240
void WebSession::processSignal(EventSignalBase *s, const std::string& se,
3241
                               const WebRequest& request, SignalKind kind)
3242
0
{
3243
0
  if (!s)
3244
0
    return;
3245
3246
0
  switch (kind) {
3247
0
  case SignalKind::LearnedStateless:
3248
0
    s->processLearnedStateless();
3249
0
    break;
3250
0
  case SignalKind::AutoLearnStateless:
3251
0
    s->processAutoLearnStateless(&renderer_);
3252
0
    break;
3253
0
  case SignalKind::Dynamic:
3254
0
    JavaScriptEvent jsEvent;
3255
0
    jsEvent.get(request, se);
3256
0
    s->processDynamic(jsEvent);
3257
3258
    // ! handler.request() may be 0 now, if there was a
3259
    // ! recursive call.
3260
    // ! what with other slots triggered after the one that
3261
    // ! did the recursive call ? That's very bad ??
3262
0
  }
3263
0
}
3264
3265
void WebSession::setPagePathInfo(const std::string& path)
3266
0
{
3267
0
  if (!useUglyInternalPaths())
3268
0
    pagePathInfo_ = path;
3269
0
}
3270
3271
bool WebSession::useUrlRewriting()
3272
0
{
3273
0
  Configuration& conf = controller_->configuration();
3274
0
  return !(conf.sessionTracking() == Configuration::CookiesURL &&
3275
0
           env_->supportsCookies());
3276
0
}
3277
3278
void WebSession::setMultiSessionId(const std::string &multiSessionId)
3279
0
{
3280
0
  multiSessionId_ = multiSessionId;
3281
0
}
3282
3283
#ifndef WT_TARGET_JAVA
3284
void WebSession::generateNewSessionId()
3285
0
{
3286
0
  if (!renderer_.isRendered())
3287
0
    return;
3288
3289
0
  std::string oldId = sessionId_;
3290
0
  sessionId_ = controller_->generateNewSessionId(shared_from_this());
3291
0
  sessionIdChanged_ = true;
3292
3293
0
  LOG_INFO("new session id for " << oldId);
3294
3295
0
  if (!useUrlRewriting()) {
3296
0
    Http::Cookie cookie(env_->deploymentPath(), sessionId_);
3297
0
    cookie.setSecure(env_->urlScheme() == "https");
3298
0
#ifndef WT_TARGET_JAVA
3299
0
    cookie.setSameSite(Http::Cookie::SameSite::Strict);
3300
#else
3301
    cookie.setHttpOnly(true);
3302
#endif
3303
0
    renderer().setCookie(cookie);
3304
0
  }
3305
3306
0
  if (controller_->configuration().sessionIdCookie()) {
3307
0
    sessionIdCookie_ = WRandom::generateId();
3308
0
    sessionIdCookieChanged_ = true;
3309
0
    Http::Cookie cookie("Wt" + sessionIdCookie_, "1");
3310
0
    cookie.setSecure(env_->urlScheme() == "https");
3311
0
    renderer().setCookie(cookie);
3312
0
  }
3313
3314
0
  if (controller_->server()->dedicatedSessionProcess()) {
3315
0
    controller_->server()->updateProcessSessionId(sessionId_);
3316
0
  }
3317
0
}
3318
#endif // WT_TARGET_JAVA
3319
3320
#ifndef WT_TARGET_JAVA
3321
void WebSession::setDocRoot(const std::string &docRoot)
3322
0
{
3323
0
  docRoot_ = docRoot;
3324
0
}
3325
#endif // WT_TARGET_JAVA
3326
3327
}