Cutelyst  2.3.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 
33 {
34 
35 }
36 
38 {
39  return m_data.value(QStringLiteral("CONTENT_DISPOSITION"));
40 }
41 
43 {
44  m_data.insert(QStringLiteral("CONTENT_DISPOSITION"), contentDisposition);
45 }
46 
47 void Headers::setContentDispositionAttachment(const QString &filename)
48 {
49  if (filename.isEmpty()) {
50  setContentDisposition(QStringLiteral("attachment"));
51  } else {
52  setContentDisposition(QLatin1String("attachment; filename=\"") + filename + QLatin1Char('"'));
53  }
54 }
55 
56 QString Headers::contentEncoding() const
57 {
58  return m_data.value(QStringLiteral("CONTENT_ENCODING"));
59 }
60 
61 void Headers::setContentEncoding(const QString &encoding)
62 {
63  m_data.insert(QStringLiteral("CONTENT_ENCODING"), encoding);
64 }
65 
66 QString Headers::contentType() const
67 {
68  QString ret;
69  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
70  if (it != m_data.constEnd()) {
71  const QString &ct = it.value();
72  ret = ct.mid(0, ct.indexOf(QLatin1Char(';'))).toLower();
73  }
74  return ret;
75 }
76 
78 {
79  m_data.insert(QStringLiteral("CONTENT_TYPE"), contentType);
80 }
81 
83 {
84  QString ret;
85  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
86  if (it != m_data.constEnd()) {
87  const QString &contentType = it.value();
88  int pos = contentType.indexOf(QLatin1String("charset="), 0, Qt::CaseInsensitive);
89  if (pos != -1) {
90  int endPos = contentType.indexOf(QLatin1Char(';'), pos);
91  ret = contentType.mid(pos + 8, endPos).trimmed().toUpper();
92  }
93  }
94 
95  return ret;
96 }
97 
98 void Headers::setContentTypeCharset(const QString &charset)
99 {
100  const auto it = m_data.constFind(QStringLiteral("CONTENT_TYPE"));
101  if (it == m_data.constEnd() || (it.value().isEmpty() && !charset.isEmpty())) {
102  m_data.insert(QStringLiteral("CONTENT_TYPE"), QLatin1String("charset=") + charset);
103  return;
104  }
105 
106  QString contentType = it.value();
107  int pos = contentType.indexOf(QLatin1String("charset="), 0, Qt::CaseInsensitive);
108  if (pos != -1) {
109  int endPos = contentType.indexOf(QLatin1Char(';'), pos);
110  if (endPos == -1) {
111  if (charset.isEmpty()) {
112  int lastPos = contentType.lastIndexOf(QLatin1Char(';'), pos);
113  if (lastPos == -1) {
114  m_data.remove(QStringLiteral("CONTENT_TYPE"));
115  return;
116  } else {
117  contentType.remove(lastPos, contentType.length() - lastPos);
118  }
119  } else {
120  contentType.replace(pos + 8, contentType.length() - pos + 8, charset);
121  }
122  } else {
123  contentType.replace(pos + 8, endPos, charset);
124  }
125  } else if (!charset.isEmpty()) {
126  contentType.append(QLatin1String("; charset=") + charset);
127  }
128  m_data.insert(QStringLiteral("CONTENT_TYPE"), contentType);
129 }
130 
132 {
133  return m_data.value(QStringLiteral("CONTENT_TYPE")).startsWith(QLatin1String("text/"));
134 }
135 
137 {
138  const QString ct = contentType();
139  return ct == QLatin1String("text/html") ||
140  ct == QLatin1String("application/xhtml+xml") ||
141  ct == QLatin1String("application/vnd.wap.xhtml+xml");
142 }
143 
145 {
146  const QString ct = contentType();
147  return ct == QLatin1String("application/xhtml+xml") ||
148  ct == QLatin1String("application/vnd.wap.xhtml+xml");
149 }
150 
152 {
153  const QString ct = contentType();
154  return ct == QLatin1String("text/xml") ||
155  ct == QLatin1String("application/xml") ||
156  ct.endsWith(QLatin1String("xml"));
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.insert(QStringLiteral("CONTENT_LENGTH"), QString::number(value));
171 }
172 
173 QString Headers::setDateWithDateTime(const QDateTime &date)
174 {
175  // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
176  // and follow RFC 822
177  const QString dt = QLocale::c().toString(date.toUTC(),
178  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT"));
179  m_data.insert(QStringLiteral("DATE"), dt);
180  return dt;
181 }
182 
183 QDateTime Headers::date() const
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(QLatin1String(" 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 header(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(QLatin1String(" 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 QString Headers::lastModified() const
229 {
230  return m_data.value(QStringLiteral("LAST_MODIFIED"));
231 }
232 
233 void Headers::setLastModified(const QString &value)
234 {
235  m_data.insert(QStringLiteral("LAST_MODIFIED"), value);
236 }
237 
238 QString Headers::setLastModified(const QDateTime &lastModified)
239 {
240  // ALL dates must be in GMT timezone http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
241  // and follow RFC 822
242  const auto dt = QLocale::c().toString(lastModified.toUTC(),
243  QStringLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT"));
244  setLastModified(dt);
245  return dt;
246 }
247 
248 QString Headers::server() const
249 {
250  return m_data.value(QStringLiteral("SERVER"));
251 }
252 
253 void Headers::setServer(const QString &value)
254 {
255  m_data.insert(QStringLiteral("SERVER"), value);
256 }
257 
258 QString Headers::connection() const
259 {
260  return m_data.value(QStringLiteral("CONNECTION"));
261 }
262 
263 QString Headers::host() const
264 {
265  return m_data.value(QStringLiteral("HOST"));
266 }
267 
268 QString Headers::userAgent() const
269 {
270  return m_data.value(QStringLiteral("USER_AGENT"));
271 }
272 
273 QString Headers::referer() const
274 {
275  return m_data.value(QStringLiteral("REFERER"));
276 }
277 
278 void Headers::setReferer(const QString &uri)
279 {
280  int fragmentPos = uri.indexOf(QLatin1Char('#'));
281  if (fragmentPos != -1) {
282  // Strip fragment per RFC 2616, section 14.36.
283  m_data.insert(QStringLiteral("REFERER"), uri.mid(0, fragmentPos));
284  } else {
285  m_data.insert(QStringLiteral("REFERER"), uri);
286  }
287 }
288 
289 void Headers::setWwwAuthenticate(const QString &value)
290 {
291  m_data.insert(QStringLiteral("WWW_AUTHENTICATE"), value);
292 }
293 
294 void Headers::setProxyAuthenticate(const QString &value)
295 {
296  m_data.insert(QStringLiteral("PROXY_AUTHENTICATE"), value);
297 }
298 
299 QString Headers::authorization() const
300 {
301  return m_data.value(QStringLiteral("AUTHORIZATION"));
302 }
303 
305 {
306  return QString::fromLatin1(decodeBasicAuth(authorization()));
307 }
308 
309 std::pair<QString, QString> Headers::authorizationBasicPair() const
310 {
311  return decodeBasicAuthPair(authorization());
312 }
313 
314 QString Headers::setAuthorizationBasic(const QString &username, const QString &password)
315 {
316  QString ret;
317  if (username.contains(QLatin1Char(':'))) {
318  qCWarning(CUTELYST_CORE) << "Headers::Basic authorization user name can't contain ':'";
319  return ret;
320  }
321 
322  const QString result = username + QLatin1Char(':') + password;
323  ret = QStringLiteral("Basic ") + QString::fromLatin1(result.toLatin1().toBase64());
324  m_data.insert(QStringLiteral("AUTHORIZATION"), ret);
325  return ret;
326 }
327 
329 {
330  return m_data.value(QStringLiteral("PROXY_AUTHORIZATION"));
331 }
332 
334 {
335  return QString::fromLatin1(decodeBasicAuth(proxyAuthorization()));
336 }
337 
338 std::pair<QString, QString> Headers::proxyAuthorizationBasicPair() const
339 {
340  return decodeBasicAuthPair(proxyAuthorization());
341 }
342 
343 QString Headers::header(const QString &field) const
344 {
345  return m_data.value(normalizeHeaderKey(field));
346 }
347 
348 QString Headers::header(const QString &field, const QString &defaultValue) const
349 {
350  return m_data.value(normalizeHeaderKey(field), defaultValue);
351 }
352 
353 void Headers::setHeader(const QString &field, const QString &value)
354 {
355  m_data.insert(normalizeHeaderKey(field), value);
356 }
357 
358 void Headers::setHeader(const QString &field, const QStringList &values)
359 {
360  setHeader(field, values.join(QStringLiteral(", ")));
361 }
362 
363 void Headers::pushHeader(const QString &field, const QString &value)
364 {
365  m_data.insertMulti(normalizeHeaderKey(field), value);
366 }
367 
368 void Headers::pushHeader(const QString &field, const QStringList &values)
369 {
370  m_data.insertMulti(normalizeHeaderKey(field), values.join(QStringLiteral(", ")));
371 }
372 
373 void Headers::removeHeader(const QString &field)
374 {
375  m_data.remove(normalizeHeaderKey(field));
376 }
377 
378 bool Headers::contains(const QString &field)
379 {
380  return m_data.contains(normalizeHeaderKey(field));
381 }
382 
383 QString &Headers::operator[](const QString &key)
384 {
385  return m_data[key];
386 }
387 
388 const QString Headers::operator[](const QString &key) const
389 {
390  return m_data[key];
391 }
392 
393 QString normalizeHeaderKey(const QString &field)
394 {
395  QString key = field;
396  int i = 0;
397  while (i < key.size()) {
398  QCharRef c = key[i];
399  if (c.isLetter()) {
400  if (c.isLower()) {
401  c = c.toUpper();
402  }
403  } else if (c == QLatin1Char('-')) {
404  c = QLatin1Char('_');
405  }
406  ++i;
407  }
408  return key;
409 }
410 
411 QByteArray decodeBasicAuth(const QString &auth)
412 {
413  QByteArray ret;
414  if (!auth.isEmpty() && auth.startsWith(QLatin1String("Basic "))) {
415  int pos = auth.lastIndexOf(QLatin1Char(' '));
416  if (pos != -1) {
417  ret = QByteArray::fromBase64(auth.mid(pos).toLatin1());
418  }
419  }
420  return ret;
421 }
422 
423 std::pair<QString, QString> decodeBasicAuthPair(const QString &auth)
424 {
425  std::pair<QString, QString> ret;
426  const QByteArray authorization = decodeBasicAuth(auth);
427  if (!authorization.isEmpty()) {
428  int pos = authorization.indexOf(':');
429  if (pos == -1) {
430  ret.first = QString::fromLatin1(authorization);
431  } else {
432  ret = { QString::fromLatin1(authorization.left(pos)),
433  QString::fromLatin1(authorization.mid(pos + 1)) };
434  }
435  }
436  return ret;
437 }
438 
439 QDebug operator<<(QDebug debug, const Headers &headers)
440 {
441  const QHash<QString, QString> data = headers.data();
442  const bool oldSetting = debug.autoInsertSpaces();
443  debug.nospace() << "Headers(";
444  for (auto it = data.constBegin();
445  it != data.constEnd(); ++it) {
446  debug << '(' << Engine::camelCaseHeader(it.key()) + QLatin1Char('=') + it.value() << ')';
447  }
448  debug << ')';
449  debug.setAutoInsertSpaces(oldSetting);
450  return debug.maybeSpace();
451 }
void pushHeader(const QString &field, const QString &value)
Definition: headers.cpp:363
QString contentEncoding() const
Definition: headers.cpp:56
QDateTime ifModifiedSinceDateTime() const
Definition: headers.cpp:208
QDateTime date() const
Definition: headers.cpp:183
QString proxyAuthorization() const
Definition: headers.cpp:328
QString contentType() const
Definition: headers.cpp:66
QHash< QString, QString > data() const
Definition: headers.h:331
static QString camelCaseHeader(const QString &headerKey)
Definition: engine.h:116
QString server() const
Definition: headers.cpp:248
void setContentDispositionAttachment(const QString &filename=QString())
Definition: headers.cpp:47
bool contentIsText() const
Definition: headers.cpp:131
QString contentTypeCharset() const
Definition: headers.cpp:82
QString userAgent() const
Definition: headers.cpp:268
QString connection() const
Definition: headers.cpp:258
QString host() const
Definition: headers.cpp:263
bool contentIsXHtml() const
Definition: headers.cpp:144
void setProxyAuthenticate(const QString &value)
Definition: headers.cpp:294
bool contentIsXml() const
Definition: headers.cpp:151
QString & operator[](const QString &key)
Definition: headers.cpp:383
void removeHeader(const QString &field)
Definition: headers.cpp:373
bool contains(const QString &field)
Definition: headers.cpp:378
QString setDateWithDateTime(const QDateTime &date)
Definition: headers.cpp:173
QString header(const QString &field) const
Definition: headers.cpp:343
void setHeader(const QString &field, const QString &value)
Definition: headers.cpp:353
void setLastModified(const QString &value)
Definition: headers.cpp:233
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setContentDisposition(const QString &contentDisposition)
Definition: headers.cpp:42
QString proxyAuthorizationBasic() const
Definition: headers.cpp:333
void setContentType(const QString &contentType)
Definition: headers.cpp:77
std::pair< QString, QString > authorizationBasicPair() const
Definition: headers.cpp:309
void setWwwAuthenticate(const QString &value)
Definition: headers.cpp:289
void setServer(const QString &value)
Definition: headers.cpp:253
QString setAuthorizationBasic(const QString &username, const QString &password)
Definition: headers.cpp:314
qint64 contentLength() const
Definition: headers.cpp:159
QString contentDisposition() const
Definition: headers.cpp:37
void setContentTypeCharset(const QString &charset)
Definition: headers.cpp:98
std::pair< QString, QString > proxyAuthorizationBasicPair() const
Definition: headers.cpp:338
QString authorizationBasic() const
Definition: headers.cpp:304
void setContentEncoding(const QString &encoding)
Definition: headers.cpp:61
void setReferer(const QString &value)
Definition: headers.cpp:278
QString ifModifiedSince() const
Definition: headers.cpp:203
void setContentLength(qint64 value)
Definition: headers.cpp:168
QString authorization() const
Definition: headers.cpp:299
QString referer() const
Definition: headers.cpp:273
QString lastModified() const
Definition: headers.cpp:228
bool contentIsHtml() const
Definition: headers.cpp:136