cutelyst  4.9.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
session.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "session_p.h"
6 #include "sessionstorefile.h"
7 #include "utils.h"
8 
9 #include <Cutelyst/Application>
10 #include <Cutelyst/Context>
11 #include <Cutelyst/Engine>
12 #include <Cutelyst/Response>
13 
14 #include <QCoreApplication>
15 #include <QHostAddress>
16 #include <QLoggingCategory>
17 #include <QUuid>
18 
19 using namespace Cutelyst;
20 using namespace Qt::Literals::StringLiterals;
21 
22 Q_LOGGING_CATEGORY(C_SESSION, "cutelyst.plugin.session", QtWarningMsg)
23 
24 #define SESSION_VALUES QStringLiteral("_c_session_values")
25 #define SESSION_EXPIRES QStringLiteral("_c_session_expires")
26 #define SESSION_TRIED_LOADING_EXPIRES QStringLiteral("_c_session_tried_loading_expires")
27 #define SESSION_EXTENDED_EXPIRES QStringLiteral("_c_session_extended_expires")
28 #define SESSION_UPDATED QStringLiteral("_c_session_updated")
29 #define SESSION_ID QStringLiteral("_c_session_id")
30 #define SESSION_TRIED_LOADING_ID QStringLiteral("_c_session_tried_loading_id")
31 #define SESSION_DELETED_ID QStringLiteral("_c_session_deleted_id")
32 #define SESSION_DELETE_REASON QStringLiteral("_c_session_delete_reason")
33 
34 static thread_local Session *m_instance = nullptr;
35 
37  : Plugin(parent)
38  , d_ptr(new SessionPrivate(this))
39 {
40 }
41 
42 Session::Session(Cutelyst::Application *parent, const QVariantMap &defaultConfig)
43  : Plugin(parent)
44  , d_ptr(new SessionPrivate(this))
45 {
46  d_ptr->defaultConfig = defaultConfig;
47 }
48 
50 {
51  delete d_ptr;
52 }
53 
55 {
56  Q_D(Session);
57  d->sessionName = QCoreApplication::applicationName().toLatin1() + "_session";
58 
59  d->loadedConfig = app->engine()->config(u"Cutelyst_Session_Plugin"_s);
60  d->sessionExpires = std::chrono::duration_cast<std::chrono::seconds>(
61  Utils::durationFromString(d->config(u"expires"_s, 7200).toString()))
62  .count();
63  d->expiryThreshold = d->config(u"expiry_threshold"_s, 0).toLongLong();
64  d->verifyAddress = d->config(u"verify_address"_s, false).toBool();
65  d->verifyUserAgent = d->config(u"verify_user_agent"_s, false).toBool();
66  d->cookieHttpOnly = d->config(u"cookie_http_only"_s, true).toBool();
67  d->cookieSecure = d->config(u"cookie_secure"_s, false).toBool();
68 
69  const QString _sameSite = d->config(u"cookie_same_site"_s, u"strict"_s).toString();
70  if (_sameSite.compare(u"default", Qt::CaseInsensitive) == 0) {
71  d->cookieSameSite = QNetworkCookie::SameSite::Default;
72  } else if (_sameSite.compare(u"none", Qt::CaseInsensitive) == 0) {
73  d->cookieSameSite = QNetworkCookie::SameSite::None;
74  } else if (_sameSite.compare(u"lax", Qt::CaseInsensitive) == 0) {
75  d->cookieSameSite = QNetworkCookie::SameSite::Lax;
76  } else {
77  d->cookieSameSite = QNetworkCookie::SameSite::Strict;
78  }
79 
80  connect(app, &Application::afterDispatch, this, &SessionPrivate::_q_saveSession);
81  connect(app, &Application::postForked, this, [this] { m_instance = this; });
82 
83  if (!d->store) {
84  d->store = std::make_unique<SessionStoreFile>(this);
85  }
86 
87  return true;
88 }
89 
90 void Session::setStorage(std::unique_ptr<Cutelyst::SessionStore> store)
91 {
92  Q_D(Session);
93  Q_ASSERT_X(d->store, "Cutelyst::Session::setStorage", "Session Storage is alread defined");
94  store->setParent(this);
95  d->store = std::move(store);
96 }
97 
99 {
100  Q_D(const Session);
101  return d->store.get();
102 }
103 
105 {
106  QByteArray ret;
107  const QVariant sid = c->stash(SESSION_ID);
108  if (sid.isNull()) {
109  if (Q_UNLIKELY(!m_instance)) {
110  qCCritical(C_SESSION) << "Session plugin not registered";
111  return ret;
112  }
113 
114  ret = SessionPrivate::loadSessionId(c, m_instance->d_ptr->sessionName);
115  } else {
116  ret = sid.toByteArray();
117  }
118 
119  return ret;
120 }
121 
123 {
124  QVariant expires = c->stash(SESSION_EXTENDED_EXPIRES);
125  if (!expires.isNull()) {
126  return expires.toULongLong();
127  }
128 
129  if (Q_UNLIKELY(!m_instance)) {
130  qCCritical(C_SESSION) << "Session plugin not registered";
131  return 0;
132  }
133 
134  expires = SessionPrivate::loadSessionExpires(m_instance, c, id(c));
135  if (!expires.isNull()) {
136  return quint64(SessionPrivate::extendSessionExpires(m_instance, c, expires.toLongLong()));
137  }
138 
139  return 0;
140 }
141 
142 void Session::changeExpires(Context *c, quint64 expires)
143 {
144  const QByteArray sid = Session::id(c);
145  const qint64 timeExp = QDateTime::currentSecsSinceEpoch() + qint64(expires);
146 
147  if (Q_UNLIKELY(!m_instance)) {
148  qCCritical(C_SESSION) << "Session plugin not registered";
149  return;
150  }
151 
152  m_instance->d_ptr->store->storeSessionData(c, sid, u"expires"_s, timeExp);
153 }
154 
155 void Session::deleteSession(Context *c, const QString &reason)
156 {
157  if (Q_UNLIKELY(!m_instance)) {
158  qCCritical(C_SESSION) << "Session plugin not registered";
159  return;
160  }
161  SessionPrivate::deleteSession(m_instance, c, reason);
162 }
163 
165 {
166  return c->stash(SESSION_DELETE_REASON).toString();
167 }
168 
169 QVariant Session::value(Cutelyst::Context *c, const QString &key, const QVariant &defaultValue)
170 {
171  QVariant ret = defaultValue;
172  QVariant session = c->stash(SESSION_VALUES);
173  if (session.isNull()) {
174  session = SessionPrivate::loadSession(c);
175  }
176 
177  if (!session.isNull()) {
178  ret = session.toHash().value(key, defaultValue);
179  }
180 
181  return ret;
182 }
183 
184 void Session::setValue(Cutelyst::Context *c, const QString &key, const QVariant &value)
185 {
186  QVariant session = c->stash(SESSION_VALUES);
187  if (session.isNull()) {
188  session = SessionPrivate::loadSession(c);
189  if (session.isNull()) {
190  if (Q_UNLIKELY(!m_instance)) {
191  qCCritical(C_SESSION) << "Session plugin not registered";
192  return;
193  }
194 
195  SessionPrivate::createSessionIdIfNeeded(
196  m_instance, c, m_instance->d_ptr->sessionExpires);
197  session = SessionPrivate::initializeSessionData(m_instance, c);
198  }
199  }
200 
201  QVariantHash data = session.toHash();
202  data.insert(key, value);
203 
204  c->setStash(SESSION_VALUES, data);
205  c->setStash(SESSION_UPDATED, true);
206 }
207 
209 {
210  QVariant session = c->stash(SESSION_VALUES);
211  if (session.isNull()) {
212  session = SessionPrivate::loadSession(c);
213  if (session.isNull()) {
214  if (Q_UNLIKELY(!m_instance)) {
215  qCCritical(C_SESSION) << "Session plugin not registered";
216  return;
217  }
218 
219  SessionPrivate::createSessionIdIfNeeded(
220  m_instance, c, m_instance->d_ptr->sessionExpires);
221  session = SessionPrivate::initializeSessionData(m_instance, c);
222  }
223  }
224 
225  QVariantHash data = session.toHash();
226  data.remove(key);
227 
228  c->setStash(SESSION_VALUES, data);
229  c->setStash(SESSION_UPDATED, true);
230 }
231 
233 {
234  QVariant session = c->stash(SESSION_VALUES);
235  if (session.isNull()) {
236  session = SessionPrivate::loadSession(c);
237  if (session.isNull()) {
238  if (Q_UNLIKELY(!m_instance)) {
239  qCCritical(C_SESSION) << "Session plugin not registered";
240  return;
241  }
242 
243  SessionPrivate::createSessionIdIfNeeded(
244  m_instance, c, m_instance->d_ptr->sessionExpires);
245  session = SessionPrivate::initializeSessionData(m_instance, c);
246  }
247  }
248 
249  QVariantHash data = session.toHash();
250  for (const QString &key : keys) {
251  data.remove(key);
252  }
253 
254  c->setStash(SESSION_VALUES, data);
255  c->setStash(SESSION_UPDATED, true);
256 }
257 
259 {
260  return !SessionPrivate::loadSession(c).isNull();
261 }
262 
263 QByteArray SessionPrivate::generateSessionId()
264 {
265  return QUuid::createUuid().toRfc4122().toHex();
266 }
267 
268 QByteArray SessionPrivate::loadSessionId(Context *c, const QByteArray &sessionName)
269 {
270  QByteArray ret;
271  if (!c->stash(SESSION_TRIED_LOADING_ID).isNull()) {
272  return ret;
273  }
274  c->setStash(SESSION_TRIED_LOADING_ID, true);
275 
276  const QByteArray sid = getSessionId(c, sessionName);
277  if (!sid.isEmpty()) {
278  if (!validateSessionId(sid)) {
279  qCCritical(C_SESSION) << "Tried to set invalid session ID" << sid;
280  return ret;
281  }
282  ret = sid;
283  c->setStash(SESSION_ID, sid);
284  }
285 
286  return ret;
287 }
288 
289 QByteArray SessionPrivate::getSessionId(Context *c, const QByteArray &sessionName)
290 {
291  QByteArray ret;
292  bool deleted = !c->stash(SESSION_DELETED_ID).isNull();
293 
294  if (!deleted) {
295  const QVariant property = c->stash(SESSION_ID);
296  if (!property.isNull()) {
297  ret = property.toByteArray();
298  return ret;
299  }
300 
301  const QByteArray cookie = c->request()->cookie(sessionName);
302  if (!cookie.isEmpty()) {
303  qCDebug(C_SESSION) << "Found sessionid" << cookie << "in cookie";
304  ret = cookie;
305  }
306  }
307 
308  return ret;
309 }
310 
311 QByteArray SessionPrivate::createSessionIdIfNeeded(Session *session, Context *c, qint64 expires)
312 {
313  QByteArray ret;
314  const QVariant sid = c->stash(SESSION_ID);
315  if (!sid.isNull()) {
316  ret = sid.toByteArray();
317  } else {
318  ret = createSessionId(session, c, expires);
319  }
320  return ret;
321 }
322 
323 QByteArray SessionPrivate::createSessionId(Session *session, Context *c, qint64 expires)
324 {
325  Q_UNUSED(expires)
326  const auto sid = generateSessionId();
327 
328  qCDebug(C_SESSION) << "Created session" << sid;
329 
330  c->setStash(SESSION_ID, sid);
331  resetSessionExpires(session, c, sid);
332  setSessionId(session, c, sid);
333 
334  return sid;
335 }
336 
337 void SessionPrivate::_q_saveSession(Context *c)
338 {
339  // fix cookie before we send headers
340  saveSessionExpires(c);
341 
342  // Force extension of session_expires before finalizing headers, so a pos
343  // up to date. First call to session_expires will extend the expiry, methods
344  // just return the previously extended value.
345  Session::expires(c);
346 
347  // Persist data
348  if (Q_UNLIKELY(!m_instance)) {
349  qCCritical(C_SESSION) << "Session plugin not registered";
350  return;
351  }
352  saveSessionExpires(c);
353 
354  if (!c->stash(SESSION_UPDATED).toBool()) {
355  return;
356  }
357  QVariantHash sessionData = c->stash(SESSION_VALUES).toHash();
358  sessionData.insert(QStringLiteral("__updated"), QDateTime::currentSecsSinceEpoch());
359 
360  const auto sid = c->stash(SESSION_ID).toByteArray();
361  m_instance->d_ptr->store->storeSessionData(c, sid, QStringLiteral("session"), sessionData);
362 }
363 
364 void SessionPrivate::deleteSession(Session *session, Context *c, const QString &reason)
365 {
366  qCDebug(C_SESSION) << "Deleting session" << reason;
367 
368  const QVariant sidVar = c->stash(SESSION_ID).toString();
369  if (!sidVar.isNull()) {
370  const auto sid = sidVar.toByteArray();
371  session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral("session"));
372  session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral("expires"));
373  session->d_ptr->store->deleteSessionData(c, sid, QStringLiteral("flash"));
374 
375  deleteSessionId(session, c, sid);
376  }
377 
378  // Reset the values in Context object
379  c->setStash(SESSION_VALUES, QVariant());
380  c->setStash(SESSION_ID, QVariant());
381  c->setStash(SESSION_EXPIRES, QVariant());
382 
383  c->setStash(SESSION_DELETE_REASON, reason);
384 }
385 
386 void SessionPrivate::deleteSessionId(Session *session, Context *c, const QByteArray &sid)
387 {
388  c->setStash(SESSION_DELETED_ID, true); // to prevent get_session_id from returning it
389 
390  updateSessionCookie(c, makeSessionCookie(session, c, sid, QDateTime::currentDateTimeUtc()));
391 }
392 
393 QVariant SessionPrivate::loadSession(Context *c)
394 {
395  QVariant ret;
396  const QVariant property = c->stash(SESSION_VALUES);
397  if (!property.isNull()) {
398  ret = property.toHash();
399  return ret;
400  }
401 
402  if (Q_UNLIKELY(!m_instance)) {
403  qCCritical(C_SESSION) << "Session plugin not registered";
404  return ret;
405  }
406 
407  const auto sid = Session::id(c);
408  if (!loadSessionExpires(m_instance, c, sid).isNull()) {
409  if (SessionPrivate::validateSessionId(sid)) {
410 
411  const QVariantHash sessionData =
412  m_instance->d_ptr->store->getSessionData(c, sid, QStringLiteral("session"))
413  .toHash();
414  c->setStash(SESSION_VALUES, sessionData);
415 
416  if (m_instance->d_ptr->verifyAddress) {
417  auto it = sessionData.constFind(u"__address"_s);
418  if (it != sessionData.constEnd() &&
419  it->toString() != c->request()->address().toString()) {
420  qCWarning(C_SESSION)
421  << "Deleting session" << sid << "due to address mismatch:" << *it
422  << "!=" << c->request()->address().toString();
423  deleteSession(m_instance, c, QStringLiteral("address mismatch"));
424  return ret;
425  }
426  }
427 
428  if (m_instance->d_ptr->verifyUserAgent) {
429  auto it = sessionData.constFind(u"__user_agent"_s);
430  if (it != sessionData.constEnd() &&
431  it->toByteArray() != c->request()->userAgent()) {
432  qCWarning(C_SESSION)
433  << "Deleting session" << sid << "due to user agent mismatch:" << *it
434  << "!=" << c->request()->userAgent();
435  deleteSession(m_instance, c, QStringLiteral("user agent mismatch"));
436  return ret;
437  }
438  }
439 
440  qCDebug(C_SESSION) << "Restored session" << sid << "keys" << sessionData.size();
441 
442  ret = sessionData;
443  }
444  }
445 
446  return ret;
447 }
448 
449 bool SessionPrivate::validateSessionId(QByteArrayView id)
450 {
451  auto it = id.begin();
452  auto end = id.end();
453  while (it != end) {
454  char c = *it;
455  if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) {
456  ++it;
457  continue;
458  }
459  return false;
460  }
461 
462  return id.size();
463 }
464 
465 qint64 SessionPrivate::extendSessionExpires(Session *session, Context *c, qint64 expires)
466 {
467  const qint64 threshold = qint64(session->d_ptr->expiryThreshold);
468 
469  const auto sid = Session::id(c);
470  if (!sid.isEmpty()) {
471  const qint64 current = getStoredSessionExpires(session, c, sid);
472  const qint64 cutoff = current - threshold;
473  const qint64 time = QDateTime::currentSecsSinceEpoch();
474 
475  if (!threshold || cutoff <= time || c->stash(SESSION_UPDATED).toBool()) {
476  qint64 updated = calculateInitialSessionExpires(session, c, sid);
477  c->setStash(SESSION_EXTENDED_EXPIRES, updated);
478  extendSessionId(session, c, sid, updated);
479 
480  return updated;
481  } else {
482  return current;
483  }
484  } else {
485  return expires;
486  }
487 }
488 
489 qint64 SessionPrivate::getStoredSessionExpires(Session *session,
490  Context *c,
491  const QByteArray &sessionid)
492 {
493  const QVariant expires =
494  session->d_ptr->store->getSessionData(c, sessionid, QStringLiteral("expires"), 0);
495  return expires.toLongLong();
496 }
497 
498 QVariant SessionPrivate::initializeSessionData(Session *session, Context *c)
499 {
500  QVariantHash ret;
501  const qint64 now = QDateTime::currentSecsSinceEpoch();
502  ret.insert(QStringLiteral("__created"), now);
503  ret.insert(QStringLiteral("__updated"), now);
504 
505  if (session->d_ptr->verifyAddress) {
506  ret.insert(QStringLiteral("__address"), c->request()->address().toString());
507  }
508 
509  if (session->d_ptr->verifyUserAgent) {
510  ret.insert(QStringLiteral("__user_agent"), c->request()->userAgent());
511  }
512 
513  return ret;
514 }
515 
516 void SessionPrivate::saveSessionExpires(Context *c)
517 {
518  const QVariant expires = c->stash(SESSION_EXPIRES);
519  if (!expires.isNull()) {
520  const auto sid = Session::id(c);
521  if (!sid.isEmpty()) {
522  if (Q_UNLIKELY(!m_instance)) {
523  qCCritical(C_SESSION) << "Session plugin not registered";
524  return;
525  }
526 
527  const qint64 current = getStoredSessionExpires(m_instance, c, sid);
528  const qint64 extended = qint64(Session::expires(c));
529  if (extended > current) {
530  m_instance->d_ptr->store->storeSessionData(
531  c, sid, QStringLiteral("expires"), extended);
532  }
533  }
534  }
535 }
536 
537 QVariant
538  SessionPrivate::loadSessionExpires(Session *session, Context *c, const QByteArray &sessionId)
539 {
540  QVariant ret;
541  if (c->stash(SESSION_TRIED_LOADING_EXPIRES).toBool()) {
542  ret = c->stash(SESSION_EXPIRES);
543  return ret;
544  }
545  c->setStash(SESSION_TRIED_LOADING_EXPIRES, true);
546 
547  if (!sessionId.isEmpty()) {
548  const qint64 expires = getStoredSessionExpires(session, c, sessionId);
549 
550  if (expires >= QDateTime::currentSecsSinceEpoch()) {
551  c->setStash(SESSION_EXPIRES, expires);
552  ret = expires;
553  } else {
554  deleteSession(session, c, QStringLiteral("session expired"));
555  ret = 0;
556  }
557  }
558  return ret;
559 }
560 
561 qint64 SessionPrivate::initialSessionExpires(Session *session, Context *c)
562 {
563  Q_UNUSED(c)
564  const qint64 expires = qint64(session->d_ptr->sessionExpires);
565  return QDateTime::currentSecsSinceEpoch() + expires;
566 }
567 
568 qint64 SessionPrivate::calculateInitialSessionExpires(Session *session,
569  Context *c,
570  const QByteArray &sessionId)
571 {
572  const qint64 stored = getStoredSessionExpires(session, c, sessionId);
573  const qint64 initial = initialSessionExpires(session, c);
574  return qMax(initial, stored);
575 }
576 
577 qint64
578  SessionPrivate::resetSessionExpires(Session *session, Context *c, const QByteArray &sessionId)
579 {
580  const qint64 exp = calculateInitialSessionExpires(session, c, sessionId);
581 
582  c->setStash(SESSION_EXPIRES, exp);
583 
584  // since we're setting _session_expires directly, make loadSessionExpires
585  // actually use that value.
586  c->setStash(SESSION_TRIED_LOADING_EXPIRES, true);
587  c->setStash(SESSION_EXTENDED_EXPIRES, exp);
588 
589  return exp;
590 }
591 
592 void SessionPrivate::updateSessionCookie(Context *c, const QNetworkCookie &updated)
593 {
594  c->response()->setCookie(updated);
595 }
596 
597 QNetworkCookie SessionPrivate::makeSessionCookie(Session *session,
598  Context *c,
599  const QByteArray &sid,
600  const QDateTime &expires)
601 {
602  Q_UNUSED(c)
603  QNetworkCookie cookie(session->d_ptr->sessionName, sid);
604  cookie.setPath(u"/"_s);
605  cookie.setExpirationDate(expires);
606  cookie.setHttpOnly(session->d_ptr->cookieHttpOnly);
607  cookie.setSecure(session->d_ptr->cookieSecure);
608  cookie.setSameSitePolicy(session->d_ptr->cookieSameSite);
609 
610  return cookie;
611 }
612 
613 void SessionPrivate::extendSessionId(Session *session,
614  Context *c,
615  const QByteArray &sid,
616  qint64 expires)
617 {
618  updateSessionCookie(c,
619  makeSessionCookie(session, c, sid, QDateTime::fromSecsSinceEpoch(expires)));
620 }
621 
622 void SessionPrivate::setSessionId(Session *session, Context *c, const QByteArray &sid)
623 {
624  updateSessionCookie(
625  c,
626  makeSessionCookie(
627  session, c, sid, QDateTime::fromSecsSinceEpoch(initialSessionExpires(session, c))));
628 }
629 
630 QVariant SessionPrivate::config(const QString &key, const QVariant &defaultValue) const
631 {
632  return loadedConfig.value(key, defaultConfig.value(key, defaultValue));
633 }
634 
636  : QObject(parent)
637 {
638 }
639 
640 #include "moc_session.cpp"
qlonglong toLongLong(bool *ok) const const
QByteArray toByteArray() const const
Request request
Definition: context.h:72
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:213
QHash< QString, QVariant > toHash() const const
void postForked(Cutelyst::Application *app)
QByteArray toHex(char separator) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isEmpty() const const
T value() const const
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:213
QString toString() const const
The Cutelyst Context.
Definition: context.h:42
QHostAddress address() const noexcept
Definition: request.cpp:34
static quint64 expires(Context *c)
Definition: session.cpp:122
bool isNull() const const
void stash(const QVariantHash &unite)
Definition: context.cpp:563
void setStorage(std::unique_ptr< SessionStore > store)
Definition: session.cpp:90
CaseInsensitive
QVariantMap config(const QString &entity) const
Definition: engine.cpp:263
static bool isValid(Context *c)
Definition: session.cpp:258
static void deleteSession(Context *c, const QString &reason=QString())
Definition: session.cpp:155
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition: session.cpp:184
The Cutelyst namespace holds all public Cutelyst API.
static QByteArray id(Context *c)
Definition: session.cpp:104
Session(Application *parent)
Definition: session.cpp:36
void setParent(QObject *parent)
SessionStore * storage() const
Definition: session.cpp:98
static QString deleteReason(Context *c)
Definition: session.cpp:164
static void deleteValue(Context *c, const QString &key)
Definition: session.cpp:208
void afterDispatch(Cutelyst::Context *c)
Abstract class to create a session store.
Definition: session.h:35
QByteArray toLatin1() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
static void deleteValues(Context *c, const QStringList &keys)
Definition: session.cpp:232
CUTELYST_EXPORT std::chrono::microseconds durationFromString(QStringView str, bool *ok=nullptr)
Definition: utils.cpp:291
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:169
virtual bool setup(Application *app) final
Definition: session.cpp:54
virtual ~Session()
Definition: session.cpp:49
Base class for Cutelyst Plugins.
Definition: plugin.h:24
The Cutelyst application.
Definition: application.h:72
SessionStore(QObject *parent=nullptr)
Definition: session.cpp:635
QByteArray cookie(QByteArrayView name) const
Definition: request.cpp:278
Plugin providing methods for session management.
Definition: session.h:179
Engine * engine() const noexcept
qint64 currentSecsSinceEpoch()
qsizetype size() const const
Response * response() const noexcept
Definition: context.cpp:98
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QByteArray toRfc4122() const const
QDateTime currentDateTimeUtc()
QUuid createUuid()
static void changeExpires(Context *c, quint64 expires)
Definition: session.cpp:142
QString applicationName()