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