filename_util.cc 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Copyright 2014 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 "net/base/filename_util.h"
  5. #include <set>
  6. #include "base/files/file_path.h"
  7. #include "base/files/file_util.h"
  8. #include "base/path_service.h"
  9. #include "base/strings/escape.h"
  10. #include "base/strings/string_util.h"
  11. #include "base/strings/sys_string_conversions.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "base/threading/thread_restrictions.h"
  14. #include "build/build_config.h"
  15. #include "net/base/filename_util_internal.h"
  16. #include "net/base/net_string_util.h"
  17. #include "net/base/url_util.h"
  18. #include "net/http/http_content_disposition.h"
  19. #include "url/gurl.h"
  20. namespace net {
  21. // Prefix to prepend to get a file URL.
  22. static const char kFileURLPrefix[] = "file:///";
  23. GURL FilePathToFileURL(const base::FilePath& path) {
  24. // Produce a URL like "file:///C:/foo" for a regular file, or
  25. // "file://///server/path" for UNC. The URL canonicalizer will fix up the
  26. // latter case to be the canonical UNC form: "file://server/path"
  27. std::string url_string(kFileURLPrefix);
  28. // GURL() strips some whitespace and trailing control chars which are valid
  29. // in file paths. It also interprets chars such as `%;#?` and maybe `\`, so we
  30. // must percent encode these first. Reserve max possible length up front.
  31. std::string utf8_path = path.AsUTF8Unsafe();
  32. url_string.reserve(url_string.size() + (3 * utf8_path.size()));
  33. for (auto c : utf8_path) {
  34. if (c == '%' || c == ';' || c == '#' || c == '?' ||
  35. #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
  36. c == '\\' ||
  37. #endif
  38. c <= ' ') {
  39. static const char kHexChars[] = "0123456789ABCDEF";
  40. url_string += '%';
  41. url_string += kHexChars[(c >> 4) & 0xf];
  42. url_string += kHexChars[c & 0xf];
  43. } else {
  44. url_string += c;
  45. }
  46. }
  47. return GURL(url_string);
  48. }
  49. bool FileURLToFilePath(const GURL& url, base::FilePath* file_path) {
  50. *file_path = base::FilePath();
  51. base::FilePath::StringType& file_path_str =
  52. const_cast<base::FilePath::StringType&>(file_path->value());
  53. file_path_str.clear();
  54. if (!url.is_valid())
  55. return false;
  56. // We may want to change this to a CHECK in the future.
  57. if (!url.SchemeIsFile())
  58. return false;
  59. #if BUILDFLAG(IS_WIN)
  60. std::string path;
  61. std::string host = url.host();
  62. if (host.empty()) {
  63. // URL contains no host, the path is the filename. In this case, the path
  64. // will probably be preceded with a slash, as in "/C:/foo.txt", so we
  65. // trim out that here.
  66. path = url.path();
  67. size_t first_non_slash = path.find_first_not_of("/\\");
  68. if (first_non_slash != std::string::npos && first_non_slash > 0)
  69. path.erase(0, first_non_slash);
  70. } else {
  71. // URL contains a host: this means it's UNC. We keep the preceding slash
  72. // on the path.
  73. path = "\\\\";
  74. path.append(host);
  75. path.append(url.path());
  76. }
  77. std::replace(path.begin(), path.end(), '/', '\\');
  78. #else // BUILDFLAG(IS_WIN)
  79. // On POSIX, there's no obvious interpretation of file:// URLs with a host.
  80. // Usually, remote mounts are still mounted onto the local filesystem.
  81. // Therefore, we discard all URLs that are not obviously local to prevent
  82. // spoofing attacks using file:// URLs. See crbug.com/881675.
  83. if (!url.host().empty() && !net::IsLocalhost(url)) {
  84. return false;
  85. }
  86. std::string path = url.path();
  87. #endif // !BUILDFLAG(IS_WIN)
  88. if (path.empty())
  89. return false;
  90. // "%2F" ('/') results in failure, because it represents a literal '/'
  91. // character in a path segment (not a path separator). If this were decoded,
  92. // it would be interpreted as a path separator on both POSIX and Windows (note
  93. // that Firefox *does* decode this, but it was decided on
  94. // https://crbug.com/585422 that this represents a potential security risk).
  95. // It isn't correct to keep it as "%2F", so this just fails. This is fine,
  96. // because '/' is not a valid filename character on either POSIX or Windows.
  97. std::set<unsigned char> illegal_encoded_bytes{'/'};
  98. #if BUILDFLAG(IS_WIN)
  99. // "%5C" ('\\') on Windows results in failure, for the same reason as '/'
  100. // above. On POSIX, "%5C" simply decodes as '\\', a valid filename character.
  101. illegal_encoded_bytes.insert('\\');
  102. #endif
  103. if (base::ContainsEncodedBytes(path, illegal_encoded_bytes))
  104. return false;
  105. // Unescape all percent-encoded sequences, including blocked-for-display
  106. // characters, control characters and invalid UTF-8 byte sequences.
  107. // Percent-encoded bytes are not meaningful in a file system.
  108. path = base::UnescapeBinaryURLComponent(path);
  109. #if BUILDFLAG(IS_WIN)
  110. if (base::IsStringUTF8(path)) {
  111. file_path_str.assign(base::UTF8ToWide(path));
  112. // We used to try too hard and see if |path| made up entirely of
  113. // the 1st 256 characters in the Unicode was a zero-extended UTF-16.
  114. // If so, we converted it to 'Latin-1' and checked if the result was UTF-8.
  115. // If the check passed, we converted the result to UTF-8.
  116. // Otherwise, we treated the result as the native OS encoding.
  117. // However, that led to http://crbug.com/4619 and http://crbug.com/14153
  118. } else {
  119. // Not UTF-8, assume encoding is native codepage and we're done. We know we
  120. // are giving the conversion function a nonempty string, and it may fail if
  121. // the given string is not in the current encoding and give us an empty
  122. // string back. We detect this and report failure.
  123. file_path_str = base::SysNativeMBToWide(path);
  124. }
  125. #else // BUILDFLAG(IS_WIN)
  126. // Collapse multiple path slashes into a single path slash.
  127. std::string new_path;
  128. do {
  129. new_path = path;
  130. base::ReplaceSubstringsAfterOffset(&new_path, 0, "//", "/");
  131. path.swap(new_path);
  132. } while (new_path != path);
  133. file_path_str.assign(path);
  134. #endif // !BUILDFLAG(IS_WIN)
  135. return !file_path_str.empty();
  136. }
  137. void GenerateSafeFileName(const std::string& mime_type,
  138. bool ignore_extension,
  139. base::FilePath* file_path) {
  140. // Make sure we get the right file extension
  141. EnsureSafeExtension(mime_type, ignore_extension, file_path);
  142. #if BUILDFLAG(IS_WIN)
  143. // Prepend "_" to the file name if it's a reserved name
  144. base::FilePath::StringType leaf_name = file_path->BaseName().value();
  145. DCHECK(!leaf_name.empty());
  146. if (IsReservedNameOnWindows(leaf_name)) {
  147. leaf_name = base::FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name;
  148. *file_path = file_path->DirName();
  149. if (file_path->value() == base::FilePath::kCurrentDirectory) {
  150. *file_path = base::FilePath(leaf_name);
  151. } else {
  152. *file_path = file_path->Append(leaf_name);
  153. }
  154. }
  155. #endif
  156. }
  157. bool IsReservedNameOnWindows(const base::FilePath::StringType& filename) {
  158. // This list is taken from the MSDN article "Naming a file"
  159. // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx
  160. // I also added clock$ because GetSaveFileName seems to consider it as a
  161. // reserved name too.
  162. static const char* const known_devices[] = {
  163. "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4",
  164. "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3",
  165. "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "clock$"};
  166. #if BUILDFLAG(IS_WIN)
  167. std::string filename_lower = base::ToLowerASCII(base::WideToUTF8(filename));
  168. #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
  169. std::string filename_lower = base::ToLowerASCII(filename);
  170. #endif
  171. for (const char* const device : known_devices) {
  172. // Exact match.
  173. if (filename_lower == device)
  174. return true;
  175. // Starts with "DEVICE.".
  176. if (base::StartsWith(filename_lower, std::string(device) + ".",
  177. base::CompareCase::SENSITIVE)) {
  178. return true;
  179. }
  180. }
  181. static const char* const magic_names[] = {
  182. // These file names are used by the "Customize folder" feature of the
  183. // shell.
  184. "desktop.ini",
  185. "thumbs.db",
  186. };
  187. for (const char* const magic_name : magic_names) {
  188. if (filename_lower == magic_name)
  189. return true;
  190. }
  191. return false;
  192. }
  193. } // namespace net