Cutelyst  3.1.0
request.cpp
1 /*
2  * Copyright (C) 2013-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 "request_p.h"
19 #include "engine.h"
20 #include "enginerequest.h"
21 #include "common.h"
22 #include "multipartformdataparser.h"
23 #include "utils.h"
24 
25 #include <QHostInfo>
26 #include <QJsonDocument>
27 #include <QJsonArray>
28 #include <QJsonObject>
29 
30 using namespace Cutelyst;
31 
33  d_ptr(new RequestPrivate)
34 {
35  d_ptr->engineRequest = engineRequest;
36  d_ptr->body = engineRequest->body;
37 }
38 
39 Request::~Request()
40 {
41  qDeleteAll(d_ptr->uploads);
42  delete d_ptr->body;
43  delete d_ptr;
44 }
45 
47 {
48  Q_D(const Request);
49  return d->engineRequest->remoteAddress;
50 }
51 
53 {
54  Q_D(const Request);
55 
56  bool ok;
57  quint32 data = d->engineRequest->remoteAddress.toIPv4Address(&ok);
58  if (ok) {
59  return QHostAddress(data).toString();
60  } else {
61  return d->engineRequest->remoteAddress.toString();
62  }
63 }
64 
65 QString Request::hostname() const
66 {
67  Q_D(const Request);
68  QString ret;
69 
70  // We have the client hostname
71  if (!d->remoteHostname.isEmpty()) {
72  ret = d->remoteHostname;
73  return ret;
74  }
75 
76  const QHostInfo ptr = QHostInfo::fromName(d->engineRequest->remoteAddress.toString());
77  if (ptr.error() != QHostInfo::NoError) {
78  qCDebug(CUTELYST_REQUEST) << "DNS lookup for the client hostname failed" << d->engineRequest->remoteAddress;
79  return ret;
80  }
81 
82  d->remoteHostname = ptr.hostName();
83  ret = d->remoteHostname;
84  return ret;
85 }
86 
87 quint16 Request::port() const
88 {
89  Q_D(const Request);
90  return d->engineRequest->remotePort;
91 }
92 
93 QUrl Request::uri() const
94 {
95  Q_D(const Request);
96 
97  QUrl uri = d->url;
98  if (!(d->parserStatus & RequestPrivate::UrlParsed)) {
99  // This is a hack just in case remote is not set
100  if (d->engineRequest->serverAddress.isEmpty()) {
102  } else {
103  uri.setAuthority(d->engineRequest->serverAddress);
104  }
105 
106  uri.setScheme(d->engineRequest->isSecure ? QStringLiteral("https") : QStringLiteral("http"));
107 
108  // if the path does not start with a slash it cleans the uri
109  uri.setPath(QLatin1Char('/') + d->engineRequest->path);
110 
111  if (!d->engineRequest->query.isEmpty()) {
112  uri.setQuery(QString::fromLatin1(d->engineRequest->query));
113  }
114 
115  d->url = uri;
116  d->parserStatus |= RequestPrivate::UrlParsed;
117  }
118  return uri;
119 }
120 
121 QString Request::base() const
122 {
123  Q_D(const Request);
124  QString base = d->base;
125  if (!(d->parserStatus & RequestPrivate::BaseParsed)) {
126  base = d->engineRequest->isSecure ? QStringLiteral("https://") : QStringLiteral("http://");
127 
128  // This is a hack just in case remote is not set
129  if (d->engineRequest->serverAddress.isEmpty()) {
131  } else {
132  base.append(d->engineRequest->serverAddress);
133  }
134 
135  // base always have a trailing slash
136  base.append(QLatin1Char('/'));
137 
138  d->base = base;
139  d->parserStatus |= RequestPrivate::BaseParsed;
140  }
141  return base;
142 }
143 
144 QString Request::path() const
145 {
146  Q_D(const Request);
147  return d->engineRequest->path;
148 }
149 
150 QString Request::match() const
151 {
152  Q_D(const Request);
153  return d->match;
154 }
155 
156 void Request::setMatch(const QString &match)
157 {
158  Q_D(Request);
159  d->match = match;
160 }
161 
162 QStringList Request::arguments() const
163 {
164  Q_D(const Request);
165  return d->args;
166 }
167 
168 void Request::setArguments(const QStringList &arguments)
169 {
170  Q_D(Request);
171  d->args = arguments;
172 }
173 
175 {
176  Q_D(const Request);
177  return d->captures;
178 }
179 
180 void Request::setCaptures(const QStringList &captures)
181 {
182  Q_D(Request);
183  d->captures = captures;
184 }
185 
186 bool Request::secure() const
187 {
188  Q_D(const Request);
189  return d->engineRequest->isSecure;
190 }
191 
193 {
194  Q_D(const Request);
195  return d->body;
196 }
197 
198 QVariant Request::bodyData() const
199 {
200  Q_D(const Request);
201  if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
202  d->parseBody();
203  }
204  return d->bodyData;
205 }
206 
208 {
209  return bodyData().toJsonDocument();
210 }
211 
213 {
214  return bodyData().toJsonDocument().object();
215 }
216 
218 {
219  return bodyData().toJsonDocument().array();
220 }
221 
223 {
224  return RequestPrivate::paramsMultiMapToVariantMap(bodyParameters());
225 }
226 
228 {
229  Q_D(const Request);
230  if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
231  d->parseBody();
232  }
233  return d->bodyParam;
234 }
235 
237 {
238  QStringList ret;
239 
240  const ParamsMultiMap query = bodyParameters();
241  auto it = query.constFind(key);
242  while (it != query.constEnd() && it.key() == key) {
243  ret.prepend(it.value());
244  ++it;
245  }
246  return ret;
247 }
248 
250 {
251  Q_D(const Request);
252  if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
253  d->parseUrlQuery();
254  }
255  return d->queryKeywords;
256 }
257 
259 {
260  return RequestPrivate::paramsMultiMapToVariantMap(queryParameters());
261 }
262 
264 {
265  Q_D(const Request);
266  if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
267  d->parseUrlQuery();
268  }
269  return d->queryParam;
270 }
271 
273 {
274  QStringList ret;
275 
276  const ParamsMultiMap query = queryParameters();
277  auto it = query.constFind(key);
278  while (it != query.constEnd() && it.key() == key) {
279  ret.prepend(it.value());
280  ++it;
281  }
282  return ret;
283 }
284 
285 QString Request::cookie(const QString &name) const
286 {
287  Q_D(const Request);
288  if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
289  d->parseCookies();
290  }
291 
292  return d->cookies.value(name);
293 }
294 
296 {
297  QStringList ret;
298  Q_D(const Request);
299 
300  if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
301  d->parseCookies();
302  }
303 
304  auto it = d->cookies.constFind(name);
305  while (it != d->cookies.constEnd() && it.key() == name) {
306  ret.prepend(it.value());
307  ++it;
308  }
309  return ret;
310 }
311 
313 {
314  Q_D(const Request);
315  if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
316  d->parseCookies();
317  }
318  return d->cookies;
319 }
320 
322 {
323  Q_D(const Request);
324  return d->engineRequest->headers;
325 }
326 
327 QString Request::method() const
328 {
329  Q_D(const Request);
330  return d->engineRequest->method;
331 }
332 
333 bool Request::isPost() const
334 {
335  Q_D(const Request);
336  return d->engineRequest->method == QStringLiteral("POST");
337 }
338 
339 bool Request::isGet() const
340 {
341  Q_D(const Request);
342  return d->engineRequest->method == QStringLiteral("GET");
343 }
344 
345 bool Request::isHead() const
346 {
347  Q_D(const Request);
348  return d->engineRequest->method == QStringLiteral("HEAD");
349 }
350 
351 bool Request::isPut() const
352 {
353  Q_D(const Request);
354  return d->engineRequest->method == QStringLiteral("PUT");
355 }
356 
357 bool Request::isPatch() const
358 {
359  Q_D(const Request);
360  return d->engineRequest->method == QStringLiteral("PATCH");
361 }
362 
363 bool Request::isDelete() const
364 {
365  Q_D(const Request);
366  return d->engineRequest->method == QStringLiteral("DELETE");
367 }
368 
369 QString Request::protocol() const
370 {
371  Q_D(const Request);
372  return d->engineRequest->protocol;
373 }
374 
375 bool Request::xhr() const
376 {
377  Q_D(const Request);
378  return d->engineRequest->headers.header(QStringLiteral("X_REQUESTED_WITH")) == QStringLiteral("XMLHttpRequest");
379 }
380 
381 QString Request::remoteUser() const
382 {
383  Q_D(const Request);
384  return d->engineRequest->remoteUser;
385 }
386 
388 {
389  Q_D(const Request);
390  if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
391  d->parseBody();
392  }
393  return d->uploads;
394 }
395 
397 {
398  Q_D(const Request);
399  if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
400  d->parseBody();
401  }
402  return d->uploadsMap;
403 }
404 
405 Uploads Request::uploads(const QString &name) const
406 {
407  Uploads ret;
408  const auto map = uploadsMap();
409  const auto range = map.equal_range(name);
410  for (auto i = range.first; i != range.second; ++i) {
411  ret.push_back(*i);
412  }
413  return ret;
414 }
415 
416 ParamsMultiMap Request::mangleParams(const ParamsMultiMap &args, bool append) const
417 {
418  ParamsMultiMap ret = queryParams();
419  if (append) {
420  ret.unite(args);
421  } else {
422  auto it = args.constEnd();
423  while (it != args.constBegin()) {
424  --it;
425  ret.replace(it.key(), it.value());
426  }
427  }
428 
429  return ret;
430 }
431 
432 QUrl Request::uriWith(const ParamsMultiMap &args, bool append) const
433 {
434  QUrl ret = uri();
435  QUrlQuery urlQuery;
436  const ParamsMultiMap query = mangleParams(args, append);
437  auto it = query.constEnd();
438  while (it != query.constBegin()) {
439  --it;
440  urlQuery.addQueryItem(it.key(), it.value());
441  }
442  ret.setQuery(urlQuery);
443 
444  return ret;
445 }
446 
448 {
449  Q_D(const Request);
450  return d->engine;
451 }
452 
453 void RequestPrivate::parseUrlQuery() const
454 {
455  // TODO move this to the asignment of query
456  if (engineRequest->query.size()) {
457  // Check for keywords (no = signs)
458  if (engineRequest->query.indexOf('=') < 0) {
459  QByteArray aux = engineRequest->query;
460  queryKeywords = Utils::decodePercentEncoding(&aux);
461  } else {
462  if (parserStatus & RequestPrivate::UrlParsed) {
463  queryParam = Utils::decodePercentEncoding(engineRequest->query.data(), engineRequest->query.size());
464  } else {
465  QByteArray aux = engineRequest->query;
466  // We can't manipulate query directly
467  queryParam = Utils::decodePercentEncoding(aux.data(), aux.size());
468  }
469  }
470  }
471  parserStatus |= RequestPrivate::QueryParsed;
472 }
473 
474 void RequestPrivate::parseBody() const
475 {
476  if (!body) {
477  parserStatus |= RequestPrivate::BodyParsed;
478  return;
479  }
480 
481  bool sequencial = body->isSequential();
482  qint64 posOrig = body->pos();
483  if (sequencial && posOrig) {
484  qCWarning(CUTELYST_REQUEST) << "Can not parse sequential post body out of beginning";
485  parserStatus |= RequestPrivate::BodyParsed;
486  return;
487  }
488 
489  const QString contentTypeKey = QStringLiteral("CONTENT_TYPE");
490  const QString contentType = engineRequest->headers.header(contentTypeKey);
491  if (contentType.startsWith(QLatin1String("application/x-www-form-urlencoded"), Qt::CaseInsensitive)) {
492  // Parse the query (BODY) of type "application/x-www-form-urlencoded"
493  // parameters ie "?foo=bar&bar=baz"
494  if (posOrig) {
495  body->seek(0);
496  }
497 
498  QByteArray line = body->readAll();
499  bodyParam = Utils::decodePercentEncoding(line.data(), line.size());
500  bodyData = QVariant::fromValue(bodyParam);
501  } else if (contentType.startsWith(QLatin1String("multipart/form-data"), Qt::CaseInsensitive)) {
502  if (posOrig) {
503  body->seek(0);
504  }
505 
506  const Uploads ups = MultiPartFormDataParser::parse(body, contentType);
507  for (Upload *upload : ups) {
508  if (upload->filename().isEmpty() && upload->headers().header(contentTypeKey).isEmpty()) {
509  bodyParam.insert(upload->name(), QString::fromUtf8(upload->readAll()));
510  upload->seek(0);
511  }
512  uploadsMap.insert(upload->name(), upload);
513  }
514  uploads = ups;
515 // bodyData = QVariant::fromValue(uploadsMap);
516  } else if (contentType.startsWith(QLatin1String("application/json"), Qt::CaseInsensitive)) {
517  if (posOrig) {
518  body->seek(0);
519  }
520 
521  bodyData = QJsonDocument::fromJson(body->readAll());
522  }
523 
524  if (!sequencial) {
525  body->seek(posOrig);
526  }
527 
528  parserStatus |= RequestPrivate::BodyParsed;
529 }
530 
531 static inline bool isSlit(QChar c)
532 {
533  return c == QLatin1Char(';') || c == QLatin1Char(',');
534 }
535 
536 int findNextSplit(const QString &text, int from, int length)
537 {
538  while (from < length) {
539  if (isSlit(text.at(from))) {
540  return from;
541  }
542  ++from;
543  }
544  return -1;
545 }
546 
547 static inline bool isLWS(QChar c)
548 {
549  return c == QLatin1Char(' ') || c == QLatin1Char('\t') || c == QLatin1Char('\r') || c == QLatin1Char('\n');
550 }
551 
552 static int nextNonWhitespace(const QString &text, int from, int length)
553 {
554  // RFC 2616 defines linear whitespace as:
555  // LWS = [CRLF] 1*( SP | HT )
556  // We ignore the fact that CRLF must come as a pair at this point
557  // It's an invalid HTTP header if that happens.
558  while (from < length) {
559  if (isLWS(text.at(from)))
560  ++from;
561  else
562  return from; // non-whitespace
563  }
564 
565  // reached the end
566  return text.length();
567 }
568 
569 static std::pair<QString, QString> nextField(const QString &text, int &position)
570 {
571  std::pair<QString, QString> ret;
572  // format is one of:
573  // (1) token
574  // (2) token = token
575  // (3) token = quoted-string
576  const int length = text.length();
577  position = nextNonWhitespace(text, position, length);
578 
579  int semiColonPosition = findNextSplit(text, position, length);
580  if (semiColonPosition < 0)
581  semiColonPosition = length; //no ';' means take everything to end of string
582 
583  int equalsPosition = text.indexOf(QLatin1Char('='), position);
584  if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
585  return ret; //'=' is required for name-value-pair (RFC6265 section 5.2, rule 2)
586  }
587 
588  ret.first = text.mid(position, equalsPosition - position).trimmed();
589  int secondLength = semiColonPosition - equalsPosition - 1;
590  if (secondLength > 0) {
591  ret.second = text.mid(equalsPosition + 1, secondLength).trimmed();
592  }
593 
594  position = semiColonPosition;
595  return ret;
596 }
597 
598 void RequestPrivate::parseCookies() const
599 {
600  const QString cookieString = engineRequest->headers.header(QStringLiteral("COOKIE"));
601  int position = 0;
602  const int length = cookieString.length();
603  while (position < length) {
604  const auto field = nextField(cookieString, position);
605  if (field.first.isEmpty()) {
606  // parsing error
607  break;
608  }
609 
610  // Some foreign cookies are not in name=value format, so ignore them.
611  if (field.second.isEmpty()) {
612  ++position;
613  continue;
614  }
615  cookies.insert(field.first, field.second);
616  ++position;
617  }
618 
619  parserStatus |= RequestPrivate::CookiesParsed;
620 }
621 
622 QVariantMap RequestPrivate::paramsMultiMapToVariantMap(const ParamsMultiMap &params)
623 {
624  QVariantMap ret;
625  auto end = params.constEnd();
626  while (params.constBegin() != end) {
627  --end;
628  ret.insert(ret.constBegin(), end.key(), end.value());
629  }
630  return ret;
631 }
632 
633 #include "moc_request.cpp"
QIODevice * body
The QIODevice containing the body (if any) of the request.
The Cutelyst Engine.
Definition: engine.h:34
static Uploads parse(QIODevice *body, const QString &contentType, int bufferSize=4096)
Parser for multipart/formdata.
QMultiMap< QString, Upload * > uploadsMap() const
Definition: request.cpp:396
QVariantMap bodyParametersVariant() const
Definition: request.cpp:222
QVariantMap queryParametersVariant() const
Definition: request.cpp:258
QString addressString() const
Definition: request.cpp:52
QString queryKeywords() const
Definition: request.cpp:249
QVector< Upload * > uploads() const
Definition: request.cpp:387
ParamsMultiMap bodyParameters() const
Definition: request.cpp:227
Engine * engine() const
Definition: request.cpp:447
QJsonArray bodyJsonArray() const
Definition: request.cpp:217
QHostAddress address() const
Definition: request.cpp:46
QJsonObject bodyJsonObject() const
Definition: request.cpp:212
bool isHead() const
Definition: request.cpp:345
bool isPatch() const
Definition: request.cpp:357
QUrl uriWith(const ParamsMultiMap &args, bool append=false) const
Definition: request.cpp:432
QJsonDocument bodyJsonDocument() const
Definition: request.cpp:207
ParamsMultiMap mangleParams(const ParamsMultiMap &args, bool append=false) const
Definition: request.cpp:416
void setCaptures(const QStringList &captures)
Definition: request.cpp:180
bool isPost() const
Definition: request.cpp:333
bool isPut() const
Definition: request.cpp:351
bool xhr() const
Definition: request.cpp:375
ParamsMultiMap queryParameters() const
Definition: request.cpp:263
QStringList captures() const
Definition: request.cpp:174
QIODevice * body() const
Definition: request.cpp:192
bool isDelete() const
Definition: request.cpp:363
QString cookie(const QString &name) const
Definition: request.cpp:285
Request(EngineRequest *engineRequest)
Definition: request.cpp:32
void setArguments(const QStringList &arguments)
Definition: request.cpp:168
bool isGet() const
Definition: request.cpp:339
void setMatch(const QString &match)
Definition: request.cpp:156
ParamsMultiMap cookies() const
Definition: request.cpp:312
Headers headers() const
Definition: request.cpp:321
Cutelyst Upload handles file upload request
Definition: upload.h:36
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
char * data()
int size() const const
QString toString() const const
QHostInfo::HostInfoError error() const const
QHostInfo fromName(const QString &name)
QString hostName() const const
QString localHostName()
QJsonArray array() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
void prepend(const T &value)
const Key & key() const const
QMap::const_iterator constBegin() const const
QMap::const_iterator constEnd() const const
typename QMap< Key, T >::const_iterator constFind(const Key &key, const T &value) const const
typename QMap< Key, T >::iterator replace(const Key &key, const T &value)
QMultiMap< K, V > & unite(const QMultiMap< K, V > &other)
QString & append(QChar ch)
const QChar at(int position) const const
QString fromLatin1(const char *str, int size)
QString fromUtf8(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
int length() const const
QString mid(int position, int n) const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString trimmed() const const
CaseInsensitive
void setAuthority(const QString &authority, QUrl::ParsingMode mode)
void setHost(const QString &host, QUrl::ParsingMode mode)
void setPath(const QString &path, QUrl::ParsingMode mode)
void setQuery(const QString &query, QUrl::ParsingMode mode)
void setScheme(const QString &scheme)
QString url(QUrl::FormattingOptions options) const const
void addQueryItem(const QString &key, const QString &value)
QVariant fromValue(const T &value)
QJsonDocument toJsonDocument() const const
void push_back(const T &value)