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