Coverage Report

Created: 2026-06-30 06:18

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