Cutelyst  3.1.0
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 Headers::Authorization 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.replace(QStringLiteral("CACHE_CONTROL"), value);
44 }
45 
46 void Headers::setContentDisposition(const QString &contentDisposition)
47 {
48  m_data.replace(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.replace(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.replace(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.replace(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.replace(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.replace(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
191  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT"));
192  m_data.replace(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.mid(1, clientETag.size() - 2) == etag ||
257  clientETag.mid(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.mid(1, clientETag.size() - 2) == etag ||
268  clientETag.mid(3, clientETag.size() - 4) == etag; // Weak ETag
269  }
270  return false;
271 }
272 
273 void Headers::setETag(const QString &etag)
274 {
275  m_data.replace(QStringLiteral("ETAG"), QLatin1Char('"') + etag + QLatin1Char('"'));
276 }
277 
279 {
280  return m_data.value(QStringLiteral("LAST_MODIFIED"));
281 }
282 
284 {
285  m_data.replace(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  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.replace(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.replace(QStringLiteral("REFERER"), uri.mid(0, fragmentPos));
334  } else {
335  m_data.replace(QStringLiteral("REFERER"), uri);
336  }
337 }
338 
340 {
341  m_data.replace(QStringLiteral("WWW_AUTHENTICATE"), value);
342 }
343 
345 {
346  m_data.replace(QStringLiteral("PROXY_AUTHENTICATE"), value);
347 }
348 
350 {
351  return m_data.value(QStringLiteral("AUTHORIZATION"));
352 }
353 
355 {
356  QString ret;
357  auto it = m_data.constFind(QStringLiteral("AUTHORIZATION"));
358  if (it != m_data.constEnd() && it.value().startsWith(QLatin1String("Bearer "))) {
359  ret = it.value().mid(7);
360  }
361  return ret;
362 }
363 
365 {
366  return QString::fromLatin1(decodeBasicAuth(authorization()));
367 }
368 
370 {
371  return decodeBasicAuthPair(authorization());
372 }
373 
374 QString Headers::setAuthorizationBasic(const QString &username, const QString &password)
375 {
376  QString ret;
377  if (username.contains(QLatin1Char(':'))) {
378  qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
379  return ret;
380  }
381 
382  const QString result = username + QLatin1Char(':') + password;
383  ret = QLatin1String("Basic ") + QString::fromLatin1(result.toLatin1().toBase64());
384  m_data.replace(QStringLiteral("AUTHORIZATION"), ret);
385  return ret;
386 }
387 
389 {
390  return m_data.value(QStringLiteral("PROXY_AUTHORIZATION"));
391 }
392 
394 {
395  return QString::fromLatin1(decodeBasicAuth(proxyAuthorization()));
396 }
397 
399 {
400  return decodeBasicAuthPair(proxyAuthorization());
401 }
402 
403 QString Headers::header(const QString &field) const
404 {
405  return m_data.value(normalizeHeaderKey(field));
406 }
407 
408 QString Headers::header(const QString &field, const QString &defaultValue) const
409 {
410  return m_data.value(normalizeHeaderKey(field), defaultValue);
411 }
412 
413 void Headers::setHeader(const QString &field, const QString &value)
414 {
415  m_data.replace(normalizeHeaderKey(field), value);
416 }
417 
418 void Headers::setHeader(const QString &field, const QStringList &values)
419 {
420  setHeader(field, values.join(QLatin1String(", ")));
421 }
422 
423 void Headers::pushHeader(const QString &field, const QString &value)
424 {
425  m_data.insert(normalizeHeaderKey(field), value);
426 }
427 
428 void Headers::pushHeader(const QString &field, const QStringList &values)
429 {
430  m_data.insert(normalizeHeaderKey(field), values.join(QLatin1String(", ")));
431 }
432 
433 void Headers::removeHeader(const QString &field)
434 {
435  m_data.remove(normalizeHeaderKey(field));
436 }
437 
438 bool Headers::contains(const QString &field)
439 {
440  return m_data.contains(normalizeHeaderKey(field));
441 }
442 
444 {
445  return m_data.value(normalizeHeaderKey(key));
446 }
447 
448 QString normalizeHeaderKey(const QString &field)
449 {
450  QString key = field;
451  int i = 0;
452  while (i < key.size()) {
453  QChar c = key[i];
454  if (c.isLetter()) {
455  if (c.isLower()) {
456  key[i] = c.toUpper();
457  }
458  } else if (c == QLatin1Char('-')) {
459  key[i] = QLatin1Char('_');
460  }
461  ++i;
462  }
463  return key;
464 }
465 
466 QByteArray decodeBasicAuth(const QString &auth)
467 {
468  QByteArray ret;
469  if (!auth.isEmpty() && auth.startsWith(QLatin1String("Basic "))) {
470  int pos = auth.lastIndexOf(QLatin1Char(' '));
471  if (pos != -1) {
472  ret = QByteArray::fromBase64(auth.mid(pos).toLatin1());
473  }
474  }
475  return ret;
476 }
477 
478 Headers::Authorization decodeBasicAuthPair(const QString &auth)
479 {
481  const QByteArray authorization = decodeBasicAuth(auth);
482  if (!authorization.isEmpty()) {
483  int pos = authorization.indexOf(':');
484  if (pos == -1) {
485  ret.user = QString::fromLatin1(authorization);
486  } else {
487  ret.user = QString::fromLatin1(authorization.left(pos));
488  ret.password = QString::fromLatin1(authorization.mid(pos + 1));
489  }
490  }
491  return ret;
492 }
493 
494 QDebug operator<<(QDebug debug, const Headers &headers)
495 {
496  const QMultiHash<QString, QString> data = headers.data();
497  const bool oldSetting = debug.autoInsertSpaces();
498  debug.nospace() << "Headers[";
499  for (auto it = data.constBegin();
500  it != data.constEnd(); ++it) {
501  debug << '(' << Engine::camelCaseHeader(it.key()) + QLatin1Char('=') + it.value() << ')';
502  }
503  debug << ']';
504  debug.setAutoInsertSpaces(oldSetting);
505  return debug.maybeSpace();
506 }
static QString camelCaseHeader(const QString &headerKey)
Definition: engine.h:116
bool ifMatch(const QString &etag) const
Definition: headers.cpp:251
QString contentDisposition() const
Definition: headers.cpp:36
QString contentEncoding() const
Definition: headers.cpp:60
bool contentIsXHtml() const
Definition: headers.cpp:148
QString operator[](const QString &key) const
Definition: headers.cpp:443
QString connection() const
Definition: headers.cpp:308
QString referer() const
Definition: headers.cpp:323
void setETag(const QString &etag)
Definition: headers.cpp:273
void setLastModified(const QString &value)
Definition: headers.cpp:283
void setContentLength(qint64 value)
Definition: headers.cpp:181
void setContentDisposition(const QString &contentDisposition)
Definition: headers.cpp:46
Authorization authorizationBasicObject() const
Definition: headers.cpp:369
QString authorization() const
Definition: headers.cpp:349
QDateTime date() const
Definition: headers.cpp:196
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:423
bool ifNoneMatch(const QString &etag) const
Definition: headers.cpp:262
qint64 contentLength() const
Definition: headers.cpp:172
void setContentEncoding(const QString &encoding)
Definition: headers.cpp:65
void setContentType(const QString &contentType)
Definition: headers.cpp:81
QString userAgent() const
Definition: headers.cpp:318
QString authorizationBearer() const
Definition: headers.cpp:354
QString server() const
Definition: headers.cpp:298
void setReferer(const QString &value)
Definition: headers.cpp:328
void setWwwAuthenticate(const QString &value)
Definition: headers.cpp:339
QString setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:186
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:221
Authorization proxyAuthorizationBasicObject() const
Definition: headers.cpp:398
QString host() const
Definition: headers.cpp:313
QString setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:374
void removeHeader(const QString &field)
Definition: headers.cpp:433
QMultiHash< QString, QString > data() const
Definition: headers.h:390
bool contentIsText() const
Definition: headers.cpp:135
QString header(const QString &field) const
Definition: headers.cpp:403
bool contains(const QString &field)
Definition: headers.cpp:438
QString ifModifiedSince() const
Definition: headers.cpp:216
void setServer(const QString &value)
Definition: headers.cpp:303
bool contentIsHtml() const
Definition: headers.cpp:140
void setCacheControl(const QString &value)
Definition: headers.cpp:41
QString authorizationBasic() const
Definition: headers.cpp:364
QString contentTypeCharset() const
Definition: headers.cpp:86
void setContentDispositionAttachment(const QString &filename=QString())
Definition: headers.cpp:51
QString proxyAuthorization() const
Definition: headers.cpp:388
QString proxyAuthorizationBasic() const
Definition: headers.cpp:393
QString lastModified() const
Definition: headers.cpp:278
bool contentIsXml() const
Definition: headers.cpp:155
bool contentIsJson() const
Definition: headers.cpp:163
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:413
void setContentTypeCharset(const QString &charset)
Definition: headers.cpp:102
QString contentType() const
Definition: headers.cpp:70
void setProxyAuthenticate(const QString &value)
Definition: headers.cpp:344
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
int indexOf(char ch, int from) const const
bool isEmpty() const const
QByteArray left(int len) const const
QByteArray mid(int pos, int len) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
bool isLetter() const const
bool isLower() const const
QChar toUpper() const const
void setTimeSpec(Qt::TimeSpec spec)
QDateTime toUTC() const const
bool autoInsertSpaces() const const
QDebug & maybeSpace()
QDebug & nospace()
void setAutoInsertSpaces(bool b)
QLocale c()
QDateTime toDateTime(const QString &string, QLocale::FormatType format) const const
QString toString(qlonglong i) const const
typename QHash< Key, T >::const_iterator constFind(const Key &key, const T &value) const const
bool contains(const Key &key, const T &value) const const
typename QHash< Key, T >::iterator insert(const Key &key, const T &value)
int remove(const Key &key, const T &value)
typename QHash< Key, T >::iterator replace(const Key &key, const T &value)
QString & append(QChar ch)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString left(int n) const const
int length() const const
QString mid(int position, int n) const const
QString number(int n, int base)
QString & remove(int position, int n)
QString & replace(int position, int n, QChar after)
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QString toLower() const const
QString toUpper() const const
QString trimmed() const const
QString join(const QString &separator) const const
CaseInsensitive