Removing IndexedDatabases that have stored blobs doesn't remove the blob files.
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Jul 2016 22:32:01 +0000 (22:32 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Jul 2016 22:32:01 +0000 (22:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=160089

Reviewed by Darin Adler.

Source/WebCore:

Tested by API test IndexedDB.StoreBlobThenDelete.

Blob filenames exist in the IDB directory with the name "[0-9]+.blob".

That is, one or more digits, followed by ".blob".

So when we delete an IndexedDB.sqlite3 and related files, we should delete those blob files as well.

* Modules/indexeddb/server/IDBServer.cpp:
(WebCore::IDBServer::removeAllDatabasesForOriginPath):

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobThenDelete.mm: Added.
* TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobToBeDeleted.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@203622 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/server/IDBServer.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobThenDelete.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobToBeDeleted.html [new file with mode: 0644]

index 0ea6869..53a9f65 100644 (file)
@@ -1,3 +1,21 @@
+2016-07-22  Brady Eidson  <beidson@apple.com>
+
+        Removing IndexedDatabases that have stored blobs doesn't remove the blob files.
+        https://bugs.webkit.org/show_bug.cgi?id=160089
+
+        Reviewed by Darin Adler.
+
+        Tested by API test IndexedDB.StoreBlobThenDelete.
+
+        Blob filenames exist in the IDB directory with the name "[0-9]+.blob".
+        
+        That is, one or more digits, followed by ".blob".
+        
+        So when we delete an IndexedDB.sqlite3 and related files, we should delete those blob files as well.
+        
+        * Modules/indexeddb/server/IDBServer.cpp:
+        (WebCore::IDBServer::removeAllDatabasesForOriginPath):
+
 2016-07-22  Chris Dumez  <cdumez@apple.com>
 
         Fix default parameter values for window.alert() / prompt() / confirm()
index aab5be5..d18059f 100644 (file)
@@ -555,10 +555,52 @@ static void removeAllDatabasesForOriginPath(const String& originPath, std::chron
                 continue;
         }
 
+        // Deleting this database means we need to delete all files that represent it.
+        // This includes:
+        //     - The directory itself, which is named after the database.
+        //     - IndexedDB.sqlite3 and related SQLite files.
+        //     - Blob files that we stored in the directory.
+        //
+        // To be conservative, we should *not* try to delete files that are unexpected;
+        // We should only delete files we think we put there.
+        //
+        // IndexedDB blob files are named "N.blob" where N is a decimal integer,
+        // so those are the only blob files we should be trying to delete.
+        for (auto& blobPath : listDirectory(databasePath, "[0-9]*.blob")) {
+            // Globbing can't give us only filenames starting with 1-or-more digits.
+            // The above globbing gives us files that start with a digit and ends with ".blob", but there might be non-digits in between.
+            // We need to validate that each filename contains only digits before deleting it, as any other files are not ones we put there.
+            String filename = pathGetFileName(blobPath);
+            auto filenameLength = filename.length();
+
+            ASSERT(filenameLength >= 6);
+            ASSERT(filename.endsWith(".blob"));
+
+            if (filename.length() < 6)
+                continue;
+            if (!filename.endsWith(".blob"))
+                continue;
+
+            bool validFilename = true;
+            for (unsigned i = 0; i < filenameLength - 5; ++i) {
+                if (!isASCIIDigit(filename[i])) {
+                    validFilename = false;
+                    break;
+                }
+            }
+
+            if (validFilename)
+                deleteFile(blobPath);
+        }
+
+        // Now delete IndexedDB.sqlite3 and related SQLite files.
         SQLiteFileSystem::deleteDatabaseFile(databaseFile);
+
+        // And finally, if we can, delete the empty directory.
         deleteEmptyDirectory(databasePath);
     }
 
+    // If no databases remain for this origin, we can delete the origin directory as well.
     deleteEmptyDirectory(originPath);
 }
 
index 2fdf92e..be17176 100644 (file)
@@ -1,3 +1,14 @@
+2016-07-22  Brady Eidson  <beidson@apple.com>
+
+        Removing IndexedDatabases that have stored blobs doesn't remove the blob files.
+        https://bugs.webkit.org/show_bug.cgi?id=160089
+
+        Reviewed by Darin Adler.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobThenDelete.mm: Added.
+        * TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobToBeDeleted.html: Added.
+
 2016-07-22  Konstantin Tokarev  <annulen@yandex.ru>
 
         [GTK] Improved exclusion patterns in make-dist.py manifest.
index eb5b89d..b8be25b 100644 (file)
@@ -79,6 +79,8 @@
                510477781D29923B009747EB /* IDBDeleteRecovery.mm in Sources */ = {isa = PBXBuildFile; fileRef = 510477751D298E03009747EB /* IDBDeleteRecovery.mm */; };
                51393E221523952D005F39C5 /* DOMWindowExtensionBasic_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51393E1D1523944A005F39C5 /* DOMWindowExtensionBasic_Bundle.cpp */; };
                5142B2731517C8C800C32B19 /* ContextMenuCanCopyURL.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5142B2721517C89100C32B19 /* ContextMenuCanCopyURL.html */; };
