cutelyst  3.9.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 
14 inline QString normalizeHeaderKey(const QString &field);
15 inline QByteArray decodeBasicAuth(const QString &auth);
16 inline Headers::Authorization decodeBasicAuthPair(const QString &auth);
17 
19  : m_data(other.m_data)
20 {
21 }
22 
24 {
25  return m_data.value(QStringLiteral("CONTENT_DISPOSITION"));
26 }
27 
29 {
30  m_data.replace(QStringLiteral("CACHE_CONTROL"), value);
31 }
32 
33 void Headers::setContentDisposition(const QString &contentDisposition)
34 {
35  m_data.replace(QStringLiteral("CONTENT_DISPOSITION"), contentDisposition);
36 }
37 
39 {
40  if (filename.isEmpty()) {
41  setContentDisposition(QStringLiteral("attachment"));
42  } else {
43  setContentDisposition(QLatin1String("attachment; filename=\"") + filename +
44  QLatin1Char('"'));
45  }
46 }
47 
49 {
50  return m_data.value(QStringLiteral("CONTENT_ENCODING"));
51 }
52 
53 void Headers::setContentEncoding(const QString &encoding)
54 {
55  m_data.replace(QStringLiteral("CONTENT_ENCODING"), encoding);
56 }
57 
59 {
60  QString ret;
61  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
62  if (it != m_data.constEnd()) {
63  const QString &ct = it.value();
64  ret = ct.mid(0, ct.indexOf(QLatin1Char(';'))).toLower();
65  }
66  return ret;
67 }
68 
69 void Headers::setContentType(const QString &contentType)
70 {
71  m_data.replace(QStringLiteral("CONTENT_TYPE"), contentType);
72 }
73 
75 {
76  QString ret;
77  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
78  if (it != m_data.constEnd()) {
79  const QString &contentType = it.value();
80  int pos = contentType.indexOf(u"charset=", 0, Qt::CaseInsensitive);
81  if (pos != -1) {
82  int endPos = contentType.indexOf(u';', pos);
83  ret = contentType.mid(pos + 8, endPos).trimmed().toUpper();
84  }
85  }
86 
87  return ret;
88 }
89 
91 {
92  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
93  if (it == m_data.constEnd() || (it.value().isEmpty() && !charset.isEmpty())) {
94  m_data.replace(QStringLiteral("CONTENT_TYPE"), QLatin1String("charset=") + charset);
95  return;
96  }
97 
98  QString contentType = it.value();
99  int pos = contentType.indexOf(QLatin1String("charset="), 0, Qt::CaseInsensitive);
100  if (pos != -1) {
101  int endPos = contentType.indexOf(QLatin1Char(';'), pos);
102  if (endPos == -1) {
103  if (charset.isEmpty()) {
104  int lastPos = contentType.lastIndexOf(QLatin1Char(';'), pos);
105  if (lastPos == -1) {
106  m_data.remove(QStringLiteral("CONTENT_TYPE"));
107  return;
108  } else {
109  contentType.remove(lastPos, contentType.length() - lastPos);
110  }
111  } else {
112  contentType.replace(pos + 8, contentType.length() - pos + 8, charset);
113  }
114  } else {
115  contentType.replace(pos + 8, endPos, charset);
116  }
117  } else if (!charset.isEmpty()) {
118  contentType.append(QLatin1String("; charset=") + charset);
119  }
120  m_data.replace(QStringLiteral("CONTENT_TYPE"), contentType);
121 }
122 
124 {
125  return m_data.value(QStringLiteral("CONTENT_TYPE")).startsWith(u"text/");
126 }
127 
129 {
130  const QString ct = contentType();
131  return ct.compare(u"text/html") == 0 || ct.compare(u"application/xhtml+xml") == 0 ||
132  ct.compare(u"application/vnd.wap.xhtml+xml") == 0;
133 }
134 
136 {
137  const QString ct = contentType();
138  return ct.compare(u"application/xhtml+xml") == 0 ||
139  ct.compare(u"application/vnd.wap.xhtml+xml") == 0;
140 }
141 
143 {
144  const QString ct = contentType();
145  return ct.compare(u"text/xml") == 0 || ct.compare(u"application/xml") == 0 ||
146  ct.endsWith(u"xml");
147 }
148 
150 {
151  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
152  if (it != m_data.constEnd()) {
153  return it.value().compare(u"application/json") == 0;
154  }
155  return false;
156 }
157 
159 {
160  auto it = m_data.constFind(QStringLiteral("CONTENT_LENGTH"));
161  if (it != m_data.constEnd()) {
162  return it.value().toLongLong();
163  }
164  return -1;
165 }
166 
167 void Headers::setContentLength(qint64 value)
168 {
169  m_data.replace(QStringLiteral("CONTENT_LENGTH"), QString::number(value));
170 }
171 
173 {
174  // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
175  // and follow RFC 822
176  QString dt = QLocale::c().toString(date.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
177  m_data.replace(QStringLiteral("DATE"), dt);
178  return dt;
179 }
180 
182 {
183  QDateTime ret;
184  auto it = m_data.constFind(QStringLiteral("DATE"));
185  if (it != m_data.constEnd()) {
186  const QString &date = it.value();
187 
188  if (date.endsWith(u" GMT")) {
189  ret = QLocale::c().toDateTime(date.left(date.size() - 4),
190  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
191  } else {
192  ret = QLocale::c().toDateTime(date, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
193  }
194  ret.setTimeSpec(Qt::UTC);
195  }
196 
197  return ret;
198 }
199 
201 {
202  return m_data.value(QStringLiteral("IF_MODIFIED_SINCE"));
203 }
204 
206 {
207  QDateTime ret;
208  auto it = m_data.constFind(QStringLiteral("IF_MODIFIED_SINCE"));
209  if (it != m_data.constEnd()) {
210  const QString &ifModifiedStr = it.value();
211 
212  if (ifModifiedStr.endsWith(u" GMT")) {
213  ret = QLocale::c().toDateTime(ifModifiedStr.left(ifModifiedStr.size() - 4),
214  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
215  } else {
216  ret =
217  QLocale::c().toDateTime(ifModifiedStr, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss"));
218  }
219  ret.setTimeSpec(Qt::UTC);
220  }
221 
222  return ret;
223 }
224 
225 bool Headers::ifModifiedSince(const QDateTime &lastModified) const
226 {
227  auto it = m_data.constFind(QStringLiteral("IF_MODIFIED_SINCE"));
228  if (it != m_data.constEnd()) {
229  return it.value() !=
230  QLocale::c().toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
231  }
232  return true;
233 }
234 
235 bool Headers::ifMatch(const QString &etag) const
236 {
237  auto it = m_data.constFind(QStringLiteral("IF_MATCH"));
238  if (it != m_data.constEnd()) {
239  const auto clientETag = QStringView(it.value());
240  return clientETag.mid(1, clientETag.size() - 2) == etag ||
241  clientETag.mid(3, clientETag.size() - 4) == etag; // Weak ETag
242  }
243  return true;
244 }
245 
246 bool Headers::ifNoneMatch(const QString &etag) const
247 {
248  auto it = m_data.constFind(QStringLiteral("IF_NONE_MATCH"));
249  if (it != m_data.constEnd()) {
250  const auto clientETag = QStringView(it.value());
251  return clientETag.mid(1, clientETag.size() - 2) == etag ||
252  clientETag.mid(3, clientETag.size() - 4) == etag; // Weak ETag
253  }
254  return false;
255 }
256 
257 void Headers::setETag(const QString &etag)
258 {
259  m_data.replace(QStringLiteral("ETAG"), QLatin1Char('"') + etag + QLatin1Char('"'));
260 }
261 
263 {
264  return m_data.value(QStringLiteral("LAST_MODIFIED"));
265 }
266 
268 {
269  m_data.replace(QStringLiteral("LAST_MODIFIED"), value);
270 }
271 
273 {
274  // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
275  // and follow RFC 822
276  auto dt = QLocale::c().toString(lastModified.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT");
277  setLastModified(dt);
278  return dt;
279 }
280 
282 {
283  return m_data.value(QStringLiteral("SERVER"));
284 }
285 
286 void Headers::setServer(const QString &value)
287 {
288  m_data.replace(QStringLiteral("SERVER"), value);
289 }
290 
292 {
293  return m_data.value(QStringLiteral("CONNECTION"));
294 }
295 
297 {
298  return m_data.value(QStringLiteral("HOST"));
299 }
300 
302 {
303  return m_data.value(QStringLiteral("USER_AGENT"));
304 }
305 
307 {
308  return m_data.value(QStringLiteral("REFERER"));
309 }
310 
311 void Headers::setReferer(const QString &uri)
312 {
313  int fragmentPos = uri.indexOf(QLatin1Char('#'));
314  if (fragmentPos != -1) {
315  // Strip fragment per RFC 2616, section 14.36.
316  m_data.replace(QStringLiteral("REFERER"), uri.mid(0, fragmentPos));
317  } else {
318  m_data.replace(QStringLiteral("REFERER"), uri);
319  }
320 }
321 
323 {
324  m_data.replace(QStringLiteral("WWW_AUTHENTICATE"), value);
325 }
326 
328 {
329  m_data.replace(QStringLiteral("PROXY_AUTHENTICATE"), value);
330 }
331 
333 {
334  return m_data.value(QStringLiteral("AUTHORIZATION"));
335 }
336 
338 {
339  QString ret;
340  auto it = m_data.constFind(QStringLiteral("AUTHORIZATION"));
341  if (it != m_data.constEnd() && it.value().startsWith(u"Bearer ")) {
342  ret = it.value().mid(7);
343  }
344  return ret;
345 }
346 
348 {
349  return QString::fromLatin1(decodeBasicAuth(authorization()));
350 }
351 
353 {
354  return decodeBasicAuthPair(authorization());
355 }
356 
357 QString Headers::setAuthorizationBasic(const QString &username, const QString &password)
358 {
359  QString ret;
360  if (username.contains(QLatin1Char(':'))) {
361  qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
362  return ret;
363  }
364 
365  const QString result = username + QLatin1Char(':') + password;
366  ret = QLatin1String("Basic ") + QString::fromLatin1(result.toLatin1().toBase64());
367  m_data.replace(QStringLiteral("AUTHORIZATION"), ret);
368  return ret;
369 }
370 
372 {
373  return m_data.value(QStringLiteral("PROXY_AUTHORIZATION"));
374 }
375 
377 {
378  return QString::fromLatin1(decodeBasicAuth(proxyAuthorization()));
379 }
380 
382 {
383  return decodeBasicAuthPair(proxyAuthorization());
384 }
385 
386 QString Headers::header(const QString &field) const
387 {
388  return m_data.value(normalizeHeaderKey(field));
389 }
390 
391 QString Headers::header(const QString &field, const QString &defaultValue) const
392 {
393  return m_data.value(normalizeHeaderKey(field), defaultValue);
394 }
395 
396 void Headers::setHeader(const QString &field, const QString &value)
397 {
398  m_data.replace(normalizeHeaderKey(field), value);
399 }
400 
401 void Headers::setHeader(const QString &field, const QStringList &values)
402 {
403  setHeader(field, values.join(QLatin1String(", ")));
404 }
405 
406 void Headers::pushHeader(const QString &field, const QString &value)
407 {
408  m_data.insert(normalizeHeaderKey(field), value);
409 }
410 
411 void Headers::pushHeader(const QString &field, const QStringList &values)
412 {
413  m_data.insert(normalizeHeaderKey(field), values.join(QLatin1String(", ")));
414 }
415 
416 void Headers::removeHeader(const QString &field)
417 {
418  m_data.remove(normalizeHeaderKey(field));
419 }
420 
421 bool Headers::contains(const QString &field) const
422 {
423  return m_data.contains(normalizeHeaderKey(field));
424 }
425 
427 {
428  return m_data.value(normalizeHeaderKey(key));
429 }
430 
431 QString normalizeHeaderKey(const QString &field)
432 {
433  QString key = field;
434  int i = 0;
435  while (i < key.size()) {
436  QChar c = key[i];
437  if (c.isLetter()) {
438  if (c.isLower()) {
439  key[i] = c.toUpper();
440  }
441  } else if (c == u'-') {
442  key[i] = u'_';
443  }
444  ++i;
445  }
446  return key;
447 }
448 
449 QByteArray decodeBasicAuth(const QString &auth)
450 {
451  QByteArray ret;
452  if (!auth.isEmpty() && auth.startsWith(u"Basic ")) {
453  int pos = auth.lastIndexOf(u' ');
454  if (pos != -1) {
455  ret = QByteArray::fromBase64(auth.mid(pos).toLatin1());
456  }
457  }
458  return ret;
459 }
460 
461 Headers::Authorization decodeBasicAuthPair(const QString &auth)
462 {
464  const QByteArray authorization = decodeBasicAuth(auth);
465  if (!authorization.isEmpty()) {
466  int pos = authorization.indexOf(':');
467  if (pos == -1) {
468  ret.user = QString::fromLatin1(authorization);
469  } else {
470  ret.user = QString::fromLatin1(authorization.left(pos));
471  ret.password = QString::fromLatin1(authorization.mid(pos + 1));
472  }
473  }
474  return ret;
475 }
476 
477 QDebug operator<<(QDebug debug, const Headers &headers)
478 {
479  const QMultiHash<QString, QString> data = headers.data();
480  const bool oldSetting = debug.autoInsertSpaces();
481  debug.nospace() << "Headers[";
482  for (auto it = data.constBegin(); it != data.constEnd(); ++it) {
483  debug << '(' << Engine::camelCaseHeader(it.key()) + QLatin1Char('=') + it.value() << ')';
484  }
485  debug << ']';
486  debug.setAutoInsertSpaces(oldSetting);
487  return debug.maybeSpace();
488 }
const_iterator constFind(const Key &key, const T &value) const const
void setTimeSpec(Qt::TimeSpec spec)
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:406
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString contentEncoding() const
Definition: headers.cpp:48
const_iterator constBegin() const const
QString & append(QChar ch)
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:205
QString toUpper() const const
void setAutoInsertSpaces(bool b)
QDateTime date() const
Definition: headers.cpp:181
QDateTime toUTC() const const
QString operator[](const QString &key) const
Definition: headers.cpp:426
QString proxyAuthorization() const
Definition: headers.cpp:371
QString contentType() const
Definition: headers.cpp:58
const_iterator constEnd() const const
static QString camelCaseHeader(const QString &headerKey)
Definition: engine.h:103
qsizetype size() const const
QStringView mid(qsizetype start, qsizetype length) const const
bool isEmpty() const const
T value(const Key &key) const const
QDebug & nospace()
char32_t toUpper(char32_t ucs4)
QString server() const
Definition: headers.cpp:281
QString join(QChar separator) const const
void setContentDispositionAttachment(const QString &filename=QString())
Definition: headers.cpp:38
QDateTime toDateTime(const QString &string, FormatType format) const const
iterator replace(const Key &key, const T &value)
QString authorizationBearer() const
Definition: headers.cpp:337
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString toString(QDate date, FormatType format) const const
bool isLetter(char32_t ucs4)
bool contains(const Key &key, const T &value) const const
bool contentIsText() const
Definition: headers.cpp:123
QLocale c()
QString contentTypeCharset() const
Definition: headers.cpp:74
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
QString number(double n, char format, int precision)
QString userAgent() const
Definition: headers.cpp:301
QString connection() const
Definition: headers.cpp:291
QString host() const
Definition: headers.cpp:296
bool contentIsXHtml() const
Definition: headers.cpp:135
void setProxyAuthenticate(const QString &value)
Definition: headers.cpp:327
bool contentIsXml() const
Definition: headers.cpp:142
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype remove(const Key &key)
CaseInsensitive
void removeHeader(const QString &field)
Definition: headers.cpp:416
bool isEmpty() const const
QString trimmed() const const
QString setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:172
QString header(const QString &field) const
Definition: headers.cpp:386
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:396
bool ifNoneMatch(const QString &etag) const
Definition: headers.cpp:246
void setLastModified(const QString &value)
Definition: headers.cpp:267
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
iterator insert(const Key &key, const T &value)
QDebug & maybeSpace()
QByteArray mid(qsizetype pos, qsizetype len) const const
void setCacheControl(const QString &value)
Definition: headers.cpp:28
void setContentDisposition(const QString &contentDisposition)
Definition: headers.cpp:33
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool ifMatch(const QString &etag) const
Definition: headers.cpp:235
QMultiHash< QString, QString > data() const
Definition: headers.h:382
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString proxyAuthorizationBasic() const
Definition: headers.cpp:376
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void setContentType(const QString &contentType)
Definition: headers.cpp:69
QString fromLatin1(QByteArrayView str)
QByteArray left(qsizetype len) const const
QByteArray toLatin1() const const
QString mid(qsizetype position, qsizetype n) const const
void setWwwAuthenticate(const QString &value)
Definition: headers.cpp:322
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
void setServer(const QString &value)
Definition: headers.cpp:286
QString setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:357
bool autoInsertSpaces() const const
qint64 contentLength() const
Definition: headers.cpp:158
void setETag(const QString &etag)
Definition: headers.cpp:257
bool contains(const QString &field) const
Definition: headers.cpp:421
QString contentDisposition() const
Definition: headers.cpp:23
void setContentTypeCharset(const QString &charset)
Definition: headers.cpp:90
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString authorizationBasic() const
Definition: headers.cpp:347
bool contentIsJson() const
Definition: headers.cpp:149
void setContentEncoding(const QString &encoding)
Definition: headers.cpp:53
qsizetype length() const const
QString left(qsizetype n) const const
void setReferer(const QString &value)
Definition: headers.cpp:311
QByteArray toBase64(Base64Options options) const const
QString ifModifiedSince() const
Definition: headers.cpp:200
void setContentLength(qint64 value)
Definition: headers.cpp:167
Authorization proxyAuthorizationBasicObject() const
Definition: headers.cpp:381
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString authorization() const
Definition: headers.cpp:332
Authorization authorizationBasicObject() const
Definition: headers.cpp:352
bool isLower(char32_t ucs4)
QString referer() const
Definition: headers.cpp:306
QString lastModified() const
Definition: headers.cpp:262
bool contentIsHtml() const
Definition: headers.cpp:128