Bug 1143959 - Set the journal mode and foreign key pragmas for all DBActions; r=bkelly

Before this patch, we would only set these pragmas as part of CreateSchema
which runs in SetupAction.  This meant that the connection used to perform
other DBActions would not have had these pragmas applied.  As a result,
sqlite would not honor foreign keys on such connections, so the cascade
delete rules responsible for deleting rows from request_headers and
response_headers would not get executed when DBSchema::CachePut deleted the
old entry before adding a new one.

The test in the patch demonstrates how this could result in an observable
breakage.  Before this patch, the response headers stored in the cache for
the overwritten entry would reflect both `Mirrored: `foo' and `Mirrored: bar'
headers, which means that attempting to get this header on the cached
response would return the first entry, `foo'.
This commit is contained in:
Ehsan Akhgari 2015-03-16 20:27:23 -04:00
Родитель e250f1ab8f
Коммит eaf970dc6a
7 изменённых файлов: 107 добавлений и 12 удалений

3
dom/cache/DBAction.cpp поставляемый
Просмотреть файл

@ -154,6 +154,9 @@ DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
}
rv = DBSchema::InitializeConnection(conn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
conn.forget(aConnOut);
return rv;

43
dom/cache/DBSchema.cpp поставляемый
Просмотреть файл

@ -34,21 +34,9 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn)
MOZ_ASSERT(aConn);
nsAutoCString pragmas(
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
// Switch the journaling mode to TRUNCATE to avoid changing the directory
// structure at the conclusion of every transaction for devices with slower
// file systems.
"PRAGMA journal_mode = TRUNCATE; "
#endif
"PRAGMA foreign_keys = ON; "
// Enable auto-vaccum but in incremental mode in order to avoid doing a lot
// of work at the end of each transaction.
"PRAGMA auto_vacuum = INCREMENTAL; "
// Note, the default encoding of UTF-8 is preferred. mozStorage does all
// the work necessary to convert UTF-16 nsString values for us. We don't
// need ordering and the binary equality operations are correct. So, do
// NOT set PRAGMA encoding to UTF-16.
);
nsresult rv = aConn->ExecuteSimpleSQL(pragmas);
@ -175,6 +163,37 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn)
return rv;
}
// static
nsresult
DBSchema::InitializeConnection(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
// This function needs to perform per-connection initialization tasks that
// need to happen regardless of the schema.
nsAutoCString pragmas(
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
// Switch the journaling mode to TRUNCATE to avoid changing the directory
// structure at the conclusion of every transaction for devices with slower
// file systems.
"PRAGMA journal_mode = TRUNCATE; "
#endif
"PRAGMA foreign_keys = ON; "
// Note, the default encoding of UTF-8 is preferred. mozStorage does all
// the work necessary to convert UTF-16 nsString values for us. We don't
// need ordering and the binary equality operations are correct. So, do
// NOT set PRAGMA encoding to UTF-16.
);
nsresult rv = aConn->ExecuteSimpleSQL(pragmas);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return NS_OK;
}
// static
nsresult
DBSchema::CreateCache(mozIStorageConnection* aConn, CacheId* aCacheIdOut)

1
dom/cache/DBSchema.h поставляемый
Просмотреть файл

@ -33,6 +33,7 @@ class DBSchema MOZ_FINAL
{
public:
static nsresult CreateSchema(mozIStorageConnection* aConn);
static nsresult InitializeConnection(mozIStorageConnection* aConn);
static nsresult CreateCache(mozIStorageConnection* aConn,
CacheId* aCacheIdOut);

5
dom/cache/test/mochitest/mirror.sjs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Mirrored", request.getHeader("Mirror"));
response.write(request.getHeader("Mirror"));
}

3
dom/cache/test/mochitest/mochitest.ini поставляемый
Просмотреть файл

@ -10,8 +10,11 @@ support-files =
serviceworker_driver.js
test_cache_match_request.js
test_cache_matchAll_request.js
test_cache_overwrite.js
mirror.sjs
[test_cache.html]
[test_cache_add.html]
[test_cache_match_request.html]
[test_cache_matchAll_request.html]
[test_cache_overwrite.html]

20
dom/cache/test/mochitest/test_cache_overwrite.html поставляемый Normal file
Просмотреть файл

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test what happens when you overwrite a cache entry</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="driver.js"></script>
</head>
<body>
<iframe id="frame"></iframe>
<script class="testbody" type="text/javascript">
runTests("test_cache_overwrite.js")
.then(function() {
SimpleTest.finish();
});
</script>
</body>
</html>

44
dom/cache/test/mochitest/test_cache_overwrite.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,44 @@
var requestURL = "//mochi.test:8888/tests/dom/cache/test/mochitest/mirror.sjs?" + context;
var response;
var c;
var responseText;
var name = "match-mirror" + context;
function checkResponse(r) {
ok(r !== response, "The objects should not be the same");
is(r.url, response.url.replace("#fragment", ""),
"The URLs should be the same");
is(r.status, response.status, "The status codes should be the same");
is(r.type, response.type, "The response types should be the same");
is(r.ok, response.ok, "Both responses should have succeeded");
is(r.statusText, response.statusText,
"Both responses should have the same status text");
is(r.headers.get("Mirrored"), response.headers.get("Mirrored"),
"Both responses should have the same Mirrored header");
return r.text().then(function(text) {
is(text, responseText, "The response body should be correct");
});
}
fetch(new Request(requestURL, {headers: {"Mirror": "bar"}})).then(function(r) {
is(r.headers.get("Mirrored"), "bar", "The server should give back the correct header");
response = r;
return response.text();
}).then(function(text) {
responseText = text;
return caches.open(name);
}).then(function(cache) {
c = cache;
return c.add(new Request(requestURL, {headers: {"Mirror": "foo"}}));
}).then(function() {
// Overwrite the request, to replace the entry stored in response_headers
// with a different value.
return c.add(new Request(requestURL, {headers: {"Mirror": "bar"}}));
}).then(function() {
return c.matchAll();
}).then(function(r) {
is(r.length, 1, "Only one request should be in the cache");
return checkResponse(r[0]);
}).then(function() {
testDone();
});