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 
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  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 
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 
455 void Headers::setHeader(const QByteArray &field, const QByteArrayList &values)
456 {
457  setHeader(field, values.join(", "));
458 }
459 
460 void Headers::pushHeader(const QByteArray &key, const QByteArray &value)
461 {
462  m_data.push_back({key, value});
463 }
464 
465 void Headers::pushHeader(const QByteArray &key, const QByteArrayList &values)
466 {
467  m_data.push_back({key, values.join(", ")});
468 }
469 
471 {
472  m_data.removeIf(
473  [key](HeaderKeyValue entry) { return key.compare(entry.key, Qt::CaseInsensitive) == 0; });
474 }
475 
476 bool Headers::contains(QByteArrayView key) const noexcept
477 {
478  auto result = findHeaderConst(m_data, key);
479  return result != m_data.end();
480 }
481 
482 QByteArrayList 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 
504 {
505  return header(key);
506 }
507 
508 bool 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 
524 QByteArray 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 
536 Headers::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 
552 QDebug 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
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
QVector< HeaderKeyValue > data() const
Definition: headers.h:419
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.
QByteArray & append(QByteArrayView data)
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
bool endsWith(QByteArrayView bv) const const
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & remove(qsizetype pos, qsizetype len)
QByteArray & replace(QByteArrayView before, QByteArrayView after)
QByteArray sliced(qsizetype pos) const const
bool startsWith(QByteArrayView bv) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
QByteArray toLower() const const
QByteArray toUpper() const const
QByteArray trimmed() const const
QByteArray join(QByteArrayView separator) const const
int compare(QByteArrayView bv, Qt::CaseSensitivity cs) const const
void setTimeSpec(Qt::TimeSpec spec)
QDateTime toUTC() const const
bool autoInsertSpaces() const const
QDebug & maybeSpace()
QDebug & nospace()
void setAutoInsertSpaces(bool b)
void append(QList::parameter_type value)
QLocale c()
QDateTime toDateTime(const QString &string, QLocale::FormatType format) const const
QString toString(QDate date, QLocale::FormatType format) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QByteArray toLatin1() const const
CaseInsensitive