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, Base64Options options)
int indexOf(char ch, int from) const const
bool isEmpty() const const
QByteArray left(int len) const const
QByteArray mid(int pos, int len) const const
QByteArray toBase64(Base64Options options) const const
bool isLetter() const const
bool isLower() const const
QChar toUpper() const const
void setTimeSpec(Qt::TimeSpec spec)
bool autoInsertSpaces() const const
QDebug & maybeSpace()
QDebug & nospace()
void setAutoInsertSpaces(bool b)
const_iterator constBegin() const const
const_iterator constEnd() const const
QLocale c()
QDateTime toDateTime(const QString &string, FormatType format) const const
QString toString(qlonglong i) const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString left(int n) const const
QString mid(int position, int n) const const
QString number(int n, int base)
QString & replace(int position, int n, QChar after)
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QString toLower() const const
QString join(const QString &separator) const const
CaseInsensitive