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