cutelyst 5.1.0
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
44static QVector<Headers::HeaderKeyValue>::const_iterator
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
57QByteArray Headers::contentDisposition() const noexcept
58{
59 return header("Content-Disposition");
60}
61
62void Headers::setCacheControl(const QByteArray &value)
63{
64 setHeader("Cache-Control"_ba, value);
65}
66
68{
69 setHeader("Content-Disposition"_ba, contentDisposition);
70}
71
72void Headers::setContentDispositionAttachment(const QByteArray &filename)
73{
74 if (filename.isEmpty()) {
75 setContentDisposition("attachment");
76 } else {
77 setContentDisposition("attachment; filename=\"" + filename + '"');
78 }
79}
80
81QByteArray Headers::contentEncoding() const noexcept
82{
83 return header("Content-Encoding");
84}
85
86void Headers::setContentEncoding(const QByteArray &encoding)
87{
88 setHeader("Content-Encoding"_ba, encoding);
89}
90
91QByteArray Headers::contentType() const
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
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
120void Headers::setContentTypeCharset(const QByteArray &charset)
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
201QByteArray Headers::setDateWithDateTime(const QDateTime &date)
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
211QDateTime Headers::date() const
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 }
223 ret.setTimeZone(QTimeZone::UTC);
224 }
225
226 return ret;
227}
228
229QByteArray Headers::ifModifiedSince() const noexcept
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 }
246 ret.setTimeZone(QTimeZone::UTC);
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
263bool Headers::ifMatch(QAnyStringView etag) const
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
274bool Headers::ifNoneMatch(QAnyStringView etag) const
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
285void Headers::setETag(const QByteArray &etag)
286{
287 setHeader("ETag"_ba, '"' + etag + '"');
288}
289
290QByteArray Headers::lastModified() const noexcept
291{
292 return header("Last-Modified");
293}
294
295void Headers::setLastModified(const QByteArray &value)
296{
297 setHeader("Last-Modified"_ba, value);
298}
299
300QString Headers::setLastModified(const QDateTime &lastModified)
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
309QByteArray Headers::server() const noexcept
310{
311 return header("Server");
312}
313
314void Headers::setServer(const QByteArray &value)
315{
316 setHeader("Server"_ba, value);
317}
318
319QByteArray Headers::connection() const noexcept
320{
321 return header("Connection");
322}
323
324QByteArray Headers::host() const noexcept
325{
326 return header("Host");
327}
328
329QByteArray Headers::userAgent() const noexcept
330{
331 return header("User-Agent");
332}
333
334QByteArray Headers::referer() const noexcept
335{
336 return header("Referer");
337}
338
339void Headers::setReferer(const QByteArray &uri)
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
350void Headers::setWwwAuthenticate(const QByteArray &value)
351{
352 setHeader("Www-Authenticate"_ba, value);
353}
354
355void Headers::setProxyAuthenticate(const QByteArray &value)
356{
357 setHeader("Proxy-Authenticate"_ba, value);
358}
359
360QByteArray Headers::authorization() const noexcept
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
387QByteArray Headers::setAuthorizationBasic(const QString &username, const QString &password)
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
401QByteArray Headers::proxyAuthorization() const noexcept
402{
403 return header("Proxy-Authorization");
404}
405
407{
408 return decodeBasicAuth(proxyAuthorization());
409}
410
412{
413 return decodeBasicAuthPair(proxyAuthorization());
414}
415
416QByteArray Headers::header(QAnyStringView key) const noexcept
417{
418 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
419 return result->value;
420 }
421 return {};
422}
423
424QString Headers::headerAsString(QAnyStringView key) const
425{
426 return QString::fromLatin1(header(key));
427}
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
437QString Headers::headerAsString(QAnyStringView key, const QString &defaultValue) const
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
445QByteArrayList Headers::headers(QAnyStringView key) const
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
454QStringList Headers::headersAsStrings(QAnyStringView key) const
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
473 QVector<HeaderKeyValue>::ConstIterator begin =
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
496void Headers::removeHeader(QAnyStringView key)
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
526QByteArray Headers::operator[](QAnyStringView key) const noexcept
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.