+               515BE16F1D428BB100DD7C68 /* StoreBlobToBeDeleted.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 515BE16E1D4288FF00DD7C68 /* StoreBlobToBeDeleted.html */; };
+               515BE1711D428E4B00DD7C68 /* StoreBlobThenDelete.mm in Sources */ = {isa = PBXBuildFile; fileRef = 515BE1701D428BD100DD7C68 /* StoreBlobThenDelete.mm */; };
                51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */; };
                51714EB51CF8C78C004723C4 /* WebProcessKillIDBCleanup-2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */; };
                51714EB81CF8CA17004723C4 /* WebProcessKillIDBCleanup.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51714EB61CF8C7A4004723C4 /* WebProcessKillIDBCleanup.mm */; };
                        dstPath = TestWebKitAPI.resources;
                        dstSubfolderSpec = 7;
                        files = (
+                               515BE16F1D428BB100DD7C68 /* StoreBlobToBeDeleted.html in Copy Resources */,
                                5C9E59411D3EB5AC00E3C62E /* ApplicationCache.db in Copy Resources */,
                                5C9E59421D3EB5AC00E3C62E /* ApplicationCache.db-shm in Copy Resources */,
                                5C9E59431D3EB5AC00E3C62E /* ApplicationCache.db-wal in Copy Resources */,
                51393E1E1523944A005F39C5 /* DOMWindowExtensionBasic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMWindowExtensionBasic.cpp; sourceTree = "<group>"; };
                5142B2701517C88B00C32B19 /* ContextMenuCanCopyURL.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContextMenuCanCopyURL.mm; sourceTree = "<group>"; };
                5142B2721517C89100C32B19 /* ContextMenuCanCopyURL.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ContextMenuCanCopyURL.html; sourceTree = "<group>"; };
+               515BE16E1D4288FF00DD7C68 /* StoreBlobToBeDeleted.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = StoreBlobToBeDeleted.html; sourceTree = "<group>"; };
+               515BE1701D428BD100DD7C68 /* StoreBlobThenDelete.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StoreBlobThenDelete.mm; sourceTree = "<group>"; };
                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "WebProcessKillIDBCleanup-1.html"; sourceTree = "<group>"; };
                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "WebProcessKillIDBCleanup-2.html"; sourceTree = "<group>"; };
                51714EB61CF8C7A4004723C4 /* WebProcessKillIDBCleanup.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebProcessKillIDBCleanup.mm; sourceTree = "<group>"; };
                                CD9E292B1C90A71F000BB800 /* RequiresUserActionForPlayback.mm */,
                                37BCA61B1B596BA9002012CA /* ShouldOpenExternalURLsInNewWindowActions.mm */,
                                2D9A53AE1B31FA8D0074D5AA /* ShrinkToFit.mm */,
+                               515BE1701D428BD100DD7C68 /* StoreBlobThenDelete.mm */,
                                7CC3E1FA197E234100BE6252 /* UserContentController.mm */,
                                7C882E031C80C624006BF731 /* UserContentWorld.mm */,
                                7C882E041C80C624006BF731 /* UserContentWorldPlugIn.mm */,
                                46C519E31D35629600DAA51A /* LocalStorageNullEntries.localstorage */,
                                46C519E41D35629600DAA51A /* LocalStorageNullEntries.localstorage-shm */,
                                7CCB99221D3B44E7003922F6 /* open-multiple-external-url.html */,
+                               515BE16E1D4288FF00DD7C68 /* StoreBlobToBeDeleted.html */,
                                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
                                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
                        );
                                7CCE7EA61A411A0F00447C4C /* PlatformUtilitiesMac.mm in Sources */,
                                7CCE7EA71A411A1300447C4C /* PlatformWebViewMac.mm in Sources */,
                                835CF9671D25FCD6001A65D4 /* RestoreSessionStateWithoutNavigation.cpp in Sources */,
