cutelyst  4.8.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 
12 using namespace Cutelyst;
13 using namespace Qt::Literals::StringLiterals;
14 
15 inline QByteArray decodeBasicAuth(const QByteArray &auth);
16 inline Headers::Authorization decodeBasicAuthPair(const QByteArray &auth);
17 
18 namespace {
20  findHeaderConst(const QVector<Headers::HeaderKeyValue> &headers, QByteArrayView key) noexcept
21 {
22  auto matchKey = [key](Headers::HeaderKeyValue entry) {
23  return key.compare(entry.key, Qt::CaseInsensitive) == 0;
24  };
25  return std::find_if(headers.cbegin(), headers.cend(), matchKey);
26 }
27 } // namespace
28 
29 Headers::Headers(const Headers &other) noexcept
30  : m_data(other.m_data)
31 {
32 }
33 
35 {
36  return header("Content-Disposition");
37 }
38 
40 {
41  setHeader("Cache-Control"_ba, value);
42 }
43 
44 void Headers::setContentDisposition(const QByteArray &contentDisposition)
45 {
46  setHeader("Content-Disposition"_ba, contentDisposition);
47 }
48 
50 {
51  if (filename.isEmpty()) {
52  setContentDisposition("attachment");
53  } else {
54  setContentDisposition("attachment; filename=\"" + filename + '"');
55  }
56 }
57 
59 {
60  return header("Content-Encoding");
61 }
62 
64 {
65  setHeader("Content-Encoding"_ba, encoding);
66 }
67 
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 
77 void 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 
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 
173 void Headers::setContentLength(qint64 value)
174 {
175  setHeader("Content-Length"_ba, QByteArray::number(value));
176 }
177 
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 
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 {
198  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
199  }
200  ret.setTimeSpec(Qt::UTC);
201  }
202 
203  return ret;
204 }
205 
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 {
221  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
222  }
223  ret.setTimeSpec(Qt::UTC);
224  }
225 
226  return ret;
227 }
228 
229 bool 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 
240 bool Headers::ifMatch(const QByteArray &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 
251 bool Headers::ifNoneMatch(const QByteArray &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 
262 void Headers::setETag(const QByteArray &etag)
263 {
264  setHeader("ETag"_ba, '"' + etag + '"');
265 }
266 
268 {
269  return header("Last-Modified");
270 }
271 
273 {
274  setHeader("Last-Modified"_ba, value);
275 }
276 
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 
286 QByteArray Headers::server() const noexcept
287 {
288  return header("Server");
289 }
290 
291 void Headers::setServer(const QByteArray &value)
292 {
293  setHeader("Server"_ba, value);
294 }
295 
297 {
298  return header("Connection");
299 }
300 
301 QByteArray Headers::host() const noexcept
302 {
303  return header("Host");
304 }
305 
307 {
308  return header("User-Agent");
309 }
310 
311 QByteArray Headers::referer() const noexcept
312 {
313  return header("Referer");
314 }
315 
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 
328 {
329  setHeader("Www-Authenticate"_ba, value);
330 }
331 
333 {
334  setHeader("Proxy-Authenticate"_ba, value);
335 }
336 
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 
364 QByteArray 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 
379 {
380  return header("Proxy-Authorization");
381 }
382 
384 {
385  return decodeBasicAuth(proxyAuthorization());
386 }
387 
389 {
390  return decodeBasicAuthPair(proxyAuthorization());
391 }
392 
394 {
395  if (auto result = findHeaderConst(m_data, key); result != m_data.end()) {
396  return result->value;
397  }
398  return {};
399 }
400 
402 {
403  return QString::fromLatin1(header(key));
404 }
405 
406 QByteArray Headers::header(QByteArrayView 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 
415 {
416  return QString::fromLatin1(header(key, defaultValue));
417 }
418 
420 {
421  QByteArrayList ret;
422  for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
423  ret.append(result->value);
424  }
425  return ret;
426 }
427 
429 {
430  QStringList ret;
431  for (auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
432  ret.append(QString::fromLatin1(result->value));
433  }
434  return ret;
435 }
436 
437 void Headers::setHeader(const QByteArray &key, const QByteArray &value)
438 {
439  const auto matchKey = [key](const Headers::HeaderKeyValue &entry) {
440  return key.compare(entry.key, Qt::CaseInsensitive) == 0;
441  };
442 
443  if (auto result = std::find_if(m_data.begin(), m_data.end(), matchKey);
444  result != m_data.end()) {
445  result->value = value;
446  ++result;
447 
449  std::remove_if(result, m_data.end(), matchKey);
450  m_data.erase(begin, m_data.cend());
451  } else {
452  m_data.emplace_back(HeaderKeyValue{key, value});
453  }
454 }
455 
456 void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
457 {
458  setHeader(field, values.join(", "));
459 }
460 
461 void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
462 {
463  m_data.push_back({key, value});
464 }
465 
466 void Headers::pushHeader(const QByteArray &key, const QByteArrayList &values)
467 {
468  m_data.push_back({key, values.join(", ")});
469 }
470 
472 {
473  m_data.removeIf(
474  [key](HeaderKeyValue entry) { return key.compare(entry.key, Qt::CaseInsensitive) == 0; });
475 }
476 
477 bool Headers::contains(QByteArrayView key) const noexcept
478 {
479  auto result = findHeaderConst(m_data, key);
480  return result != m_data.end();
481 }
482 
483 QByteArrayList Headers::keys() const
484 {
485  QByteArrayList ret;
486 
487  for (const auto &header : m_data) {
488  bool found = false;
489  for (const auto &key : ret) {
490  if (header.key.compare(key, Qt::CaseInsensitive) == 0) {
491  found = true;
492  break;
493  }
494  }
495 
496  if (!found) {
497  ret.append(header.key);
498  }
499  }
500 
501  return ret;
502 }
503 
505 {
506  return header(key);
507 }
508 
509 bool 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  for (const auto &myValue : m_data) {
517  if (!other.data().contains(myValue)) {
518  return false;
519  }
520  }
521 
522  return true;
523 }
524 
525 QByteArray decodeBasicAuth(const QByteArray &auth)
526 {
527  QByteArray ret;
528  int pos = auth.indexOf("Basic ");
529  if (pos != -1) {
530  pos += 6;
531  ret = auth.mid(pos, auth.indexOf(',', pos) - pos);
532  ret = QByteArray::fromBase64(ret);
533  }
534  return ret;
535 }
536 
537 Headers::Authorization decodeBasicAuthPair(const QByteArray &auth)
538 {
540  const QByteArray authorization = decodeBasicAuth(auth);
541  if (!authorization.isEmpty()) {
542  int pos = authorization.indexOf(':');
543  if (pos == -1) {
544  ret.user = QString::fromLatin1(authorization);
545  } else {
546  ret.user = QString::fromLatin1(authorization.left(pos));
547  ret.password = QString::fromLatin1(authorization.mid(pos + 1));
548  }
549  }
550  return ret;
551 }
552 
553 QDebug operator<<(QDebug debug, const Headers &headers)
554 {
555  const auto data = headers.data();
556  const bool oldSetting = debug.autoInsertSpaces();
557  debug.nospace() << "Headers[";
558  for (auto it = data.begin(); it != data.end(); ++it) {
559  debug << '(' << it->key + '=' + it->value << ')';
560  }
561  debug << ']';
562  debug.setAutoInsertSpaces(oldSetting);
563  return debug.maybeSpace();
564 }
void setTimeSpec(Qt::TimeSpec spec)
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:211
void setAutoInsertSpaces(bool b)
QDateTime date() const
Definition: headers.cpp:188
QDateTime toUTC() const const
bool operator==(const Headers &other) const noexcept
Definition: headers.cpp:509
void setProxyAuthenticate(const QByteArray &value)
Definition: headers.cpp:332
QByteArray trimmed() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
QByteArray contentType() const
Definition: headers.cpp:68
iterator erase(const_iterator begin, const_iterator end)
QByteArray proxyAuthorizationBasic() const
Definition: headers.cpp:383
QByteArray toUpper() const const
void setContentEncoding(const QByteArray &encoding)
Definition: headers.cpp:63
bool isEmpty() const const
QDebug & nospace()
QByteArray contentDisposition() const noexcept
Definition: headers.cpp:34
Container for HTTP headers.
Definition: headers.h:23
QByteArray lastModified() const noexcept
Definition: headers.cpp:267
void setLastModified(const QByteArray &value)
Definition: headers.cpp:272
QByteArray proxyAuthorization() const noexcept
Definition: headers.cpp:378
QDateTime toDateTime(const QString &string, FormatType format) const const
QByteArrayList headers(QByteArrayView key) const
Definition: headers.cpp:419
qsizetype length() const const
QByteArray referer() const noexcept
Definition: headers.cpp:311
void setWwwAuthenticate(const QByteArray &value)
Definition: headers.cpp:327
QByteArray authorizationBasic() const
Definition: headers.cpp:354
QString toString(QDate date, FormatType format) const const
void removeHeader(QByteArrayView key)
Definition: headers.cpp:471
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QStringList headersAsStrings(QByteArrayView key) const
Definition: headers.cpp:428
bool contentIsText() const
Definition: headers.cpp:130
void setCacheControl(const QByteArray &value)
Definition: headers.cpp:39
QByteArray contentTypeCharset() const
Definition: headers.cpp:82
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:44
bool contentIsXHtml() const
Definition: headers.cpp:142
QByteArray operator[](QByteArrayView key) const noexcept
Definition: headers.cpp:504
void setContentType(const QByteArray &contentType)
Definition: headers.cpp:77
bool contentIsXml() const
Definition: headers.cpp:149
void setServer(const QByteArray &value)
Definition: headers.cpp:291
CaseInsensitive
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QByteArray setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:178
The Cutelyst namespace holds all public Cutelyst API.
QVector< HeaderKeyValue > data() const
Definition: headers.h:419
QByteArray sliced(qsizetype pos) const const
QDebug & maybeSpace()
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray authorizationBearer() const
Definition: headers.cpp:342
QByteArray & append(QByteArrayView data)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QByteArray connection() const noexcept
Definition: headers.cpp:296
QByteArray setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:364
QByteArray userAgent() const noexcept
Definition: headers.cpp:306
QByteArray ifModifiedSince() const noexcept
Definition: headers.cpp:206
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:461
QByteArray contentEncoding() const noexcept
Definition: headers.cpp:58
QByteArray header(QByteArrayView key) const noexcept
Definition: headers.cpp:393
QString fromLatin1(QByteArrayView str)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QByteArray left(qsizetype len) const const
const_iterator cend() const const
QByteArray toLatin1() const const
Headers() noexcept=default
QByteArray host() const noexcept
Definition: headers.cpp:301
bool ifNoneMatch(const QByteArray &etag) const
Definition: headers.cpp:251
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
void append(QList< T > &&value)
bool autoInsertSpaces() const const
qint64 contentLength() const
Definition: headers.cpp:164
QString headerAsString(QByteArrayView key) const
Definition: headers.cpp:401
bool contentIsJson() const
Definition: headers.cpp:155
void setReferer(const QByteArray &value)
Definition: headers.cpp:316
void setContentDispositionAttachment(const QByteArray &filename={})
Definition: headers.cpp:49
QByteArray toBase64(Base64Options options) const const
void setContentLength(qint64 value)
Definition: headers.cpp:173
void setContentTypeCharset(const QByteArray &charset)
Definition: headers.cpp:97
bool contains(QByteArrayView key) const noexcept
Definition: headers.cpp:477
bool ifMatch(const QByteArray &etag) const
Definition: headers.cpp:240
Authorization proxyAuthorizationBasicObject() const
Definition: headers.cpp:388
QByteArray & remove(qsizetype pos, qsizetype len)
Authorization authorizationBasicObject() const
Definition: headers.cpp:359
void setETag(const QByteArray &etag)
Definition: headers.cpp:262
bool endsWith(QByteArrayView bv) const const
bool contentIsHtml() const
Definition: headers.cpp:135
QByteArray server() const noexcept
Definition: headers.cpp:286
QByteArray authorization() const noexcept
Definition: headers.cpp:337
void setHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:437