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