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
19using namespace Cutelyst;
20
21Q_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
33static thread_local Session *m_instance = nullptr;
34
36 : Plugin(parent)
37 , d_ptr(new SessionPrivate(this))
38{
39}
40
41Session::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
89void 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
141void 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
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
168QVariant 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
183void 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
262QByteArray SessionPrivate::generateSessionId()
263{
264 return QUuid::createUuid().toRfc4122().toHex();
265}
266
267QByteArray 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
288QByteArray 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
310QByteArray 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
322QByteArray 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
336void 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.
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
363void 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
385void 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
392QVariant 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
448bool 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
464qint64 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
488qint64 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
497QVariant 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
515void 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
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
560qint64 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
567qint64 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
576qint64
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
591void SessionPrivate::updateSessionCookie(Context *c, const QNetworkCookie &updated)
592{
593 c->response()->setCookie(updated);
594}
595
596QNetworkCookie 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
612void 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
621void 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
629QVariant 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"
The Cutelyst application.
Definition application.h:66
Engine * engine() const noexcept
void afterDispatch(Cutelyst::Context *c)
void postForked(Cutelyst::Application *app)
The Cutelyst Context.
Definition context.h:42
void stash(const QVariantHash &unite)
Definition context.cpp:562
Request * request
Definition context.h:71
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:212
Response * response() const noexcept
Definition context.cpp:97
QVariantMap config(const QString &entity) const
Definition engine.cpp:263
Base class for Cutelyst Plugins.
Definition plugin.h:25
QByteArray cookie(QByteArrayView name) const
Definition request.cpp:277
QHostAddress address() const noexcept
Definition request.cpp:33
void setCookie(const QNetworkCookie &cookie)
Definition response.cpp:212
Abstract class to create a session store.
Definition session.h:36
SessionStore(QObject *parent=nullptr)
Definition session.cpp:634
Plugin providing methods for session management.
Definition session.h:161
static void deleteSession(Context *c, const QString &reason=QString())
Definition session.cpp:154
static QString deleteReason(Context *c)
Definition session.cpp:163
virtual bool setup(Application *app) final
Definition session.cpp:53
Session(Application *parent)
Definition session.cpp:35
static bool isValid(Context *c)
Definition session.cpp:257
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition session.cpp:168
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition session.cpp:183
void setStorage(std::unique_ptr< SessionStore > store)
Definition session.cpp:89
static void changeExpires(Context *c, quint64 expires)
Definition session.cpp:141
static QByteArray id(Context *c)
Definition session.cpp:103
SessionStore * storage() const
Definition session.cpp:97
static void deleteValue(Context *c, const QString &key)
Definition session.cpp:207
static quint64 expires(Context *c)
Definition session.cpp:121
virtual ~Session()
Definition session.cpp:48
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
The Cutelyst namespace holds all public Cutelyst API.
bool isEmpty() const const
qsizetype size() const const
QByteArray toHex(char separator) const const
QDateTime currentDateTimeUtc()
qint64 currentSecsSinceEpoch()
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
QString toString() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
CaseInsensitive
QUuid createUuid()
QByteArray toRfc4122() const const
bool isNull() const const
QByteArray toByteArray() const const
QHash< QString, QVariant > toHash() const const
qlonglong toLongLong(bool *ok) const const
T value() const const