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
14namespace base {
15
16namespace {
17
18FilePath 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
37FileEnumerator::FileInfo::FileInfo() {
38  memset(&find_data_, 0, sizeof(find_data_));
39}
40
41bool FileEnumerator::FileInfo::IsDirectory() const {
42  return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
43}
44
45FilePath FileEnumerator::FileInfo::GetName() const {
46  return FilePath(reinterpret_cast<const char16_t*>(find_data_.cFileName));
47}
48
49int64_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
58Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const {
59  return *reinterpret_cast<const uint64_t*>(&find_data_.ftLastWriteTime);
60}
61
62// FileEnumerator --------------------------------------------------------------
63
64FileEnumerator::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
73FileEnumerator::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
83FileEnumerator::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
98FileEnumerator::~FileEnumerator() {
99  if (find_handle_ != INVALID_HANDLE_VALUE)
100    FindClose(find_handle_);
101}
102
103FileEnumerator::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
113FilePath 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
178bool 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