Add HTTP caching headers to KnownHeaders
QNetworkRequest is already aware of the Last-Modified header but has been lacking support for the If-Modified-Since, ETag, If-Match and If-None-Match headers. These headers are used with HTTP to signal conditional download requests. See RFC 7232 for more information. Change-Id: I248577b28e875fafd3e4c44fb31e8d712b6c14f1 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Anton Kudryavtsev <antkudr@mail.ru> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>bb10
parent
c08197ccab
commit
b0726e8641
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ public:
|
|||
SetCookieHeader,
|
||||
ContentDispositionHeader, // added for QMultipartMessage
|
||||
UserAgentHeader,
|
||||
ServerHeader
|
||||
ServerHeader,
|
||||
IfModifiedSinceHeader,
|
||||
ETagHeader,
|
||||
IfMatchHeader,
|
||||
IfNoneMatchHeader
|
||||
};
|
||||
enum Attribute {
|
||||
HttpStatusCodeAttribute,
|
||||
|
|
|
|||
|
|
@ -236,6 +236,46 @@ void tst_QNetworkRequest::setHeader_data()
|
|||
<< true << "Last-Modified"
|
||||
<< "Thu, 01 Nov 2007 18:08:30 GMT";
|
||||
|
||||
QTest::newRow("If-Modified-Since-Date") << QNetworkRequest::IfModifiedSinceHeader
|
||||
<< QVariant(QDate(2017, 7, 01))
|
||||
<< true << "If-Modified-Since"
|
||||
<< "Sat, 01 Jul 2017 00:00:00 GMT";
|
||||
QTest::newRow("If-Modified-Since-DateTime") << QNetworkRequest::IfModifiedSinceHeader
|
||||
<< QVariant(QDateTime(QDate(2017, 7, 01),
|
||||
QTime(3, 14, 15),
|
||||
Qt::UTC))
|
||||
<< true << "If-Modified-Since"
|
||||
<< "Sat, 01 Jul 2017 03:14:15 GMT";
|
||||
|
||||
QTest::newRow("Etag-strong") << QNetworkRequest::ETagHeader << QVariant(R"("xyzzy")")
|
||||
<< true << "ETag" << R"("xyzzy")";
|
||||
QTest::newRow("Etag-weak") << QNetworkRequest::ETagHeader << QVariant(R"(W/"xyzzy")")
|
||||
<< true << "ETag" << R"(W/"xyzzy")";
|
||||
QTest::newRow("Etag-empty") << QNetworkRequest::ETagHeader << QVariant(R"("")")
|
||||
<< true << "ETag" << R"("")";
|
||||
|
||||
QTest::newRow("If-Match-empty") << QNetworkRequest::IfMatchHeader << QVariant(R"("")")
|
||||
<< true << "If-Match" << R"("")";
|
||||
QTest::newRow("If-Match-any") << QNetworkRequest::IfMatchHeader << QVariant(R"("*")")
|
||||
<< true << "If-Match" << R"("*")";
|
||||
QTest::newRow("If-Match-single") << QNetworkRequest::IfMatchHeader << QVariant(R"("xyzzy")")
|
||||
<< true << "If-Match" << R"("xyzzy")";
|
||||
QTest::newRow("If-Match-multiple") << QNetworkRequest::IfMatchHeader
|
||||
<< QVariant(R"("xyzzy", "r2d2xxxx", "c3piozzzz")")
|
||||
<< true << "If-Match"
|
||||
<< R"("xyzzy", "r2d2xxxx", "c3piozzzz")";
|
||||
|
||||
QTest::newRow("If-None-Match-empty") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("")")
|
||||
<< true << "If-None-Match" << R"("")";
|
||||
QTest::newRow("If-None-Match-any") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("*")")
|
||||
<< true << "If-None-Match" << R"("*")";
|
||||
QTest::newRow("If-None-Match-single") << QNetworkRequest::IfNoneMatchHeader << QVariant(R"("xyzzy")")
|
||||
<< true << "If-None-Match" << R"("xyzzy")";
|
||||
QTest::newRow("If-None-Match-multiple") << QNetworkRequest::IfNoneMatchHeader
|
||||
<< QVariant(R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")")
|
||||
<< true << "If-None-Match"
|
||||
<< R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")";
|
||||
|
||||
QNetworkCookie cookie;
|
||||
cookie.setName("a");
|
||||
cookie.setValue("b");
|
||||
|
|
@ -327,6 +367,62 @@ void tst_QNetworkRequest::rawHeaderParsing_data()
|
|||
<< true << "Last-Modified"
|
||||
<< "Sun Nov 6 08:49:37 1994";
|
||||
|
||||
QTest::newRow("If-Modified-Since-RFC1123") << QNetworkRequest::IfModifiedSinceHeader
|
||||
<< QVariant(QDateTime(QDate(1994, 8, 06),
|
||||
QTime(8, 49, 37),
|
||||
Qt::UTC))
|
||||
<< true << "If-Modified-Since"
|
||||
<< "Sun, 06 Aug 1994 08:49:37 GMT";
|
||||
QTest::newRow("If-Modified-Since-RFC850") << QNetworkRequest::IfModifiedSinceHeader
|
||||
<< QVariant(QDateTime(QDate(1994, 8, 06),
|
||||
QTime(8, 49, 37),
|
||||
Qt::UTC))
|
||||
<< true << "If-Modified-Since"
|
||||
<< "Sunday, 06-Aug-94 08:49:37 GMT";
|
||||
QTest::newRow("If-Modified-Since-asctime") << QNetworkRequest::IfModifiedSinceHeader
|
||||
<< QVariant(QDateTime(QDate(1994, 8, 06),
|
||||
QTime(8, 49, 37),
|
||||
Qt::UTC))
|
||||
<< true << "If-Modified-Since"
|
||||
<< "Sun Aug 6 08:49:37 1994";
|
||||
|
||||
QTest::newRow("Etag-strong") << QNetworkRequest::ETagHeader << QVariant(R"("xyzzy")")
|
||||
<< true << "ETag" << R"("xyzzy")";
|
||||
QTest::newRow("Etag-weak") << QNetworkRequest::ETagHeader << QVariant(R"(W/"xyzzy")")
|
||||
<< true << "ETag" << R"(W/"xyzzy")";
|
||||
QTest::newRow("Etag-empty") << QNetworkRequest::ETagHeader << QVariant(R"("")")
|
||||
<< true << "ETag" << R"("")";
|
||||
|
||||
QTest::newRow("If-Match-empty") << QNetworkRequest::IfMatchHeader << QVariant(QStringList(R"("")"))
|
||||
<< true << "If-Match" << R"("")";
|
||||
QTest::newRow("If-Match-any") << QNetworkRequest::IfMatchHeader << QVariant(QStringList(R"("*")"))
|
||||
<< true << "If-Match" << R"("*")";
|
||||
QTest::newRow("If-Match-single") << QNetworkRequest::IfMatchHeader
|
||||
<< QVariant(QStringList(R"("xyzzy")"))
|
||||
<< true << "If-Match" << R"("xyzzy")";
|
||||
QTest::newRow("If-Match-multiple") << QNetworkRequest::IfMatchHeader
|
||||
<< QVariant(QStringList({R"("xyzzy")",
|
||||
R"("r2d2xxxx")",
|
||||
R"("c3piozzzz")"}))
|
||||
<< true << "If-Match"
|
||||
<< R"("xyzzy", "r2d2xxxx", "c3piozzzz")";
|
||||
|
||||
QTest::newRow("If-None-Match-empty") << QNetworkRequest::IfNoneMatchHeader
|
||||
<< QVariant(QStringList(R"("")"))
|
||||
<< true << "If-None-Match" << R"("")";
|
||||
QTest::newRow("If-None-Match-any") << QNetworkRequest::IfNoneMatchHeader
|
||||
<< QVariant(QStringList(R"("*")"))
|
||||
<< true << "If-None-Match" << R"("*")";
|
||||
QTest::newRow("If-None-Match-single") << QNetworkRequest::IfNoneMatchHeader
|
||||
<< QVariant(QStringList(R"("xyzzy")"))
|
||||
<< true << "If-None-Match" << R"("xyzzy")";
|
||||
QTest::newRow("If-None-Match-multiple") << QNetworkRequest::IfNoneMatchHeader
|
||||
<< QVariant(QStringList({R"("xyzzy")",
|
||||
R"(W/"r2d2xxxx")",
|
||||
R"("c3piozzzz")"}))
|
||||
<< true << "If-None-Match"
|
||||
<< R"("xyzzy", W/"r2d2xxxx", "c3piozzzz")";
|
||||
|
||||
QTest::newRow("Content-Length-invalid1") << QNetworkRequest::ContentLengthHeader << QVariant()
|
||||
<< false << "Content-Length" << "1a";
|
||||
QTest::newRow("Content-Length-invalid2") << QNetworkRequest::ContentLengthHeader << QVariant()
|
||||
|
|
|
|||
Loading…
Reference in New Issue