cutelyst 4.4.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 {
18QVector<Headers::HeaderKeyValue>::const_iterator
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
33QByteArray Headers::contentDisposition() const noexcept
34{
35 return header("Content-Disposition");
36}
37
38void Headers::setCacheControl(const QByteArray &value)
39{
40 setHeader("Cache-Control"_qba, value);
41}
42
43void Headers::setContentDisposition(const QByteArray &contentDisposition)
44{
45 setHeader("Content-Disposition"_qba, contentDisposition);
46}
47
48void Headers::setContentDispositionAttachment(const QByteArray &filename)
49{
50 if (filename.isEmpty()) {
51 setContentDisposition("attachment");
52 } else {
53 setContentDisposition("attachment; filename=\"" + filename + '"');
54 }
55}
56
57QByteArray Headers::contentEncoding() const noexcept
58{
59 return header("Content-Encoding");
60}
61
62void Headers::setContentEncoding(const QByteArray &encoding)
63{
64 setHeader("Content-Encoding"_qba, encoding);
65}
66
67QByteArray Headers::contentType() const
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
96void Headers::setContentTypeCharset(const QByteArray &charset)
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
177QByteArray Headers::setDateWithDateTime(const QDateTime &date)
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
187QDateTime Headers::date() const
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 {
196 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
197 QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
198 }
199 ret.setTimeSpec(Qt::UTC);
200 }
201
202 return ret;
203}
204
205QByteArray Headers::ifModifiedSince() const noexcept
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 {
219 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
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
261void Headers::setETag(const QByteArray &etag)
262{
263 setHeader("ETag"_qba, '"' + etag + '"');
264}
265
266QByteArray Headers::lastModified() const noexcept
267{
268 return header("Last-Modified");
269}
270
271void Headers::setLastModified(const QByteArray &value)
272{
273 setHeader("Last-Modified"_qba, value);
274}
275
276QString Headers::setLastModified(const QDateTime &lastModified)
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
285QByteArray Headers::server() const noexcept
286{
287 return header("Server");
288}
289
290void Headers::setServer(const QByteArray &value)
291{
292 setHeader("Server"_qba, value);
293}
294
295QByteArray Headers::connection() const noexcept
296{
297 return header("Connection");
298}
299
300QByteArray Headers::host() const noexcept
301{
302 return header("Host");
303}
304
305QByteArray Headers::userAgent() const noexcept
306{
307 return header("User-Agent");
308}
309
310QByteArray Headers::referer() const noexcept
311{
312 return header("Referer");
313}
314
315void Headers::setReferer(const QByteArray &uri)
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
326void Headers::setWwwAuthenticate(const QByteArray &value)
327{
328 setHeader("Www-Authenticate"_qba, value);
329}
330
331void Headers::setProxyAuthenticate(const QByteArray &value)
332{
333 setHeader("Proxy-Authenticate"_qba, value);
334}
335
336QByteArray Headers::authorization() const noexcept
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
363QByteArray Headers::setAuthorizationBasic(const QString &username, const QString &password)
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
377QByteArray Headers::proxyAuthorization() const noexcept
378{
379 return header("Proxy-Authorization");
380}
381
383{
384 return decodeBasicAuth(proxyAuthorization());
385}
386
388{
389 return decodeBasicAuthPair(proxyAuthorization());
390}
391
392QByteArray Headers::header(QByteArrayView key) const noexcept
393{
394 if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
395 return result->value;
396 }
397 return {};
398}
399
400QString Headers::headerAsString(QByteArrayView key) const
401{
402 return QString::fromLatin1(header(key));
403}
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
413QString Headers::headerAsString(QByteArrayView key, const QByteArray &defaultValue) const
414{
415 return QString::fromLatin1(header(key, defaultValue));
416}
417
418QByteArrayList Headers::headers(QByteArrayView key) const
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
427QStringList Headers::headersAsStrings(QByteArrayView key) const
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 const auto matchKey = [key](const 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 QVector<HeaderKeyValue>::ConstIterator begin =
448 std::remove_if(result, m_data.end(), matchKey);
449 m_data.erase(begin, m_data.cend());
450 } else {
451 m_data.emplace_back(HeaderKeyValue{key, value});
452 }
453}
454
455void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
456{
457 setHeader(field, values.join(", "));
458}
459
460void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
461{
462 m_data.push_back({key, value});
463}
464
465void Headers::pushHeader(const QByteArray &key, const QByteArrayList &values)
466{
467 m_data.push_back({key, values.join(", ")});
468}
469
470void Headers::removeHeader(QByteArrayView key)
471{
472 m_data.removeIf(
473 [key](HeaderKeyValue entry) { return key.compare(entry.key, Qt::CaseInsensitive) == 0; });
474}
475
476bool Headers::contains(QByteArrayView key) const noexcept
477{
478 auto result = findHeaderConst(m_data, key);
479 return result != m_data.end();
480}
481
482QByteArrayList Headers::keys() const
483{
484 QByteArrayList ret;
485
486 for (const auto &header : m_data) {
487 bool found = false;
488 for (const auto &key : ret) {
489 if (header.key.compare(key, Qt::CaseInsensitive) == 0) {
490 found = true;
491 break;
492 }
493 }
494
495 if (!found) {
496 ret.append(header.key);
497 }
498 }
499
500 return ret;
501}
502
503QByteArray Headers::operator[](QByteArrayView key) const noexcept
504{
505 return header(key);
506}
507
508bool Headers::operator==(const Headers &other) const noexcept
509{
510 const auto otherData = other.data();
511 if (m_data.size() != otherData.size()) {
512 return false;
513 }
514
515 for (const auto &myValue : m_data) {
516 if (!other.data().contains(myValue)) {
517 return false;
518 }
519 }
520
521 return true;
522}
523
524QByteArray decodeBasicAuth(const QByteArray &auth)
525{
526 QByteArray ret;
527 int pos = auth.indexOf("Basic ");
528 if (pos != -1) {
529 pos += 6;
530 ret = auth.mid(pos, auth.indexOf(',', pos) - pos);
531 ret = QByteArray::fromBase64(ret);
532 }
533 return ret;
534}
535
536Headers::Authorization decodeBasicAuthPair(const QByteArray &auth)
537{
539 const QByteArray authorization = decodeBasicAuth(auth);
540 if (!authorization.isEmpty()) {
541 int pos = authorization.indexOf(':');
542 if (pos == -1) {
543 ret.user = QString::fromLatin1(authorization);
544 } else {
545 ret.user = QString::fromLatin1(authorization.left(pos));
546 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
547 }
548 }
549 return ret;
550}
551
552QDebug operator<<(QDebug debug, const Headers &headers)
553{
554 const auto data = headers.data();
555 const bool oldSetting = debug.autoInsertSpaces();
556 debug.nospace() << "Headers[";
557 for (auto it = data.begin(); it != data.end(); ++it) {
558 debug << '(' << it->key + '=' + it->value << ')';
559 }
560 debug << ']';
561 debug.setAutoInsertSpaces(oldSetting);
562 return debug.maybeSpace();
563}
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:508
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:476
QByteArray operator[](QByteArrayView key) const noexcept
Definition: headers.cpp:503
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:470
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:460
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.