Cutelyst  2.13.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 
46 QHostAddress Request::address() const
47 {
48  Q_D(const Request);
49  return d->engineRequest->remoteAddress;
50 }
51 
52 QString Request::addressString() const
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()) {
101  uri.setHost(QHostInfo::localHostName());
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()) {
130  base.append(QHostInfo::localHostName());
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 
174 QStringList Request::captures() const
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 
192 QIODevice *Request::body() const
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 
207 QJsonDocument Request::bodyJsonDocument() const
208 {
209  return bodyData().toJsonDocument();
210 }
211 
212 QJsonObject Request::bodyJsonObject() const
213 {
214  return bodyData().toJsonDocument().object();
215 }
216 
217 QJsonArray Request::bodyJsonArray() const
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 
236 QStringList Request::bodyParameters(const QString &key) const
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 
249 QString Request::queryKeywords() const
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 
272 QStringList Request::queryParameters(const QString &key) const
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 
295 QStringList Request::cookies(const QString &name) const
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 
321 Headers Request::headers() const
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 
387 QVector<Upload *> Request::uploads() const
388 {
389  Q_D(const Request);
390  if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
391  d->parseBody();
392  }
393  return d->uploads;
394 }
395 
396 QMap<QString, Cutelyst::Upload *> Request::uploadsMap() const
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.insert(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  queryParam = parseUrlEncoded(engineRequest->query);
463  }
464  }
465  parserStatus |= RequestPrivate::QueryParsed;
466 }
467 
468 void RequestPrivate::parseBody() const
469 {
470  if (!body) {
471  parserStatus |= RequestPrivate::BodyParsed;
472  return;
473  }
474 
475  bool sequencial = body->isSequential();
476  qint64 posOrig = body->pos();
477  if (sequencial && posOrig) {
478  qCWarning(CUTELYST_REQUEST) << "Can not parse sequential post body out of beginning";
479  parserStatus |= RequestPrivate::BodyParsed;
480  return;
481  }
482 
483  const QString contentType = engineRequest->headers.contentType();
484  if (contentType == QLatin1String("application/x-www-form-urlencoded")) {
485  // Parse the query (BODY) of type "application/x-www-form-urlencoded"
486  // parameters ie "?foo=bar&bar=baz"
487  if (posOrig) {
488  body->seek(0);
489  }
490 
491  bodyParam = parseUrlEncoded(body->readLine());
492  bodyData = QVariant::fromValue(bodyParam);
493  } else if (contentType == QLatin1String("multipart/form-data")) {
494  if (posOrig) {
495  body->seek(0);
496  }
497 
498  const Uploads ups = MultiPartFormDataParser::parse(body, engineRequest->headers.header(QStringLiteral("CONTENT_TYPE")));
499  for (Upload *upload : ups) {
500  if (upload->filename().isEmpty() && upload->contentType().isEmpty()) {
501  bodyParam.insertMulti(upload->name(), QString::fromUtf8(upload->readAll()));
502  upload->seek(0);
503  }
504  uploadsMap.insertMulti(upload->name(), upload);
505  }
506  uploads = ups;
507  bodyData = QVariant::fromValue(uploadsMap);
508  } else if (contentType == QLatin1String("application/json")) {
509  if (posOrig) {
510  body->seek(0);
511  }
512 
513  bodyData = QJsonDocument::fromJson(body->readAll());
514  }
515 
516  if (!sequencial) {
517  body->seek(posOrig);
518  }
519 
520  parserStatus |= RequestPrivate::BodyParsed;
521 }
522 
523 static inline bool isSlit(QChar c)
524 {
525  return c == QLatin1Char(';') || c == QLatin1Char(',');
526 }
527 
528 int findNextSplit(const QString &text, int from, int length)
529 {
530  while (from < length) {
531  if (isSlit(text.at(from))) {
532  return from;
533  }
534  ++from;
535  }
536  return -1;
537 }
538 
539 static inline bool isLWS(QChar c)
540 {
541  return c == QLatin1Char(' ') || c == QLatin1Char('\t') || c == QLatin1Char('\r') || c == QLatin1Char('\n');
542 }
543 
544 static int nextNonWhitespace(const QString &text, int from, int length)
545 {
546  // RFC 2616 defines linear whitespace as:
547  // LWS = [CRLF] 1*( SP | HT )
548  // We ignore the fact that CRLF must come as a pair at this point
549  // It's an invalid HTTP header if that happens.
550  while (from < length) {
551  if (isLWS(text.at(from)))
552  ++from;
553  else
554  return from; // non-whitespace
555  }
556 
557  // reached the end
558  return text.length();
559 }
560 
561 static std::pair<QString, QString> nextField(const QString &text, int &position)
562 {
563  std::pair<QString, QString> ret;
564  // format is one of:
565  // (1) token
566  // (2) token = token
567  // (3) token = quoted-string
568  const int length = text.length();
569  position = nextNonWhitespace(text, position, length);
570 
571  int semiColonPosition = findNextSplit(text, position, length);
572  if (semiColonPosition < 0)
573  semiColonPosition = length; //no ';' means take everything to end of string
574 
575  int equalsPosition = text.indexOf(QLatin1Char('='), position);
576  if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
577  return ret; //'=' is required for name-value-pair (RFC6265 section 5.2, rule 2)
578  }
579 
580  ret.first = text.midRef(position, equalsPosition - position).trimmed().toString();
581  int secondLength = semiColonPosition - equalsPosition - 1;
582  if (secondLength > 0) {
583  ret.second = text.midRef(equalsPosition + 1, secondLength).trimmed().toString();
584  }
585 
586  position = semiColonPosition;
587  return ret;
588 }
589 
590 void RequestPrivate::parseCookies() const
591 {
592  const QString cookieString = engineRequest->headers.header(QStringLiteral("COOKIE"));
593  int position = 0;
594  const int length = cookieString.length();
595  while (position < length) {
596  const auto field = nextField(cookieString, position);
597  if (field.first.isEmpty()) {
598  // parsing error
599  break;
600  }
601 
602  // Some foreign cookies are not in name=value format, so ignore them.
603  if (field.second.isEmpty()) {
604  ++position;
605  continue;
606  }
607  cookies.insertMulti(field.first, field.second);
608  ++position;
609  }
610 
611  parserStatus |= RequestPrivate::CookiesParsed;
612 }
613 
614 ParamsMultiMap RequestPrivate::parseUrlEncoded(const QByteArray &line)
615 {
616  ParamsMultiMap ret;
617 
618  int from = 0;
619  while (from < line.length()) {
620  const int pos = line.indexOf('&', from);
621 
622  int len;
623  if (pos == -1) {
624  len = line.length() - from;
625  } else {
626  len = pos - from;
627  }
628 
629  if (len == 0 || (len == 1 && line[from] == '=')) {
630  // Skip empty strings
631  ++from;
632  continue;
633  }
634 
635  QByteArray data = line.mid(from, len);
636 
637  int equal = data.indexOf('=');
638  if (equal != -1) {
639  QByteArray key = data.mid(0, equal);
640  if (++equal < data.size()) {
641  QByteArray value = data.mid(equal);
642  ret.insertMulti(Utils::decodePercentEncoding(&key),
643  Utils::decodePercentEncoding(&value));
644  } else {
645  ret.insertMulti(Utils::decodePercentEncoding(&key),
646  QString());
647  }
648  } else {
649  ret.insertMulti(Utils::decodePercentEncoding(&data),
650  QString());
651  }
652 
653  if (pos == -1) {
654  break;
655  }
656  from = pos + 1;
657  }
658 
659  return ret;
660 }
661 
662 QVariantMap RequestPrivate::paramsMultiMapToVariantMap(const ParamsMultiMap &params)
663 {
664  QVariantMap ret;
665  auto end = params.constEnd();
666  while (params.constBegin() != end) {
667  --end;
668  ret.insertMulti(ret.constBegin(), end.key(), end.value());
669  }
670  return ret;
671 }
672 
673 #include "moc_request.cpp"
Cutelyst::Request::Request
Request(EngineRequest *engineRequest)
Definition: request.cpp:32
Cutelyst::Request::isGet
bool isGet() const
Definition: request.cpp:339
Cutelyst::ParamsMultiMap
QMap< QString, QString > ParamsMultiMap
Definition: paramsmultimap.h:36
Cutelyst::Request::bodyParametersVariant
QVariantMap bodyParametersVariant() const
Definition: request.cpp:222
Cutelyst::Request::isPatch
bool isPatch() const
Definition: request.cpp:357
Cutelyst::Request::bodyJsonArray
QJsonArray bodyJsonArray() const
Definition: request.cpp:217
Cutelyst::Request::addressString
QString addressString() const
Definition: request.cpp:52
Cutelyst::Request::body
QIODevice * body() const
Definition: request.cpp:192
Cutelyst::MultiPartFormDataParser::parse
static Uploads parse(QIODevice *body, const QString &contentType, int bufferSize=4096)
Parser for multipart/formdata.
Definition: multipartformdataparser.cpp:24
Cutelyst::Request::cookies
ParamsMultiMap cookies() const
Definition: request.cpp:312
Cutelyst::Request::queryParametersVariant
QVariantMap queryParametersVariant() const
Definition: request.cpp:258
Cutelyst::Request::bodyJsonDocument
QJsonDocument bodyJsonDocument() const
Definition: request.cpp:207
Cutelyst::Request::setCaptures
void setCaptures(const QStringList &captures)
Definition: request.cpp:180
Cutelyst::Request::uploadsMap
QMap< QString, Upload * > uploadsMap() const
Definition: request.cpp:396
Cutelyst::Request
Definition: request.h:42
Cutelyst::Request::uploads
QVector< Upload * > uploads() const
Definition: request.cpp:387
Cutelyst::EngineRequest
Definition: enginerequest.h:31
Cutelyst::Request::queryParameters
ParamsMultiMap queryParameters() const
Definition: request.cpp:263
Cutelyst::Request::setMatch
void setMatch(const QString &match)
Definition: request.cpp:156
Cutelyst::Headers
Definition: headers.h:29
Cutelyst::Request::mangleParams
ParamsMultiMap mangleParams(const ParamsMultiMap &args, bool append=false) const
Definition: request.cpp:416
Cutelyst::Request::setArguments
void setArguments(const QStringList &arguments)
Definition: request.cpp:168
Cutelyst::Request::engine
Engine * engine() const
Definition: request.cpp:447
Cutelyst::Request::uriWith
QUrl uriWith(const ParamsMultiMap &args, bool append=false) const
Definition: request.cpp:432
Cutelyst::EngineRequest::body
QIODevice * body
The QIODevice containing the body (if any) of the request.
Definition: enginerequest.h:171
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::Request::queryKeywords
QString queryKeywords() const
Definition: request.cpp:249
Cutelyst::Uploads
QVector< Upload * > Uploads
Definition: request.h:35
Cutelyst::Engine
The Cutelyst Engine.
Definition: engine.h:33
Cutelyst::Request::isHead
bool isHead() const
Definition: request.cpp:345
Cutelyst::Request::address
QHostAddress address() const
Definition: request.cpp:46
Cutelyst::Request::captures
QStringList captures() const
Definition: request.cpp:174
Cutelyst::Request::cookie
QString cookie(const QString &name) const
Definition: request.cpp:285
Cutelyst::Upload
Cutelyst Upload handles file upload request
Definition: upload.h:35
Cutelyst::Request::bodyParameters
ParamsMultiMap bodyParameters() const
Definition: request.cpp:227
Cutelyst::Request::xhr
bool xhr() const
Definition: request.cpp:375
Cutelyst::Request::isPost
bool isPost() const
Definition: request.cpp:333
Cutelyst::Request::bodyJsonObject
QJsonObject bodyJsonObject() const
Definition: request.cpp:212
Cutelyst::Request::isPut
bool isPut() const
Definition: request.cpp:351
Cutelyst::Request::isDelete
bool isDelete() const
Definition: request.cpp:363