Cutelyst  2.13.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 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 
41 void Headers::setCacheControl(const QString &value)
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 
51 void Headers::setContentDispositionAttachment(const QString &filename)
52 {
53  if (filename.isEmpty()) {
54  setContentDisposition(QStringLiteral("attachment"));
55  } else {
56  setContentDisposition(QLatin1String("attachment; filename=\"") + filename + QLatin1Char('"'));
57  }
58 }
59 
60 QString Headers::contentEncoding() const
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 
70 QString Headers::contentType() const
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 
102 void Headers::setContentTypeCharset(const QString &charset)
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 
186 QString Headers::setDateWithDateTime(const QDateTime &date)
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 
196 QDateTime Headers::date() const
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 
278 QString Headers::lastModified() const
279 {
280  return m_data.value(QStringLiteral("LAST_MODIFIED"));
281 }
282 
283 void Headers::setLastModified(const QString &value)
284 {
285  m_data.insert(QStringLiteral("LAST_MODIFIED"), value);
286 }
287 
288 QString Headers::setLastModified(const QDateTime &lastModified)
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 
298 QString Headers::server() const
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 
308 QString Headers::connection() const
309 {
310  return m_data.value(QStringLiteral("CONNECTION"));
311 }
312 
313 QString Headers::host() const
314 {
315  return m_data.value(QStringLiteral("HOST"));
316 }
317 
318 QString Headers::userAgent() const
319 {
320  return m_data.value(QStringLiteral("USER_AGENT"));
321 }
322 
323 QString Headers::referer() const
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 
339 void Headers::setWwwAuthenticate(const QString &value)
340 {
341  m_data.insert(QStringLiteral("WWW_AUTHENTICATE"), value);
342 }
343 
344 void Headers::setProxyAuthenticate(const QString &value)
345 {
346  m_data.insert(QStringLiteral("PROXY_AUTHENTICATE"), value);
347 }
348 
349 QString Headers::authorization() const
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 
433 QString &Headers::operator[](const QString &key)
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 }
Cutelyst::Headers::contentIsXHtml
bool contentIsXHtml() const
Definition: headers.cpp:148
Cutelyst::Headers::lastModified
QString lastModified() const
Definition: headers.cpp:278
Cutelyst::Headers::removeHeader
void removeHeader(const QString &field)
Definition: headers.cpp:423
Cutelyst::Headers::connection
QString connection() const
Definition: headers.cpp:308
Cutelyst::Headers::setLastModified
void setLastModified(const QString &value)
Definition: headers.cpp:283
Cutelyst::Headers::date
QDateTime date() const
Definition: headers.cpp:196
Cutelyst::Headers::setContentTypeCharset
void setContentTypeCharset(const QString &charset)
Definition: headers.cpp:102
Cutelyst::Headers::setServer
void setServer(const QString &value)
Definition: headers.cpp:303
Cutelyst::Headers::setContentLength
void setContentLength(qint64 value)
Definition: headers.cpp:181
Cutelyst::Headers::contentIsJson
bool contentIsJson() const
Definition: headers.cpp:163
Cutelyst::Headers::ifNoneMatch
bool ifNoneMatch(const QString &etag) const
Definition: headers.cpp:262
Cutelyst::Headers::ifModifiedSince
QString ifModifiedSince() const
Definition: headers.cpp:216
Cutelyst::Headers::setCacheControl
void setCacheControl(const QString &value)
Definition: headers.cpp:41
Cutelyst::Headers::pushHeader
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:413
Cutelyst::Headers::host
QString host() const
Definition: headers.cpp:313
Cutelyst::Headers::setContentDisposition
void setContentDisposition(const QString &contentDisposition)
Definition: headers.cpp:46
Cutelyst::Headers::server
QString server() const
Definition: headers.cpp:298
Cutelyst::Headers::setETag
void setETag(const QString &etag)
Definition: headers.cpp:273
Cutelyst::Headers::referer
QString referer() const
Definition: headers.cpp:323
Cutelyst::Headers::setProxyAuthenticate
void setProxyAuthenticate(const QString &value)
Definition: headers.cpp:344
Cutelyst::Headers::setContentEncoding
void setContentEncoding(const QString &encoding)
Definition: headers.cpp:65
Cutelyst::Headers::contains
bool contains(const QString &field)
Definition: headers.cpp:428
Cutelyst::Headers::setContentType
void setContentType(const QString &contentType)
Definition: headers.cpp:81
Cutelyst::Headers::authorizationBasicPair
std::pair< QString, QString > authorizationBasicPair() const
Definition: headers.cpp:359
Cutelyst::Headers::data
QHash< QString, QString > data() const
Definition: headers.h:380
Cutelyst::Headers
Definition: headers.h:29
Cutelyst::Headers::ifModifiedSinceDateTime
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:221
Cutelyst::Headers::proxyAuthorizationBasicPair
std::pair< QString, QString > proxyAuthorizationBasicPair() const
Definition: headers.cpp:388
Cutelyst::Headers::contentLength
qint64 contentLength() const
Definition: headers.cpp:172
Cutelyst::Headers::header
QString header(const QString &field) const
Definition: headers.cpp:393
Cutelyst::Headers::contentDisposition
QString contentDisposition() const
Definition: headers.cpp:36
Cutelyst::Headers::contentIsHtml
bool contentIsHtml() const
Definition: headers.cpp:140
Cutelyst::Headers::ifMatch
bool ifMatch(const QString &etag) const
Definition: headers.cpp:251
Cutelyst::Headers::setReferer
void setReferer(const QString &value)
Definition: headers.cpp:328
Cutelyst::Headers::proxyAuthorizationBasic
QString proxyAuthorizationBasic() const
Definition: headers.cpp:383
Cutelyst::Headers::contentType
QString contentType() const
Definition: headers.cpp:70
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::Headers::setDateWithDateTime
QString setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:186
Cutelyst::Headers::Headers
Headers()=default
Cutelyst::Headers::authorizationBasic
QString authorizationBasic() const
Definition: headers.cpp:354
Cutelyst::Headers::contentIsXml
bool contentIsXml() const
Definition: headers.cpp:155
Cutelyst::Headers::userAgent
QString userAgent() const
Definition: headers.cpp:318
Cutelyst::Headers::operator[]
QString & operator[](const QString &key)
Definition: headers.cpp:433
Cutelyst::Headers::setHeader
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:403
Cutelyst::Engine::camelCaseHeader
static QString camelCaseHeader(const QString &headerKey)
Definition: engine.h:116
Cutelyst::Headers::proxyAuthorization
QString proxyAuthorization() const
Definition: headers.cpp:378
Cutelyst::Headers::setContentDispositionAttachment
void setContentDispositionAttachment(const QString &filename=QString())
Definition: headers.cpp:51
Cutelyst::Headers::setAuthorizationBasic
QString setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:364
Cutelyst::Headers::contentEncoding
QString contentEncoding() const
Definition: headers.cpp:60
Cutelyst::Headers::setWwwAuthenticate
void setWwwAuthenticate(const QString &value)
Definition: headers.cpp:339
Cutelyst::Headers::authorization
QString authorization() const
Definition: headers.cpp:349
Cutelyst::Headers::contentIsText
bool contentIsText() const
Definition: headers.cpp:135
Cutelyst::Headers::contentTypeCharset
QString contentTypeCharset() const
Definition: headers.cpp:86