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 
12 using namespace Cutelyst;
13 
14 inline QByteArray decodeBasicAuth(const QByteArray &auth);
15 inline Headers::Authorization decodeBasicAuthPair(const QByteArray &auth);
16 
17 namespace {
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 
28 Headers::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 
43 void 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 
76 void 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 
172 void Headers::setContentLength(qint64 value)
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 
228 bool 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 
239 bool 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 
250 bool 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 
261 void Headers::setETag(const QByteArray &etag)
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 
285 QByteArray Headers::server() const noexcept
286 {
287  return header("Server");
288 }
289 
290 void Headers::setServer(const QByteArray &value)
291 {
292  setHeader("Server"_qba, value);
293 }
294 
296 {
297  return header("Connection");
298 }
299 
300 QByteArray Headers::host() const noexcept
301 {
302  return header("Host");
303 }
304 
306 {
307  return header("User-Agent");
308 }
309 
310 QByteArray Headers::referer() const noexcept
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 
363 QByteArray 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 
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 
401 {
402  return QString::fromLatin1(header(key));
403 }
404 
405 QByteArray 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 
436 void 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 
453 void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
454 {
455  setHeader(field, values.join(", "));
456 }
457 
458 void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
459 {
460  m_data.push_back({key, value});
461 }
462 
463 void 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 
474 bool Headers::contains(QByteArrayView key) const noexcept
475 {
476  auto result = findHeaderConst(m_data, key);
477  return result != m_data.end();
478 }
479 
480 QByteArrayList 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 
506 bool 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 
522 QByteArray 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 
534 Headers::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 
550 QDebug 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 }
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:210
void setAutoInsertSpaces(bool b)
QDateTime date() const
Definition: headers.cpp:187
QDateTime toUTC() const const
bool endsWith(char ch) const const
bool operator==(const Headers &other) const noexcept
Definition: headers.cpp:506
void setProxyAuthenticate(const QByteArray &value)
Definition: headers.cpp:331
QString toString(qlonglong i) const const
QByteArray trimmed() const const
qsizetype lastIndexOf(char ch, qsizetype from) const const
QByteArray contentType() const
Definition: headers.cpp:67
QByteArray proxyAuthorizationBasic() const
Definition: headers.cpp:382
QByteArray toUpper() const const
void setContentEncoding(const QByteArray &encoding)
Definition: headers.cpp:62
bool isEmpty() const const
bool startsWith(QByteArrayView bv) const const
QDebug & nospace()
QByteArray contentDisposition() const noexcept
Definition: headers.cpp:33
Container for HTTP headers.
Definition: headers.h:23
QByteArray lastModified() const noexcept
Definition: headers.cpp:266
void setLastModified(const QByteArray &value)
Definition: headers.cpp:271
QByteArray proxyAuthorization() const noexcept
Definition: headers.cpp:377
QDateTime toDateTime(const QString &string, QLocale::FormatType format) const const
QByteArrayList headers(QByteArrayView key) const
Definition: headers.cpp:418
qsizetype length() const const
QByteArray referer() const noexcept
Definition: headers.cpp:310
void setWwwAuthenticate(const QByteArray &value)
Definition: headers.cpp:326
QByteArray authorizationBasic() const
Definition: headers.cpp:353
void removeHeader(QByteArrayView key)
Definition: headers.cpp:468
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QStringList headersAsStrings(QByteArrayView key) const
Definition: headers.cpp:427
bool contentIsText() const
Definition: headers.cpp:129
void setCacheControl(const QByteArray &value)
Definition: headers.cpp:38
void setTimeSpec(Qt::TimeSpec spec)
QByteArray contentTypeCharset() const
Definition: headers.cpp:81
QLocale c()
qsizetype indexOf(char ch, qsizetype from) const const
QByteArray join() const const
void append(QList::parameter_type value)
void setContentDisposition(const QByteArray &contentDisposition)
Definition: headers.cpp:43
bool contentIsXHtml() const
Definition: headers.cpp:141
QByteArray operator[](QByteArrayView key) const noexcept
Definition: headers.cpp:501
void setContentType(const QByteArray &contentType)
Definition: headers.cpp:76
bool contentIsXml() const
Definition: headers.cpp:148
void setServer(const QByteArray &value)
Definition: headers.cpp:290
CaseInsensitive
QByteArray number(int n, int base)
QByteArray & replace(qsizetype pos, qsizetype len, const char *after, qsizetype alen)
QByteArray setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:177
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:341
QByteArray & append(char ch)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QByteArray connection() const noexcept
Definition: headers.cpp:295
QByteArray setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:363
QByteArray userAgent() const noexcept
Definition: headers.cpp:305
QByteArray ifModifiedSince() const noexcept
Definition: headers.cpp:205
void pushHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:458
QByteArray contentEncoding() const noexcept
Definition: headers.cpp:57
QByteArray header(QByteArrayView key) const noexcept
Definition: headers.cpp:392
QString fromLatin1(QByteArrayView str)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
QByteArray left(qsizetype len) const const
QByteArray toLatin1() const const
Headers() noexcept=default
QByteArray host() const noexcept
Definition: headers.cpp:300
bool ifNoneMatch(const QByteArray &etag) const
Definition: headers.cpp:250
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
bool autoInsertSpaces() const const
qint64 contentLength() const
Definition: headers.cpp:163
QString headerAsString(QByteArrayView key) const
Definition: headers.cpp:400
bool contentIsJson() const
Definition: headers.cpp:154
void setReferer(const QByteArray &value)
Definition: headers.cpp:315
void setContentDispositionAttachment(const QByteArray &filename={})
Definition: headers.cpp:48
QByteArray toBase64(QByteArray::Base64Options options) const const
void setContentLength(qint64 value)
Definition: headers.cpp:172
void setContentTypeCharset(const QByteArray &charset)
Definition: headers.cpp:96
bool contains(QByteArrayView key) const noexcept
Definition: headers.cpp:474
bool ifMatch(const QByteArray &etag) const
Definition: headers.cpp:239
Authorization proxyAuthorizationBasicObject() const
Definition: headers.cpp:387
QByteArray & remove(qsizetype pos, qsizetype len)
Authorization authorizationBasicObject() const
Definition: headers.cpp:358
void setETag(const QByteArray &etag)
Definition: headers.cpp:261
bool contentIsHtml() const
Definition: headers.cpp:134
QByteArray server() const noexcept
Definition: headers.cpp:285
QByteArray authorization() const noexcept
Definition: headers.cpp:336
void setHeader(const QByteArray &key, const QByteArray &value)
Definition: headers.cpp:436