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