filename_generation_unittest.cc 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/filename_generation/filename_generation.h"
  5. #include "base/files/file_path.h"
  6. #include "base/files/file_util.h"
  7. #include "base/files/scoped_temp_dir.h"
  8. #include "base/strings/string_util.h"
  9. #include "base/strings/utf_string_conversions.h"
  10. #include "build/build_config.h"
  11. #include "build/chromeos_buildflags.h"
  12. #include "testing/gtest/include/gtest/gtest.h"
  13. #include "url/gurl.h"
  14. namespace filename_generation {
  15. #define FPL FILE_PATH_LITERAL
  16. #define HTML_EXTENSION ".html"
  17. #if BUILDFLAG(IS_WIN)
  18. #define FPL_HTML_EXTENSION L".html"
  19. #else
  20. #define FPL_HTML_EXTENSION ".html"
  21. #endif
  22. namespace {
  23. base::FilePath GetLongNamePathInDirectory(
  24. int max_length,
  25. const base::FilePath::CharType* suffix,
  26. const base::FilePath& dir) {
  27. base::FilePath::StringType name(max_length, FILE_PATH_LITERAL('a'));
  28. base::FilePath path = dir.Append(name + suffix).NormalizePathSeparators();
  29. return path;
  30. }
  31. } // namespace
  32. static const struct {
  33. const base::FilePath::CharType* page_title;
  34. const base::FilePath::CharType* expected_name;
  35. } kExtensionTestCases[] = {
  36. // Extension is preserved if it is already proper for HTML.
  37. {FPL("filename.html"), FPL("filename.html")},
  38. {FPL("filename.HTML"), FPL("filename.HTML")},
  39. {FPL("filename.XHTML"), FPL("filename.XHTML")},
  40. {FPL("filename.xhtml"), FPL("filename.xhtml")},
  41. {FPL("filename.htm"), FPL("filename.htm")},
  42. // ".htm" is added if the extension is improper for HTML.
  43. {FPL("hello.world"), FPL("hello.world") FPL_HTML_EXTENSION},
  44. {FPL("hello.txt"), FPL("hello.txt") FPL_HTML_EXTENSION},
  45. {FPL("is.html.good"), FPL("is.html.good") FPL_HTML_EXTENSION},
  46. // ".htm" is added if the name doesn't have an extension.
  47. {FPL("helloworld"), FPL("helloworld") FPL_HTML_EXTENSION},
  48. {FPL("helloworld."), FPL("helloworld.") FPL_HTML_EXTENSION},
  49. };
  50. // Crashing on Windows, see http://crbug.com/79365
  51. #if BUILDFLAG(IS_WIN)
  52. #define MAYBE_TestEnsureHtmlExtension DISABLED_TestEnsureHtmlExtension
  53. #else
  54. #define MAYBE_TestEnsureHtmlExtension TestEnsureHtmlExtension
  55. #endif
  56. TEST(FilenameGenerationTest, MAYBE_TestEnsureHtmlExtension) {
  57. for (size_t i = 0; i < std::size(kExtensionTestCases); ++i) {
  58. base::FilePath original = base::FilePath(kExtensionTestCases[i].page_title);
  59. base::FilePath expected =
  60. base::FilePath(kExtensionTestCases[i].expected_name);
  61. base::FilePath actual = EnsureHtmlExtension(original);
  62. EXPECT_EQ(expected.value(), actual.value())
  63. << "Failed for page title: " << kExtensionTestCases[i].page_title;
  64. }
  65. }
  66. // Crashing on Windows, see http://crbug.com/79365
  67. #if BUILDFLAG(IS_WIN)
  68. #define MAYBE_TestEnsureMimeExtension DISABLED_TestEnsureMimeExtension
  69. #else
  70. #define MAYBE_TestEnsureMimeExtension TestEnsureMimeExtension
  71. #endif
  72. TEST(FilenameGenerationTest, MAYBE_TestEnsureMimeExtension) {
  73. static const struct {
  74. const base::FilePath::CharType* page_title;
  75. const base::FilePath::CharType* expected_name;
  76. const char* contents_mime_type;
  77. } kExtensionTests[] = {
  78. {FPL("filename.html"), FPL("filename.html"), "text/html"},
  79. {FPL("filename.htm"), FPL("filename.htm"), "text/html"},
  80. {FPL("filename.xhtml"), FPL("filename.xhtml"), "text/html"},
  81. #if BUILDFLAG(IS_WIN)
  82. {FPL("filename"), FPL("filename.htm"), "text/html"},
  83. #else // BUILDFLAG(IS_WIN)
  84. {FPL("filename"), FPL("filename.html"), "text/html"},
  85. #endif // BUILDFLAG(IS_WIN)
  86. {FPL("filename.html"), FPL("filename.html"), "text/xml"},
  87. {FPL("filename.xml"), FPL("filename.xml"), "text/xml"},
  88. {FPL("filename"), FPL("filename.xml"), "text/xml"},
  89. {FPL("filename.xhtml"), FPL("filename.xhtml"), "application/xhtml+xml"},
  90. {FPL("filename.html"), FPL("filename.html"), "application/xhtml+xml"},
  91. {FPL("filename"), FPL("filename.xhtml"), "application/xhtml+xml"},
  92. {FPL("filename.txt"), FPL("filename.txt"), "text/plain"},
  93. {FPL("filename"), FPL("filename.txt"), "text/plain"},
  94. {FPL("filename.css"), FPL("filename.css"), "text/css"},
  95. {FPL("filename"), FPL("filename.css"), "text/css"},
  96. {FPL("filename.mhtml"), FPL("filename.mhtml"), "multipart/related"},
  97. {FPL("filename.html"), FPL("filename.html.mhtml"), "multipart/related"},
  98. {FPL("filename.txt"), FPL("filename.txt.mhtml"), "multipart/related"},
  99. {FPL("filename"), FPL("filename.mhtml"), "multipart/related"},
  100. {FPL("filename.abc"), FPL("filename.abc"), "unknown/unknown"},
  101. {FPL("filename"), FPL("filename"), "unknown/unknown"},
  102. };
  103. for (uint32_t i = 0; i < std::size(kExtensionTests); ++i) {
  104. base::FilePath original = base::FilePath(kExtensionTests[i].page_title);
  105. base::FilePath expected = base::FilePath(kExtensionTests[i].expected_name);
  106. std::string mime_type(kExtensionTests[i].contents_mime_type);
  107. base::FilePath actual = EnsureMimeExtension(original, mime_type);
  108. EXPECT_EQ(expected.value(), actual.value())
  109. << "Failed for page title: " << kExtensionTests[i].page_title
  110. << " MIME:" << mime_type;
  111. }
  112. }
  113. // Test that the suggested names generated are reasonable:
  114. // If the name is a URL, retrieve only the path component since the path name
  115. // generation code will turn the entire URL into the file name leading to bad
  116. // extension names. For example, a page with no title and a URL:
  117. // http://www.foo.com/a/path/name.txt will turn into file:
  118. // "http www.foo.com a path name.txt", when we want to save it as "name.txt".
  119. static const struct GenerateFilenameTestCase {
  120. const char* page_url;
  121. const std::u16string page_title;
  122. const base::FilePath::CharType* expected_name;
  123. bool ensure_html_extension;
  124. } kGenerateFilenameCases[] = {
  125. // Title overrides the URL.
  126. {"http://foo.com", u"A page title", FPL("A page title") FPL_HTML_EXTENSION,
  127. true},
  128. // Extension is preserved.
  129. {"http://foo.com", u"A page title with.ext", FPL("A page title with.ext"),
  130. false},
  131. // If the title matches the URL, use the last component of the URL.
  132. {"http://foo.com/bar", u"foo.com/bar", FPL("bar"), false},
  133. // A URL with escaped special characters, when title matches the URL.
  134. {"http://foo.com/%40.txt", u"foo.com/%40.txt", FPL("@.txt"), false},
  135. // A URL with unescaped special characters, when title matches the URL.
  136. {"http://foo.com/@.txt", u"foo.com/@.txt", FPL("@.txt"), false},
  137. // A URL with punycode in the host name, when title matches the URL.
  138. {"http://xn--bcher-kva.com", u"bücher.com", FPL("bücher.com"), false},
  139. // If the title matches the URL, but there is no "filename" component,
  140. // use the domain.
  141. {"http://foo.com", u"foo.com", FPL("foo.com"), false},
  142. // Make sure fuzzy matching works.
  143. {"http://foo.com/bar", u"foo.com/bar", FPL("bar"), false},
  144. // A URL-like title that does not match the title is respected in full.
  145. {"http://foo.com", u"http://www.foo.com/path/title.txt",
  146. FPL("http___www.foo.com_path_title.txt"), false},
  147. };
  148. // Crashing on Windows, see http://crbug.com/79365
  149. #if BUILDFLAG(IS_WIN)
  150. #define MAYBE_TestGenerateFilename DISABLED_TestGenerateFilename
  151. #else
  152. #define MAYBE_TestGenerateFilename TestGenerateFilename
  153. #endif
  154. TEST(FilenameGenerationTest, MAYBE_TestGenerateFilename) {
  155. for (size_t i = 0; i < std::size(kGenerateFilenameCases); ++i) {
  156. base::FilePath save_name = GenerateFilename(
  157. kGenerateFilenameCases[i].page_title,
  158. GURL(kGenerateFilenameCases[i].page_url),
  159. kGenerateFilenameCases[i].ensure_html_extension, std::string());
  160. EXPECT_EQ(kGenerateFilenameCases[i].expected_name, save_name.value())
  161. << "Test case " << i;
  162. }
  163. }
  164. TEST(FilenameGenerationTest, TestBasicTruncation) {
  165. base::ScopedTempDir temp_dir;
  166. ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  167. int max_length = base::GetMaximumPathComponentLength(temp_dir.GetPath());
  168. ASSERT_NE(-1, max_length);
  169. base::FilePath::StringType extension(FILE_PATH_LITERAL(".txt"));
  170. base::FilePath path(GetLongNamePathInDirectory(
  171. max_length, FILE_PATH_LITERAL(".txt"), temp_dir.GetPath()));
  172. base::FilePath truncated_path = path;
  173. // The file path will only be truncated o the platforms that have known
  174. // encoding. Otherwise no truncation will be performed.
  175. #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_CHROMEOS_ASH)
  176. // The file name length is truncated to max_length.
  177. EXPECT_TRUE(TruncateFilename(&truncated_path, max_length));
  178. EXPECT_EQ(size_t(max_length), truncated_path.BaseName().value().size());
  179. #else
  180. EXPECT_FALSE(TruncateFilename(&truncated_path, max_length));
  181. EXPECT_EQ(truncated_path, path);
  182. EXPECT_LT(size_t(max_length), truncated_path.BaseName().value().size());
  183. #endif
  184. // But the extension is kept unchanged.
  185. EXPECT_EQ(path.Extension(), truncated_path.Extension());
  186. }
  187. TEST(FilenameGenerationTest, TestTruncationFail) {
  188. base::ScopedTempDir temp_dir;
  189. ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  190. int max_length = base::GetMaximumPathComponentLength(temp_dir.GetPath());
  191. ASSERT_NE(-1, max_length);
  192. base::FilePath path(
  193. (FILE_PATH_LITERAL("a.") + base::FilePath::StringType(max_length, 'b'))
  194. .c_str());
  195. path = temp_dir.GetPath().Append(path);
  196. base::FilePath truncated_path = path;
  197. // We cannot truncate a path with very long extension. This will fail and no
  198. // truncation will be performed on all platforms.
  199. EXPECT_FALSE(TruncateFilename(&truncated_path, max_length));
  200. EXPECT_EQ(truncated_path, path);
  201. }
  202. } // filename_generation