| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/indexed_db/file_path_util.h" |
| |
| #include <string> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_locator.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/origin.h" |
| |
| namespace content::indexed_db { |
| |
| TEST(FilePathUtilTest, GetSqliteDbDirectory) { |
| // First party, default bucket: need to append an origin. |
| storage::BucketLocator bucket_locator( |
| storage::BucketId::FromUnsafeValue(1), |
| blink::StorageKey::CreateFirstParty( |
| url::Origin::Create(GURL("https://example.com/"))), |
| /*is_default=*/true); |
| EXPECT_EQ(base::FilePath::FromASCII("IndexedDB") |
| .Append(GetSqliteDbDirectory(bucket_locator)), |
| base::FilePath::FromASCII("IndexedDB") |
| .AppendASCII("https_example.com_0")); |
| |
| // Non-default bucket: no origin, since the base path includes the bucket ID. |
| storage::BucketLocator bucket_locator_non_default( |
| storage::BucketId::FromUnsafeValue(2), |
| blink::StorageKey::CreateFirstParty( |
| url::Origin::Create(GURL("https://example.com/"))), |
| /*is_default=*/false); |
| EXPECT_EQ(base::FilePath::FromASCII("2") |
| .AppendASCII("IndexedDB") |
| .Append(GetSqliteDbDirectory(bucket_locator_non_default)), |
| base::FilePath::FromASCII("2").AppendASCII("IndexedDB")); |
| |
| // Third party bucket: no origin, since the base path includes the bucket ID. |
| storage::BucketLocator bucket_locator_third_party( |
| storage::BucketId::FromUnsafeValue(3), |
| blink::StorageKey::Create( |
| url::Origin::Create(GURL("https://example.com/")), |
| net::SchemefulSite(GURL("https://foo.com/")), |
| blink::mojom::AncestorChainBit::kCrossSite, |
| /*third_party_partitioning_allowed=*/true), |
| /*is_default=*/true); |
| EXPECT_EQ(base::FilePath::FromASCII("3") |
| .AppendASCII("IndexedDB") |
| .Append(GetSqliteDbDirectory(bucket_locator_third_party)), |
| base::FilePath::FromASCII("3").AppendASCII("IndexedDB")); |
| } |
| |
| TEST(FilePathUtilTest, DatabaseNameToFileName) { |
| struct { |
| std::u16string dom_string_input; |
| base::FilePath::StringType expected_file_name; |
| } test_cases[] = { |
| {u"basic_name", |
| FILE_PATH_LITERAL( |
| "6ZV5AGIHZJWHPZ2UQ4IQUJJWBTQPYXGGRWTDQLBFA7AK4XTIXGCQ")}, |
| |
| // Output is case sensitive. |
| {u"BASIC_name", |
| FILE_PATH_LITERAL( |
| "PMOE3RT3OU4BAFK6RL7E4MHR7N5Q65X2POQZUH3CRZCE6IXHRXTA")}, |
| |
| // Empty string. |
| {u"", FILE_PATH_LITERAL("0")}, |
| |
| // Characters that aren't valid filename characters. |
| {u"invalid/name", |
| FILE_PATH_LITERAL( |
| "EYZCOEWM37YXW6VHDSELIIN7IGHYYHWW2FHMD6PJJO7ROTVYRBRQ")}, |
| {u"invalid\\name", |
| FILE_PATH_LITERAL( |
| "J46FRRIBKKJPJYXWUUGN252JKBAI2HMZAP4TAU2NYPAM36S74OJQ")}, |
| {u"invalid name", |
| FILE_PATH_LITERAL( |
| "IGMS2QMW3GNPSJXWTHHFO5VSHBL6IMGXYDD5JSKAZSXH44T4SO6A")}, |
| {u"sneaky.text", |
| FILE_PATH_LITERAL( |
| "IE4ONQ2VLLMXKWXWB6B4HBN3JICQDPQBXBDYUUPBJX6UU26VD2SQ")}, |
| |
| // Valid UTF16. |
| {u"\x4f60\x597d ", |
| FILE_PATH_LITERAL( |
| "NDE2443GY5Z36EMF2LPWR54H47YOVZ3EEF4V5J4JIAXO4O2RCS5A")}, |
| |
| // Invalid UTF16. The first character is a truncated UTF-16 character. |
| {u"\xd800\x597d", |
| FILE_PATH_LITERAL( |
| "VYO3RQVZ43IVZ3MZLKNC7BOZULNVR2S4EPLOBM527U4WG5MISGZQ")}, |
| |
| // Long string with invalid characters. |
| {u"too_long_name_too_long_name_too_long_name_too_long_name\xd800\x597d", |
| FILE_PATH_LITERAL( |
| "IDYIO2S422KGRTC2OQ24OSGW3HSSSF6RWM3O64SKMLFIPZ5ICCZQ")}, |
| }; |
| |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| const base::FilePath& dir = temp_dir.GetPath(); |
| std::set<base::FilePath> expected_files; |
| std::set<base::FilePath> enumerated_files; |
| |
| for (const auto& test_case : test_cases) { |
| // Encode it. |
| base::FilePath file_name = |
| DatabaseNameToFileName(test_case.dom_string_input); |
| EXPECT_TRUE(!file_name.IsAbsolute()); |
| EXPECT_EQ(file_name.BaseName(), file_name); |
| EXPECT_EQ(file_name.value(), test_case.expected_file_name); |
| expected_files.insert(file_name); |
| |
| // Write the file in a directory so we can enumerate it later. |
| ASSERT_TRUE(base::WriteFile(dir.Append(file_name), |
| base::byte_span_from_cstring("42"))); |
| // Also write another file which is generated by SQLite. |
| ASSERT_TRUE(base::WriteFile( |
| dir.Append(file_name).InsertBeforeExtensionASCII("-wal"), |
| base::byte_span_from_cstring("42"))); |
| } |
| |
| EnumerateDatabasesInDirectory(dir, [&](const base::FilePath& path) { |
| enumerated_files.insert(path.BaseName()); |
| }); |
| EXPECT_EQ(expected_files, enumerated_files); |
| } |
| |
| } // namespace content::indexed_db |