blob: 10ab1643773cc0fa13a7d77e38e0a52b208ca1ba [file] [log] [blame]
license.botf003cfe2008-08-24 09:55:55 +09001// Copyright (c) 2006-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.
initial.commit3f4a7322008-07-27 06:49:38 +09004
5#include "base/file_util.h"
6
mark@chromium.orgd1bafc62008-10-02 02:40:13 +09007#include <stdio.h>
8
initial.commit3f4a7322008-07-27 06:49:38 +09009#include <fstream>
initial.commit3f4a7322008-07-27 06:49:38 +090010
evanm@google.com874d1672008-10-31 08:54:04 +090011#include "base/file_path.h"
initial.commit3f4a7322008-07-27 06:49:38 +090012#include "base/logging.h"
initial.commit3f4a7322008-07-27 06:49:38 +090013#include "base/string_util.h"
initial.commit3f4a7322008-07-27 06:49:38 +090014#include "unicode/uniset.h"
15
initial.commit3f4a7322008-07-27 06:49:38 +090016namespace file_util {
17
initial.commit3f4a7322008-07-27 06:49:38 +090018const wchar_t kExtensionSeparator = L'.';
19
erikkay@google.comdfb51b22008-08-16 02:32:10 +090020void PathComponents(const std::wstring& path,
21 std::vector<std::wstring>* components) {
22 DCHECK(components != NULL);
23 if (components == NULL)
24 return;
25 std::wstring::size_type start = 0;
26 std::wstring::size_type end = path.find(kPathSeparator, start);
27
28 // Special case the "/" or "\" directory. On Windows with a drive letter,
estade@chromium.orgd778b8f2008-11-26 07:04:37 +090029 // this code path won't hit, but the right thing should still happen.
erikkay@google.comdfb51b22008-08-16 02:32:10 +090030 // "E:\foo" will turn into "E:","foo".
31 if (end == start) {
32 components->push_back(std::wstring(path, 0, 1));
33 start = end + 1;
34 end = path.find(kPathSeparator, start);
35 }
36 while (end != std::wstring::npos) {
37 std::wstring component = std::wstring(path, start, end - start);
38 components->push_back(component);
39 start = end + 1;
40 end = path.find(kPathSeparator, start);
41 }
42 std::wstring component = std::wstring(path, start);
43 components->push_back(component);
44}
estade@chromium.orgd778b8f2008-11-26 07:04:37 +090045
46bool EndsWithSeparator(const FilePath& file_path) {
47 std::wstring path = file_path.ToWStringHack();
48 bool is_sep = (path.length() > 0 &&
49 path[path.length() - 1] == kPathSeparator);
erikkay@google.comdfb51b22008-08-16 02:32:10 +090050 return is_sep;
initial.commit3f4a7322008-07-27 06:49:38 +090051}
52
estade@chromium.orgd778b8f2008-11-26 07:04:37 +090053bool EnsureEndsWithSeparator(FilePath* path) {
54 if (!DirectoryExists(*path))
55 return false;
56
57 if (EndsWithSeparator(*path))
58 return true;
59
60 FilePath::StringType& path_str =
61 const_cast<FilePath::StringType&>(path->value());
62 path_str.append(&FilePath::kSeparators[0], 1);
63
64 return true;
65}
66
initial.commit3f4a7322008-07-27 06:49:38 +090067void TrimTrailingSeparator(std::wstring* dir) {
erikkay@google.comdfb51b22008-08-16 02:32:10 +090068 while (dir->length() > 1 && EndsWithSeparator(dir))
initial.commit3f4a7322008-07-27 06:49:38 +090069 dir->resize(dir->length() - 1);
70}
71
72void UpOneDirectory(std::wstring* dir) {
73 TrimTrailingSeparator(dir);
74
erikkay@google.com9fc57d02008-08-09 05:16:08 +090075 std::wstring::size_type last_sep = dir->find_last_of(kPathSeparator);
76 if (last_sep != std::wstring::npos)
initial.commit3f4a7322008-07-27 06:49:38 +090077 dir->resize(last_sep);
78}
79
80void UpOneDirectoryOrEmpty(std::wstring* dir) {
81 TrimTrailingSeparator(dir);
82
erikkay@google.com9fc57d02008-08-09 05:16:08 +090083 std::wstring::size_type last_sep = dir->find_last_of(kPathSeparator);
84 if (last_sep != std::wstring::npos)
initial.commit3f4a7322008-07-27 06:49:38 +090085 dir->resize(last_sep);
86 else
87 dir->clear();
88}
89
90void TrimFilename(std::wstring* path) {
91 if (EndsWithSeparator(path)) {
92 TrimTrailingSeparator(path);
93 } else {
erikkay@google.com9fc57d02008-08-09 05:16:08 +090094 std::wstring::size_type last_sep = path->find_last_of(kPathSeparator);
95 if (last_sep != std::wstring::npos)
initial.commit3f4a7322008-07-27 06:49:38 +090096 path->resize(last_sep);
97 }
98}
99
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900100std::wstring GetFilenameFromPath(const std::wstring& path) {
erikkay@google.com60b2dfc2008-08-16 03:00:48 +0900101 // TODO(erikkay): fix this - it's not using kPathSeparator, but win unit test
102 // are exercising '/' as a path separator as well.
103 std::wstring::size_type pos = path.find_last_of(L"\\/");
glen@chromium.org40eb4922008-11-04 01:14:46 +0900104 return std::wstring(path, pos == std::wstring::npos ? 0 : pos + 1);
initial.commit3f4a7322008-07-27 06:49:38 +0900105}
106
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900107std::wstring GetFileExtensionFromPath(const std::wstring& path) {
108 std::wstring file_name = GetFilenameFromPath(path);
109 std::wstring::size_type last_dot = file_name.rfind(L'.');
glen@chromium.org40eb4922008-11-04 01:14:46 +0900110 return std::wstring(last_dot == std::wstring::npos ?
111 L"" :
112 file_name, last_dot+1);
113}
114
115std::wstring GetFilenameWithoutExtensionFromPath(const std::wstring& path) {
116 std::wstring file_name = GetFilenameFromPath(path);
117 std::wstring::size_type last_dot = file_name.rfind(L'.');
118 return file_name.substr(0, last_dot);
initial.commit3f4a7322008-07-27 06:49:38 +0900119}
120
121void AppendToPath(std::wstring* path, const std::wstring& new_ending) {
122 if (!path) {
123 NOTREACHED();
124 return; // Don't crash in this function in release builds.
125 }
126
127 if (!EndsWithSeparator(path))
128 path->push_back(kPathSeparator);
129 path->append(new_ending);
130}
131
132void InsertBeforeExtension(std::wstring* path, const std::wstring& suffix) {
133 DCHECK(path);
134
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900135 const std::wstring::size_type last_dot = path->rfind(kExtensionSeparator);
136 const std::wstring::size_type last_sep = path->rfind(kPathSeparator);
initial.commit3f4a7322008-07-27 06:49:38 +0900137
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900138 if (last_dot == std::wstring::npos ||
139 (last_sep != std::wstring::npos && last_dot < last_sep)) {
initial.commit3f4a7322008-07-27 06:49:38 +0900140 // The path looks something like "C:\pics.old\jojo" or "C:\pics\jojo".
141 // We should just append the suffix to the entire path.
142 path->append(suffix);
143 return;
144 }
145
146 path->insert(last_dot, suffix);
147}
148
149void ReplaceIllegalCharacters(std::wstring* file_name, int replace_char) {
150 DCHECK(file_name);
151
ericroman@google.comdbff4f52008-08-19 01:00:38 +0900152 // Control characters, formatting characters, non-characters, and
initial.commit3f4a7322008-07-27 06:49:38 +0900153 // some printable ASCII characters regarded as dangerous ('"*/:<>?\\').
154 // See http://blogs.msdn.com/michkap/archive/2006/11/03/941420.aspx
155 // and http://msdn2.microsoft.com/en-us/library/Aa365247.aspx
156 // TODO(jungshik): Revisit the set. ZWJ and ZWNJ are excluded because they
157 // are legitimate in Arabic and some S/SE Asian scripts. However, when used
158 // elsewhere, they can be confusing/problematic.
159 // Also, consider wrapping the set with our Singleton class to create and
160 // freeze it only once. Note that there's a trade-off between memory and
161 // speed.
162
163 UErrorCode status = U_ZERO_ERROR;
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900164#if defined(WCHAR_T_IS_UTF16)
initial.commit3f4a7322008-07-27 06:49:38 +0900165 UnicodeSet illegal_characters(UnicodeString(
166 L"[[\"*/:<>?\\\\|][:Cc:][:Cf:] - [\u200c\u200d]]"), status);
167#else
168 UnicodeSet illegal_characters(UNICODE_STRING_SIMPLE(
169 "[[\"*/:<>?\\\\|][:Cc:][:Cf:] - [\\u200c\\u200d]]").unescape(), status);
170#endif
171 DCHECK(U_SUCCESS(status));
172 // Add non-characters. If this becomes a performance bottleneck by
173 // any chance, check |ucs4 & 0xFFFEu == 0xFFFEu|, instead.
174 illegal_characters.add(0xFDD0, 0xFDEF);
175 for (int i = 0; i <= 0x10; ++i) {
176 int plane_base = 0x10000 * i;
177 illegal_characters.add(plane_base + 0xFFFE, plane_base + 0xFFFF);
178 }
179 illegal_characters.freeze();
180 DCHECK(!illegal_characters.contains(replace_char) && replace_char < 0x10000);
181
182 // Remove leading and trailing whitespace.
183 TrimWhitespace(*file_name, TRIM_ALL, file_name);
184
185 std::wstring::size_type i = 0;
186 std::wstring::size_type length = file_name->size();
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900187 const wchar_t* wstr = file_name->data();
188#if defined(WCHAR_T_IS_UTF16)
initial.commit3f4a7322008-07-27 06:49:38 +0900189 // Using |span| method of UnicodeSet might speed things up a bit, but
190 // it's not likely to matter here.
initial.commit3f4a7322008-07-27 06:49:38 +0900191 std::wstring temp;
192 temp.reserve(length);
193 while (i < length) {
194 UChar32 ucs4;
195 std::wstring::size_type prev = i;
196 U16_NEXT(wstr, i, length, ucs4);
197 if (illegal_characters.contains(ucs4)) {
198 temp.push_back(replace_char);
199 } else if (ucs4 < 0x10000) {
200 temp.push_back(ucs4);
201 } else {
202 temp.push_back(wstr[prev]);
203 temp.push_back(wstr[prev + 1]);
204 }
205 }
206 file_name->swap(temp);
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900207#elif defined(WCHAR_T_IS_UTF32)
initial.commit3f4a7322008-07-27 06:49:38 +0900208 while (i < length) {
209 if (illegal_characters.contains(wstr[i])) {
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900210 (*file_name)[i] = replace_char;
initial.commit3f4a7322008-07-27 06:49:38 +0900211 }
erikkay@google.comdfb51b22008-08-16 02:32:10 +0900212 ++i;
initial.commit3f4a7322008-07-27 06:49:38 +0900213 }
214#else
215#error wchar_t* should be either UTF-16 or UTF-32
216#endif
217}
218
sky@google.com71e7c6f2008-09-20 02:32:18 +0900219// Appends the extension to file adding a '.' if extension doesn't contain one.
220// This does nothing if extension is empty or '.'. This is used internally by
221// ReplaceExtension.
222static void AppendExtension(const std::wstring& extension,
223 std::wstring* file) {
224 if (!extension.empty() && extension != L".") {
225 if (extension[0] != L'.')
226 file->append(L".");
227 file->append(extension);
228 }
229}
230
initial.commit3f4a7322008-07-27 06:49:38 +0900231void ReplaceExtension(std::wstring* file_name, const std::wstring& extension) {
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900232 const std::wstring::size_type last_dot = file_name->rfind(L'.');
sky@google.com71e7c6f2008-09-20 02:32:18 +0900233 if (last_dot == std::wstring::npos) {
234 // No extension, just append the supplied extension.
235 AppendExtension(extension, file_name);
236 return;
initial.commit3f4a7322008-07-27 06:49:38 +0900237 }
sky@google.com71e7c6f2008-09-20 02:32:18 +0900238 const std::wstring::size_type last_separator =
239 file_name->rfind(kPathSeparator);
240 if (last_separator != std::wstring::npos && last_dot < last_separator) {
241 // File name doesn't have extension, but one of the directories does; don't
242 // replace it, just append the supplied extension. For example
243 // 'c:\tmp.bar\foo'.
244 AppendExtension(extension, file_name);
245 return;
246 }
247 std::wstring result = file_name->substr(0, last_dot);
248 AppendExtension(extension, &result);
initial.commit3f4a7322008-07-27 06:49:38 +0900249 file_name->swap(result);
250}
251
evanm@google.com874d1672008-10-31 08:54:04 +0900252bool ContentsEqual(const FilePath& filename1, const FilePath& filename2) {
initial.commit3f4a7322008-07-27 06:49:38 +0900253 // We open the file in binary format even if they are text files because
254 // we are just comparing that bytes are exactly same in both files and not
255 // doing anything smart with text formatting.
evanm@google.com874d1672008-10-31 08:54:04 +0900256 std::ifstream file1(filename1.value().c_str(),
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900257 std::ios::in | std::ios::binary);
evanm@google.com874d1672008-10-31 08:54:04 +0900258 std::ifstream file2(filename2.value().c_str(),
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900259 std::ios::in | std::ios::binary);
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900260
initial.commit3f4a7322008-07-27 06:49:38 +0900261 // Even if both files aren't openable (and thus, in some sense, "equal"),
262 // any unusable file yields a result of "false".
263 if (!file1.is_open() || !file2.is_open())
264 return false;
265
266 const int BUFFER_SIZE = 2056;
267 char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
268 do {
269 file1.read(buffer1, BUFFER_SIZE);
270 file2.read(buffer2, BUFFER_SIZE);
271
272 if ((file1.eof() && !file2.eof()) ||
273 (!file1.eof() && file2.eof()) ||
274 (file1.gcount() != file2.gcount()) ||
275 (memcmp(buffer1, buffer2, file1.gcount()))) {
276 file1.close();
277 file2.close();
278 return false;
279 }
280 } while (!file1.eof() && !file2.eof());
281
282 file1.close();
283 file2.close();
284 return true;
285}
286
287bool ReadFileToString(const std::wstring& path, std::string* contents) {
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900288 FILE* file = OpenFile(path, "rb");
289 if (!file) {
initial.commit3f4a7322008-07-27 06:49:38 +0900290 return false;
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900291 }
initial.commit3f4a7322008-07-27 06:49:38 +0900292
293 char buf[1 << 16];
294 size_t len;
295 while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
296 contents->append(buf, len);
297 }
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900298 CloseFile(file);
initial.commit3f4a7322008-07-27 06:49:38 +0900299
300 return true;
301}
302
dkegel@google.com44982682008-11-05 06:00:46 +0900303bool GetFileSize(const FilePath& file_path, int64* file_size) {
darin@google.com7f479f22008-09-26 10:04:08 +0900304 FileInfo info;
305 if (!GetFileInfo(file_path, &info))
306 return false;
307 *file_size = info.size;
308 return true;
309}
310
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900311bool CloseFile(FILE* file) {
sidchat@google.comd3b26432008-10-22 02:14:45 +0900312 if (file == NULL)
313 return true;
mark@chromium.orgd1bafc62008-10-02 02:40:13 +0900314 return fclose(file) == 0;
315}
316
evanm@google.com874d1672008-10-31 08:54:04 +0900317// Deprecated functions ----------------------------------------------------
318
319bool AbsolutePath(std::wstring* path_str) {
evanm@google.com104f22e2008-10-31 11:03:07 +0900320 FilePath path(FilePath::FromWStringHack(*path_str));
evanm@google.com874d1672008-10-31 08:54:04 +0900321 if (!AbsolutePath(&path))
322 return false;
323 *path_str = path.ToWStringHack();
324 return true;
325}
326bool Delete(const std::wstring& path, bool recursive) {
327 return Delete(FilePath::FromWStringHack(path), recursive);
328}
estade@chromium.orgd778b8f2008-11-26 07:04:37 +0900329bool EndsWithSeparator(std::wstring* path) {
330 return EndsWithSeparator(FilePath::FromWStringHack(*path));
331}
332bool EndsWithSeparator(const std::wstring& path) {
333 return EndsWithSeparator(FilePath::FromWStringHack(path));
334}
evanm@google.com874d1672008-10-31 08:54:04 +0900335bool Move(const std::wstring& from_path, const std::wstring& to_path) {
336 return Move(FilePath::FromWStringHack(from_path),
337 FilePath::FromWStringHack(to_path));
338}
339bool CopyFile(const std::wstring& from_path, const std::wstring& to_path) {
340 return CopyFile(FilePath::FromWStringHack(from_path),
341 FilePath::FromWStringHack(to_path));
342}
343bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
344 bool recursive) {
345 return CopyDirectory(FilePath::FromWStringHack(from_path),
346 FilePath::FromWStringHack(to_path),
347 recursive);
348}
349bool PathExists(const std::wstring& path) {
350 return PathExists(FilePath::FromWStringHack(path));
351}
352bool DirectoryExists(const std::wstring& path) {
353 return DirectoryExists(FilePath::FromWStringHack(path));
354}
355bool ContentsEqual(const std::wstring& filename1,
356 const std::wstring& filename2) {
357 return ContentsEqual(FilePath::FromWStringHack(filename1),
358 FilePath::FromWStringHack(filename2));
359}
360bool CreateDirectory(const std::wstring& full_path) {
361 return CreateDirectory(FilePath::FromWStringHack(full_path));
362}
estade@chromium.orgf474a1b2008-11-11 09:01:38 +0900363bool CreateTemporaryFileName(std::wstring* temp_file) {
364 FilePath temp_file_path;
365 if (!CreateTemporaryFileName(&temp_file_path))
366 return false;
367 *temp_file = temp_file_path.ToWStringHack();
368 return true;
369}
evanm@google.com874d1672008-10-31 08:54:04 +0900370bool GetCurrentDirectory(std::wstring* path_str) {
371 FilePath path;
372 if (!GetCurrentDirectory(&path))
373 return false;
374 *path_str = path.ToWStringHack();
375 return true;
376}
dkegel@google.com44982682008-11-05 06:00:46 +0900377bool GetFileInfo(const std::wstring& file_path, FileInfo* results) {
378 return GetFileInfo(FilePath::FromWStringHack(file_path), results);
379}
380bool GetFileSize(const std::wstring& file_path, int64* file_size) {
381 return GetFileSize(FilePath::FromWStringHack(file_path), file_size);
382}
evanm@google.com874d1672008-10-31 08:54:04 +0900383bool GetTempDir(std::wstring* path_str) {
384 FilePath path;
385 if (!GetTempDir(&path))
386 return false;
387 *path_str = path.ToWStringHack();
388 return true;
389}
estade@chromium.orgb1d358a2008-11-18 06:01:19 +0900390FILE* OpenFile(const std::wstring& filename, const char* mode) {
391 return OpenFile(FilePath::FromWStringHack(filename), mode);
392}
393bool SetCurrentDirectory(const std::wstring& directory) {
394 return SetCurrentDirectory(FilePath::FromWStringHack(directory));
395}
evanm@google.com874d1672008-10-31 08:54:04 +0900396
initial.commit3f4a7322008-07-27 06:49:38 +0900397} // namespace
license.botf003cfe2008-08-24 09:55:55 +0900398