cutelyst 5.0.2
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#include <QTimeZone>
12
13using namespace Cutelyst;
14using namespace Qt::Literals::StringLiterals;
15
16inline static QByteArray decodeBasicAuth(const QByteArray &auth)
17{
18 QByteArray ret;
19 int pos = auth.indexOf("Basic ");
20 if (pos != -1) {
21 pos += 6;
22 ret = auth.mid(pos, auth.indexOf(',', pos) - pos);
23 ret = QByteArray::fromBase64(ret);
24 }
25 return ret;
26}
27
28inline static Headers::Authorization decodeBasicAuthPair(const QByteArray &auth)
29{
31 const QByteArray authorization = decodeBasicAuth(auth);
32 if (!authorization.isEmpty()) {
33 int pos = authorization.indexOf(':');
34 if (pos == -1) {
35 ret.user = QString::fromLatin1(authorization);
36 } else {
37 ret.user = QString::fromLatin1(authorization.left(pos));
38 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
39 }
40 }
41 return ret;
42}
43
45 findHeaderConst(const QVector<Headers::HeaderKeyValue> &headers, QAnyStringView key) noexcept
46{
47 return std::ranges::find_if(headers, [key](Headers::HeaderKeyValue entry) {
48 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
49 });
50}
51
52Headers::Headers(const Headers &other) noexcept
53 : m_data(other.m_data)
54{
55}
56
58{
59 return header("Content-Disposition");
60}
61
63{
64 setHeader("Cache-Control"_ba, value);
65}
66
67void Headers::setContentDisposition(const QByteArray &contentDisposition)
68{
69 setHeader("Content-Disposition"_ba, contentDisposition);
70}
71
73{
74 if (filename.isEmpty()) {
75 setContentDisposition("attachment");
76 } else {
77 setContentDisposition("attachment; filename=\"" + filename + '"');
78 }
79}
80
82{
83 return header("Content-Encoding");
84}
85
87{
88 setHeader("Content-Encoding"_ba, encoding);
89}
90
92{
93 QByteArray ret = header("Content-Type");
94 if (!ret.isEmpty()) {
95 ret = ret.mid(0, ret.indexOf(';')).toLower();
96 }
97 return ret;
98}
99
100void Headers::setContentType(const QByteArray &contentType)
101{
102 setHeader("Content-Type"_ba, contentType);
103}
104
106{
107 QByteArray ret;
108 const QByteArray _contentType = header("Content-Type");
109 if (!_contentType.isEmpty()) {
110 int pos = _contentType.indexOf("charset=", 0);
111 if (pos != -1) {
112 int endPos = _contentType.indexOf(u';', pos);
113 ret = _contentType.mid(pos + 8, endPos).trimmed().toUpper();
114 }
115 }
116
117 return ret;
118}
119
121{
122 auto result = findHeaderConst(m_data, "Content-Type");
123 if (result == m_data.end() || (result->value.isEmpty() && !charset.isEmpty())) {
124 setContentType("charset=" + charset);
125 return;
126 }
127
128 QByteArray _contentType = result->value;
129 int pos = _contentType.indexOf("charset=", 0);
130 if (pos != -1) {
131 int endPos = _contentType.indexOf(';', pos);
132 if (endPos == -1) {
133 if (charset.isEmpty()) {
134 int lastPos = _contentType.lastIndexOf(';', pos);
135 if (lastPos == -1) {
136 removeHeader("Content-Type");
137 return;
138 } else {
139 _contentType.remove(lastPos, _contentType.length() - lastPos);
140 }
141 } else {
142 _contentType.replace(pos + 8, _contentType.length() - pos + 8, charset);
143 }
144 } else {
145 _contentType.replace(pos + 8, endPos, charset);
146 }
147 } else if (!charset.isEmpty()) {
148 _contentType.append("; charset=" + charset);
149 }
150 setContentType(_contentType);
151}
152
154{
155 return header("Content-Type").startsWith("text/");
156}
157
159{
160 const QByteArray ct = contentType();
161 return ct.compare("text/html") == 0 || ct.compare("application/xhtml+xml") == 0 ||
162 ct.compare("application/vnd.wap.xhtml+xml") == 0;
163}
164
166{
167 const QByteArray ct = contentType();
168 return ct.compare("application/xhtml+xml") == 0 ||
169 ct.compare("application/vnd.wap.xhtml+xml") == 0;
170}
171
173{
174 const QByteArray ct = contentType();
175 return ct.compare("text/xml") == 0 || ct.compare("application/xml") == 0 || ct.endsWith("xml");
176}
177
179{
180 auto value = header("Content-Type");
181 if (!value.isEmpty()) {
182 return value.compare("application/json") == 0;
183 }
184 return false;
185}
186
188{
189 auto value = header("Content-Length");
190 if (!value.isEmpty()) {
191 return value.toLongLong();
192 }
193 return -1;
194}
195
197{
198 setHeader("Content-Length"_ba, QByteArray::number(value));
199}
200
202{
203 // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
204 // and follow RFC 822
205 QByteArray dt =
206 QLocale::c().toString(date.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT").toLatin1();
207 setHeader("Date"_ba, dt);
208 return dt;
209}
210
212{
213 QDateTime ret;
214 auto value = header("Date");
215 if (!value.isEmpty()) {
216 if (value.endsWith(" GMT")) {
217 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
218 u"ddd, dd MMM yyyy hh:mm:ss"_s);
219 } else {
220 ret =
221 QLocale::c().toDateTime(QString::fromLatin1(value), u"ddd, dd MMM yyyy hh:mm:ss"_s);
222 }
224 }
225
226 return ret;
227}
228
230{
231 return header("If-Modified-Since");
232}
233
235{
236 QDateTime ret;
237 auto value = header("If-Modified-Since");
238 if (!value.isEmpty()) {
239 if (value.endsWith(" GMT")) {
240 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
241 u"ddd, dd MMM yyyy hh:mm:ss"_s);
242 } else {
243 ret =
244 QLocale::c().toDateTime(QString::fromLatin1(value), u"ddd, dd MMM yyyy hh:mm:ss"_s);
245 }
247 }
248
249 return ret;
250}
251
252bool Headers::ifModifiedSince(const QDateTime &lastModified) const
253{
254 auto value = header("If-Modified-Since");
255 if (!value.isEmpty()) {
256 return value != QLocale::c()
257 .toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT")
258 .toLatin1();
259 }
260 return true;
261}
262
264{
265 auto value = header("If-Match");
266 if (!value.isEmpty()) {
267 const auto clientETag = QByteArrayView(value);
268 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
269 clientETag.sliced(3, clientETag.size() - 4) == etag; // Weak ETag
270 }
271 return true;
272}
273
275{
276 auto value = header("If-None-Match");
277 if (!value.isEmpty()) {
278 const auto clientETag = QByteArrayView(value);
279 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
280 clientETag.sliced(3, clientETag.size() - 4) == etag; // Weak ETag
281 }
282 return false;
283}
284
286{
287 setHeader("ETag"_ba, '"' + etag + '"');
288}
289
291{
292 return header("Last-Modified");
293}
294
296{
297 setHeader("Last-Modified"_ba, value);
298}
299
301{
302 // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
303 // and follow RFC 822
304 auto dt = QLocale::c().toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
305 setLastModified(dt.toLatin1());
306 return dt;
307}
308
310{
311 return header("Server");
312}
313
315{
316 setHeader("Server"_ba, value);
317}
318
320{
321 return header("Connection");
322}
323
324QByteArray Headers::host() const noexcept
325{
326 return header("Host");
327}
328
330{
331 return header("User-Agent");
332}
333
335{
336 return header("Referer");
337}
338
340{
341 int fragmentPos = uri.indexOf('#');
342 if (fragmentPos != -1) {
343 // Strip fragment per RFC 2616, section 14.36.
344 setHeader("Referer"_ba, uri.mid(0, fragmentPos));
345 } else {
346 setHeader("Referer"_ba, uri);
347 }
348}
349
351{
352 setHeader("Www-Authenticate"_ba, value);
353}
354
356{
357 setHeader("Proxy-Authenticate"_ba, value);
358}
359
361{
362 return header("Authorization");
363}
364
366{
367 QByteArray ret;
368 auto auth = authorization();
369 int pos = auth.indexOf("Bearer ");
370 if (pos != -1) {
371 pos += 7;
372 ret = auth.mid(pos, auth.indexOf(',', pos) - pos);
373 }
374 return ret;
375}
376
378{
379 return decodeBasicAuth(authorization());
380}
381
383{
384 return decodeBasicAuthPair(authorization());
385}
386
388{
389 QByteArray ret;
390 if (username.contains(u':')) {
391 qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
392 return ret;
393 }
394
395 const QString result = username + u':' + password;
396 ret = "Basic " + result.toLatin1().toBase64();
397 setHeader("Authorization"_ba, ret);
398 return ret;
399}
400
402{
403 return header("Proxy-Authorization");
404}
405
407{
408 return decodeBasicAuth(proxyAuthorization());
409}
410
412{
413 return decodeBasicAuthPair(proxyAuthorization());
414}
415
417{
418 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
419 return result->value;
420 }
421 return {};
422}
423
428
429QByteArray Headers::header(QAnyStringView key, const QByteArray &defaultValue) const noexcept
430{
431 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
432 return result->value;
433 }
434 return defaultValue;
435}
436
438{
439 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
440 return QString::fromLatin1(result->value);
441 }
442 return defaultValue;
443}
444
446{
447 QByteArrayList ret;
448 for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
449 ret.append(result->value);
450 }
451 return ret;
452}
453
455{
456 QStringList ret;
457 for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
458 ret.append(QString::fromLatin1(result->value));
459 }
460 return ret;
461}
462
463void Headers::setHeader(const QByteArray &key, const QByteArray &value)
464{
465 const auto matchKey = [key](const Headers::HeaderKeyValue &entry) {
466 return key.compare(entry.key, Qt::CaseInsensitive) == 0;
467 };
468
469 if (auto result = std::ranges::find_if(m_data, matchKey); result != m_data.end()) {
470 result->value = value;
471 ++result;
472
474 std::remove_if(result, m_data.end(), matchKey);
475 m_data.erase(begin, m_data.cend());
476 } else {
477 m_data.emplace_back(HeaderKeyValue{.key = key, .value = value});
478 }
479}
480
481void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
482{
483 setHeader(field, values.join(", "));
484}
485
486void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
487{
488 m_data.emplace_back(HeaderKeyValue{.key = key, .value = value});
489}
490
491void Headers::pushHeader(const QByteArray &key, const QByteArrayList &values)
492{
493 m_data.emplace_back(HeaderKeyValue{.key = key, .value = values.join(", ")});
494}
495
497{
498 m_data.removeIf([key](HeaderKeyValue entry) {
499 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
500 });
501}
502
503bool Headers::contains(QAnyStringView key) const noexcept
504{
505 auto result = findHeaderConst(m_data, key);
506 return result != m_data.end();
507}
508
509QByteArrayList Headers::keys() const
510{
511 QByteArrayList ret;
512
513 for (const auto &_header : m_data) {
514 const bool exists = std::ranges::any_of(ret, [&](const QByteArray &key) {
515 return _header.key.compare(key, Qt::CaseInsensitive) == 0;
516 });
517
518 if (!exists) {
519 ret.append(_header.key);
520 }
521 }
522
523 return ret;
524}
525
527{
528 return header(key);
529}
530
531bool Headers::operator==(const Headers &other) const noexcept
532{
533 const auto otherData = other.data();
534 if (m_data.size() != otherData.size()) {
535 return false;
536 }
537
538 return std::ranges::all_of(
539 m_data, [otherData](const auto &myValue) { return otherData.contains(myValue); });
540}
541
542QDebug operator<<(QDebug debug, const Headers &headers)
543{
544 const auto data = headers.data();
545 const bool oldSetting = debug.autoInsertSpaces();
546 debug.nospace() << "Headers[";
547 for (auto it = data.begin(); it != data.end(); ++it) {
548 debug << '(' << it->key + '=' + it->value << ')';
549 }
550 debug << ']';
551 debug.setAutoInsertSpaces(oldSetting);
552 return debug.maybeSpace();
553}
Container for HTTP headers.
Definition headers.h:24
QByteArray contentEncoding() const noexcept
Definition headers.cpp:81
void removeHeader(QAnyStringView key)
Definition headers.cpp:496
QByteArray contentType() const
Definition headers.cpp:91
void setWwwAuthenticate(const QByteArray &value)
Definition headers.cpp:350
QByteArray userAgent() const noexcept
Definition headers.cpp:329
bool contentIsXHtml() const
Definition headers.cpp:165
QByteArray ifModifiedSince() const noexcept
Definition headers.cpp:229
bool operator==(const Headers &other) const noexcept
Definition headers.cpp:531
QVector< HeaderKeyValue > data() const
Definition headers.h:420
QByteArray server() const noexcept
Definition headers.cpp:309
QByteArray authorizationBasic() const
Definition headers.cpp:377
void setContentLength(qint64 value)
Definition headers.cpp:196
Authorization authorizationBasicObject() const
Definition headers.cpp:382
QDateTime date() const
Definition headers.cpp:211
void setETag(const QByteArray &etag)
Definition headers.cpp:285
void setReferer(const QByteArray &value)
Definition headers.cpp:339
QByteArray referer() const noexcept
Definition headers.cpp:334
void setContentDispositionAttachment(const QByteArray &filename={})
Definition headers.cpp:72
QByteArray proxyAuthorizationBasic() const
Definition headers.cpp:406
qint64 contentLength() const
Definition headers.cpp:187
QByteArray contentTypeCharset() const
Definition headers.cpp:105
QStringList headersAsStrings(QAnyStringView key) const
Definition headers.cpp:454
bool contains(QAnyStringView key) const noexcept
Definition headers.cpp:503
QByteArray setAuthorizationBasic(const QString &username, const QString &password)
Definition headers.cpp:387
void setLastModified(const QByteArray &value)
Definition headers.cpp:295
QByteArray connection() const noexcept
Definition headers.cpp:319
QByteArray header(QAnyStringView key) const noexcept
Definition headers.cpp:416
void setCacheControl(const QByteArray &value)
Definition headers.cpp:62
QByteArray setDateWithDateTime(const QDateTime &date)
Definition headers.cpp:201
void setContentDisposition(const QByteArray &contentDisposition)
Definition headers.cpp:67
QString headerAsString(QAnyStringView key) const
Definition headers.cpp:424
QByteArray authorizationBearer() const
Definition headers.cpp:365
QDateTime ifModifiedSinceDateTime() const
Definition headers.cpp:234
Authorization proxyAuthorizationBasicObject() const
Definition headers.cpp:411
void setContentTypeCharset(const QByteArray &charset)
Definition headers.cpp:120
Headers() noexcept=default
QByteArray authorization() const noexcept
Definition headers.cpp:360
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:486
void setServer(const QByteArray &value)
Definition headers.cpp:314
bool contentIsText() const
Definition headers.cpp:153
bool ifNoneMatch(QAnyStringView etag) const
Definition headers.cpp:274
QByteArray lastModified() const noexcept
Definition headers.cpp:290
bool contentIsHtml() const
Definition headers.cpp:158
void setContentType(const QByteArray &contentType)
Definition headers.cpp:100
QByteArray operator[](QAnyStringView key) const noexcept
Definition headers.cpp:526
bool ifMatch(QAnyStringView etag) const
Definition headers.cpp:263
QByteArray host() const noexcept
Definition headers.cpp:324
void setProxyAuthenticate(const QByteArray &value)
Definition headers.cpp:355
bool contentIsXml() const
Definition headers.cpp:172
void setHeader(const QByteArray &key, const QByteArray &value)
Definition headers.cpp:463
bool contentIsJson() const
Definition headers.cpp:178
void setContentEncoding(const QByteArray &encoding)
Definition headers.cpp:86
QByteArray contentDisposition() const noexcept
Definition headers.cpp:57
QByteArray proxyAuthorization() const noexcept
Definition headers.cpp:401
QByteArrayList headers(QAnyStringView key) const
Definition headers.cpp:445
The Cutelyst namespace holds all public Cutelyst API.
int compare(QAnyStringView lhs, QAnyStringView rhs, Qt::CaseSensitivity cs)
QAnyStringView sliced(qsizetype pos) const const
QByteArray & append(QByteArrayView data)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
bool endsWith(QByteArrayView bv) const const
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & remove(qsizetype pos, qsizetype len)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
bool startsWith(QByteArrayView bv) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
QByteArray toLower() const const
QByteArray toUpper() const const
QByteArray trimmed() const const
QByteArray join(QByteArrayView separator) const const
void setTimeZone(const QTimeZone &toZone)
QDateTime toUTC() const const
bool autoInsertSpaces() const const
QDebug & maybeSpace()
QDebug & nospace()
void setAutoInsertSpaces(bool b)
void append(QList::parameter_type value)
QLocale c()
QDateTime toDateTime(const QString &string, QLocale::FormatType format) const const
QString toString(QDate date, QLocale::FormatType format) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QByteArray toLatin1() const const
CaseInsensitive