1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qhstsstore_p.h"
5#include "qhstspolicy.h"
6
7#include "qstandardpaths.h"
8#include "qdatastream.h"
9#include "qbytearray.h"
10#include "qdatetime.h"
11#include "qvariant.h"
12#include "qstring.h"
13#include "qdir.h"
14
15#include <utility>
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21static QString host_name_to_settings_key(const QString &hostName)
22{
23 const QByteArray hostNameAsHex(hostName.toUtf8().toHex());
24 return QString::fromLatin1(ba: hostNameAsHex);
25}
26
27static QString settings_key_to_host_name(const QString &key)
28{
29 const QByteArray hostNameAsUtf8(QByteArray::fromHex(hexEncoded: key.toLatin1()));
30 return QString::fromUtf8(ba: hostNameAsUtf8);
31}
32
33QHstsStore::QHstsStore(const QString &dirName)
34 : store(absoluteFilePath(dirName), QSettings::IniFormat)
35{
36 // Disable fallbacks, we do not want to use anything but our own ini file.
37 store.setFallbacksEnabled(false);
38}
39
40QHstsStore::~QHstsStore()
41{
42 synchronize();
43}
44
45QList<QHstsPolicy> QHstsStore::readPolicies()
46{
47 // This function only attempts to read policies, making no decision about
48 // expired policies. It's up to a user (QHstsCache) to mark these policies
49 // for deletion and sync the store later. But we immediately remove keys/values
50 // (if the store isWritable) for the policies that we fail to read.
51 QList<QHstsPolicy> policies;
52
53 beginHstsGroups();
54
55 const QStringList keys = store.childKeys();
56 for (const auto &key : keys) {
57 QHstsPolicy restoredPolicy;
58 if (deserializePolicy(key, policy&: restoredPolicy)) {
59 restoredPolicy.setHost(host: settings_key_to_host_name(key));
60 policies.push_back(t: std::move(restoredPolicy));
61 } else if (isWritable()) {
62 evictPolicy(key);
63 }
64 }
65
66 endHstsGroups();
67
68 return policies;
69}
70
71void QHstsStore::addToObserved(const QHstsPolicy &policy)
72{
73 observedPolicies.push_back(t: policy);
74}
75
76void QHstsStore::synchronize()
77{
78 if (!isWritable())
79 return;
80
81 if (observedPolicies.size()) {
82 beginHstsGroups();
83 for (const QHstsPolicy &policy : std::as_const(t&: observedPolicies)) {
84 const QString key(host_name_to_settings_key(hostName: policy.host()));
85 // If we fail to write a new, updated policy, we also remove the old one.
86 if (policy.isExpired() || !serializePolicy(key, policy))
87 evictPolicy(key);
88 }
89 observedPolicies.clear();
90 endHstsGroups();
91 }
92
93 store.sync();
94}
95
96bool QHstsStore::isWritable() const
97{
98 return store.isWritable();
99}
100
101QString QHstsStore::absoluteFilePath(const QString &dirName)
102{
103 const QDir dir(dirName.isEmpty() ? QStandardPaths::writableLocation(type: QStandardPaths::CacheLocation)
104 : dirName);
105 return dir.absoluteFilePath(fileName: "hstsstore"_L1);
106}
107
108void QHstsStore::beginHstsGroups()
109{
110 store.beginGroup(prefix: "StrictTransportSecurity"_L1);
111 store.beginGroup(prefix: "Policies"_L1);
112}
113
114void QHstsStore::endHstsGroups()
115{
116 store.endGroup();
117 store.endGroup();
118}
119
120bool QHstsStore::deserializePolicy(const QString &key, QHstsPolicy &policy)
121{
122 Q_ASSERT(store.contains(key));
123
124 const QVariant data(store.value(key));
125 if (data.isNull() || !data.canConvert<QByteArray>())
126 return false;
127
128 const QByteArray serializedData(data.toByteArray());
129 QDataStream streamer(serializedData);
130 qint64 expiryInMS = 0;
131 streamer >> expiryInMS;
132 if (streamer.status() != QDataStream::Ok)
133 return false;
134 bool includesSubDomains = false;
135 streamer >> includesSubDomains;
136 if (streamer.status() != QDataStream::Ok)
137 return false;
138
139 policy.setExpiry(QDateTime::fromMSecsSinceEpoch(msecs: expiryInMS));
140 policy.setIncludesSubDomains(includesSubDomains);
141
142 return true;
143}
144
145bool QHstsStore::serializePolicy(const QString &key, const QHstsPolicy &policy)
146{
147 Q_ASSERT(store.isWritable());
148
149 QByteArray serializedData;
150 QDataStream streamer(&serializedData, QIODevice::WriteOnly);
151 streamer << policy.expiry().toMSecsSinceEpoch();
152 streamer << policy.includesSubDomains();
153
154 if (streamer.status() != QDataStream::Ok)
155 return false;
156
157 store.setValue(key, value: serializedData);
158 return true;
159}
160
161void QHstsStore::evictPolicy(const QString &key)
162{
163 Q_ASSERT(store.isWritable());
164 if (store.contains(key))
165 store.remove(key);
166}
167
168QT_END_NAMESPACE
169

source code of qtbase/src/network/access/qhstsstore.cpp