1 // Copyright 2021 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 "quiche_platform_impl/quiche_file_utils_impl.h"
6 
7 #if defined(_WIN32)
8 #include <windows.h>
9 #else
10 #include <dirent.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14 #endif  // defined(_WIN32)
15 
16 #include <fstream>
17 #include <ios>
18 #include <iostream>
19 #include <optional>
20 
21 #include "absl/strings/str_cat.h"
22 #include "absl/strings/string_view.h"
23 #include "absl/strings/strip.h"
24 
25 namespace quiche {
26 
27 #if defined(_WIN32)
JoinPathImpl(absl::string_view a,absl::string_view b)28 std::string JoinPathImpl(absl::string_view a, absl::string_view b) {
29   if (a.empty()) {
30     return std::string(b);
31   }
32   if (b.empty()) {
33     return std::string(a);
34   }
35   // Win32 actually provides two different APIs for combining paths; one of them
36   // has issues that could potentially lead to buffer overflow, and another is
37   // not supported in Windows 7, which is why we're doing it manually.
38   a = absl::StripSuffix(a, "/");
39   a = absl::StripSuffix(a, "\\");
40   return absl::StrCat(a, "\\", b);
41 }
42 #else
43 std::string JoinPathImpl(absl::string_view a, absl::string_view b) {
44   if (a.empty()) {
45     return std::string(b);
46   }
47   if (b.empty()) {
48     return std::string(a);
49   }
50   return absl::StrCat(absl::StripSuffix(a, "/"), "/", b);
51 }
52 #endif  // defined(_WIN32)
53 
ReadFileContentsImpl(absl::string_view file)54 std::optional<std::string> ReadFileContentsImpl(absl::string_view file) {
55   std::ifstream input_file(std::string{file}, std::ios::binary);
56   if (!input_file || !input_file.is_open()) {
57     return std::nullopt;
58   }
59 
60   input_file.seekg(0, std::ios_base::end);
61   auto file_size = input_file.tellg();
62   if (!input_file) {
63     return std::nullopt;
64   }
65   input_file.seekg(0, std::ios_base::beg);
66 
67   std::string output;
68   output.resize(file_size);
69   input_file.read(&output[0], file_size);
70   if (!input_file) {
71     return std::nullopt;
72   }
73 
74   return output;
75 }
76 
77 #if defined(_WIN32)
78 
79 class ScopedDir {
80  public:
ScopedDir(HANDLE dir)81   ScopedDir(HANDLE dir) : dir_(dir) {}
~ScopedDir()82   ~ScopedDir() {
83     if (dir_ != INVALID_HANDLE_VALUE) {
84       // The API documentation explicitly says that CloseHandle() should not be
85       // used on directory search handles.
86       FindClose(dir_);
87       dir_ = INVALID_HANDLE_VALUE;
88     }
89   }
90 
get()91   HANDLE get() { return dir_; }
92 
93  private:
94   HANDLE dir_;
95 };
96 
EnumerateDirectoryImpl(absl::string_view path,std::vector<std::string> & directories,std::vector<std::string> & files)97 bool EnumerateDirectoryImpl(absl::string_view path,
98                             std::vector<std::string>& directories,
99                             std::vector<std::string>& files) {
100   std::string path_owned(path);
101 
102   // Explicitly check that the directory we are trying to search is in fact a
103   // directory.
104   DWORD attributes = GetFileAttributesA(path_owned.c_str());
105   if (attributes == INVALID_FILE_ATTRIBUTES) {
106     return false;
107   }
108   if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
109     return false;
110   }
111 
112   std::string search_path = JoinPathImpl(path, "*");
113   WIN32_FIND_DATAA file_data;
114   ScopedDir dir(FindFirstFileA(search_path.c_str(), &file_data));
115   if (dir.get() == INVALID_HANDLE_VALUE) {
116     return GetLastError() == ERROR_FILE_NOT_FOUND;
117   }
118   do {
119     std::string filename(file_data.cFileName);
120     if (filename == "." || filename == "..") {
121       continue;
122     }
123     if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
124       directories.push_back(std::move(filename));
125     } else {
126       files.push_back(std::move(filename));
127     }
128   } while (FindNextFileA(dir.get(), &file_data));
129   return GetLastError() == ERROR_NO_MORE_FILES;
130 }
131 
132 #else  // defined(_WIN32)
133 
134 class ScopedDir {
135  public:
ScopedDir(DIR * dir)136   ScopedDir(DIR* dir) : dir_(dir) {}
~ScopedDir()137   ~ScopedDir() {
138     if (dir_ != nullptr) {
139       closedir(dir_);
140       dir_ = nullptr;
141     }
142   }
143 
get()144   DIR* get() { return dir_; }
145 
146  private:
147   DIR* dir_;
148 };
149 
EnumerateDirectoryImpl(absl::string_view path,std::vector<std::string> & directories,std::vector<std::string> & files)150 bool EnumerateDirectoryImpl(absl::string_view path,
151                             std::vector<std::string>& directories,
152                             std::vector<std::string>& files) {
153   std::string path_owned(path);
154   ScopedDir dir(opendir(path_owned.c_str()));
155   if (dir.get() == nullptr) {
156     return false;
157   }
158 
159   dirent* entry;
160   while ((entry = readdir(dir.get()))) {
161     const std::string filename(entry->d_name);
162     if (filename == "." || filename == "..") {
163       continue;
164     }
165 
166     const std::string entry_path = JoinPathImpl(path, filename);
167     struct stat stat_entry;
168     if (stat(entry_path.c_str(), &stat_entry) != 0) {
169       return false;
170     }
171     if (S_ISREG(stat_entry.st_mode)) {
172       files.push_back(std::move(filename));
173     } else if (S_ISDIR(stat_entry.st_mode)) {
174       directories.push_back(std::move(filename));
175     }
176   }
177   return true;
178 }
179 
180 #endif  // defined(_WIN32)
181 
182 }  // namespace quiche
183