summaryrefslogtreecommitdiffstats
path: root/dom/cache/test/mochitest/test_cache_padding.html
blob: 61b890e948439d7ae88223f4ed065ba596109e8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<!-- Any copyright is dedicated to the Public Domain.
   - http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
  <title>Test Cache generate padding size for opaque repsonse</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="large_url_list.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
function setupTestIframe() {
  return new Promise(function(resolve) {
    var iframe = document.createElement("iframe");
    iframe.src = "empty.html";
    iframe.onload = function() {
      window.caches = iframe.contentWindow.caches;
      resolve();
    };
    document.body.appendChild(iframe);
  });
}

function clearStorage() {
  return new Promise(function(resolve, reject) {
    var qms = SpecialPowers.Services.qms;
    var principal = SpecialPowers.wrap(document).nodePrincipal;
    var request = qms.clearStoragesForPrincipal(principal);
    var cb = SpecialPowers.wrapCallback(resolve);
    request.callback = cb;
  });
}

function resetStorage() {
  return new Promise(function(resolve, reject) {
    var qms = SpecialPowers.Services.qms;
    var principal = SpecialPowers.wrap(document).nodePrincipal;
    var request = qms.resetStoragesForPrincipal(principal);
    var cb = SpecialPowers.wrapCallback(resolve);
    request.callback = cb;
  });
}

function getStorageUsage(fromMemory) {
  return new Promise(function(resolve, reject) {
    var qms = SpecialPowers.Services.qms;
    var principal = SpecialPowers.wrap(document).nodePrincipal;
    var cb = SpecialPowers.wrapCallback(function(request) {
      var result = request.result;
      resolve(result.usage);
    });

    // Actually, the flag is used to distingulish getting group usage and origin
    // usage, but we utilize this to get usage from in-memory and the disk.
    // Default value for "fromMemory" is false.
    qms.getUsageForPrincipal(principal, cb, !!fromMemory);
  });
}

async function verifyUsage() {
  // Although it returns group usage when passing true, it calculate the usage
  // from tracking usage object (in-memory object) in QuotaManager.
  let memoryUsage = await getStorageUsage(/* fromMemory */ true);
  // This will returns the origin usage by re-calculating usage from directory.
  let diskUsage = await getStorageUsage(/* fromMemory */ false);

  is(memoryUsage, diskUsage,
     "In-memory usage and disk usage should be the same.");
  return memoryUsage;
}

async function waitForIOToComplete(cache, request) {
  info("Wait for IO complete.");
  // The following lines ensure we've deleted orphaned body.
  // First, wait for cache operation delete the orphaned body.
  await cache.match(request);

  // Finally, wait for -wal file finish its job.
  return resetStorage();
}

function fetchOpaqueResponse(url) {
  return fetch(url, { mode: "no-cors" });
}

SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
  "set": [["dom.caches.enabled", true],
          ["dom.caches.testing.enabled", true],
          ["dom.quotaManager.testing", true]],
}, async function() {
  // This test is mainly to verify we only generate different padding size for
  // the opaque response which is comming from netwrok.
  // Besides, this test utilizes verifyUsage() to ensure Cache Acions does
  // update thier usage/padding size to the QM, does record padding size to
  // the directory padding file and does do above two things synchronously.
  // So that, opaque response's size is bigger than the normal response's size
  // and we always have the same usage bewteen from in-memory and from
  // the file-system.
  // Note: For the cloned and cached opaque response, the padding size shouldn't
  // be changed. Thus, it makes the attacker harder to get the padding size.

  const name = "cachePadding";
  const other_name = "cachePaddingOther";
  const cors_base = "https://example.com/tests/dom/cache/test/mochitest/";
  const url = "test_cache_add.js";

  await setupTestIframe();

  info("Stage 1: Clean storage.");
  await clearStorage();

  let cache = await caches.open(name);

  // XXX This arbitrary loop is a hack to restore the same growth database
  // behavior as it was before the schema upgrade from version 28 to 29.
  // XXX Obviously, this test shouldn't use the total usage (which includes
  // both the database and the file usage) to compute the disk size of
  // responses. It should use the file usage only. The problem is that the
  // quota client implementation currently doesn't differentiate between the
  // database and the file usage. Even if that gets fixed, there would be a
  // problem with checking cached usage which currently doesn't differentiate
  // between the database usage and the file usage as well.
  for (let i = 0; i < 19; i++) {
    const request = new Request("https://example.com/index" + i + ".html");
    const response = new Response("hello world");
    await cache.put(request, response);
  }

  await waitForIOToComplete(cache, url);
  let usage1 = await verifyUsage();

  info("Stage 2: Verify opaque responses have padding.");
  cache = await caches.open(name);
  await cache.add(url);
  await waitForIOToComplete(cache, url);
  let usage2 = await verifyUsage();
  let sizeForNormalResponse = usage2 - usage1;

  let opaqueResponse = await fetchOpaqueResponse(cors_base + url);
  cache = await caches.open(name);
  await cache.put(cors_base + url, opaqueResponse.clone());
  await waitForIOToComplete(cache, url);
  let usage3 = await verifyUsage();
  let sizeForOpaqueResponse = usage3 - usage2;
  ok(sizeForOpaqueResponse > sizeForNormalResponse,
     "The opaque response should have larger size than the normal response.");

  info("Stage 3: Verify the cloned response has the same size.");
  cache = await caches.open(name);
  await cache.put(cors_base + url, opaqueResponse.clone());
  await waitForIOToComplete(cache, url);
  let usage4 = await verifyUsage();
  // Since we put the same request and response again, the size should be the
  // same (DOM Cache removes the previous cached request and response)
  ok(usage4 == usage3,
     "We won't generate different padding for cloned response");

  info("Stage 4: Verify the cached response has the same size.");
  cache = await caches.open(name);
  opaqueResponse = await cache.match(cors_base + url);
  ok(opaqueResponse);

  await cache.put(cors_base + url, opaqueResponse);
  await waitForIOToComplete(cache, url);
  let usage5 = await verifyUsage();
  ok(usage5 == usage3,
     "We won't generate different padding for cached response");

  info("Stage 5: Verify padding size may changes in different fetch()s.");
  let paddingSizeChange = false;
  // Since we randomly generate padding size and rounding the overall size up,
  // we will probably have the same size. So, fetch it multiple times.
  for (let i = 0; i < 10; i++) {
    opaqueResponse = await fetchOpaqueResponse(cors_base + url);
    cache = await caches.open(name);
    await cache.put(cors_base + url, opaqueResponse);
    await waitForIOToComplete(cache, url);
    let usage6  = await verifyUsage();
    if (usage6 != usage5) {
      paddingSizeChange = true;
      break;
    }
  }
  ok(paddingSizeChange,
     "We should generate different padding size for fetching response");

  info("Stage 6: Verify the padding is removed once on caches.delete() and " +
       "cache.delete().");
  // Add an opauqe response on other cache storage and then delete that storage.
  cache = await caches.open(other_name);
  opaqueResponse = await fetchOpaqueResponse(cors_base + url);
  await cache.put(cors_base + url, opaqueResponse);
  await caches.delete(other_name);
  await caches.has(other_name);
  // Force remove orphaned cached in the next action
  await resetStorage();

  // Delete the opauqe repsonse on current cache storage.
  cache = await caches.open(name);
  await cache.delete(cors_base + url);
  await waitForIOToComplete(cache, url);
  let usage7  = await verifyUsage();
  ok(usage7 == usage2,
     "The opaque response should be removed by caches.delete() and " +
     "cache.delete()");

  await SimpleTest.finish();
});
</script>
</body>
</html>