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