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 {
19QVector<Headers::HeaderKeyValue>::const_iterator
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
34QByteArray Headers::contentDisposition() const noexcept
35{
36 return header("Content-Disposition");
37}
38
39void Headers::setCacheControl(const QByteArray &value)
40{
41 setHeader("Cache-Control"_ba, value);
42}
43
44void Headers::setContentDisposition(const QByteArray &contentDisposition)
45{
46 setHeader("Content-Disposition"_ba, contentDisposition);
47}
48
49void Headers::setContentDispositionAttachment(const QByteArray &filename)
50{
51 if (filename.isEmpty()) {
52 setContentDisposition("attachment");
53 } else {
54 setContentDisposition("attachment; filename=\"" + filename + '"');
55 }
56}
57
58QByteArray Headers::contentEncoding() const noexcept
59{
60 return header("Content-Encoding");
61}
62
63void Headers::setContentEncoding(const QByteArray &encoding)
64{
65 setHeader("Content-Encoding"_ba, encoding);
66}
67
68QByteArray Headers::contentType() const
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
97void Headers::setContentTypeCharset(const QByteArray &charset)
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
178QByteArray Headers::setDateWithDateTime(const QDateTime &date)
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
188QDateTime Headers::date() const
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 {
197 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
198 QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
199 }
200 ret.setTimeSpec(Qt::UTC);
201 }
202
203 return ret;
204}
205
206QByteArray Headers::ifModifiedSince() const noexcept
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 {
220 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
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
240bool Headers::ifMatch(QAnyStringView 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(QAnyStringView 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
262void Headers::setETag(const QByteArray &etag)
263{
264 setHeader("ETag"_ba, '"' + etag + '"');
265}
266
267QByteArray Headers::lastModified() const noexcept
268{
269 return header("Last-Modified");
270}
271
272void Headers::setLastModified(const QByteArray &value)
273{
274 setHeader("Last-Modified"_ba, value);
275}
276
277QString Headers::setLastModified(const QDateTime &lastModified)
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
286QByteArray Headers::server() const noexcept
287{
288 return header("Server");
289}
290
291void Headers::setServer(const QByteArray &value)
292{
293 setHeader("Server"_ba, value);
294}
295
296QByteArray Headers::connection() const noexcept
297{
298 return header("Connection");
299}
300
301QByteArray Headers::host() const noexcept
302{
303 return header("Host");
304}
305
306QByteArray Headers::userAgent() const noexcept
307{
308 return header("User-Agent");
309}
310
311QByteArray Headers::referer() const noexcept
312{
313 return header("Referer");
314}
315
316void Headers::setReferer(const QByteArray &uri)
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
327void Headers::setWwwAuthenticate(const QByteArray &value)
328{
329 setHeader("Www-Authenticate"_ba, value);
330}
331
332void Headers::setProxyAuthenticate(const QByteArray &value)
333{
334 setHeader("Proxy-Authenticate"_ba, value);
335}
336
337QByteArray Headers::authorization() const noexcept
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
364QByteArray Headers::setAuthorizationBasic(const QString &username, const QString &password)
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
378QByteArray Headers::proxyAuthorization() const noexcept
379{
380 return header("Proxy-Authorization");
381}
382
384{
385 return decodeBasicAuth(proxyAuthorization());
386}
387
389{
390 return decodeBasicAuthPair(proxyAuthorization());
391}
392
393QByteArray Headers::header(QAnyStringView key) const noexcept
394{
395 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
396 return result->value;
397 }
398 return {};
399}
400
401QString Headers::headerAsString(QAnyStringView key) const
402{
403 return QString::fromLatin1(header(key));
404}
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
414QString Headers::headerAsString(QAnyStringView key, const QString &defaultValue) const
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
422QByteArrayList Headers::headers(QAnyStringView key) const
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
431QStringList Headers::headersAsStrings(QAnyStringView key) const
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
451 QVector<HeaderKeyValue>::ConstIterator begin =
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
474void Headers::removeHeader(QAnyStringView key)
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
504QByteArray Headers::operator[](QAnyStringView key) const noexcept
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.