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 5 #include "net/base/filename_util.h" 6 7 #include "base/files/file_path.h" 8 #include "base/files/file_util.h" 9 #include "base/path_service.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 "net/base/escape.h" 15 #include "net/base/filename_util_internal.h" 16 #include "net/base/mime_util.h" 17 #include "net/base/net_string_util.h" 18 #include "net/http/http_content_disposition.h" 19 #include "url/gurl.h" 20 21 namespace net { 22 23 // Prefix to prepend to get a file URL. 24 static const base::FilePath::CharType kFileURLPrefix[] = 25 FILE_PATH_LITERAL("file:///"); 26 27 GURL FilePathToFileURL(const base::FilePath& path) { 28 // Produce a URL like "file:///C:/foo" for a regular file, or 29 // "file://///server/path" for UNC. The URL canonicalizer will fix up the 30 // latter case to be the canonical UNC form: "file://server/path" 31 base::FilePath::StringType url_string(kFileURLPrefix); 32 if (!path.IsAbsolute()) { 33 base::FilePath current_dir; 34 PathService::Get(base::DIR_CURRENT, ¤t_dir); 35 url_string.append(current_dir.value()); 36 url_string.push_back(base::FilePath::kSeparators[0]); 37 } 38 url_string.append(path.value()); 39 40 // Now do replacement of some characters. Since we assume the input is a 41 // literal filename, anything the URL parser might consider special should 42 // be escaped here. 43 44 // must be the first substitution since others will introduce percents as the 45 // escape character 46 ReplaceSubstringsAfterOffset( 47 &url_string, 0, FILE_PATH_LITERAL("%"), FILE_PATH_LITERAL("%25")); 48 49 // semicolon is supposed to be some kind of separator according to RFC 2396 50 ReplaceSubstringsAfterOffset( 51 &url_string, 0, FILE_PATH_LITERAL(";"), FILE_PATH_LITERAL("%3B")); 52 53 ReplaceSubstringsAfterOffset( 54 &url_string, 0, FILE_PATH_LITERAL("#"), FILE_PATH_LITERAL("%23")); 55 56 ReplaceSubstringsAfterOffset( 57 &url_string, 0, FILE_PATH_LITERAL("?"), FILE_PATH_LITERAL("%3F")); 58 59 #if defined(OS_POSIX) 60 ReplaceSubstringsAfterOffset( 61 &url_string, 0, FILE_PATH_LITERAL("\\"), FILE_PATH_LITERAL("%5C")); 62 #endif 63 64 return GURL(url_string); 65 } 66 67 bool FileURLToFilePath(const GURL& url, base::FilePath* file_path) { 68 *file_path = base::FilePath(); 69 base::FilePath::StringType& file_path_str = 70 const_cast<base::FilePath::StringType&>(file_path->value()); 71 file_path_str.clear(); 72 73 if (!url.is_valid()) 74 return false; 75 76 #if defined(OS_WIN) 77 std::string path; 78 std::string host = url.host(); 79 if (host.empty()) { 80 // URL contains no host, the path is the filename. In this case, the path 81 // will probably be preceeded with a slash, as in "/C:/foo.txt", so we 82 // trim out that here. 83 path = url.path(); 84 size_t first_non_slash = path.find_first_not_of("/\\"); 85 if (first_non_slash != std::string::npos && first_non_slash > 0) 86 path.erase(0, first_non_slash); 87 } else { 88 // URL contains a host: this means it's UNC. We keep the preceeding slash 89 // on the path. 90 path = "\\\\"; 91 path.append(host); 92 path.append(url.path()); 93 } 94 std::replace(path.begin(), path.end(), '/', '\\'); 95 #else // defined(OS_WIN) 96 // Firefox seems to ignore the "host" of a file url if there is one. That is, 97 // file://foo/bar.txt maps to /bar.txt. 98 // TODO(dhg): This should probably take into account UNCs which could 99 // include a hostname other than localhost or blank 100 std::string path = url.path(); 101 #endif // !defined(OS_WIN) 102 103 if (path.empty()) 104 return false; 105 106 // GURL stores strings as percent-encoded 8-bit, this will undo if possible. 107 path = UnescapeURLComponent( 108 path, UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); 109 110 #if defined(OS_WIN) 111 if (base::IsStringUTF8(path)) { 112 file_path_str.assign(base::UTF8ToWide(path)); 113 // We used to try too hard and see if |path| made up entirely of 114 // the 1st 256 characters in the Unicode was a zero-extended UTF-16. 115 // If so, we converted it to 'Latin-1' and checked if the result was UTF-8. 116 // If the check passed, we converted the result to UTF-8. 117 // Otherwise, we treated the result as the native OS encoding. 118 // However, that led to http://crbug.com/4619 and http://crbug.com/14153 119 } else { 120 // Not UTF-8, assume encoding is native codepage and we're done. We know we 121 // are giving the conversion function a nonempty string, and it may fail if 122 // the given string is not in the current encoding and give us an empty 123 // string back. We detect this and report failure. 124 file_path_str = base::SysNativeMBToWide(path); 125 } 126 #else // defined(OS_WIN) 127 // Collapse multiple path slashes into a single path slash. 128 std::string new_path; 129 do { 130 new_path = path; 131 ReplaceSubstringsAfterOffset(&new_path, 0, "//", "/"); 132 path.swap(new_path); 133 } while (new_path != path); 134 135 file_path_str.assign(path); 136 #endif // !defined(OS_WIN) 137 138 return !file_path_str.empty(); 139 } 140 141 void GenerateSafeFileName(const std::string& mime_type, 142 bool ignore_extension, 143 base::FilePath* file_path) { 144 // Make sure we get the right file extension 145 EnsureSafeExtension(mime_type, ignore_extension, file_path); 146 147 #if defined(OS_WIN) 148 // Prepend "_" to the file name if it's a reserved name 149 base::FilePath::StringType leaf_name = file_path->BaseName().value(); 150 DCHECK(!leaf_name.empty()); 151 if (IsReservedName(leaf_name)) { 152 leaf_name = base::FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name; 153 *file_path = file_path->DirName(); 154 if (file_path->value() == base::FilePath::kCurrentDirectory) { 155 *file_path = base::FilePath(leaf_name); 156 } else { 157 *file_path = file_path->Append(leaf_name); 158 } 159 } 160 #endif 161 } 162 163 } // namespace net 164