1 // Copyright (c) 2013 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 "base/files/file_enumerator.h"
6
7 #include <shlwapi.h>
8 #include <stdint.h>
9 #include <string.h>
10
11 #include "base/logging.h"
12 #include "base/win/win_util.h"
13
14 namespace base {
15
16 namespace {
17
BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,const FilePath & root_path,const FilePath::StringType & pattern)18 FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy,
19 const FilePath& root_path,
20 const FilePath::StringType& pattern) {
21 // MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy
22 // collects all files and filters them manually.
23 switch (policy) {
24 case FileEnumerator::FolderSearchPolicy::MATCH_ONLY:
25 return root_path.Append(pattern);
26 case FileEnumerator::FolderSearchPolicy::ALL:
27 return root_path.Append(u"*");
28 }
29 NOTREACHED();
30 return {};
31 }
32
33 } // namespace
34
35 // FileEnumerator::FileInfo ----------------------------------------------------
36
FileInfo()37 FileEnumerator::FileInfo::FileInfo() {
38 memset(&find_data_, 0, sizeof(find_data_));
39 }
40
IsDirectory() const41 bool FileEnumerator::FileInfo::IsDirectory() const {
42 return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
43 }
44
GetName() const45 FilePath FileEnumerator::FileInfo::GetName() const {
46 return FilePath(reinterpret_cast<const char16_t*>(find_data_.cFileName));
47 }
48
GetSize() const49 int64_t FileEnumerator::FileInfo::GetSize() const {
50 ULARGE_INTEGER size;
51 size.HighPart = find_data_.nFileSizeHigh;
52 size.LowPart = find_data_.nFileSizeLow;
53 DCHECK_LE(size.QuadPart,
54 static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max()));
55 return static_cast<int64_t>(size.QuadPart);
56 }
57
GetLastModifiedTime() const58 Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
59 return *reinterpret_cast<const uint64_t*>(&find_data_.ftLastWriteTime);
60 }
61
62 // FileEnumerator --------------------------------------------------------------
63
FileEnumerator(const FilePath & root_path,bool recursive,int file_type)64 FileEnumerator::FileEnumerator(const FilePath& root_path,
65 bool recursive,
66 int file_type)
67 : FileEnumerator(root_path,
68 recursive,
69 file_type,
70 FilePath::StringType(),
71 FolderSearchPolicy::MATCH_ONLY) {}
72
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern)73 FileEnumerator::FileEnumerator(const FilePath& root_path,
74 bool recursive,
75 int file_type,
76 const FilePath::StringType& pattern)
77 : FileEnumerator(root_path,
78 recursive,
79 file_type,
80 pattern,
81 FolderSearchPolicy::MATCH_ONLY) {}
82
FileEnumerator(const FilePath & root_path,bool recursive,int file_type,const FilePath::StringType & pattern,FolderSearchPolicy folder_search_policy)83 FileEnumerator::FileEnumerator(const FilePath& root_path,
84 bool recursive,
85 int file_type,
86 const FilePath::StringType& pattern,
87 FolderSearchPolicy folder_search_policy)
88 : recursive_(recursive),
89 file_type_(file_type),
90 pattern_(!pattern.empty() ? pattern : u"*"),
91 folder_search_policy_(folder_search_policy) {
92 // INCLUDE_DOT_DOT must not be specified if recursive.
93 DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
94 memset(&find_data_, 0, sizeof(find_data_));
95 pending_paths_.push(root_path);
96 }
97
~FileEnumerator()98 FileEnumerator::~FileEnumerator() {
99 if (find_handle_ != INVALID_HANDLE_VALUE)
100 FindClose(find_handle_);
101 }
102
GetInfo() const103 FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
104 if (!has_find_data_) {
105 NOTREACHED();
106 return FileInfo();
107 }
108 FileInfo ret;
109 memcpy(&ret.find_data_, &find_data_, sizeof(find_data_));
110 return ret;
111 }
112
Next()113 FilePath FileEnumerator::Next() {
114 while (has_find_data_ || !pending_paths_.empty()) {
115 if (!has_find_data_) {
116 // The last find FindFirstFile operation is done, prepare a new one.
117 root_path_ = pending_paths_.top();
118 pending_paths_.pop();
119
120 // Start a new find operation.
121 const FilePath src =
122 BuildSearchFilter(folder_search_policy_, root_path_, pattern_);
123 find_handle_ = FindFirstFileEx(ToWCharT(&src.value()),
124 FindExInfoBasic, // Omit short name.
125 &find_data_, FindExSearchNameMatch,
126 nullptr, FIND_FIRST_EX_LARGE_FETCH);
127 has_find_data_ = true;
128 } else {
129 // Search for the next file/directory.
130 if (!FindNextFile(find_handle_, &find_data_)) {
131 FindClose(find_handle_);
132 find_handle_ = INVALID_HANDLE_VALUE;
133 }
134 }
135
136 if (INVALID_HANDLE_VALUE == find_handle_) {
137 has_find_data_ = false;
138
139 // MATCH_ONLY policy clears pattern for matched subfolders. ALL policy
140 // applies pattern for all subfolders.
141 if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) {
142 // This is reached when we have finished a directory and are advancing
143 // to the next one in the queue. We applied the pattern (if any) to the
144 // files in the root search directory, but for those directories which
145 // were matched, we want to enumerate all files inside them. This will
146 // happen when the handle is empty.
147 pattern_ = u"*";
148 }
149
150 continue;
151 }
152
153 const FilePath filename(reinterpret_cast<char16_t*>(find_data_.cFileName));
154 if (ShouldSkip(filename))
155 continue;
156
157 const bool is_dir =
158 (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
159 const FilePath abs_path = root_path_.Append(filename);
160
161 // Check if directory should be processed recursive.
162 if (is_dir && recursive_) {
163 // If |cur_file| is a directory, and we are doing recursive searching,
164 // add it to pending_paths_ so we scan it after we finish scanning this
165 // directory. However, don't do recursion through reparse points or we
166 // may end up with an infinite cycle.
167 DWORD attributes = GetFileAttributes(ToWCharT(&abs_path.value()));
168 if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT))
169 pending_paths_.push(abs_path);
170 }
171
172 if (IsTypeMatched(is_dir) && IsPatternMatched(filename))
173 return abs_path;
174 }
175 return FilePath();
176 }
177
IsPatternMatched(const FilePath & src) const178 bool FileEnumerator::IsPatternMatched(const FilePath& src) const {
179 switch (folder_search_policy_) {
180 case FolderSearchPolicy::MATCH_ONLY:
181 // MATCH_ONLY policy filters by pattern on search request, so all found
182 // files already fits to pattern.
183 return true;
184 case FolderSearchPolicy::ALL:
185 // ALL policy enumerates all files, we need to check pattern match
186 // manually.
187 return PathMatchSpec(ToWCharT(&src.value()), ToWCharT(&pattern_)) == TRUE;
188 }
189 NOTREACHED();
190 return false;
191 }
192
193 } // namespace base
194