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