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