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