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