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 ? u
"https"_s : u
"http"_s);
99 uri.setPath(d->engineRequest->path);
101 if (!d->engineRequest->query.isEmpty()) {
102 uri.setQuery(QString::fromLatin1(d->engineRequest->query));
106 d->parserStatus |= RequestPrivate::UrlParsed;
111QString Request::base()
const
114 QString base = d->base;
115 if (!(d->parserStatus & RequestPrivate::BaseParsed)) {
116 base = d->engineRequest->isSecure ? u
"https://"_s : u
"http://"_s;
119 if (d->engineRequest->serverAddress.isEmpty()) {
120 base.append(QHostInfo::localHostName());
122 base.append(QString::fromLatin1(d->engineRequest->serverAddress));
126 d->parserStatus |= RequestPrivate::BaseParsed;
131QString Request::path() const noexcept
134 return d->engineRequest->path;
137QString Request::match() const noexcept
149QStringList Request::arguments() const noexcept
173bool Request::secure() const noexcept
176 return d->engineRequest->isSecure;
185QVariant Request::bodyData()
const
188 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
196 return bodyData().value<QCborValue>();
201 return bodyData().toJsonDocument();
206 return bodyData().toJsonDocument().object();
211 return bodyData().toJsonDocument().array();
216 return RequestPrivate::paramsMultiMapToVariantMap(
bodyParameters());
222 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
233 auto it = query.constFind(key);
234 while (it != query.constEnd() && it.key() == key) {
235 ret.prepend(it.value());
244 if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
247 return d->queryKeywords;
258 if (!(d->parserStatus & RequestPrivate::QueryParsed)) {
261 return d->queryParam;
269 auto it = query.constFind(key);
270 while (it != query.constEnd() && it.key() == key) {
271 ret.prepend(it.value());
280 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
284 return d->cookies.value(name).value;
292 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
296 for (
auto it = d->cookies.constFind(name); it != d->cookies.constEnd() && it->name == name;
298 ret.prepend(it->value);
306 if (!(d->parserStatus & RequestPrivate::CookiesParsed)) {
315 return d->engineRequest->headers;
318QByteArray Request::method() const noexcept
321 return d->engineRequest->method;
327 return d->engineRequest->method.compare(
"POST") == 0;
333 return d->engineRequest->method.compare(
"GET") == 0;
339 return d->engineRequest->method.compare(
"HEAD") == 0;
345 return d->engineRequest->method.compare(
"PUT") == 0;
351 return d->engineRequest->method.compare(
"PATCH") == 0;
357 return d->engineRequest->method.compare(
"DELETE") == 0;
360QByteArray Request::protocol() const noexcept
363 return d->engineRequest->protocol;
369 return d->engineRequest->headers.header(
"X-Requested-With").compare(
"XMLHttpRequest") == 0;
372QString Request::remoteUser() const noexcept
375 return d->engineRequest->remoteUser;
381 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
390 if (!(d->parserStatus & RequestPrivate::BodyParsed)) {
393 return d->uploadsMap;
400 const auto range = map.equal_range(name);
401 for (
auto i = range.first; i != range.second; ++i) {
413 auto it = args.constEnd();
414 while (it != args.constBegin()) {
416 ret.replace(it.key(), it.value());
428 auto it = query.constEnd();
429 while (it != query.constBegin()) {
431 urlQuery.addQueryItem(it.key(), it.value());
433 ret.setQuery(urlQuery);
444void RequestPrivate::parseUrlQuery()
const
447 if (engineRequest->query.size()) {
449 if (engineRequest->query.indexOf(
'=') < 0) {
450 QByteArray aux = engineRequest->query;
451 queryKeywords = Utils::decodePercentEncoding(&aux);
453 if (parserStatus & RequestPrivate::UrlParsed) {
454 queryParam = Utils::decodePercentEncoding(engineRequest->query.data(),
455 engineRequest->query.size());
457 QByteArray aux = engineRequest->query;
459 queryParam = Utils::decodePercentEncoding(aux.data(), aux.size());
463 parserStatus |= RequestPrivate::QueryParsed;
466void RequestPrivate::parseBody()
const
469 parserStatus |= RequestPrivate::BodyParsed;
473 bool sequencial = body->isSequential();
474 qint64 posOrig = body->pos();
475 if (sequencial && posOrig) {
476 qCWarning(CUTELYST_REQUEST) <<
"Can not parse sequential post body out of beginning";
477 parserStatus |= RequestPrivate::BodyParsed;
481 const QByteArray contentType = engineRequest->headers.header(
"Content-Type");
482 if (contentType.startsWith(
"application/x-www-form-urlencoded")) {
489 QByteArray line = body->readAll();
490 bodyParam = Utils::decodePercentEncoding(line.data(), line.size());
491 bodyData = QVariant::fromValue(bodyParam);
492 }
else if (contentType.startsWith(
"multipart/form-data")) {
498 for (
Upload *upload : ups) {
499 if (upload->filename().isEmpty() &&
500 upload->headers().header(
"Content-Type").isEmpty()) {
501 bodyParam.insert(upload->name(), QString::fromUtf8(upload->readAll()));
504 uploadsMap.insert(upload->name(), upload);
508 }
else if (contentType.startsWith(
"application/cbor")) {
513 bodyData = QVariant::fromValue(QCborValue::fromCbor(body->readAll()));
514 }
else if (contentType.startsWith(
"application/json")) {
519 bodyData = QJsonDocument::fromJson(body->readAll());
526 parserStatus |= RequestPrivate::BodyParsed;
530inline bool isSlit(
char c)
532 return c ==
';' || c ==
',';
535int findNextSplit(QByteArrayView text,
int from,
int length)
537 while (from < length) {
538 if (isSlit(text.at(from))) {
546inline bool isLWS(
char c)
548 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n';
551int nextNonWhitespace(QByteArrayView text,
int from,
int length)
557 while (from < length) {
558 if (isLWS(text.at(from))) {
566 return text.length();
576 const int length = text.length();
577 position = nextNonWhitespace(text, position, length);
579 int semiColonPosition = findNextSplit(text, position, length);
580 if (semiColonPosition < 0) {
581 semiColonPosition = length;
584 int equalsPosition = text.indexOf(
'=', position);
585 if (equalsPosition < 0 || equalsPosition > semiColonPosition) {
589 cookie.name = text.sliced(position, equalsPosition - position).trimmed().toByteArray();
590 int secondLength = semiColonPosition - equalsPosition - 1;
591 if (secondLength > 0) {
592 cookie.value = text.sliced(equalsPosition + 1, secondLength).trimmed().toByteArray();
595 position = semiColonPosition;
600void RequestPrivate::parseCookies()
const
602 const QByteArray cookieString = engineRequest->headers.header(
"Cookie");
604 const int length = cookieString.length();
605 while (position < length) {
606 const auto cookie = nextField(cookieString, position);
607 if (cookie.name.isEmpty()) {
613 if (cookie.value.isEmpty()) {
617 cookies.insert(cookie.name, cookie);
621 parserStatus |= RequestPrivate::CookiesParsed;
624QVariantMap RequestPrivate::paramsMultiMapToVariantMap(
const ParamsMultiMap ¶ms)
627 auto end = params.constEnd();
628 while (params.constBegin() != end) {
630 ret.insert(ret.constBegin(), end.key(), end.value());
635#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.