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