cutelyst 3.9.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
headers.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2014-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "headers.h"
6
7#include "common.h"
8#include "engine.h"
9
10#include <QStringList>
11
12using namespace Cutelyst;
13
14inline QString normalizeHeaderKey(const QString &field);
15inline QByteArray decodeBasicAuth(const QString &auth);
16inline Headers::Authorization decodeBasicAuthPair(const QString &auth);
17
19 : m_data(other.m_data)
20{
21}
22
24{
25 return m_data.value(QStringLiteral("CONTENT_DISPOSITION"));
26}
27
29{
30 m_data.replace(QStringLiteral("CACHE_CONTROL"), value);
31}
32
34{
35 m_data.replace(QStringLiteral("CONTENT_DISPOSITION"), contentDisposition);
36}
37
39{
40 if (filename.isEmpty()) {
41 setContentDisposition(QStringLiteral("attachment"));
42 } else {
43 setContentDisposition(QLatin1String("attachment; filename=\"") + filename +
44 QLatin1Char('"'));
45 }
46}
47
49{
50 return m_data.value(QStringLiteral("CONTENT_ENCODING"));
51}
52
54{
55 m_data.replace(QStringLiteral("CONTENT_ENCODING"), encoding);
56}
57
59{
60 QString ret;
61 const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
62 if (it != m_data.constEnd()) {
63 const QString &ct = it.value();
64 ret = ct.mid(0, ct.indexOf(QLatin1Char(';'))).toLower();
65 }
66 return ret;
67}
68
70{
71 m_data.replace(QStringLiteral("CONTENT_TYPE"), contentType);
72}
73
75{
76 QString ret;
77 const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
78 if (it != m_data.constEnd()) {
79 const QString &contentType = it.value();
80 int pos = contentType.indexOf(u"charset=", 0, Qt::CaseInsensitive);
81 if (pos != -1) {
82 int endPos = contentType.indexOf(u';', pos);
83 ret = contentType.mid(pos + 8, endPos).trimmed().toUpper();
84 }
85 }
86
87 return ret;
88}
89
91{
92 const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
93 if (it == m_data.constEnd() || (it.value().isEmpty() && !charset.isEmpty())) {
94 m_data.replace(QStringLiteral("CONTENT_TYPE"), QLatin1String("charset=") + charset);
95 return;
96 }
97
98 QString contentType = it.value();
99 int pos = contentType.indexOf(QLatin1String("charset="), 0, Qt::CaseInsensitive);
100 if (pos != -1) {
101 int endPos = contentType.indexOf(QLatin1Char(';'), pos);
102 if (endPos == -1) {
103 if (charset.isEmpty()) {
104 int lastPos = contentType.lastIndexOf(QLatin1Char(';'), pos);
105 if (lastPos == -1) {
106 m_data.remove(QStringLiteral("CONTENT_TYPE"));
107 return;
108 } else {
109 contentType.remove(lastPos, contentType.length() - lastPos);
110 }
111 } else {
112 contentType.replace(pos + 8, contentType.length() - pos + 8, charset);
113 }
114 } else {
115 contentType.replace(pos + 8, endPos, charset);
116 }
117 } else if (!charset.isEmpty()) {
118 contentType.append(QLatin1String("; charset=") + charset);
119 }
120 m_data.replace(QStringLiteral("CONTENT_TYPE"), contentType);
121}
122
124{
125 return m_data.value(QStringLiteral("CONTENT_TYPE")).startsWith(u"text/");
126}
127
129{
130 const QString ct = contentType();
131 return ct.compare(u"text/html") == 0 || ct.compare(u"application/xhtml+xml") == 0 ||
132 ct.compare(u"application/vnd.wap.xhtml+xml") == 0;
133}
134
136{
137 const QString ct = contentType();
138 return ct.compare(u"application/xhtml+xml") == 0 ||
139 ct.compare(u"application/vnd.wap.xhtml+xml") == 0;
140}
141
143{
144 const QString ct = contentType();
145 return ct.compare(u"text/xml") == 0 || ct.compare(u"application/xml") == 0 ||
146 ct.endsWith(u"xml");
147}
148
150{
151 const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
152 if (it != m_data.constEnd()) {
153 return it.value().compare(u"application/json") == 0;
154 }
155 return false;
156}
157
159{
160 auto it = m_data.constFind(QStringLiteral("CONTENT_LENGTH"));
161 if (it != m_data.constEnd()) {
162 return it.value().toLongLong();
163 }
164 return -1;
165}
166
168{
169 m_data.replace(QStringLiteral("CONTENT_LENGTH"), QString::number(value));
170}
171
173{
174 // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
175 // and follow RFC 822
176 QString dt = QLocale::c().toString(date.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
177 m_data.replace(QStringLiteral("DATE"), dt);
178 return dt;
179}
180
182{
183 QDateTime ret;
184 auto it = m_data.constFind(QStringLiteral("DATE"));
185 if (it != m_data.constEnd()) {
186 const QString &date = it.value();
187
188 if (date.endsWith(u" GMT")) {
189 ret = QLocale::c().toDateTime(date.left(date.size() - 4),
190 QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
191 } else {
192 ret = QLocale::c().toDateTime(date, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
193 }
194 ret.setTimeSpec(Qt::UTC);
195 }
196
197 return ret;
198}
199
201{
202 return m_data.value(QStringLiteral("IF_MODIFIED_SINCE"));
203}
204
206{
207 QDateTime ret;
208 auto it = m_data.constFind(QStringLiteral("IF_MODIFIED_SINCE"));
209 if (it != m_data.constEnd()) {
210 const QString &ifModifiedStr = it.value();
211
212 if (ifModifiedStr.endsWith(u" GMT")) {
213 ret = QLocale::c().toDateTime(ifModifiedStr.left(ifModifiedStr.size() - 4),
214 QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
215 } else {
216 ret =
217 QLocale::c().toDateTime(ifModifiedStr, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
218 }
219 ret.setTimeSpec(Qt::UTC);
220 }
221
222 return ret;
223}
224
226{
227 auto it = m_data.constFind(QStringLiteral("IF_MODIFIED_SINCE"));
228 if (it != m_data.constEnd()) {
229 return it.value() !=
230 QLocale::c().toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
231 }
232 return true;
233}
234
235bool Headers::ifMatch(const QString &etag) const
236{
237 auto it = m_data.constFind(QStringLiteral("IF_MATCH"));
238 if (it != m_data.constEnd()) {
239 const auto clientETag = QStringView(it.value());
240 return clientETag.mid(1, clientETag.size() - 2) == etag ||
241 clientETag.mid(3, clientETag.size() - 4) == etag; // Weak ETag
242 }
243 return true;
244}
245
246bool Headers::ifNoneMatch(const QString &etag) const
247{
248 auto it = m_data.constFind(QStringLiteral("IF_NONE_MATCH"));
249 if (it != m_data.constEnd()) {
250 const auto clientETag = QStringView(it.value());
251 return clientETag.mid(1, clientETag.size() - 2) == etag ||
252 clientETag.mid(3, clientETag.size() - 4) == etag; // Weak ETag
253 }
254 return false;
255}
256
257void Headers::setETag(const QString &etag)
258{
259 m_data.replace(QStringLiteral("ETAG"), QLatin1Char('"') + etag + QLatin1Char('"'));
260}
261
263{
264 return m_data.value(QStringLiteral("LAST_MODIFIED"));
265}
266
268{
269 m_data.replace(QStringLiteral("LAST_MODIFIED"), value);
270}
271
273{
274 // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
275 // and follow RFC 822
276 auto dt = QLocale::c().toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
277 setLastModified(dt);
278 return dt;
279}
280
282{
283 return m_data.value(QStringLiteral("SERVER"));
284}
285
286void Headers::setServer(const QString &value)
287{
288 m_data.replace(QStringLiteral("SERVER"), value);
289}
290
292{
293 return m_data.value(QStringLiteral("CONNECTION"));
294}
295
297{
298 return m_data.value(QStringLiteral("HOST"));
299}
300
302{
303 return m_data.value(QStringLiteral("USER_AGENT"));
304}
305
307{
308 return m_data.value(QStringLiteral("REFERER"));
309}
310
312{
313 int fragmentPos = uri.indexOf(QLatin1Char('#'));
314 if (fragmentPos != -1) {
315 // Strip fragment per RFC 2616, section 14.36.
316 m_data.replace(QStringLiteral("REFERER"), uri.mid(0, fragmentPos));
317 } else {
318 m_data.replace(QStringLiteral("REFERER"), uri);
319 }
320}
321
323{
324 m_data.replace(QStringLiteral("WWW_AUTHENTICATE"), value);
325}
326
328{
329 m_data.replace(QStringLiteral("PROXY_AUTHENTICATE"), value);
330}
331
333{
334 return m_data.value(QStringLiteral("AUTHORIZATION"));
335}
336
338{
339 QString ret;
340 auto it = m_data.constFind(QStringLiteral("AUTHORIZATION"));
341 if (it != m_data.constEnd() && it.value().startsWith(u"Bearer ")) {
342 ret = it.value().mid(7);
343 }
344 return ret;
345}
346
348{
349 return QString::fromLatin1(decodeBasicAuth(authorization()));
350}
351
353{
354 return decodeBasicAuthPair(authorization());
355}
356
357QString Headers::setAuthorizationBasic(const QString &username, const QString &password)
358{
359 QString ret;
360 if (username.contains(QLatin1Char(':'))) {
361 qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
362 return ret;
363 }
364
365 const QString result = username + QLatin1Char(':') + password;
366 ret = QLatin1String("Basic ") + QString::fromLatin1(result.toLatin1().toBase64());
367 m_data.replace(QStringLiteral("AUTHORIZATION"), ret);
368 return ret;
369}
370
372{
373 return m_data.value(QStringLiteral("PROXY_AUTHORIZATION"));
374}
375
377{
378 return QString::fromLatin1(decodeBasicAuth(proxyAuthorization()));
379}
380
382{
383 return decodeBasicAuthPair(proxyAuthorization());
384}
385
386QString Headers::header(const QString &field) const
387{
388 return m_data.value(normalizeHeaderKey(field));
389}
390
391QString Headers::header(const QString &field, const QString &defaultValue) const
392{
393 return m_data.value(normalizeHeaderKey(field), defaultValue);
394}
395
396void Headers::setHeader(const QString &field, const QString &value)
397{
398 m_data.replace(normalizeHeaderKey(field), value);
399}
400
401void Headers::setHeader(const QString &field, const QStringList &values)
402{
403 setHeader(field, values.join(QLatin1String(", ")));
404}
405
406void Headers::pushHeader(const QString &field, const QString &value)
407{
408 m_data.insert(normalizeHeaderKey(field), value);
409}
410
411void Headers::pushHeader(const QString &field, const QStringList &values)
412{
413 m_data.insert(normalizeHeaderKey(field), values.join(QLatin1String(", ")));
414}
415
417{
418 m_data.remove(normalizeHeaderKey(field));
419}
420
421bool Headers::contains(const QString &field) const
422{
423 return m_data.contains(normalizeHeaderKey(field));
424}
425
427{
428 return m_data.value(normalizeHeaderKey(key));
429}
430
431QString normalizeHeaderKey(const QString &field)
432{
433 QString key = field;
434 int i = 0;
435 while (i < key.size()) {
436 QChar c = key[i];
437 if (c.isLetter()) {
438 if (c.isLower()) {
439 key[i] = c.toUpper();
440 }
441 } else if (c == u'-') {
442 key[i] = u'_';
443 }
444 ++i;
445 }
446 return key;
447}
448
449QByteArray decodeBasicAuth(const QString &auth)
450{
451 QByteArray ret;
452 if (!auth.isEmpty() && auth.startsWith(u"Basic ")) {
453 int pos = auth.lastIndexOf(u' ');
454 if (pos != -1) {
455 ret = QByteArray::fromBase64(auth.mid(pos).toLatin1());
456 }
457 }
458 return ret;
459}
460
461Headers::Authorization decodeBasicAuthPair(const QString &auth)
462{
464 const QByteArray authorization = decodeBasicAuth(auth);
465 if (!authorization.isEmpty()) {
466 int pos = authorization.indexOf(':');
467 if (pos == -1) {
468 ret.user = QString::fromLatin1(authorization);
469 } else {
470 ret.user = QString::fromLatin1(authorization.left(pos));
471 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
472 }
473 }
474 return ret;
475}
476
477QDebug operator<<(QDebug debug, const Headers &headers)
478{
479 const QMultiHash<QString, QString> data = headers.data();
480 const bool oldSetting = debug.autoInsertSpaces();
481 debug.nospace() << "Headers[";
482 for (auto it = data.constBegin(); it != data.constEnd(); ++it) {
483 debug << '(' << Engine::camelCaseHeader(it.key()) + QLatin1Char('=') + it.value() << ')';
484 }
485 debug << ']';
486 debug.setAutoInsertSpaces(oldSetting);
487 return debug.maybeSpace();
488}
static QString camelCaseHeader(const QString &headerKey)
Definition engine.h:103
bool ifMatch(const QString &etag) const
Definition headers.cpp:235
QString contentDisposition() const
Definition headers.cpp:23
QString contentEncoding() const
Definition headers.cpp:48
bool contentIsXHtml() const
Definition headers.cpp:135
QString operator[](const QString &key) const
Definition headers.cpp:426
QString connection() const
Definition headers.cpp:291
QString referer() const
Definition headers.cpp:306
void setETag(const QString &etag)
Definition headers.cpp:257
void setLastModified(const QString &value)
Definition headers.cpp:267
void setContentLength(qint64 value)
Definition headers.cpp:167
void setContentDisposition(const QString &contentDisposition)
Definition headers.cpp:33
QMultiHash< QString, QString > data() const
Definition headers.h:382
Authorization authorizationBasicObject() const
Definition headers.cpp:352
QString authorization() const
Definition headers.cpp:332
QDateTime date() const
Definition headers.cpp:181
void pushHeader(const QString &field, const QString &value)
Definition headers.cpp:406
bool ifNoneMatch(const QString &etag) const
Definition headers.cpp:246
qint64 contentLength() const
Definition headers.cpp:158
void setContentEncoding(const QString &encoding)
Definition headers.cpp:53
void setContentType(const QString &contentType)
Definition headers.cpp:69
QString userAgent() const
Definition headers.cpp:301
QString authorizationBearer() const
Definition headers.cpp:337
QString server() const
Definition headers.cpp:281
void setReferer(const QString &value)
Definition headers.cpp:311
void setWwwAuthenticate(const QString &value)
Definition headers.cpp:322
QString setDateWithDateTime(const QDateTime &date)
Definition headers.cpp:172
QDateTime ifModifiedSinceDateTime() const
Definition headers.cpp:205
Authorization proxyAuthorizationBasicObject() const
Definition headers.cpp:381
QString host() const
Definition headers.cpp:296
QString setAuthorizationBasic(const QString &username, const QString &password)
Definition headers.cpp:357
void removeHeader(const QString &field)
Definition headers.cpp:416
bool contentIsText() const
Definition headers.cpp:123
QString header(const QString &field) const
Definition headers.cpp:386
QString ifModifiedSince() const
Definition headers.cpp:200
bool contains(const QString &field) const
Definition headers.cpp:421
void setServer(const QString &value)
Definition headers.cpp:286
bool contentIsHtml() const
Definition headers.cpp:128
void setCacheControl(const QString &value)
Definition headers.cpp:28
QString authorizationBasic() const
Definition headers.cpp:347
QString contentTypeCharset() const
Definition headers.cpp:74
void setContentDispositionAttachment(const QString &filename=QString())
Definition headers.cpp:38
QString proxyAuthorization() const
Definition headers.cpp:371
QString proxyAuthorizationBasic() const
Definition headers.cpp:376
QString lastModified() const
Definition headers.cpp:262
bool contentIsXml() const
Definition headers.cpp:142
bool contentIsJson() const
Definition headers.cpp:149
void setHeader(const QString &field, const QString &value)
Definition headers.cpp:396
void setContentTypeCharset(const QString &charset)
Definition headers.cpp:90
QString contentType() const
Definition headers.cpp:58
void setProxyAuthenticate(const QString &value)
Definition headers.cpp:327
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) &&
QByteArray mid(qsizetype pos, qsizetype len) &&
QByteArray toBase64(QByteArray::Base64Options options) const const
void setTimeSpec(Qt::TimeSpec spec)
QLocale c()
QString toString(QDate date, QLocale::FormatType format) const const
QMultiHash< Key, T >::const_iterator constBegin() const const
QMultiHash< Key, T >::const_iterator constEnd() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) &&
QString mid(qsizetype position, qsizetype n) &&
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QString toLower() const const
QString toUpper() const const
QString join(QChar separator) const const
CaseInsensitive