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 &params)
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