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
Jan Murawski 2017-07-28 16:03:01 +02:00
parent c08197ccab
commit b0726e8641
3 changed files with 215 additions and 1 deletions

View File

@ -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);

View File

@ -63,7 +63,11 @@ public:
SetCookieHeader,
ContentDispositionHeader, // added for QMultipartMessage
UserAgentHeader,
ServerHeader
ServerHeader,
IfModifiedSinceHeader,
ETagHeader,
IfMatchHeader,
IfNoneMatchHeader
};
enum Attribute {
HttpStatusCodeAttribute,

View File

@ -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()