file_path.cc (11413B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 // Copyright (c) 2008 The Chromium Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style license that can be 5 // found in the LICENSE file. 6 7 #include <fstream> 8 9 #include "base/file_path.h" 10 #include "base/logging.h" 11 12 // These includes are just for the *Hack functions, and should be removed 13 // when those functions are removed. 14 #include "base/string_piece.h" 15 #include "base/string_util.h" 16 #include "base/sys_string_conversions.h" 17 18 #if defined(FILE_PATH_USES_WIN_SEPARATORS) 19 const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("\\/"); 20 #else // FILE_PATH_USES_WIN_SEPARATORS 21 const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/"); 22 #endif // FILE_PATH_USES_WIN_SEPARATORS 23 24 const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL("."); 25 const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL(".."); 26 27 const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.'); 28 29 namespace { 30 31 // If this FilePath contains a drive letter specification, returns the 32 // position of the last character of the drive letter specification, 33 // otherwise returns npos. This can only be true on Windows, when a pathname 34 // begins with a letter followed by a colon. On other platforms, this always 35 // returns npos. 36 FilePath::StringType::size_type FindDriveLetter( 37 const FilePath::StringType& path) { 38 #if defined(FILE_PATH_USES_DRIVE_LETTERS) 39 // This is dependent on an ASCII-based character set, but that's a 40 // reasonable assumption. iswalpha can be too inclusive here. 41 if (path.length() >= 2 && path[1] == L':' && 42 ((path[0] >= L'A' && path[0] <= L'Z') || 43 (path[0] >= L'a' && path[0] <= L'z'))) { 44 return 1; 45 } 46 #endif // FILE_PATH_USES_DRIVE_LETTERS 47 return FilePath::StringType::npos; 48 } 49 50 bool IsPathAbsolute(const FilePath::StringType& path) { 51 #if defined(FILE_PATH_USES_DRIVE_LETTERS) 52 FilePath::StringType::size_type letter = FindDriveLetter(path); 53 if (letter != FilePath::StringType::npos) { 54 // Look for a separator right after the drive specification. 55 return path.length() > letter + 1 && 56 FilePath::IsSeparator(path[letter + 1]); 57 } 58 // Look for a pair of leading separators. 59 return path.length() > 1 && FilePath::IsSeparator(path[0]) && 60 FilePath::IsSeparator(path[1]); 61 #else // FILE_PATH_USES_DRIVE_LETTERS 62 // Look for a separator in the first position. 63 return path.length() > 0 && FilePath::IsSeparator(path[0]); 64 #endif // FILE_PATH_USES_DRIVE_LETTERS 65 } 66 67 } // namespace 68 69 bool FilePath::IsSeparator(CharType character) { 70 for (size_t i = 0; i < arraysize(kSeparators) - 1; ++i) { 71 if (character == kSeparators[i]) { 72 return true; 73 } 74 } 75 76 return false; 77 } 78 79 // libgen's dirname and basename aren't guaranteed to be thread-safe and aren't 80 // guaranteed to not modify their input strings, and in fact are implemented 81 // differently in this regard on different platforms. Don't use them, but 82 // adhere to their behavior. 83 FilePath FilePath::DirName() const { 84 FilePath new_path(path_); 85 new_path.StripTrailingSeparatorsInternal(); 86 87 // The drive letter, if any, always needs to remain in the output. If there 88 // is no drive letter, as will always be the case on platforms which do not 89 // support drive letters, letter will be npos, or -1, so the comparisons and 90 // resizes below using letter will still be valid. 91 StringType::size_type letter = FindDriveLetter(new_path.path_); 92 93 StringType::size_type last_separator = new_path.path_.find_last_of( 94 kSeparators, StringType::npos, arraysize(kSeparators) - 1); 95 if (last_separator == StringType::npos) { 96 // path_ is in the current directory. 97 new_path.path_.resize(letter + 1); 98 } else if (last_separator == letter + 1) { 99 // path_ is in the root directory. 100 new_path.path_.resize(letter + 2); 101 } else if (last_separator == letter + 2 && 102 IsSeparator(new_path.path_[letter + 1])) { 103 // path_ is in "//" (possibly with a drive letter); leave the double 104 // separator intact indicating alternate root. 105 new_path.path_.resize(letter + 3); 106 } else if (last_separator != 0) { 107 // path_ is somewhere else, trim the basename. 108 new_path.path_.resize(last_separator); 109 } 110 111 new_path.StripTrailingSeparatorsInternal(); 112 if (!new_path.path_.length()) new_path.path_ = kCurrentDirectory; 113 114 return new_path; 115 } 116 117 FilePath FilePath::BaseName() const { 118 FilePath new_path(path_); 119 new_path.StripTrailingSeparatorsInternal(); 120 121 // The drive letter, if any, is always stripped. 122 StringType::size_type letter = FindDriveLetter(new_path.path_); 123 if (letter != StringType::npos) { 124 new_path.path_.erase(0, letter + 1); 125 } 126 127 // Keep everything after the final separator, but if the pathname is only 128 // one character and it's a separator, leave it alone. 129 StringType::size_type last_separator = new_path.path_.find_last_of( 130 kSeparators, StringType::npos, arraysize(kSeparators) - 1); 131 if (last_separator != StringType::npos && 132 last_separator < new_path.path_.length() - 1) { 133 new_path.path_.erase(0, last_separator + 1); 134 } 135 136 return new_path; 137 } 138 139 FilePath::StringType FilePath::Extension() const { 140 // BaseName() calls StripTrailingSeparators, so cases like /foo.baz/// work. 141 StringType base = BaseName().value(); 142 143 // Special case "." and ".." 144 if (base == kCurrentDirectory || base == kParentDirectory) 145 return StringType(); 146 147 const StringType::size_type last_dot = base.rfind(kExtensionSeparator); 148 if (last_dot == StringType::npos) return StringType(); 149 return StringType(base, last_dot); 150 } 151 152 FilePath FilePath::RemoveExtension() const { 153 StringType ext = Extension(); 154 // It's important to check Extension() since that verifies that the 155 // kExtensionSeparator actually appeared in the last path component. 156 if (ext.empty()) return FilePath(path_); 157 // Since Extension() verified that the extension is in fact in the last path 158 // component, this substr will effectively strip trailing separators. 159 const StringType::size_type last_dot = path_.rfind(kExtensionSeparator); 160 return FilePath(path_.substr(0, last_dot)); 161 } 162 163 FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const { 164 if (suffix.empty()) return FilePath(path_); 165 166 if (path_.empty()) return FilePath(); 167 168 StringType base = BaseName().value(); 169 if (base.empty()) return FilePath(); 170 if (*(base.end() - 1) == kExtensionSeparator) { 171 // Special case "." and ".." 172 if (base == kCurrentDirectory || base == kParentDirectory) { 173 return FilePath(); 174 } 175 } 176 177 StringType ext = Extension(); 178 StringType ret = RemoveExtension().value(); 179 ret.append(suffix); 180 ret.append(ext); 181 return FilePath(ret); 182 } 183 184 FilePath FilePath::ReplaceExtension(const StringType& extension) const { 185 if (path_.empty()) return FilePath(); 186 187 StringType base = BaseName().value(); 188 if (base.empty()) return FilePath(); 189 if (*(base.end() - 1) == kExtensionSeparator) { 190 // Special case "." and ".." 191 if (base == kCurrentDirectory || base == kParentDirectory) { 192 return FilePath(); 193 } 194 } 195 196 FilePath no_ext = RemoveExtension(); 197 // If the new extension is "" or ".", then just remove the current extension. 198 if (extension.empty() || extension == StringType(1, kExtensionSeparator)) 199 return no_ext; 200 201 StringType str = no_ext.value(); 202 if (extension[0] != kExtensionSeparator) str.append(1, kExtensionSeparator); 203 str.append(extension); 204 return FilePath(str); 205 } 206 207 FilePath FilePath::Append(const StringType& component) const { 208 DCHECK(!IsPathAbsolute(component)); 209 if (path_.compare(kCurrentDirectory) == 0) { 210 // Append normally doesn't do any normalization, but as a special case, 211 // when appending to kCurrentDirectory, just return a new path for the 212 // component argument. Appending component to kCurrentDirectory would 213 // serve no purpose other than needlessly lengthening the path, and 214 // it's likely in practice to wind up with FilePath objects containing 215 // only kCurrentDirectory when calling DirName on a single relative path 216 // component. 217 return FilePath(component); 218 } 219 220 FilePath new_path(path_); 221 new_path.StripTrailingSeparatorsInternal(); 222 223 // Don't append a separator if the path is empty (indicating the current 224 // directory) or if the path component is empty (indicating nothing to 225 // append). 226 if (component.length() > 0 && new_path.path_.length() > 0) { 227 // Don't append a separator if the path still ends with a trailing 228 // separator after stripping (indicating the root directory). 229 if (!IsSeparator(new_path.path_[new_path.path_.length() - 1])) { 230 // Don't append a separator if the path is just a drive letter. 231 if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) { 232 new_path.path_.append(1, kSeparators[0]); 233 } 234 } 235 } 236 237 new_path.path_.append(component); 238 return new_path; 239 } 240 241 FilePath FilePath::Append(const FilePath& component) const { 242 return Append(component.value()); 243 } 244 245 FilePath FilePath::AppendASCII(const std::string& component) const { 246 DCHECK(IsStringASCII(component)); 247 #if defined(XP_WIN) 248 return Append(ASCIIToWide(component)); 249 #else 250 return Append(component); 251 #endif 252 } 253 254 bool FilePath::IsAbsolute() const { return IsPathAbsolute(path_); } 255 256 #if defined(XP_UNIX) 257 // See file_path.h for a discussion of the encoding of paths on POSIX 258 // platforms. These *Hack() functions are not quite correct, but they're 259 // only temporary while we fix the remainder of the code. 260 // Remember to remove the #includes at the top when you remove these. 261 262 // static 263 FilePath FilePath::FromWStringHack(const std::wstring& wstring) { 264 return FilePath(base::SysWideToNativeMB(wstring)); 265 } 266 std::wstring FilePath::ToWStringHack() const { 267 return base::SysNativeMBToWide(path_); 268 } 269 #else 270 // static 271 FilePath FilePath::FromWStringHack(const std::wstring& wstring) { 272 return FilePath(wstring); 273 } 274 std::wstring FilePath::ToWStringHack() const { return path_; } 275 #endif 276 277 void FilePath::OpenInputStream(std::ifstream& stream) const { 278 stream.open( 279 #ifndef __MINGW32__ 280 path_.c_str(), 281 #else 282 base::SysWideToNativeMB(path_).c_str(), 283 #endif 284 std::ios::in | std::ios::binary); 285 } 286 287 FilePath FilePath::StripTrailingSeparators() const { 288 FilePath new_path(path_); 289 new_path.StripTrailingSeparatorsInternal(); 290 291 return new_path; 292 } 293 294 void FilePath::StripTrailingSeparatorsInternal() { 295 // If there is no drive letter, start will be 1, which will prevent stripping 296 // the leading separator if there is only one separator. If there is a drive 297 // letter, start will be set appropriately to prevent stripping the first 298 // separator following the drive letter, if a separator immediately follows 299 // the drive letter. 300 StringType::size_type start = FindDriveLetter(path_) + 2; 301 302 StringType::size_type last_stripped = StringType::npos; 303 for (StringType::size_type pos = path_.length(); 304 pos > start && IsSeparator(path_[pos - 1]); --pos) { 305 // If the string only has two separators and they're at the beginning, 306 // don't strip them, unless the string began with more than two separators. 307 if (pos != start + 1 || last_stripped == start + 2 || 308 !IsSeparator(path_[start - 1])) { 309 path_.resize(pos - 1); 310 last_stripped = pos; 311 } 312 } 313 }