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