Bug 1149987 - Part 5: Do not store or match Response objects with a Vary:* header; r=bkelly

This commit is contained in:
Ehsan Akhgari 2015-04-07 09:13:05 -04:00
parent 49ca5f135b
commit bbf8a918b8
5 changed files with 95 additions and 35 deletions

View File

@ -73,4 +73,5 @@ MSG_DEF(MSG_INVALID_RESPONSE_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid respo
MSG_DEF(MSG_INVALID_REDIRECT_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid redirect status code.")
MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN_TYPEERR, "{0} URL {1} must be either http:// or https://.")
MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.")
MSG_DEF(MSG_RESPONSE_HAS_VARY_STAR, 0, JSEXN_TYPEERR, "Invalid Response object with a 'Vary: *' header.")
MSG_DEF(MSG_BAD_FORMDATA, 0, JSEXN_TYPEERR, "Could not parse content as FormData.")

View File

@ -944,9 +944,9 @@ DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn,
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
if (header.EqualsLiteral("*")) {
continue;
}
MOZ_ASSERT(!header.EqualsLiteral("*"),
"We should have already caught this in "
"TypeUtils::ToPCacheResponseWithoutBody()");
ErrorResult errorResult;
nsAutoCString queryValue;

View File

@ -259,10 +259,10 @@ FetchPut::FetchComplete(FetchObserver* aObserver,
ToPCacheResponseWithoutBody(mStateList[i].mPCacheResponse,
*aInternalResponse, rv);
if (rv.Failed()) {
MaybeSetError(rv.ErrorCode());
return;
mResult = Move(rv);
} else {
aInternalResponse->GetBody(getter_AddRefs(mStateList[i].mResponseStream));
}
aInternalResponse->GetBody(getter_AddRefs(mStateList[i].mResponseStream));
mStateList[i].mFetchObserver = nullptr;
MOZ_ASSERT(mPendingCount > 0);
mPendingCount -= 1;
@ -377,9 +377,9 @@ FetchPut::MatchInPutList(const PCacheRequest& aRequest,
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
if (header.EqualsLiteral("*")) {
continue;
}
MOZ_ASSERT(!header.EqualsLiteral("*"),
"We should have already caught this in "
"TypeUtils::ToPCacheResponseWithoutBody()");
ErrorResult headerRv;
nsAutoCString value;

View File

@ -26,6 +26,8 @@
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsURLParsers.h"
#include "nsCRT.h"
#include "nsHttp.h"
namespace {
@ -104,6 +106,29 @@ ProcessURL(nsAString& aUrl, bool* aSchemeValidOut,
*aUrlWithoutQueryOut = Substring(aUrl, 0, queryPos - 1);
}
static bool
HasVaryStar(mozilla::dom::InternalHeaders* aHeaders)
{
nsAutoTArray<nsCString, 16> varyHeaders;
ErrorResult rv;
aHeaders->GetAll(NS_LITERAL_CSTRING("vary"), varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
for (uint32_t i = 0; i < varyHeaders.Length(); ++i) {
nsAutoCString varyValue(varyHeaders[i]);
char* rawBuffer = varyValue.BeginWriting();
char* token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer);
for (; token;
token = nsCRT::strtok(rawBuffer, NS_HTTP_HEADER_SEPS, &rawBuffer)) {
nsDependentCString header(token);
if (header.EqualsLiteral("*")) {
return true;
}
}
}
return false;
}
void
SerializeNormalStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut)
{
@ -266,6 +291,10 @@ TypeUtils::ToPCacheResponseWithoutBody(PCacheResponse& aOut,
aOut.statusText() = aIn.GetStatusText();
nsRefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
MOZ_ASSERT(headers);
if (HasVaryStar(headers)) {
aRv.ThrowTypeError(MSG_RESPONSE_HAS_VARY_STAR);
return;
}
headers->GetPHeaders(aOut.headers());
aOut.headersGuard() = headers->Guard();
aOut.securityInfo() = aIn.GetSecurityInfo();
@ -281,6 +310,9 @@ TypeUtils::ToPCacheResponse(PCacheResponse& aOut, Response& aIn, ErrorResult& aR
nsRefPtr<InternalResponse> ir = aIn.GetInternalResponse();
ToPCacheResponseWithoutBody(aOut, *ir, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
nsCOMPtr<nsIInputStream> stream;
aIn.GetBody(getter_AddRefs(stream));

View File

@ -107,15 +107,59 @@ function testBasicKeys() {
}
function testStar() {
function ensurePromiseRejected(promise) {
return promise
.then(function() {
ok(false, "Promise should be rejected");
}, function(err) {
is(err.name, "TypeError", "Attempting to store a Response with a Vary:* header must fail");
});
}
var test;
return setupTest({"WhatToVary": "*", "Cookie": "foo=bar"})
.then(function(t) {
test = t;
// Ensure that searching with a different Cookie header with Vary:* succeeds.
return test.cache.match(new Request(requestURL, {headers: {"Cookie": "bar=baz"}}));
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
return new Promise(function(resolve, reject) {
var cache;
caches.open(name).then(function(c) {
cache = c;
Promise.all([
ensurePromiseRejected(
cache.add(new Request(requestURL + "1", {headers: {"WhatToVary": "*"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "2", {headers: {"WhatToVary": "*"}}),
requestURL + "3",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "4", {headers: {"WhatToVary": "*"}}))
.then(function(response) {
return cache.put(requestURL + "4", response);
})),
ensurePromiseRejected(
cache.add(new Request(requestURL + "5", {headers: {"WhatToVary": "*,User-Agent"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "6", {headers: {"WhatToVary": "*,User-Agent"}}),
requestURL + "7",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "8", {headers: {"WhatToVary": "*,User-Agent"}}))
.then(function(response) {
return cache.put(requestURL + "8", response);
})),
ensurePromiseRejected(
cache.add(new Request(requestURL + "9", {headers: {"WhatToVary": "User-Agent,*"}}))),
ensurePromiseRejected(
cache.addAll([
new Request(requestURL + "10", {headers: {"WhatToVary": "User-Agent,*"}}),
requestURL + "10",
])),
ensurePromiseRejected(
fetch(new Request(requestURL + "11", {headers: {"WhatToVary": "User-Agent,*"}}))
.then(function(response) {
return cache.put(requestURL + "11", response);
})),
]).then(reject, resolve);
});
});
}
function testMatch() {
@ -134,23 +178,6 @@ function testMatch() {
});
}
function testStarAndAnotherHeader() {
var test;
return setupTest({"WhatToVary": "*,User-Agent"})
.then(function(t) {
test = t;
// Ensure that searching with a different User-Agent header fails.
return test.cache.match(new Request(requestURL, {headers: {"User-Agent": "MyUA"}}));
}).then(function(r) {
is(typeof r, "undefined", "Searching for a request with a non-matching User-Agent header should not succeed");
// Ensure that searching with a different User-Agent header but with ignoreVary succeeds.
return test.cache.match(new Request(requestURL, {headers: {"User-Agent": "MyUA"}}),
{ignoreVary: true});
}).then(function(r) {
return checkResponse(r, test.response, test.responseText);
});
}
function testInvalidHeaderName() {
var test;
return setupTest({"WhatToVary": "Foo/Bar, User-Agent"})
@ -285,6 +312,8 @@ function testMultipleCacheEntries() {
function step(testPromise) {
return testPromise.then(function() {
caches.delete(name);
}, function() {
caches.delete(name);
});
}
@ -294,8 +323,6 @@ step(testBasics()).then(function() {
return step(testStar());
}).then(function() {
return step(testMatch());
}).then(function() {
return step(testStarAndAnotherHeader());
}).then(function() {
return step(testInvalidHeaderName());
}).then(function() {