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