Automatic update from web-platform-tests IDB: Handle reported blob size differing from actual size The renderer provides the size for blobs (including files) in IndexedDBExternalObject, which is typically stored as-is in the backing store while the contents of the blob are asynchronously written at commit time. A compromised or buggy renderer may supply a size that is smaller than the actual size of the blob, which may cause several problems including (a) transient under-reporting of quota usage, and (b) writing more data than clients who are reading the stored blob expect. The SQLite backing store implicitly handles this because it allocates the declared size in the blob row and an attempt to write more data than allocated fails. For LevelDB, blobs are written to a flat file on disk and currently, the blob remote is transparently passed through to the blob storage context without the accompanying information of the expected size. This CL mitigates that by plumbing through the expected size when writing the blob to a file on disk and then erroring out if the blob size does not match expected size. Additionally, WriteBlobToFile now deletes any partially written output file on error, which would earlier be left on disk. //third_party/blink/public/mojom/blob/data_element.mojom:DataElementFile specifies that a blob can contain at most one file element with unknown size, and it must have offset 0. On the browser process side, the former invariant was only incidentally enforced, while the latter was not. This CL also enforces these explicitly in `BlobRegistryImpl`. Bug: 497660733 Change-Id: I37953ea35e24a0bcf2a0e952e0e97a82a5491c7a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7716739 Reviewed-by: Evan Stade <evanstade@microsoft.com> Reviewed-by: Alex Gough <ajgo@chromium.org> Commit-Queue: Abhishek Shanthkumar <abhishek.shanthkumar@microsoft.com> Cr-Commit-Position: refs/heads/main@{#1617602} -- wpt-commits: a71025ce359cb1f54f5bae90df78523bbf4edc42 wpt-pr: 59362
157 lines
5.8 KiB
HTML
157 lines
5.8 KiB
HTML
<!doctype html>
|
|
<meta charset=utf8>
|
|
<title>File support in IndexedDB</title>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/resources/testdriver.js"></script>
|
|
<script src="/resources/testdriver-vendor.js"></script>
|
|
<script src="resources/support-promises.js"></script>
|
|
<form id="form">
|
|
<input id="file_input" name="file_input" type="file">
|
|
</form>
|
|
<script>
|
|
|
|
function assert_file_metadata_equal(file1, file2) {
|
|
assert_true(file1 instanceof File);
|
|
assert_true(file2 instanceof File)
|
|
assert_equals(file1.lastModified, file2.lastModified);
|
|
assert_equals(file1.name, file2.name);
|
|
assert_equals(file1.size, file2.size);
|
|
assert_equals(file1.type, file2.type);
|
|
}
|
|
|
|
async function assert_file_contents_equals(file1, file2) {
|
|
const file1_text = await file1.text();
|
|
const file2_text = await file2.text();
|
|
assert_equals(file1_text, file2_text);
|
|
}
|
|
|
|
promise_test(async (testCase) => {
|
|
const input = document.getElementById("file_input");
|
|
await test_driver.send_keys(input, String.raw`{{fs_path(resources/file_to_save.txt)}}`);
|
|
assert_equals(input.files.length, 1);
|
|
|
|
const file = input.files[0];
|
|
|
|
const db = await createDatabase(testCase, db => {
|
|
db.createObjectStore('objectStore');
|
|
});
|
|
|
|
const txn = db.transaction(['objectStore'], 'readwrite');
|
|
txn.objectStore('objectStore').add(file, 'key1');
|
|
txn.objectStore('objectStore').add({file: file, other: 'data'}, 'key2');
|
|
await promiseForTransaction(testCase, txn);
|
|
|
|
const readTxn = db.transaction(['objectStore'], 'readonly');
|
|
const fileByItself = await promiseForRequest(
|
|
testCase, readTxn.objectStore('objectStore').get('key1'));
|
|
const fileInDict = await promiseForRequest(
|
|
testCase, readTxn.objectStore('objectStore').get('key2'));
|
|
|
|
assert_file_metadata_equal(fileByItself, file);
|
|
assert_file_metadata_equal(fileInDict.file, file);
|
|
assert_file_metadata_equal(fileInDict.file, fileByItself);
|
|
|
|
await assert_file_contents_equals(fileByItself, file);
|
|
await assert_file_contents_equals(fileInDict.file, file);
|
|
|
|
db.close();
|
|
}, "Saves and loads back File objects from IndexedDB");
|
|
|
|
promise_test(async (testCase) => {
|
|
const input = document.getElementById("file_input");
|
|
await test_driver.send_keys(input, String.raw`{{fs_path(resources/file_to_save.txt)}}`);
|
|
assert_equals(input.files.length, 1);
|
|
|
|
const file = input.files[0];
|
|
const originalText = await file.text();
|
|
assert_greater_than(originalText.length, 10,
|
|
"Test file must be long enough to slice");
|
|
|
|
const sliceStart = 5;
|
|
const sliceEnd = originalText.length - 5;
|
|
const slicedBlob = file.slice(sliceStart, sliceEnd);
|
|
const expectedText = originalText.substring(sliceStart, sliceEnd);
|
|
|
|
assert_equals(slicedBlob.size, sliceEnd - sliceStart,
|
|
"Sliced blob size should match the slice range");
|
|
|
|
const db = await createDatabase(testCase, db => {
|
|
db.createObjectStore('objectStore');
|
|
});
|
|
|
|
const writeTxn = db.transaction(['objectStore'], 'readwrite');
|
|
writeTxn.objectStore('objectStore').add(slicedBlob, 'slicedKey');
|
|
await promiseForTransaction(testCase, writeTxn);
|
|
|
|
const readTxn = db.transaction(['objectStore'], 'readonly');
|
|
const idbBlob = await promiseForRequest(
|
|
testCase, readTxn.objectStore('objectStore').get('slicedKey'));
|
|
|
|
assert_equals(idbBlob.size, slicedBlob.size,
|
|
"Retrieved blob size should match the sliced blob");
|
|
|
|
const directText = await idbBlob.text();
|
|
assert_equals(directText, expectedText,
|
|
"Direct read should match the sliced content");
|
|
|
|
const blobUrl = URL.createObjectURL(idbBlob);
|
|
testCase.add_cleanup(() => URL.revokeObjectURL(blobUrl));
|
|
const response = await fetch(blobUrl);
|
|
const fetchedText = await response.text();
|
|
assert_equals(fetchedText, expectedText,
|
|
"Fetched content should match the sliced content");
|
|
|
|
db.close();
|
|
}, "Sliced file can be stored and retrieved from IndexedDB");
|
|
|
|
promise_test(async (testCase) => {
|
|
const input = document.getElementById("file_input");
|
|
await test_driver.send_keys(input, String.raw`{{fs_path(resources/file_to_save.txt)}}`);
|
|
assert_equals(input.files.length, 1);
|
|
|
|
const file = input.files[0];
|
|
const originalText = await file.text();
|
|
assert_greater_than(originalText.length, 10,
|
|
"Test file must be long enough to slice");
|
|
|
|
const mid = Math.floor(originalText.length / 2);
|
|
const slice1 = file.slice(0, mid);
|
|
const slice2 = file.slice(mid);
|
|
const composedBlob = new Blob([slice1, slice2]);
|
|
const expectedText = originalText;
|
|
|
|
assert_equals(composedBlob.size, originalText.length,
|
|
"Composed blob size should equal the full file size");
|
|
|
|
const db = await createDatabase(testCase, db => {
|
|
db.createObjectStore('objectStore');
|
|
});
|
|
|
|
const writeTxn = db.transaction(['objectStore'], 'readwrite');
|
|
writeTxn.objectStore('objectStore').add(composedBlob, 'composedKey');
|
|
await promiseForTransaction(testCase, writeTxn);
|
|
|
|
const readTxn = db.transaction(['objectStore'], 'readonly');
|
|
const idbBlob = await promiseForRequest(
|
|
testCase, readTxn.objectStore('objectStore').get('composedKey'));
|
|
|
|
assert_equals(idbBlob.size, composedBlob.size,
|
|
"Retrieved blob size should match the composed blob");
|
|
|
|
const directText = await idbBlob.text();
|
|
assert_equals(directText, expectedText,
|
|
"Direct read should match the original file content");
|
|
|
|
const blobUrl = URL.createObjectURL(idbBlob);
|
|
testCase.add_cleanup(() => URL.revokeObjectURL(blobUrl));
|
|
const response = await fetch(blobUrl);
|
|
const fetchedText = await response.text();
|
|
assert_equals(fetchedText, expectedText,
|
|
"Fetched content should match the original file content");
|
|
|
|
db.close();
|
|
}, "Composed slices of a file can be stored and retrieved from IndexedDB");
|
|
|
|
</script>
|