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