diff options
-rw-r--r-- | chrome/browser/extensions/web_view_browsertest.cc | 225 | ||||
-rw-r--r-- | chrome/test/data/extensions/platform_apps/web_view_isolation/main.js | 16 | ||||
-rw-r--r-- | content/browser/browser_context.cc | 6 | ||||
-rw-r--r-- | content/browser/storage_partition_impl.cc | 59 | ||||
-rw-r--r-- | content/browser/storage_partition_impl.h | 62 | ||||
-rw-r--r-- | content/browser/storage_partition_impl_map.cc | 90 | ||||
-rw-r--r-- | content/browser/storage_partition_impl_map.h | 52 | ||||
-rw-r--r-- | content/browser/storage_partition_impl_map_unittest.cc (renamed from content/browser/storage_partition_impl_unittest.cc) | 24 | ||||
-rw-r--r-- | content/content_tests.gypi | 2 |
9 files changed, 398 insertions, 138 deletions
diff --git a/chrome/browser/extensions/web_view_browsertest.cc b/chrome/browser/extensions/web_view_browsertest.cc index 8e68ece..6c6cbaa 100644 --- a/chrome/browser/extensions/web_view_browsertest.cc +++ b/chrome/browser/extensions/web_view_browsertest.cc @@ -32,12 +32,23 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { // the expected process allocation and storage partition assignment. // The |navigate_to_url| parameter is used to navigate the main browser // window. + // + // TODO(ajwong): This function is getting to be too large. Either refactor it + // so the test can specify a configuration of WebView tags that we will + // dynamically inject JS to generate, or move this test wholesale into + // something that RunPlatformAppTest() can execute purely in Javascript. This + // won't let us do a white-box examination of the StoragePartition equivalence + // directly, but we will be able to view the black box effects which is good + // enough. http://crbug.com/160361 void NavigateAndOpenAppForIsolation( GURL navigate_to_url, content::WebContents** default_tag_contents1, content::WebContents** default_tag_contents2, content::WebContents** named_partition_contents1, - content::WebContents** named_partition_contents2) { + content::WebContents** named_partition_contents2, + content::WebContents** persistent_partition_contents1, + content::WebContents** persistent_partition_contents2, + content::WebContents** persistent_partition_contents3) { GURL::Replacements replace_host; std::string host_str("localhost"); // Must stay in scope with replace_host. replace_host.SetHostStr(host_str); @@ -56,6 +67,15 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { GURL tag_url4 = test_server()->GetURL( "files/extensions/platform_apps/web_view_isolation/storage2.html"); tag_url4 = tag_url4.ReplaceComponents(replace_host); + GURL tag_url5 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/storage1.html#p1"); + tag_url5 = tag_url5.ReplaceComponents(replace_host); + GURL tag_url6 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/storage1.html#p2"); + tag_url6 = tag_url6.ReplaceComponents(replace_host); + GURL tag_url7 = test_server()->GetURL( + "files/extensions/platform_apps/web_view_isolation/storage1.html#p3"); + tag_url7 = tag_url7.ReplaceComponents(replace_host); ui_test_utils::NavigateToURLWithDisposition( browser(), navigate_to_url, CURRENT_TAB, @@ -69,11 +89,20 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { tag_url3, content::NotificationService::AllSources()); ui_test_utils::UrlLoadObserver observer4( tag_url4, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer5( + tag_url5, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer6( + tag_url6, content::NotificationService::AllSources()); + ui_test_utils::UrlLoadObserver observer7( + tag_url7, content::NotificationService::AllSources()); LoadAndLaunchPlatformApp("web_view_isolation"); observer1.Wait(); observer2.Wait(); observer3.Wait(); observer4.Wait(); + observer5.Wait(); + observer6.Wait(); + observer7.Wait(); content::Source<content::NavigationController> source1 = observer1.source(); EXPECT_TRUE(source1->GetWebContents()->GetRenderProcessHost()->IsGuest()); @@ -83,6 +112,12 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { EXPECT_TRUE(source3->GetWebContents()->GetRenderProcessHost()->IsGuest()); content::Source<content::NavigationController> source4 = observer4.source(); EXPECT_TRUE(source4->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source5 = observer5.source(); + EXPECT_TRUE(source5->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source6 = observer6.source(); + EXPECT_TRUE(source6->GetWebContents()->GetRenderProcessHost()->IsGuest()); + content::Source<content::NavigationController> source7 = observer7.source(); + EXPECT_TRUE(source7->GetWebContents()->GetRenderProcessHost()->IsGuest()); // Check that the first two tags use the same process and it is different // than the process used by the other two. @@ -119,10 +154,41 @@ class WebViewTest : public extensions::PlatformAppBrowserTest { source3->GetWebContents()->GetRenderProcessHost()-> GetStoragePartition()); + // Ensure the persistent storage partitions are different. + EXPECT_EQ( + source5->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source6->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + EXPECT_NE( + source5->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source7->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + EXPECT_NE( + source1->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source5->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + EXPECT_NE( + source1->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition(), + source7->GetWebContents()->GetRenderProcessHost()-> + GetStoragePartition()); + *default_tag_contents1 = source1->GetWebContents(); *default_tag_contents2 = source2->GetWebContents(); *named_partition_contents1 = source3->GetWebContents(); *named_partition_contents2 = source4->GetWebContents(); + if (persistent_partition_contents1) { + *persistent_partition_contents1 = source5->GetWebContents(); + } + if (persistent_partition_contents2) { + *persistent_partition_contents2 = source6->GetWebContents(); + } + if (persistent_partition_contents3) { + *persistent_partition_contents3 = source7->GetWebContents(); + } } void ExecuteScriptWaitForTitle(content::WebContents* web_contents, @@ -182,7 +248,7 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, CookieIsolation) { NavigateAndOpenAppForIsolation(set_cookie_url, &cookie_contents1, &cookie_contents2, &named_partition_contents1, - &named_partition_contents2); + &named_partition_contents2, NULL, NULL, NULL); EXPECT_TRUE(content::ExecuteJavaScript( cookie_contents1->GetRenderViewHost(), std::wstring(), cookie_script1)); @@ -219,6 +285,157 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, CookieIsolation) { EXPECT_EQ("", cookie_value); } +// This tests that in-memory storage partitions are reset on browser restart, +// but persistent ones maintain state for cookies and HTML5 storage. +IN_PROC_BROWSER_TEST_F(WebViewTest, PRE_StoragePersistence) { + ASSERT_TRUE(StartTestServer()); + const std::wstring kExpire = + L"var expire = new Date(Date.now() + 24 * 60 * 60 * 1000);"; + std::wstring cookie_script1(kExpire); + cookie_script1.append( + L"document.cookie = 'inmemory=true; path=/; expires=' + expire + ';';"); + std::wstring cookie_script2(kExpire); + cookie_script2.append( + L"document.cookie = 'persist1=true; path=/; expires=' + expire + ';';"); + std::wstring cookie_script3(kExpire); + cookie_script3.append( + L"document.cookie = 'persist2=true; path=/; expires=' + expire + ';';"); + + // We don't care where the main browser is on this test. + GURL blank_url("about:blank"); + + // The first two partitions will be used to set cookies and ensure they are + // shared. The named partition is used to ensure that cookies are isolated + // between partitions within the same app. + content::WebContents* cookie_contents1; + content::WebContents* cookie_contents2; + content::WebContents* named_partition_contents1; + content::WebContents* named_partition_contents2; + content::WebContents* persistent_partition_contents1; + content::WebContents* persistent_partition_contents2; + content::WebContents* persistent_partition_contents3; + NavigateAndOpenAppForIsolation(blank_url, &cookie_contents1, + &cookie_contents2, &named_partition_contents1, + &named_partition_contents2, + &persistent_partition_contents1, + &persistent_partition_contents2, + &persistent_partition_contents3); + + // Set the inmemory=true cookie for tags with inmemory partitions. + EXPECT_TRUE(content::ExecuteJavaScript( + cookie_contents1->GetRenderViewHost(), std::wstring(), + cookie_script1)); + EXPECT_TRUE(content::ExecuteJavaScript( + named_partition_contents1->GetRenderViewHost(), std::wstring(), + cookie_script1)); + + // For the two different persistent storage partitions, set the + // two different cookies so we can check that they aren't comingled below. + EXPECT_TRUE(content::ExecuteJavaScript( + persistent_partition_contents1->GetRenderViewHost(), std::wstring(), + cookie_script2)); + + EXPECT_TRUE(content::ExecuteJavaScript( + persistent_partition_contents3->GetRenderViewHost(), std::wstring(), + cookie_script3)); + + int cookie_size; + std::string cookie_value; + + // Check that all in-memory partitions have a cookie set. + automation_util::GetCookies(GURL("http://localhost"), + cookie_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("inmemory=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + cookie_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("inmemory=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + named_partition_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("inmemory=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + named_partition_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("inmemory=true", cookie_value); + + // Check that all persistent partitions kept their state. + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("persist1=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("persist1=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents3, + &cookie_size, &cookie_value); + EXPECT_EQ("persist2=true", cookie_value); +} + +// This is the post-reset portion of the StoragePersistence test. See +// PRE_StoragePersistence for main comment. +IN_PROC_BROWSER_TEST_F(WebViewTest, StoragePersistence) { + ASSERT_TRUE(StartTestServer()); + + // We don't care where the main browser is on this test. + GURL blank_url("about:blank"); + + // The first two partitions will be used to set cookies and ensure they are + // shared. The named partition is used to ensure that cookies are isolated + // between partitions within the same app. + content::WebContents* cookie_contents1; + content::WebContents* cookie_contents2; + content::WebContents* named_partition_contents1; + content::WebContents* named_partition_contents2; + content::WebContents* persistent_partition_contents1; + content::WebContents* persistent_partition_contents2; + content::WebContents* persistent_partition_contents3; + NavigateAndOpenAppForIsolation(blank_url, &cookie_contents1, + &cookie_contents2, &named_partition_contents1, + &named_partition_contents2, + &persistent_partition_contents1, + &persistent_partition_contents2, + &persistent_partition_contents3); + + int cookie_size; + std::string cookie_value; + + // Check that all in-memory partitions lost their state. + automation_util::GetCookies(GURL("http://localhost"), + cookie_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + cookie_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + named_partition_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + named_partition_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("", cookie_value); + + // Check that all persistent partitions kept their state. + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents1, + &cookie_size, &cookie_value); + EXPECT_EQ("persist1=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents2, + &cookie_size, &cookie_value); + EXPECT_EQ("persist1=true", cookie_value); + automation_util::GetCookies(GURL("http://localhost"), + persistent_partition_contents3, + &cookie_size, &cookie_value); + EXPECT_EQ("persist2=true", cookie_value); +} + // This tests DOM storage isolation for packaged apps with webview tags. It // loads an app with multiple webview tags and each tag sets DOM storage // entries, which the test checks to ensure proper storage isolation is @@ -240,7 +457,7 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, DOMStorageIsolation) { NavigateAndOpenAppForIsolation(regular_url, &default_tag_contents1, &default_tag_contents2, &storage_contents1, - &storage_contents2); + &storage_contents2, NULL, NULL, NULL); // Initialize the storage for the first of the two tags that share a storage // partition. @@ -321,7 +538,7 @@ IN_PROC_BROWSER_TEST_F(WebViewTest, IndexedDBIsolation) { NavigateAndOpenAppForIsolation(regular_url, &default_tag_contents1, &default_tag_contents2, &storage_contents1, - &storage_contents2); + &storage_contents2, NULL, NULL, NULL); // Initialize the storage for the first of the two tags that share a storage // partition. diff --git a/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js b/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js index ad95ea9..6b44dff 100644 --- a/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js +++ b/chrome/test/data/extensions/platform_apps/web_view_isolation/main.js @@ -11,15 +11,27 @@ chrome.test.getConfig(function(config) { '/files/extensions/platform_apps/web_view_isolation/storage1.html'; var url4 = 'http://localhost:' + config.testServer.port + '/files/extensions/platform_apps/web_view_isolation/storage2.html'; + var url5 = 'http://localhost:' + config.testServer.port + + '/files/extensions/platform_apps/web_view_isolation/storage1.html#p1'; + var url6 = 'http://localhost:' + config.testServer.port + + '/files/extensions/platform_apps/web_view_isolation/storage1.html#p2'; + var url7 = 'http://localhost:' + config.testServer.port + + '/files/extensions/platform_apps/web_view_isolation/storage1.html#p3'; var node = document.getElementById('web_view_container'); node.innerHTML = "<object id='webview' src=" + url + " type='application/browser-plugin' width=500 height=550></object>" + "<object id='webview2' src=" + url2 + " type='application/browser-plugin' width=500 height=550></object>" + - "<object id='webview2' partition='partition1' src=" + url3 + + "<object id='webview3' partition='partition1' src=" + url3 + " type='application/browser-plugin' width=500 height=550></object>" + - "<object id='webview2' partition='partition1' src=" + url4 + + "<object id='webview4' partition='partition1' src=" + url4 + + " type='application/browser-plugin' width=500 height=550></object>" + + "<object id='webview5' partition='persist:1' src=" + url5 + + " type='application/browser-plugin' width=500 height=550></object>" + + "<object id='webview6' partition='persist:1' src=" + url6 + + " type='application/browser-plugin' width=500 height=550></object>" + + "<object id='webview7' partition='persist:2' src=" + url7 + " type='application/browser-plugin' width=500 height=550></object>"; chrome.test.sendMessage('Launched'); }); diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc index 99905f8..d8bc87e 100644 --- a/content/browser/browser_context.cc +++ b/content/browser/browser_context.cc @@ -53,12 +53,6 @@ StoragePartition* GetStoragePartitionFromConfig( if (browser_context->IsOffTheRecord()) in_memory = true; - // TODO(nasko): Webview tags with named partitions will have both - // partition_domain and partition_name set. In this case, mark them in-memory - // until the on-disk storage code has landed. http://crbug.com/159464 - if (!partition_domain.empty() && !partition_name.empty()) - in_memory = true; - return partition_map->Get(partition_domain, partition_name, in_memory); } diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc index ff95c38..f2d1383 100644 --- a/content/browser/storage_partition_impl.cc +++ b/content/browser/storage_partition_impl.cc @@ -14,50 +14,6 @@ namespace content { -namespace { - -// These constants are used to create the directory structure under the profile -// where renderers with a non-default storage partition keep their persistent -// state. This will contain a set of directories that partially mirror the -// directory structure of BrowserContext::GetPath(). -// -// The kStoragePartitionDirname is contains an extensions directory which is -// further partitioned by extension id, followed by another level of directories -// for the "default" extension storage partition and one directory for each -// persistent partition used by an extension's browser tags. Example: -// -// Storage/ext/ABCDEF/def -// Storage/ext/ABCDEF/{hash(guest partition)} -// -// The code in GetStoragePartitionPath() constructs these path names. -// -// TODO(nasko): Move extension related path code out of content. -const FilePath::CharType kStoragePartitionDirname[] = - FILE_PATH_LITERAL("Storage"); -const FilePath::CharType kExtensionsDirname[] = - FILE_PATH_LITERAL("ext"); -const FilePath::CharType kDefaultPartitionDirname[] = - FILE_PATH_LITERAL("def"); - -} // namespace - -// static -FilePath StoragePartitionImpl::GetStoragePartitionPath( - const StoragePartitionConfig& config) { - if (config.partition_domain.empty()) - return FilePath(); - - CHECK(IsStringUTF8(config.partition_domain)); - - FilePath path = FilePath(kStoragePartitionDirname).Append(kExtensionsDirname) - .Append(FilePath::FromUTF8Unsafe(config.partition_domain)); - - if (!config.partition_name.empty()) - return path.Append(FilePath::FromUTF8Unsafe(config.partition_name)); - - return path.Append(kDefaultPartitionDirname); -} - StoragePartitionImpl::StoragePartitionImpl( const FilePath& partition_path, quota::QuotaManager* quota_manager, @@ -93,23 +49,20 @@ StoragePartitionImpl::~StoragePartitionImpl() { // need 3 pieces of info from it. StoragePartitionImpl* StoragePartitionImpl::Create( BrowserContext* context, - const StoragePartitionConfig& partition_config, - const FilePath& profile_path) { + bool in_memory, + const FilePath& partition_path) { // Ensure that these methods are called on the UI thread, except for // unittests where a UI thread might not have been created. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || !BrowserThread::IsMessageLoopValid(BrowserThread::UI)); - FilePath partition_path = - profile_path.Append(GetStoragePartitionPath(partition_config)); - // All of the clients have to be created and registered with the // QuotaManager prior to the QuotaManger being used. We do them // all together here prior to handing out a reference to anything // that utilizes the QuotaManager. scoped_refptr<quota::QuotaManager> quota_manager = new quota::QuotaManager( - partition_config.in_memory, partition_path, + in_memory, partition_path, BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO), BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB), context->GetSpecialStoragePolicy()); @@ -117,17 +70,17 @@ StoragePartitionImpl* StoragePartitionImpl::Create( // Each consumer is responsible for registering its QuotaClient during // its construction. scoped_refptr<fileapi::FileSystemContext> filesystem_context = - CreateFileSystemContext(partition_path, partition_config.in_memory, + CreateFileSystemContext(partition_path, in_memory, context->GetSpecialStoragePolicy(), quota_manager->proxy()); scoped_refptr<webkit_database::DatabaseTracker> database_tracker = new webkit_database::DatabaseTracker( - partition_path, partition_config.in_memory, + partition_path, in_memory, context->GetSpecialStoragePolicy(), quota_manager->proxy(), BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)); - FilePath path = partition_config.in_memory ? FilePath() : partition_path; + FilePath path = in_memory ? FilePath() : partition_path; scoped_refptr<DOMStorageContextImpl> dom_storage_context = new DOMStorageContextImpl(path, context->GetSpecialStoragePolicy()); diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h index 9712837..c5af368 100644 --- a/content/browser/storage_partition_impl.h +++ b/content/browser/storage_partition_impl.h @@ -7,7 +7,6 @@ #include "base/compiler_specific.h" #include "base/file_path.h" -#include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "content/browser/appcache/chrome_appcache_service.h" #include "content/browser/dom_storage/dom_storage_context_impl.h" @@ -32,62 +31,17 @@ class StoragePartitionImpl : public StoragePartition { virtual IndexedDBContextImpl* GetIndexedDBContext() OVERRIDE; private: - FRIEND_TEST_ALL_PREFIXES(StoragePartitionConfigTest, OperatorLess); friend class StoragePartitionImplMap; - // Each StoragePartition is uniquely identified by which partition domain - // it belongs to (such as an app or the browser itself), the user supplied - // partition name and the bit indicating whether it should be persisted on - // disk or not. This structure contains those elements and is used as - // uniqueness key to lookup StoragePartition objects in the global map. + // The |partition_path| is the absolute path to the root of this + // StoragePartition's on-disk storage. // - // TODO(nasko): It is equivalent, though not identical to the same structure - // that lives in chrome profiles. The difference is that this one has - // partition_domain and partition_name separate, while the latter one has - // the path produced by combining the two pieces together. - // The fix for http://crbug.com/159193 will remove the chrome version. - struct StoragePartitionConfig { - const std::string partition_domain; - const std::string partition_name; - const bool in_memory; - - StoragePartitionConfig(const std::string& domain, - const std::string& partition, - const bool& in_memory_only) - : partition_domain(domain), - partition_name(partition), - in_memory(in_memory_only) {} - }; - - // Functor for operator <. - struct StoragePartitionConfigLess { - bool operator()(const StoragePartitionConfig& lhs, - const StoragePartitionConfig& rhs) const { - if (lhs.partition_domain != rhs.partition_domain) - return lhs.partition_domain < rhs.partition_domain; - else if (lhs.partition_name != rhs.partition_name) - return lhs.partition_name < rhs.partition_name; - else if (lhs.in_memory != rhs.in_memory) - return lhs.in_memory < rhs.in_memory; - else - return false; - } - }; - - // TODO(ajwong): Break the direct dependency on |context|. We only - // need 3 pieces of info from it. - static StoragePartitionImpl* Create( - BrowserContext* context, - const StoragePartitionConfig& partition_id, - const FilePath& profile_path); - - // Returns the relative path from the profile's base directory, to the - // directory that holds all the state for storage contexts in - // |partition_config|. If any of the strings in |partition_config| contain - // embedded nuls, the values will be truncated and only the portion prior to - // the nul will be used. - static FilePath GetStoragePartitionPath( - const StoragePartitionConfig& partition_config); + // If |in_memory| is true, the |partition_path| is (ab)used as a way of + // distinguishing different in-memory partitions, but nothing is persisted + // on to disk. + static StoragePartitionImpl* Create(BrowserContext* context, + bool in_memory, + const FilePath& profile_path); StoragePartitionImpl( const FilePath& partition_path, diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc index 93f52d0..eb7ae78 100644 --- a/content/browser/storage_partition_impl_map.cc +++ b/content/browser/storage_partition_impl_map.cc @@ -9,6 +9,7 @@ #include "base/file_path.h" #include "base/stl_util.h" #include "base/string_util.h" +#include "base/string_number_conversions.h" #include "content/browser/appcache/chrome_appcache_service.h" #include "content/browser/fileapi/browser_file_system_helper.h" #include "content/browser/fileapi/chrome_blob_storage_context.h" @@ -24,6 +25,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/common/content_constants.h" #include "content/public/common/url_constants.h" +#include "crypto/sha2.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_context.h" #include "webkit/appcache/view_appcache_internals_job.h" @@ -183,8 +185,86 @@ void InitializeURLRequestContext( // TODO(jam): Add the ProtocolHandlerRegistryIntercepter here! } +// These constants are used to create the directory structure under the profile +// where renderers with a non-default storage partition keep their persistent +// state. This will contain a set of directories that partially mirror the +// directory structure of BrowserContext::GetPath(). +// +// The kStoragePartitionDirname contains an extensions directory which is +// further partitioned by extension id, followed by another level of directories +// for the "default" extension storage partition and one directory for each +// persistent partition used by a webview tag. Example: +// +// Storage/ext/ABCDEF/def +// Storage/ext/ABCDEF/hash(partition name) +// +// The code in GetStoragePartitionPath() constructs these path names. +// +// TODO(nasko): Move extension related path code out of content. +const FilePath::CharType kStoragePartitionDirname[] = + FILE_PATH_LITERAL("Storage"); +const FilePath::CharType kExtensionsDirname[] = + FILE_PATH_LITERAL("ext"); +const FilePath::CharType kDefaultPartitionDirname[] = + FILE_PATH_LITERAL("def"); + +// Because partition names are user specified, they can be arbitrarily long +// which makes them unsuitable for paths names. We use a truncation of a +// SHA256 hash to perform a deterministic shortening of the string. The +// kPartitionNameHashBytes constant controls the length of the truncation. +// We use 6 bytes, which gives us 99.999% reliability against collisions over +// 1 million partition domains. +// +// Analysis: +// We assume that all partition names within one partition domain are +// controlled by the the same entity. Thus there is no chance for adverserial +// attack and all we care about is accidental collision. To get 5 9s over +// 1 million domains, we need the probability of a collision in any one domain +// to be +// +// p < nroot(1000000, .99999) ~= 10^-11 +// +// We use the following birthday attack approximation to calculate the max +// number of unique names for this probability: +// +// n(p,H) = sqrt(2*H * ln(1/(1-p))) +// +// For a 6-byte hash, H = 2^(6*8). n(10^-11, H) ~= 75 +// +// An average partition domain is likely to have less than 10 unique +// partition names which is far lower than 75. +// +// Note, that for 4 9s of reliability, the limit is 237 partition names per +// partition domain. +const int kPartitionNameHashBytes = 6; + } // namespace +// static +FilePath StoragePartitionImplMap::GetStoragePartitionPath( + const std::string& partition_domain, + const std::string& partition_name) { + if (partition_domain.empty()) + return FilePath(); + + CHECK(IsStringUTF8(partition_domain)); + + FilePath path = FilePath(kStoragePartitionDirname).Append(kExtensionsDirname) + .Append(FilePath::FromUTF8Unsafe(partition_domain)); + + if (!partition_name.empty()) { + // For analysis of why we can ignore collisions, see the comment above + // kPartitionNameHashBytes. + char buffer[kPartitionNameHashBytes]; + crypto::SHA256HashString(partition_name, &buffer[0], + sizeof(buffer)); + return path.AppendASCII(base::HexEncode(buffer, sizeof(buffer))); + } + + return path.Append(kDefaultPartitionDirname); +} + + StoragePartitionImplMap::StoragePartitionImplMap( BrowserContext* browser_context) : browser_context_(browser_context), @@ -210,17 +290,19 @@ StoragePartitionImpl* StoragePartitionImplMap::Get( } // Find the previously created partition if it's available. - StoragePartitionImpl::StoragePartitionConfig partition_config( + StoragePartitionConfig partition_config( partition_domain, partition_name, in_memory); PartitionMap::const_iterator it = partitions_.find(partition_config); if (it != partitions_.end()) return it->second; - // There was no previous partition, so let's make a new one. + FilePath partition_path = + browser_context_->GetPath().Append( + GetStoragePartitionPath(partition_domain, partition_name)); StoragePartitionImpl* partition = - StoragePartitionImpl::Create(browser_context_, partition_config, - browser_context_->GetPath()); + StoragePartitionImpl::Create(browser_context_, in_memory, + partition_path); partitions_[partition_config] = partition; // These calls must happen after StoragePartitionImpl::Create(). diff --git a/content/browser/storage_partition_impl_map.h b/content/browser/storage_partition_impl_map.h index 14b78ce..9da35c3 100644 --- a/content/browser/storage_partition_impl_map.h +++ b/content/browser/storage_partition_impl_map.h @@ -9,6 +9,7 @@ #include <string> #include "base/callback_forward.h" +#include "base/gtest_prod_util.h" #include "base/supports_user_data.h" #include "content/browser/storage_partition_impl.h" #include "content/public/browser/browser_context.h" @@ -34,11 +35,58 @@ class StoragePartitionImplMap : public base::SupportsUserData::Data { void ForEach(const BrowserContext::StoragePartitionCallback& callback); private: - typedef std::map<StoragePartitionImpl::StoragePartitionConfig, + FRIEND_TEST_ALL_PREFIXES(StoragePartitionConfigTest, OperatorLess); + + // Each StoragePartition is uniquely identified by which partition domain + // it belongs to (such as an app or the browser itself), the user supplied + // partition name and the bit indicating whether it should be persisted on + // disk or not. This structure contains those elements and is used as + // uniqueness key to lookup StoragePartition objects in the global map. + // + // TODO(nasko): It is equivalent, though not identical to the same structure + // that lives in chrome profiles. The difference is that this one has + // partition_domain and partition_name separate, while the latter one has + // the path produced by combining the two pieces together. + // The fix for http://crbug.com/159193 will remove the chrome version. + struct StoragePartitionConfig { + const std::string partition_domain; + const std::string partition_name; + const bool in_memory; + + StoragePartitionConfig(const std::string& domain, + const std::string& partition, + const bool& in_memory_only) + : partition_domain(domain), + partition_name(partition), + in_memory(in_memory_only) {} + }; + + // Functor for operator <. + struct StoragePartitionConfigLess { + bool operator()(const StoragePartitionConfig& lhs, + const StoragePartitionConfig& rhs) const { + if (lhs.partition_domain != rhs.partition_domain) + return lhs.partition_domain < rhs.partition_domain; + else if (lhs.partition_name != rhs.partition_name) + return lhs.partition_name < rhs.partition_name; + else if (lhs.in_memory != rhs.in_memory) + return lhs.in_memory < rhs.in_memory; + else + return false; + } + }; + + typedef std::map<StoragePartitionConfig, StoragePartitionImpl*, - StoragePartitionImpl::StoragePartitionConfigLess> + StoragePartitionConfigLess> PartitionMap; + // Returns the relative path from the profile's base directory, to the + // directory that holds all the state for storage contexts in the given + // |partition_domain| and |partition_name|. + static FilePath GetStoragePartitionPath(const std::string& partition_domain, + const std::string& partition_name); + // This must always be called *after* |partition| has been added to the // partitions_. // diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_map_unittest.cc index bf74943..1c44fcc 100644 --- a/content/browser/storage_partition_impl_unittest.cc +++ b/content/browser/storage_partition_impl_map_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/storage_partition_impl.h" +#include "content/browser/storage_partition_impl_map.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { @@ -13,17 +13,17 @@ class StoragePartitionConfigTest : public testing::Test { // Test that the Less comparison function is implemented properly to uniquely // identify storage partitions used as keys in a std::map. TEST_F(StoragePartitionConfigTest, OperatorLess) { - StoragePartitionImpl::StoragePartitionConfig c1("", "", false); - StoragePartitionImpl::StoragePartitionConfig c2("", "", false); - StoragePartitionImpl::StoragePartitionConfig c3("", "", true); - StoragePartitionImpl::StoragePartitionConfig c4("a", "", true); - StoragePartitionImpl::StoragePartitionConfig c5("b", "", true); - StoragePartitionImpl::StoragePartitionConfig c6("", "abc", false); - StoragePartitionImpl::StoragePartitionConfig c7("", "abc", true); - StoragePartitionImpl::StoragePartitionConfig c8("a", "abc", false); - StoragePartitionImpl::StoragePartitionConfig c9("a", "abc", true); - - StoragePartitionImpl::StoragePartitionConfigLess less; + StoragePartitionImplMap::StoragePartitionConfig c1("", "", false); + StoragePartitionImplMap::StoragePartitionConfig c2("", "", false); + StoragePartitionImplMap::StoragePartitionConfig c3("", "", true); + StoragePartitionImplMap::StoragePartitionConfig c4("a", "", true); + StoragePartitionImplMap::StoragePartitionConfig c5("b", "", true); + StoragePartitionImplMap::StoragePartitionConfig c6("", "abc", false); + StoragePartitionImplMap::StoragePartitionConfig c7("", "abc", true); + StoragePartitionImplMap::StoragePartitionConfig c8("a", "abc", false); + StoragePartitionImplMap::StoragePartitionConfig c9("a", "abc", true); + + StoragePartitionImplMap::StoragePartitionConfigLess less; // Let's ensure basic comparison works. EXPECT_TRUE(less(c1, c3)); diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 93b60b2..42c4ef1 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -321,7 +321,7 @@ 'browser/speech/google_streaming_remote_engine_unittest.cc', 'browser/speech/speech_recognizer_unittest.cc', 'browser/ssl/ssl_host_state_unittest.cc', - 'browser/storage_partition_impl_unittest.cc', + 'browser/storage_partition_impl_map_unittest.cc', 'browser/system_message_window_win_unittest.cc', 'browser/trace_subscriber_stdio_unittest.cc', 'browser/web_contents/navigation_controller_impl_unittest.cc', |