diff options
Diffstat (limited to 'src/network/access')
-rw-r--r-- | src/network/access/qnetworkaccessftpbackend.cpp | 61 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessftpbackend_p.h | 5 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 114 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 6 |
4 files changed, 179 insertions, 7 deletions
diff --git a/src/network/access/qnetworkaccessftpbackend.cpp b/src/network/access/qnetworkaccessftpbackend.cpp index c5404e4221..269845ed39 100644 --- a/src/network/access/qnetworkaccessftpbackend.cpp +++ b/src/network/access/qnetworkaccessftpbackend.cpp @@ -102,8 +102,8 @@ public: }; QNetworkAccessFtpBackend::QNetworkAccessFtpBackend() - : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1), - supportsSize(false), supportsMdtm(false), state(Idle) + : ftp(0), uploadDevice(0), totalBytes(0), helpId(-1), sizeId(-1), mdtmId(-1), pwdId(-1), + supportsSize(false), supportsMdtm(false), supportsPwd(false), state(Idle) { } @@ -302,13 +302,38 @@ void QNetworkAccessFtpBackend::ftpDone() if (state == LoggingIn) { state = CheckingFeatures; - if (operation() == QNetworkAccessManager::GetOperation) { - // send help command to find out if server supports "SIZE" and "MDTM" + // send help command to find out if server supports SIZE, MDTM, and PWD + if (operation() == QNetworkAccessManager::GetOperation + || operation() == QNetworkAccessManager::PutOperation) { helpId = ftp->rawCommand(QLatin1String("HELP")); // get supported commands } else { ftpDone(); } } else if (state == CheckingFeatures) { + // If a URL path starts with // prefix (/%2F decoded), the resource will + // be retrieved by an absolute path starting with the root directory. + // For the other URLs, the working directory is retrieved by PWD command + // and prepended to the resource path as an absolute path starting with + // the working directory. + state = ResolvingPath; + QString path = url().path(); + if (path.startsWith(QLatin1String("//")) || supportsPwd == false) { + ftpDone(); // no commands sent, move to the next state + } else { + // If a path starts with /~/ prefix, its prefix will be replaced by + // the working directory as an absolute path starting with working + // directory. + if (path.startsWith(QLatin1String("/~/"))) { + // Remove leading /~ symbols + QUrl newUrl = url(); + newUrl.setPath(path.mid(2)); + setUrl(newUrl); + } + + // send PWD command to retrieve the working directory + pwdId = ftp->rawCommand(QLatin1String("PWD")); + } + } else if (state == ResolvingPath) { state = Statting; if (operation() == QNetworkAccessManager::GetOperation) { // logged in successfully, send the stat requests (if supported) @@ -366,6 +391,34 @@ void QNetworkAccessFtpBackend::ftpRawCommandReply(int code, const QString &text) supportsSize = true; if (text.contains(QLatin1String("MDTM"), Qt::CaseSensitive)) supportsMdtm = true; + if (text.contains(QLatin1String("PWD"), Qt::CaseSensitive)) + supportsPwd = true; + } else if (id == pwdId && code == 257) { + QString pwdPath; + int startIndex = text.indexOf('"'); + int stopIndex = text.lastIndexOf('"'); + if (stopIndex - startIndex) { + // The working directory is a substring between \" symbols. + startIndex++; // skip the first \" symbol + pwdPath = text.mid(startIndex, stopIndex - startIndex); + } else { + // If there is no or only one \" symbol, use all the characters of + // text. + pwdPath = text; + } + + // If a URL path starts with the working directory prefix, its resource + // will be retrieved from the working directory. Otherwise, the path of + // the working directory is prepended to the resource path. + QString urlPath = url().path(); + if (!urlPath.startsWith(pwdPath)) { + if (pwdPath.endsWith(QLatin1Char('/'))) + pwdPath.chop(1); + // Prepend working directory to the URL path + QUrl newUrl = url(); + newUrl.setPath(pwdPath % urlPath); + setUrl(newUrl); + } } else if (code == 213) { // file status if (id == sizeId) { // reply to the size command diff --git a/src/network/access/qnetworkaccessftpbackend_p.h b/src/network/access/qnetworkaccessftpbackend_p.h index 4bd082fb67..0b3d35dcd3 100644 --- a/src/network/access/qnetworkaccessftpbackend_p.h +++ b/src/network/access/qnetworkaccessftpbackend_p.h @@ -76,6 +76,7 @@ public: //Connecting, LoggingIn, CheckingFeatures, + ResolvingPath, Statting, Transferring, Disconnecting @@ -107,8 +108,8 @@ private: QPointer<QNetworkAccessCachedFtpConnection> ftp; QIODevice *uploadDevice; qint64 totalBytes; - int helpId, sizeId, mdtmId; - bool supportsSize, supportsMdtm; + int helpId, sizeId, mdtmId, pwdId; + bool supportsSize, supportsMdtm, supportsPwd; State state; }; diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 1d7c5bec51..2d6df9de21 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -98,6 +98,25 @@ QT_BEGIN_NAMESPACE header and contains a QDateTime representing the last modification date of the contents. + \value IfModifiedSinceHeader Corresponds to the HTTP If-Modified-Since + header and contains a QDateTime. It is usually added to a + QNetworkRequest. The server shall send a 304 (Not Modified) response + if the resource has not changed since this time. + + \value ETagHeader Corresponds to the HTTP ETag + header and contains a QString representing the last modification + state of the contents. + + \value IfMatchHeader Corresponds to the HTTP If-Match + header and contains a QStringList. It is usually added to a + QNetworkRequest. The server shall send a 412 (Precondition Failed) + response if the resource does not match. + + \value IfNoneMatchHeader Corresponds to the HTTP If-None-Match + header and contains a QStringList. It is usually added to a + QNetworkRequest. The server shall send a 304 (Not Modified) response + if the resource does match. + \value CookieHeader Corresponds to the HTTP Cookie header and contains a QList<QNetworkCookie> representing the cookies to be sent back to the server. @@ -788,6 +807,18 @@ static QByteArray headerName(QNetworkRequest::KnownHeaders header) case QNetworkRequest::LastModifiedHeader: return "Last-Modified"; + case QNetworkRequest::IfModifiedSinceHeader: + return "If-Modified-Since"; + + case QNetworkRequest::ETagHeader: + return "ETag"; + + case QNetworkRequest::IfMatchHeader: + return "If-Match"; + + case QNetworkRequest::IfNoneMatchHeader: + return "If-None-Match"; + case QNetworkRequest::CookieHeader: return "Cookie"; @@ -818,6 +849,9 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria case QNetworkRequest::ContentDispositionHeader: case QNetworkRequest::UserAgentHeader: case QNetworkRequest::ServerHeader: + case QNetworkRequest::ETagHeader: + case QNetworkRequest::IfMatchHeader: + case QNetworkRequest::IfNoneMatchHeader: return value.toByteArray(); case QNetworkRequest::LocationHeader: @@ -830,6 +864,7 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria } case QNetworkRequest::LastModifiedHeader: + case QNetworkRequest::IfModifiedSinceHeader: switch (value.userType()) { case QMetaType::QDate: case QMetaType::QDateTime: @@ -891,6 +926,20 @@ static int parseHeaderName(const QByteArray &headerName) return QNetworkRequest::CookieHeader; break; + case 'e': + if (qstricmp(headerName.constData(), "etag") == 0) + return QNetworkRequest::ETagHeader; + break; + + case 'i': + if (qstricmp(headerName.constData(), "if-modified-since") == 0) + return QNetworkRequest::IfModifiedSinceHeader; + if (qstricmp(headerName.constData(), "if-match") == 0) + return QNetworkRequest::IfMatchHeader; + if (qstricmp(headerName.constData(), "if-none-match") == 0) + return QNetworkRequest::IfNoneMatchHeader; + break; + case 'l': if (qstricmp(headerName.constData(), "location") == 0) return QNetworkRequest::LocationHeader; @@ -937,6 +986,61 @@ static QVariant parseCookieHeader(const QByteArray &raw) return QVariant::fromValue(result); } +static QVariant parseETag(const QByteArray &raw) +{ + const QByteArray trimmed = raw.trimmed(); + if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")")) + return QVariant(); + + if (!trimmed.endsWith('"')) + return QVariant(); + + return QString::fromLatin1(trimmed); +} + +static QVariant parseIfMatch(const QByteArray &raw) +{ + const QByteArray trimmedRaw = raw.trimmed(); + if (trimmedRaw == "*") + return QStringList(QStringLiteral("*")); + + QStringList tags; + const QList<QByteArray> split = trimmedRaw.split(','); + for (const QByteArray &element : split) { + const QByteArray trimmed = element.trimmed(); + if (!trimmed.startsWith('"')) + continue; + + if (!trimmed.endsWith('"')) + continue; + + tags += QString::fromLatin1(trimmed); + } + return tags; +} + +static QVariant parseIfNoneMatch(const QByteArray &raw) +{ + const QByteArray trimmedRaw = raw.trimmed(); + if (trimmedRaw == "*") + return QStringList(QStringLiteral("*")); + + QStringList tags; + const QList<QByteArray> split = trimmedRaw.split(','); + for (const QByteArray &element : split) { + const QByteArray trimmed = element.trimmed(); + if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")")) + continue; + + if (!trimmed.endsWith('"')) + continue; + + tags += QString::fromLatin1(trimmed); + } + return tags; +} + + static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QByteArray &value) { // header is always a valid value @@ -963,8 +1067,18 @@ static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QBy } case QNetworkRequest::LastModifiedHeader: + case QNetworkRequest::IfModifiedSinceHeader: return parseHttpDate(value); + case QNetworkRequest::ETagHeader: + return parseETag(value); + + case QNetworkRequest::IfMatchHeader: + return parseIfMatch(value); + + case QNetworkRequest::IfNoneMatchHeader: + return parseIfNoneMatch(value); + case QNetworkRequest::CookieHeader: return parseCookieHeader(value); diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index e104c139d9..8462eae8c8 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -63,7 +63,11 @@ public: SetCookieHeader, ContentDispositionHeader, // added for QMultipartMessage UserAgentHeader, - ServerHeader + ServerHeader, + IfModifiedSinceHeader, + ETagHeader, + IfMatchHeader, + IfNoneMatchHeader }; enum Attribute { HttpStatusCodeAttribute, |