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