cutelyst 5.0.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;
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
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
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
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) &&
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) &&
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)
bool autoInsertSpaces() const const
QDebug & maybeSpace()
QDebug & nospace()
void setAutoInsertSpaces(bool b)
typedef ConstIterator
void append(QList< T > &&value)
QList< T >::const_iterator cend() const const
QLocale c()
QDateTime toDateTime(const QString &string, QLocale::FormatType format, QCalendar cal, int baseYear) 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