| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/clipboard/file_info.h" |
| |
| #include <string_view> |
| |
| #include "base/strings/escape.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| |
| namespace ui { |
| namespace { |
| |
| constexpr char kFileUrlPrefix[] = "file://"; |
| constexpr char kFileSchemePrefix[] = "file:"; |
| constexpr char kURIListSeparator[] = "\r\n"; |
| |
| // Returns true if path starts with a letter, followed by a colon, followed |
| // by a path separator. |
| bool StartsWithDriveLetter(std::string_view path) { |
| return path.length() > 2 && base::IsAsciiAlpha(path[0]) && path[1] == ':' && |
| base::FilePath::IsSeparator(path[2]); |
| } |
| |
| // We implement our own URLToPath() and PathToURL() rather than use |
| // net::FileUrlToFilePath() or net::FilePathToFileURL() since //net code works |
| // differently on each platform and is overly strict. In particular, it doesn't |
| // allow Windows network paths such as //ChromeOS/MyFiles on OS_CHROMEOS. |
| // |
| // This code is a little different in nature to most other path handling in that |
| // we expect this code to roundtrip both posix and windows paths (local or |
| // network) when running on either platform. |
| // |
| // Convert file:// |url| to a FilePath. Returns empty if |url| is invalid. |
| // This function expects an absolute path since it is not possible to encode |
| // a relative path as a file:// URL. The third slash in 'file:///' is not |
| // mandatory, but without it, the path is considered a network path |
| // (file://host/path). If a drive letter followed by colon and slash is detected |
| // (file:///C:/path), the path is assumed to be windows path 'C:/path', but |
| // without the slash (file:///C:path), the path is assumed to posix '/C:path' |
| // rather than windows relative path 'C:path'. |
| base::FilePath URLToPath(std::string_view url) { |
| // Must start with 'file://' with at least 1 more char. |
| std::string prefix(kFileUrlPrefix); |
| if (url.size() <= prefix.size() || !url.starts_with(prefix)) { |
| return base::FilePath(); |
| } |
| |
| // Skip slashes after 'file:' if needed: |
| size_t path_start; |
| if (url[prefix.size()] == '/') { |
| // file:///path => /path |
| path_start = prefix.size(); |
| if (StartsWithDriveLetter(url.substr(path_start + 1))) { |
| // file:///C:/path => C:/path |
| ++path_start; |
| } |
| } else { |
| // file://host/path => //host/path |
| DCHECK_EQ(prefix.size(), 7u); |
| path_start = prefix.size() - 2; |
| } |
| |
| std::string result = base::UnescapeBinaryURLComponent(url.substr(path_start)); |
| #if BUILDFLAG(IS_WIN) |
| return base::FilePath(base::UTF8ToWide(result)).NormalizePathSeparators(); |
| #else |
| return base::FilePath(result); |
| #endif |
| } |
| |
| } // namespace |
| |
| FileInfo::FileInfo() = default; |
| |
| FileInfo::FileInfo(const base::FilePath& path, |
| const base::FilePath& display_name) |
| : path(path), display_name(display_name) {} |
| |
| FileInfo::~FileInfo() = default; |
| |
| bool FileInfo::operator==(const FileInfo& other) const { |
| return path == other.path && display_name == other.display_name; |
| } |
| |
| std::vector<FileInfo> URIListToFileInfos(std::string_view uri_list) { |
| std::vector<FileInfo> result; |
| std::vector<std::string_view> lines = |
| base::SplitStringPiece(uri_list, kURIListSeparator, base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| for (std::string_view line : lines) { |
| base::FilePath path = URLToPath(line); |
| if (!path.empty()) { |
| result.push_back(FileInfo(path, base::FilePath())); |
| } |
| } |
| return result; |
| } |
| |
| std::string FilePathToFileURL(const base::FilePath& file_path) { |
| std::string url; |
| #if BUILDFLAG(IS_WIN) |
| std::string path = base::WideToUTF8(file_path.value()); |
| #else |
| std::string path = file_path.value(); |
| #endif |
| // Allocate maximum possible size upfront: |
| // 'file:' + '///' + (3 x path.size() for percent encoding). |
| url.reserve((sizeof(kFileSchemePrefix) - 1) + 3 + (3 * path.size())); |
| url += kFileSchemePrefix; |
| |
| // Add slashes after 'file:' if needed: |
| if (path.size() > 1 && base::FilePath::IsSeparator(path[0]) && |
| base::FilePath::IsSeparator(path[1])) { |
| // //host/path => file://host/path |
| } else if (path.size() > 0 && base::FilePath::IsSeparator(path[0])) { |
| // /absolute/path => file:///absolute/path |
| url += "//"; |
| } else { |
| // relative/path => file:///relative/path |
| // C:/path => file:///C:/path |
| // A relative path can't be encoded as a file:// URL, so we will produce an |
| // absolute path like GURL() does. We do expect input to be absolute, and |
| // ideally, we would DCHECK(file_path.IsAbsolute()), but it is possible that |
| // we are interpreting a windows path while running on a posix platform. |
| url += "///"; |
| } |
| |
| for (char c : path) { |
| // Encode special characters `%;#?\`. |
| if (c == '%' || c == ';' || c == '#' || c == '?' || |
| #if !BUILDFLAG(IS_WIN) |
| // Backslash is percent-encoded on posix platforms. |
| c == '\\' || |
| #endif |
| // Encode space and all control chars. |
| c <= ' ') { |
| url += '%'; |
| base::AppendHexEncodedByte(static_cast<uint8_t>(c), url); |
| #if BUILDFLAG(IS_WIN) |
| } else if (c == '\\') { |
| // Backslash is converted to slash on windows. |
| url += '/'; |
| #endif |
| } else { |
| url += c; |
| } |
| } |
| |
| return url; |
| } |
| |
| std::string FileInfosToURIList(const std::vector<FileInfo>& filenames) { |
| std::vector<std::string> uri_list; |
| for (const FileInfo& file : filenames) { |
| uri_list.push_back(FilePathToFileURL(file.path)); |
| } |
| return base::JoinString(uri_list, kURIListSeparator); |
| } |
| |
| } // namespace ui |