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