8#include "multipartformdataparser.h"
14#include <QJsonDocument>
18using namespace Qt::Literals::StringLiterals;
21 : d_ptr(new RequestPrivate)
23 d_ptr->engineRequest = engineRequest;
29 qDeleteAll(d_ptr->uploads);
37 return d->engineRequest->remoteAddress;
45 quint32 data = d->engineRequest->remoteAddress.toIPv4Address(&ok);
47 return QHostAddress(data).toString();
49 return d->engineRequest->remoteAddress.toString();
53QString Request::hostname()
const
59 if (!d->remoteHostname.isEmpty()) {
60 ret = d->remoteHostname;
64 const QHostInfo ptr = QHostInfo::fromName(d->engineRequest->remoteAddress.toString());
65 if (ptr.error() != QHostInfo::NoError) {
66 qCDebug(CUTELYST_REQUEST) <<
"DNS lookup for the client hostname failed"
67 << d->engineRequest->remoteAddress;
71 d->remoteHostname = ptr.hostName();
72 ret = d->remoteHostname;
76quint16 Request::port() const noexcept
79 return d->engineRequest->remotePort;
82QUrl Request::uri()
const
87 if (!(d->parserStatus & RequestPrivate::UrlParsed)) {
89 if (d->engineRequest->serverAddress.isEmpty()) {
90 uri.setHost(QHostInfo::localHostName());
92 uri.setAuthority(QString::fromLatin1(d->engineRequest->serverAddress));
95 uri.setScheme(d->engineRequest->isSecure ? QStringLiteral(
"https")
96 : QStringLiteral(
"http"));
100 uri.setPath(d->engineRequest->path);
102 if (!d->engineRequest->query.isEmpty()) {
103 uri.setQuery(QString::fromLatin1(d->engineRequest->query));
107 d->parserStatus |= RequestPrivate::UrlParsed;
112QString Request::base()
const
115 QString base = d->base;
116 if (!(d->parserStatus & RequestPrivate::BaseParsed)) {
117 base = d->engineRequest->isSecure ? QStringLiteral(
"https://") : QStringLiteral(
"http://");
120 if (d->engineRequest->serverAddress.isEmpty()) {
121 base.append(QHostInfo::localHostName());
123 base.append(QString::fromLatin1(d->engineRequest->serverAddress));
127 d->parserStatus |= RequestPrivate::BaseParsed;
132QString Request::path() const noexcept
135 return d->engineRequest->path;
138QString Request::match() const noexcept
150QStringList Request::arguments() const noexcept
174bool Request::secure() const noexcept
177 return d->engineRequest->isSecure;
186QVariant Request::bodyData()
const
189 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
197 return bodyData().value<QCborValue>();
202 return bodyData().toJsonDocument();
207 return bodyData().toJsonDocument().object();
212 return bodyData().toJsonDocument().array();
217 return RequestPrivate::paramsMultiMapToVariantMap(
bodyParameters());
223 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
234 auto it = query.constFind(key);
235 while (it != query.constEnd() && it.key() == key) {
236 ret.prepend(it.value());
245 if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
248 return d->queryKeywords;
259 if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
262 return d->queryParam;
270 auto it = query.constFind(key);
271 while (it != query.constEnd() && it.key() == key) {
272 ret.prepend(it.value());
281 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
285 return d->cookies.value(name).value;
293 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
297 for (
auto it = d->cookies.constFind(name); it != d->cookies.constEnd() && it->name == name;
299 ret.prepend(it->value);
307 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
316 return d->engineRequest->headers;
319QByteArray Request::method() const noexcept
322 return d->engineRequest->method;
328 return d->engineRequest->method.compare(
"POST") == 0;
334 return d->engineRequest->method.compare(
"GET") == 0;
340 return d->engineRequest->method.compare(
"HEAD") == 0;
346 return d->engineRequest->method.compare(
"PUT") == 0;
352 return d->engineRequest->method.compare(
"PATCH") == 0;
358 return d->engineRequest->method.compare(
"DELETE") == 0;
361QByteArray Request::protocol() const noexcept
364 return d->engineRequest->protocol;
370 return d->engineRequest->headers.header(
"X-Requested-With").compare(
"XMLHttpRequest") == 0;
373QString Request::remoteUser() const noexcept
376 return d->engineRequest->remoteUser;
382 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
391 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
394 return d->uploadsMap;
401 const auto range = map.equal_range(name);
402 for (
auto i = range.first; i != range.second; ++i) {
414 auto it = args.constEnd();
415 while (it != args.constBegin()) {
417 ret.replace(it.key(), it.value());
429 auto it = query.constEnd();
430 while (it != query.constBegin()) {
432 urlQuery.addQueryItem(it.key(), it.value());
434 ret.setQuery(urlQuery);
445void RequestPrivate::parseUrlQuery()
const
448 if (engineRequest->query.size()) {
450 if (engineRequest->query.indexOf(
'=') < 0) {
451 QByteArray aux = engineRequest->query;
452 queryKeywords = Utils::decodePercentEncoding(&aux);
454 if (parserStatus & RequestPrivate::UrlParsed) {
455 queryParam = Utils::decodePercentEncoding(engineRequest->query.data(),
456 engineRequest->query.size());
458 QByteArray aux = engineRequest->query;
460 queryParam = Utils::decodePercentEncoding(aux.data(), aux.size());
464 parserStatus |= RequestPrivate::QueryParsed;
467void RequestPrivate::parseBody()
const
470 parserStatus |= RequestPrivate::BodyParsed;
474 bool sequencial = body->isSequential();
475 qint64 posOrig = body->pos();
476 if (sequencial && posOrig) {
477 qCWarning(CUTELYST_REQUEST) <<
"Can not parse sequential post body out of beginning";
478 parserStatus |= RequestPrivate::BodyParsed;
482 const QByteArray contentType = engineRequest->headers.header(
"Content-Type");
483 if (contentType.startsWith(
"application/x-www-form-urlencoded")) {
490 QByteArray line = body->readAll();
491 bodyParam = Utils::decodePercentEncoding(line.data(), line.size());
492 bodyData = QVariant::fromValue(bodyParam);
493 }
else if (contentType.startsWith(
"multipart/form-data")) {
499 for (
Upload *upload : ups) {
500 if (upload->filename().isEmpty() &&
501 upload->headers().header(
"Content-Type").isEmpty()) {
502 bodyParam.insert(upload->name(), QString::fromUtf8(upload->readAll()));
505 uploadsMap.insert(upload->name(), upload);
509 }
else if (contentType.startsWith(
"application/cbor")) {
514 bodyData = QVariant::fromValue(QCborValue::fromCbor(body->readAll()));
515 }
else if (contentType.startsWith(
"application/json")) {
520 bodyData = QJsonDocument::fromJson(body->readAll());
527 parserStatus |= RequestPrivate::BodyParsed;
531inline bool isSlit(
char c)
533 return c ==
';' || c ==
',';
536int findNextSplit(QByteArrayView text,
int from,
int length)
538 while (from < length) {
539 if (isSlit(text.at(from))) {
547inline bool isLWS(
char c)
549 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n';
552int nextNonWhitespace(QByteArrayView text,
int from,
int length)
558 while (from < length) {
559 if (isLWS(text.at(from))) {
567 return text.length();
577 const int length = text.length();
578 position = nextNonWhitespace(text, position, length);
580 int semiColonPosition = findNextSplit(text, position, length);
581 if (semiColonPosition < 0) {
582 semiColonPosition = length;
585 int equalsPosition = text.indexOf(
'=', position);
586 if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
590 cookie.name = text.sliced(position, equalsPosition - position).trimmed().toByteArray();
591 int secondLength = semiColonPosition - equalsPosition - 1;
592 if (secondLength > 0) {
593 cookie.value = text.sliced(equalsPosition + 1, secondLength).trimmed().toByteArray();
596 position = semiColonPosition;
601void RequestPrivate::parseCookies()
const
603 const QByteArray cookieString = engineRequest->headers.header(
"Cookie");
605 const int length = cookieString.length();
606 while (position < length) {
607 const auto cookie = nextField(cookieString, position);
608 if (cookie.name.isEmpty()) {
614 if (cookie.value.isEmpty()) {
618 cookies.insert(cookie.name, cookie);
622 parserStatus |= RequestPrivate::CookiesParsed;
625QVariantMap RequestPrivate::paramsMultiMapToVariantMap(
const ParamsMultiMap ¶ms)
628 auto end = params.constEnd();
629 while (params.constBegin() != end) {
631 ret.insert(ret.constBegin(), end.key(), end.value());
636#include "moc_request.cpp"
QVariantMap bodyParametersVariant() const
QCborValue bodyCbor() const
QVariantMap queryParametersVariant() const
QMultiMap< QAnyStringView, Cookie > cookies() const
QString addressString() const
bool isGet() const noexcept
QString queryKeywords() const
QVector< Upload * > uploads() const
ParamsMultiMap bodyParameters() const
QJsonArray bodyJsonArray() const
bool xhr() const noexcept
QJsonObject bodyJsonObject() const
QStringList captures() const noexcept
bool isPut() const noexcept
bool isDelete() const noexcept
QMultiMap< QAnyStringView, Upload * > uploadsMap() const
QUrl uriWith(const ParamsMultiMap &args, bool append=false) const
bool isPost() const noexcept
QJsonDocument bodyJsonDocument() const
ParamsMultiMap mangleParams(const ParamsMultiMap &args, bool append=false) const
void setCaptures(const QStringList &captures)
Headers headers() const noexcept
QIODevice * body() const noexcept
ParamsMultiMap queryParameters() const
bool isPatch() const noexcept
Engine * engine() const noexcept
QByteArray cookie(QAnyStringView name) const
Request(EngineRequest *engineRequest)
bool isHead() const noexcept
void setArguments(const QStringList &arguments)
QHostAddress address() const noexcept
void setMatch(const QString &match)
Cutelyst Upload handles file upload requests.
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.