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