| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/filename_generation/filename_generation.h" |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace filename_generation { |
| |
| #define FPL FILE_PATH_LITERAL |
| #define HTML_EXTENSION ".html" |
| #if defined(OS_WIN) |
| #define FPL_HTML_EXTENSION L".html" |
| #else |
| #define FPL_HTML_EXTENSION ".html" |
| #endif |
| |
| namespace { |
| |
| base::FilePath GetLongNamePathInDirectory( |
| int max_length, |
| const base::FilePath::CharType* suffix, |
| const base::FilePath& dir) { |
| base::FilePath::StringType name(max_length, FILE_PATH_LITERAL('a')); |
| base::FilePath path = dir.Append(name + suffix).NormalizePathSeparators(); |
| return path; |
| } |
| |
| } // namespace |
| |
| static const struct { |
| const base::FilePath::CharType* page_title; |
| const base::FilePath::CharType* expected_name; |
| } kExtensionTestCases[] = { |
| // Extension is preserved if it is already proper for HTML. |
| {FPL("filename.html"), FPL("filename.html")}, |
| {FPL("filename.HTML"), FPL("filename.HTML")}, |
| {FPL("filename.XHTML"), FPL("filename.XHTML")}, |
| {FPL("filename.xhtml"), FPL("filename.xhtml")}, |
| {FPL("filename.htm"), FPL("filename.htm")}, |
| // ".htm" is added if the extension is improper for HTML. |
| {FPL("hello.world"), FPL("hello.world") FPL_HTML_EXTENSION}, |
| {FPL("hello.txt"), FPL("hello.txt") FPL_HTML_EXTENSION}, |
| {FPL("is.html.good"), FPL("is.html.good") FPL_HTML_EXTENSION}, |
| // ".htm" is added if the name doesn't have an extension. |
| {FPL("helloworld"), FPL("helloworld") FPL_HTML_EXTENSION}, |
| {FPL("helloworld."), FPL("helloworld.") FPL_HTML_EXTENSION}, |
| }; |
| |
| // Crashing on Windows, see http://crbug.com/79365 |
| #if defined(OS_WIN) |
| #define MAYBE_TestEnsureHtmlExtension DISABLED_TestEnsureHtmlExtension |
| #else |
| #define MAYBE_TestEnsureHtmlExtension TestEnsureHtmlExtension |
| #endif |
| TEST(FilenameGenerationTest, MAYBE_TestEnsureHtmlExtension) { |
| for (size_t i = 0; i < arraysize(kExtensionTestCases); ++i) { |
| base::FilePath original = base::FilePath(kExtensionTestCases[i].page_title); |
| base::FilePath expected = |
| base::FilePath(kExtensionTestCases[i].expected_name); |
| base::FilePath actual = EnsureHtmlExtension(original); |
| EXPECT_EQ(expected.value(), actual.value()) |
| << "Failed for page title: " << kExtensionTestCases[i].page_title; |
| } |
| } |
| |
| // Crashing on Windows, see http://crbug.com/79365 |
| #if defined(OS_WIN) |
| #define MAYBE_TestEnsureMimeExtension DISABLED_TestEnsureMimeExtension |
| #else |
| #define MAYBE_TestEnsureMimeExtension TestEnsureMimeExtension |
| #endif |
| TEST(FilenameGenerationTest, MAYBE_TestEnsureMimeExtension) { |
| static const struct { |
| const base::FilePath::CharType* page_title; |
| const base::FilePath::CharType* expected_name; |
| const char* contents_mime_type; |
| } kExtensionTests[] = { |
| {FPL("filename.html"), FPL("filename.html"), "text/html"}, |
| {FPL("filename.htm"), FPL("filename.htm"), "text/html"}, |
| {FPL("filename.xhtml"), FPL("filename.xhtml"), "text/html"}, |
| #if defined(OS_WIN) |
| {FPL("filename"), FPL("filename.htm"), "text/html"}, |
| #else // defined(OS_WIN) |
| {FPL("filename"), FPL("filename.html"), "text/html"}, |
| #endif // defined(OS_WIN) |
| {FPL("filename.html"), FPL("filename.html"), "text/xml"}, |
| {FPL("filename.xml"), FPL("filename.xml"), "text/xml"}, |
| {FPL("filename"), FPL("filename.xml"), "text/xml"}, |
| {FPL("filename.xhtml"), FPL("filename.xhtml"), "application/xhtml+xml"}, |
| {FPL("filename.html"), FPL("filename.html"), "application/xhtml+xml"}, |
| {FPL("filename"), FPL("filename.xhtml"), "application/xhtml+xml"}, |
| {FPL("filename.txt"), FPL("filename.txt"), "text/plain"}, |
| {FPL("filename"), FPL("filename.txt"), "text/plain"}, |
| {FPL("filename.css"), FPL("filename.css"), "text/css"}, |
| {FPL("filename"), FPL("filename.css"), "text/css"}, |
| {FPL("filename.mhtml"), FPL("filename.mhtml"), "multipart/related"}, |
| {FPL("filename.html"), FPL("filename.html.mhtml"), "multipart/related"}, |
| {FPL("filename.txt"), FPL("filename.txt.mhtml"), "multipart/related"}, |
| {FPL("filename"), FPL("filename.mhtml"), "multipart/related"}, |
| {FPL("filename.abc"), FPL("filename.abc"), "unknown/unknown"}, |
| {FPL("filename"), FPL("filename"), "unknown/unknown"}, |
| }; |
| for (uint32_t i = 0; i < arraysize(kExtensionTests); ++i) { |
| base::FilePath original = base::FilePath(kExtensionTests[i].page_title); |
| base::FilePath expected = base::FilePath(kExtensionTests[i].expected_name); |
| std::string mime_type(kExtensionTests[i].contents_mime_type); |
| base::FilePath actual = EnsureMimeExtension(original, mime_type); |
| EXPECT_EQ(expected.value(), actual.value()) |
| << "Failed for page title: " << kExtensionTests[i].page_title |
| << " MIME:" << mime_type; |
| } |
| } |
| |
| // Test that the suggested names generated are reasonable: |
| // If the name is a URL, retrieve only the path component since the path name |
| // generation code will turn the entire URL into the file name leading to bad |
| // extension names. For example, a page with no title and a URL: |
| // http://www.foo.com/a/path/name.txt will turn into file: |
| // "http www.foo.com a path name.txt", when we want to save it as "name.txt". |
| |
| static const struct GenerateFilenameTestCase { |
| const char* page_url; |
| const base::string16 page_title; |
| const base::FilePath::CharType* expected_name; |
| bool ensure_html_extension; |
| } kGenerateFilenameCases[] = { |
| // Title overrides the URL. |
| {"http://foo.com", base::ASCIIToUTF16("A page title"), |
| FPL("A page title") FPL_HTML_EXTENSION, true}, |
| // Extension is preserved. |
| {"http://foo.com", base::ASCIIToUTF16("A page title with.ext"), |
| FPL("A page title with.ext"), false}, |
| // If the title matches the URL, use the last component of the URL. |
| {"http://foo.com/bar", base::ASCIIToUTF16("foo.com/bar"), FPL("bar"), |
| false}, |
| // A URL with escaped special characters, when title matches the URL. |
| {"http://foo.com/%40.txt", base::ASCIIToUTF16("foo.com/%40.txt"), |
| FPL("@.txt"), false}, |
| // A URL with unescaped special characters, when title matches the URL. |
| {"http://foo.com/@.txt", base::ASCIIToUTF16("foo.com/@.txt"), FPL("@.txt"), |
| false}, |
| // A URL with punycode in the host name, when title matches the URL. |
| {"http://xn--bcher-kva.com", base::UTF8ToUTF16("bücher.com"), |
| FPL("bücher.com"), false}, |
| // If the title matches the URL, but there is no "filename" component, |
| // use the domain. |
| {"http://foo.com", base::ASCIIToUTF16("foo.com"), FPL("foo.com"), false}, |
| // Make sure fuzzy matching works. |
| {"http://foo.com/bar", base::ASCIIToUTF16("foo.com/bar"), FPL("bar"), |
| false}, |
| // A URL-like title that does not match the title is respected in full. |
| {"http://foo.com", base::ASCIIToUTF16("http://www.foo.com/path/title.txt"), |
| FPL("http___www.foo.com_path_title.txt"), false}, |
| }; |
| |
| // Crashing on Windows, see http://crbug.com/79365 |
| #if defined(OS_WIN) |
| #define MAYBE_TestGenerateFilename DISABLED_TestGenerateFilename |
| #else |
| #define MAYBE_TestGenerateFilename TestGenerateFilename |
| #endif |
| TEST(FilenameGenerationTest, MAYBE_TestGenerateFilename) { |
| for (size_t i = 0; i < arraysize(kGenerateFilenameCases); ++i) { |
| base::FilePath save_name = GenerateFilename( |
| kGenerateFilenameCases[i].page_title, |
| GURL(kGenerateFilenameCases[i].page_url), |
| kGenerateFilenameCases[i].ensure_html_extension, std::string()); |
| EXPECT_EQ(kGenerateFilenameCases[i].expected_name, save_name.value()) |
| << "Test case " << i; |
| } |
| } |
| |
| TEST(FilenameGenerationTest, TestBasicTruncation) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| int max_length = base::GetMaximumPathComponentLength(temp_dir.GetPath()); |
| ASSERT_NE(-1, max_length); |
| |
| base::FilePath::StringType extension(FILE_PATH_LITERAL(".txt")); |
| base::FilePath path(GetLongNamePathInDirectory( |
| max_length, FILE_PATH_LITERAL(".txt"), temp_dir.GetPath())); |
| base::FilePath truncated_path = path; |
| |
| // The file path will only be truncated o the platforms that have known |
| // encoding. Otherwise no truncation will be performed. |
| #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) |
| // The file name length is truncated to max_length. |
| EXPECT_TRUE(TruncateFilename(&truncated_path, max_length)); |
| EXPECT_EQ(size_t(max_length), truncated_path.BaseName().value().size()); |
| #else |
| EXPECT_FALSE(TruncateFilename(&truncated_path, max_length)); |
| EXPECT_EQ(truncated_path, path); |
| EXPECT_LT(size_t(max_length), truncated_path.BaseName().value().size()); |
| #endif |
| // But the extension is kept unchanged. |
| EXPECT_EQ(path.Extension(), truncated_path.Extension()); |
| } |
| |
| TEST(FilenameGenerationTest, TestTruncationFail) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| int max_length = base::GetMaximumPathComponentLength(temp_dir.GetPath()); |
| ASSERT_NE(-1, max_length); |
| |
| base::FilePath path( |
| (FILE_PATH_LITERAL("a.") + base::FilePath::StringType(max_length, 'b')) |
| .c_str()); |
| path = temp_dir.GetPath().Append(path); |
| |
| base::FilePath truncated_path = path; |
| |
| // We cannot truncate a path with very long extension. This will fail and no |
| // truncation will be performed on all platforms. |
| EXPECT_FALSE(TruncateFilename(&truncated_path, max_length)); |
| EXPECT_EQ(truncated_path, path); |
| } |
| |
| } // filename_generation |