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
Request(Cutelyst::EngineRequest * engineRequest)32 Request::Request(Cutelyst::EngineRequest *engineRequest) :
33 d_ptr(new RequestPrivate)
34 {
35 d_ptr->engineRequest = engineRequest;
36 d_ptr->body = engineRequest->body;
37 }
38
~Request()39 Request::~Request()
40 {
41 qDeleteAll(d_ptr->uploads);
42 delete d_ptr->body;
43 delete d_ptr;
44 }
45
address() const46 QHostAddress Request::address() const
47 {
48 Q_D(const Request);
49 return d->engineRequest->remoteAddress;
50 }
51
addressString() const52 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
hostname() const65 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
port() const87 quint16 Request::port() const
88 {
89 Q_D(const Request);
90 return d->engineRequest->remotePort;
91 }
92
uri() const93 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
base() const121 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
path() const144 QString Request::path() const
145 {
146 Q_D(const Request);
147 return d->engineRequest->path;
148 }
149
match() const150 QString Request::match() const
151 {
152 Q_D(const Request);
153 return d->match;
154 }
155
setMatch(const QString & match)156 void Request::setMatch(const QString &match)
157 {
158 Q_D(Request);
159 d->match = match;
160 }
161
arguments() const162 QStringList Request::arguments() const
163 {
164 Q_D(const Request);
165 return d->args;
166 }
167
setArguments(const QStringList & arguments)168 void Request::setArguments(const QStringList &arguments)
169 {
170 Q_D(Request);
171 d->args = arguments;
172 }
173
captures() const174 QStringList Request::captures() const
175 {
176 Q_D(const Request);
177 return d->captures;
178 }
179
setCaptures(const QStringList & captures)180 void Request::setCaptures(const QStringList &captures)
181 {
182 Q_D(Request);
183 d->captures = captures;
184 }
185
secure() const186 bool Request::secure() const
187 {
188 Q_D(const Request);
189 return d->engineRequest->isSecure;
190 }
191
body() const192 QIODevice *Request::body() const
193 {
194 Q_D(const Request);
195 return d->body;
196 }
197
bodyData() const198 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
bodyJsonDocument() const207 QJsonDocument Request::bodyJsonDocument() const
208 {
209 return bodyData().toJsonDocument();
210 }
211
bodyJsonObject() const212 QJsonObject Request::bodyJsonObject() const
213 {
214 return bodyData().toJsonDocument().object();
215 }
216
bodyJsonArray() const217 QJsonArray Request::bodyJsonArray() const
218 {
219 return bodyData().toJsonDocument().array();
220 }
221
bodyParametersVariant() const222 QVariantMap Request::bodyParametersVariant() const
223 {
224 return RequestPrivate::paramsMultiMapToVariantMap(bodyParameters());
225 }
226
bodyParameters() const227 ParamsMultiMap Request::bodyParameters() const
228 {
229 Q_D(const Request);
230 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
231 d->parseBody();
232 }
233 return d->bodyParam;
234 }
235
bodyParameters(const QString & key) const236 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
queryKeywords() const249 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
queryParametersVariant() const258 QVariantMap Request::queryParametersVariant() const
259 {
260 return RequestPrivate::paramsMultiMapToVariantMap(queryParameters());
261 }
262
queryParameters() const263 ParamsMultiMap Request::queryParameters() const
264 {
265 Q_D(const Request);
266 if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
267 d->parseUrlQuery();
268 }
269 return d->queryParam;
270 }
271
queryParameters(const QString & key) const272 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
cookie(const QString & name) const285 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
cookies(const QString & name) const295 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
cookies() const312 Cutelyst::ParamsMultiMap Request::cookies() const
313 {
314 Q_D(const Request);
315 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
316 d->parseCookies();
317 }
318 return d->cookies;
319 }
320
headers() const321 Headers Request::headers() const
322 {
323 Q_D(const Request);
324 return d->engineRequest->headers;
325 }
326
method() const327 QString Request::method() const
328 {
329 Q_D(const Request);
330 return d->engineRequest->method;
331 }
332
isPost() const333 bool Request::isPost() const
334 {
335 Q_D(const Request);
336 return d->engineRequest->method == QStringLiteral("POST");
337 }
338
isGet() const339 bool Request::isGet() const
340 {
341 Q_D(const Request);
342 return d->engineRequest->method == QStringLiteral("GET");
343 }
344
isHead() const345 bool Request::isHead() const
346 {
347 Q_D(const Request);
348 return d->engineRequest->method == QStringLiteral("HEAD");
349 }
350
isPut() const351 bool Request::isPut() const
352 {
353 Q_D(const Request);
354 return d->engineRequest->method == QStringLiteral("PUT");
355 }
356
isPatch() const357 bool Request::isPatch() const
358 {
359 Q_D(const Request);
360 return d->engineRequest->method == QStringLiteral("PATCH");
361 }
362
isDelete() const363 bool Request::isDelete() const
364 {
365 Q_D(const Request);
366 return d->engineRequest->method == QStringLiteral("DELETE");
367 }
368
protocol() const369 QString Request::protocol() const
370 {
371 Q_D(const Request);
372 return d->engineRequest->protocol;
373 }
374
xhr() const375 bool Request::xhr() const
376 {
377 Q_D(const Request);
378 return d->engineRequest->headers.header(QStringLiteral("X_REQUESTED_WITH")) == QStringLiteral("XMLHttpRequest");
379 }
380
remoteUser() const381 QString Request::remoteUser() const
382 {
383 Q_D(const Request);
384 return d->engineRequest->remoteUser;
385 }
386
uploads() const387 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
uploadsMap() const396 QMultiMap<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
uploads(const QString & name) const405 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
mangleParams(const ParamsMultiMap & args,bool append) const416 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
uriWith(const ParamsMultiMap & args,bool append) const432 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
engine() const447 Engine *Request::engine() const
448 {
449 Q_D(const Request);
450 return d->engine;
451 }
452
parseUrlQuery() const453 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
parseBody() const474 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
isSlit(QChar c)531 static inline bool isSlit(QChar c)
532 {
533 return c == QLatin1Char(';') || c == QLatin1Char(',');
534 }
535
findNextSplit(const QString & text,int from,int length)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
isLWS(QChar c)547 static inline bool isLWS(QChar c)
548 {
549 return c == QLatin1Char(' ') || c == QLatin1Char('\t') || c == QLatin1Char('\r') || c == QLatin1Char('\n');
550 }
551
nextNonWhitespace(const QString & text,int from,int length)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
nextField(const QString & text,int & position)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
parseCookies() const598 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
paramsMultiMapToVariantMap(const ParamsMultiMap & params)622 QVariantMap RequestPrivate::paramsMultiMapToVariantMap(const ParamsMultiMap ¶ms)
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"
634