+                               515BE1711D428E4B00DD7C68 /* StoreBlobThenDelete.mm in Sources */,
                                7CCE7F261A411AF600447C4C /* Preferences.mm in Sources */,
                                7CCE7F0B1A411AE600447C4C /* PreventEmptyUserAgent.cpp in Sources */,
                                7C83E0BD1D0A650C00FEBCF3 /* FullscreenTopContentInset.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobThenDelete.mm b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobThenDelete.mm
new file mode 100644 (file)
index 0000000..b408b28
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import <WebKit/WebKit.h>
+#import <WebKit/WKProcessPoolPrivate.h>
+#import <WebKit/WKUserContentControllerPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <WebKit/_WKProcessPoolConfiguration.h>
+#import <WebKit/_WKUserStyleSheet.h>
+#import <wtf/RetainPtr.h>
+
+#if WK_API_ENABLED
+
+static bool readyToContinue;
+static RetainPtr<WKScriptMessage> lastScriptMessage;
+
+@interface StoreBlobMessageHandler : NSObject <WKScriptMessageHandler>
+@end
+
+@implementation StoreBlobMessageHandler
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+    readyToContinue = true;
+    lastScriptMessage = message;
+}
+
+@end
+
+TEST(IndexedDB, StoreBlobThenDelete)
+{
+    RetainPtr<StoreBlobMessageHandler> handler = adoptNS([[StoreBlobMessageHandler alloc] init]);
+    RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [[configuration userContentController] addScriptMessageHandler:handler.get() name:@"testHandler"];
+
+    [configuration _setAllowUniversalAccessFromFileURLs:YES];
+
+    RetainPtr<WKWebView> webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+    NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"StoreBlobToBeDeleted" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
+    [webView loadRequest:request];
+
+    TestWebKitAPI::Util::run(&readyToContinue);
+    EXPECT_WK_STREQ(@"Success", (NSString *)[lastScriptMessage body]);
+
+    NSString *blobFilePath = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/1.blob" stringByExpandingTildeInPath];
+    NSString *databaseFilePath = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/IndexedDB.sqlite3" stringByExpandingTildeInPath];
+
+    // The database file and blob file should definitely be there right now.
+    EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:blobFilePath]);
+    EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
+
+    // Make some other .blob files in the database directory to later validate that only appropriate files are deleted.
+    NSString *otherBlob1 = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/7182.blob" stringByExpandingTildeInPath];
+    NSString *otherBlob2 = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/1a.blob" stringByExpandingTildeInPath];
+    NSString *otherBlob3 = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/a1.blob" stringByExpandingTildeInPath];
+    NSString *otherBlob4 = [@"~/Library/WebKit/TestWebKitAPI/WebsiteData/IndexedDB/file__0/StoreBlobToBeDeleted/.blob" stringByExpandingTildeInPath];
+    [[NSFileManager defaultManager] copyItemAtPath:blobFilePath toPath:otherBlob1 error:nil];
+    [[NSFileManager defaultManager] copyItemAtPath:blobFilePath toPath:otherBlob2 error:nil];
+    [[NSFileManager defaultManager] copyItemAtPath:blobFilePath toPath:otherBlob3 error:nil];
+    [[NSFileManager defaultManager] copyItemAtPath:blobFilePath toPath:otherBlob4 error:nil];
+
+    readyToContinue = false;
+    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:[NSDate distantPast] completionHandler:^() {
+        EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:blobFilePath]);
+        EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
+
+        // Make sure the fake blob file with a valid blob file name is gone.
+        EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:otherBlob1]);
+
+        // Make sure the fake blob files with invalid blob file names are still there.
+        EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:otherBlob2]);
+        EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:otherBlob3]);
+        EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:otherBlob4]);
+
+        // Now delete them so we're not leaving files around.
+        [[NSFileManager defaultManager] removeItemAtPath:otherBlob2 error:nil];
+        [[NSFileManager defaultManager] removeItemAtPath:otherBlob3 error:nil];
+        [[NSFileManager defaultManager] removeItemAtPath:otherBlob4 error:nil];
+
+        readyToContinue = true;
+    }];
+    TestWebKitAPI::Util::run(&readyToContinue);
+}
+
+#endif
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobToBeDeleted.html b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/StoreBlobToBeDeleted.html
new file mode 100644 (file)
index 0000000..bcaf972
--- /dev/null
@@ -0,0 +1,41 @@
+<script>
+
+var dbName = "StoreBlobToBeDeleted";
+var request = window.indexedDB.deleteDatabase(dbName);
+request.onsuccess = function(e)
+{
+    continueTest();
+}
+request.onerror = function(e)
+{
+    window.webkit.messageHandlers.testHandler.postMessage('Error deleting database');
+}
+
+var db;
+
+function continueTest(event)
+{
+    var request = window.indexedDB.open(dbName);
+
+    request.onupgradeneeded = function(e) {
+        db = e.target.result;
+        var objectStore = db.createObjectStore("TestStore");
+
+        const blobData = ["fun ", "times ", "all ", "around!"];
+        let blob = new Blob(blobData, { type: "text/plain" });
+
+        var addRequest = objectStore.add(blob, "BlobKey");
+        addRequest.onerror = function() {
+            window.webkit.messageHandlers.testHandler.postMessage('Error storing blob in database');
+        }
+    }
+
+    request.onsuccess = function() {
+        db.close();
+        window.webkit.messageHandlers.testHandler.postMessage('Success');
+    }
+    request.onerror = function() {
+        window.webkit.messageHandlers.testHandler.postMessage('Error storing blob in database');
+    }
+}
+</script>