cutelyst 4.8.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, QByteArrayView key) noexcept
21{
22 auto matchKey = [key](Headers::HeaderKeyValue entry) {
23 return key.compare(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
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
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 }
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
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
240bool Headers::ifMatch(const QByteArray &etag) const
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
251bool Headers::ifNoneMatch(const QByteArray &etag) const
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(QByteArrayView 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 return QString::fromLatin1(header(key, defaultValue));
417}
418
420{
421 QByteArrayList ret;
422 for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
423 ret.append(result->value);
424 }
425 return ret;
426}
427
429{
430 QStringList ret;
431 for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
432 ret.append(QString::fromLatin1(result->value));
433 }
434 return ret;
435}
436
437void Headers::setHeader(const QByteArray &key, const QByteArray &value)
438{
439 const auto matchKey = [key](const Headers::HeaderKeyValue &entry) {
440 return key.compare(entry.key, Qt::CaseInsensitive) == 0;
441 };
442
443 if (auto result = std::find_if(m_data.begin(), m_data.end(), matchKey);
444 result != m_data.end()) {
445 result->value = value;
446 ++result;
447
449 std::remove_if(result, m_data.end(), matchKey);
450 m_data.erase(begin, m_data.cend());
451 } else {
452 m_data.emplace_back(HeaderKeyValue{key, value});
453 }
454}
455
456void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
457{
458 setHeader(field, values.join(", "));
459}
460
461void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
462{
463 m_data.push_back({key, value});
464}
465
466void Headers::pushHeader(const QByteArray &key, const QByteArrayList &values)
467{
468 m_data.push_back({key, values.join(", ")});
469}
470
472{
473 m_data.removeIf(
474 [key](HeaderKeyValue entry) { return key.compare(entry.key, Qt::CaseInsensitive) == 0; });
475}
476
477bool Headers::contains(QByteArrayView key) const noexcept
478{
479 auto result = findHeaderConst(m_data, key);
480 return result != m_data.end();
481}
482
483QByteArrayList Headers::keys() const
484{
485 QByteArrayList ret;
486
487 for (const auto &header : m_data) {
488 bool found = false;
489 for (const auto &key : ret) {
490 if (header.key.compare(key, Qt::CaseInsensitive) == 0) {
491 found = true;
492 break;
493 }
494 }
495
496 if (!found) {
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 for (const auto &myValue : m_data) {
517 if (!other.data().contains(myValue)) {
518 return false;
519 }
520 }
521
522 return true;
523}
524
525QByteArray decodeBasicAuth(const QByteArray &auth)
526{
527 QByteArray ret;
528 int pos = auth.indexOf("Basic ");
529 if (pos != -1) {
530 pos += 6;
531 ret = auth.mid(pos, auth.indexOf(',', pos) - pos);
532 ret = QByteArray::fromBase64(ret);
533 }
534 return ret;
535}
536
537Headers::Authorization decodeBasicAuthPair(const QByteArray &auth)
538{
540 const QByteArray authorization = decodeBasicAuth(auth);
541 if (!authorization.isEmpty()) {
542 int pos = authorization.indexOf(':');
543 if (pos == -1) {
544 ret.user = QString::fromLatin1(authorization);
545 } else {
546 ret.user = QString::fromLatin1(authorization.left(pos));
547 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
548 }
549 }
550 return ret;
551}
552
553QDebug operator<<(QDebug debug, const Headers &headers)
554{
555 const auto data = headers.data();
556 const bool oldSetting = debug.autoInsertSpaces();
557 debug.nospace() << "Headers[";
558 for (auto it = data.begin(); it != data.end(); ++it) {
559 debug << '(' << it->key + '=' + it->value << ')';
560 }
561 debug << ']';
562 debug.setAutoInsertSpaces(oldSetting);
563 return debug.maybeSpace();
564}
Container for HTTP headers.
Definition headers.h:24
QByteArray contentEncoding() const noexcept
Definition headers.cpp:58
QByteArray contentType() const
Definition headers.cpp:68
bool ifMatch(const QByteArray &etag) const
Definition headers.cpp:240
QByteArrayList headers(QByteArrayView key) const
Definition headers.cpp:419
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:419
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
bool contains(QByteArrayView key) const noexcept
Definition headers.cpp:477
QByteArray operator[](QByteArrayView key) const noexcept
Definition headers.cpp:504
QStringList headersAsStrings(QByteArrayView key) const
Definition headers.cpp:428
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
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
void removeHeader(QByteArrayView key)
Definition headers.cpp:471
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:461
void setServer(const QByteArray &value)
Definition headers.cpp:291
bool contentIsText() const
Definition headers.cpp:130
QByteArray header(QByteArrayView key) const noexcept
Definition headers.cpp:393
QByteArray lastModified() const noexcept
Definition headers.cpp:267
bool ifNoneMatch(const QByteArray &etag) const
Definition headers.cpp:251
bool contentIsHtml() const
Definition headers.cpp:135
void setContentType(const QByteArray &contentType)
Definition headers.cpp:77
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:437
bool contentIsJson() const
Definition headers.cpp:155
void setContentEncoding(const QByteArray &encoding)
Definition headers.cpp:63
QByteArray contentDisposition() const noexcept
Definition headers.cpp:34
QString headerAsString(QByteArrayView key) const
Definition headers.cpp:401
QByteArray proxyAuthorization() const noexcept
Definition headers.cpp:378
The Cutelyst namespace holds all public Cutelyst API.
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
QByteArray left(qsizetype len) &&
QByteArray mid(qsizetype pos, qsizetype len) &&
QByteArray number(double n, char format, int precision)
QByteArray sliced(qsizetype pos) &&
bool startsWith(QByteArrayView bv) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
QByteArray toLower() const const
QByteArray join(QByteArrayView separator) const const
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) 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