Provide a default implementation of quiche_file_utils.

This also adds support for building on Windows, at least far enough to get the file_utils test to build.

PiperOrigin-RevId: 382741424
diff --git a/common/platform/api/quiche_file_utils.cc b/common/platform/api/quiche_file_utils.cc
new file mode 100644
index 0000000..fc0c189
--- /dev/null
+++ b/common/platform/api/quiche_file_utils.cc
@@ -0,0 +1,51 @@
+#include "common/platform/api/quiche_file_utils.h"
+
+#include "quiche_platform_impl/quiche_file_utils_impl.h"
+
+namespace quiche {
+
+std::string JoinPath(absl::string_view a, absl::string_view b) {
+  return JoinPathImpl(a, b);
+}
+
+absl::optional<std::string> ReadFileContents(absl::string_view file) {
+  return ReadFileContentsImpl(file);
+}
+
+bool EnumerateDirectory(absl::string_view path,
+                        std::vector<std::string>& directories,
+                        std::vector<std::string>& files) {
+  return EnumerateDirectoryImpl(path, directories, files);
+}
+
+bool EnumerateDirectoryRecursivelyInner(absl::string_view path,
+                                        int recursion_limit,
+                                        std::vector<std::string>& files) {
+  if (recursion_limit < 0) {
+    return false;
+  }
+
+  std::vector<std::string> local_files;
+  std::vector<std::string> directories;
+  if (!EnumerateDirectory(path, directories, local_files)) {
+    return false;
+  }
+  for (const std::string& directory : directories) {
+    if (!EnumerateDirectoryRecursivelyInner(JoinPath(path, directory),
+                                            recursion_limit - 1, files)) {
+      return false;
+    }
+  }
+  for (const std::string& file : local_files) {
+    files.push_back(JoinPath(path, file));
+  }
+  return true;
+}
+
+bool EnumerateDirectoryRecursively(absl::string_view path,
+                                   std::vector<std::string>& files) {
+  constexpr int kRecursionLimit = 20;
+  return EnumerateDirectoryRecursivelyInner(path, kRecursionLimit, files);
+}
+
+}  // namespace quiche
diff --git a/common/platform/api/quiche_file_utils.h b/common/platform/api/quiche_file_utils.h
new file mode 100644
index 0000000..47723d1
--- /dev/null
+++ b/common/platform/api/quiche_file_utils.h
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header contains basic filesystem functions for use in unit tests and CLI
+// tools.  Note that those are not 100% suitable for production use, as in, they
+// might be prone to race conditions and not always handle non-ASCII filenames
+// correctly.
+#ifndef QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_
+#define QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+namespace quiche {
+
+// Join two paths in a platform-specific way.  Returns |a| if |b| is empty, and
+// vice versa.
+std::string JoinPath(absl::string_view a, absl::string_view b);
+
+// Reads the entire file into the memory.
+absl::optional<std::string> ReadFileContents(absl::string_view file);
+
+// Lists all files and directories in the directory specified by |path|. Returns
+// true on success, false on failure.
+bool EnumerateDirectory(absl::string_view path,
+                        std::vector<std::string>& directories,
+                        std::vector<std::string>& files);
+
+// Recursively enumerates all of the files in the directory and all of the
+// internal subdirectories.  Has a fairly small recursion limit.
+bool EnumerateDirectoryRecursively(absl::string_view path,
+                                   std::vector<std::string>& files);
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_PLATFORM_API_QUICHE_FILE_UTILS_H_
diff --git a/common/platform/api/quiche_file_utils_test.cc b/common/platform/api/quiche_file_utils_test.cc
new file mode 100644
index 0000000..ddf1a40
--- /dev/null
+++ b/common/platform/api/quiche_file_utils_test.cc
@@ -0,0 +1,86 @@
+#include "common/platform/api/quiche_file_utils.h"
+
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/strip.h"
+#include "absl/types/optional.h"
+#include "common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace test {
+namespace {
+
+using testing::UnorderedElementsAre;
+using testing::UnorderedElementsAreArray;
+
+TEST(QuicheFileUtilsTest, ReadFileContents) {
+  std::string path = absl::StrCat(QuicheGetCommonSourcePath(),
+                                  "/platform/api/testdir/testfile");
+  absl::optional<std::string> contents = ReadFileContents(path);
+  ASSERT_TRUE(contents.has_value());
+  EXPECT_EQ(*contents, "This is a test file.");
+}
+
+TEST(QuicheFileUtilsTest, ReadFileContentsFileNotFound) {
+  std::string path =
+      absl::StrCat(QuicheGetCommonSourcePath(),
+                   "/platform/api/testdir/file-that-does-not-exist");
+  absl::optional<std::string> contents = ReadFileContents(path);
+  EXPECT_FALSE(contents.has_value());
+}
+
+TEST(QuicheFileUtilsTest, EnumerateDirectory) {
+  std::string path =
+      absl::StrCat(QuicheGetCommonSourcePath(), "/platform/api/testdir");
+  std::vector<std::string> dirs;
+  std::vector<std::string> files;
+  bool success = EnumerateDirectory(path, dirs, files);
+  EXPECT_TRUE(success);
+  EXPECT_THAT(files, UnorderedElementsAre("testfile", "README.md"));
+  EXPECT_THAT(dirs, UnorderedElementsAre("a"));
+}
+
+TEST(QuicheFileUtilsTest, EnumerateDirectoryNoSuchDirectory) {
+  std::string path = absl::StrCat(QuicheGetCommonSourcePath(),
+                                  "/platform/api/testdir/no-such-directory");
+  std::vector<std::string> dirs;
+  std::vector<std::string> files;
+  bool success = EnumerateDirectory(path, dirs, files);
+  EXPECT_FALSE(success);
+}
+
+TEST(QuicheFileUtilsTest, EnumerateDirectoryNotADirectory) {
+  std::string path = absl::StrCat(QuicheGetCommonSourcePath(),
+                                  "/platform/api/testdir/testfile");
+  std::vector<std::string> dirs;
+  std::vector<std::string> files;
+  bool success = EnumerateDirectory(path, dirs, files);
+  EXPECT_FALSE(success);
+}
+
+TEST(QuicheFileUtilsTest, EnumerateDirectoryRecursively) {
+  std::vector<std::string> expected_paths = {"a/b/c/d/e", "a/subdir/testfile",
+                                             "a/z", "testfile", "README.md"};
+
+  std::string root_path =
+      absl::StrCat(QuicheGetCommonSourcePath(), "/platform/api/testdir");
+  for (std::string& path : expected_paths) {
+    // For Windows, use Windows path separators.
+    if (JoinPath("a", "b") == "a\\b") {
+      absl::c_replace(path, '/', '\\');
+    }
+
+    path = JoinPath(root_path, path);
+  }
+
+  std::vector<std::string> files;
+  bool success = EnumerateDirectoryRecursively(root_path, files);
+  EXPECT_TRUE(success);
+  EXPECT_THAT(files, UnorderedElementsAreArray(expected_paths));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quiche
diff --git a/common/platform/api/quiche_test.h b/common/platform/api/quiche_test.h
index 1af25df..efec638 100644
--- a/common/platform/api/quiche_test.h
+++ b/common/platform/api/quiche_test.h
@@ -12,6 +12,18 @@
 template <class T>
 using QuicheTestWithParam = quiche::test::QuicheTestWithParamImpl<T>;
 
+namespace quiche {
+namespace test {
+
+// Returns the path to quiche/common directory where the test data could be
+// located.
+inline std::string QuicheGetCommonSourcePath() {
+  return QuicheGetCommonSourcePathImpl();
+}
+
+}  // namespace test
+}  // namespace quiche
+
 #define EXPECT_QUICHE_DEBUG_DEATH(condition, message) \
   EXPECT_QUICHE_DEBUG_DEATH_IMPL(condition, message)
 
diff --git a/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc b/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc
new file mode 100644
index 0000000..65965b2
--- /dev/null
+++ b/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.cc
@@ -0,0 +1,182 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche_platform_impl/quiche_file_utils_impl.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+#else
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif  // defined(_WIN32)
+
+#include <fstream>
+#include <ios>
+#include <iostream>
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "absl/types/optional.h"
+
+namespace quiche {
+
+#if defined(_WIN32)
+std::string JoinPathImpl(absl::string_view a, absl::string_view b) {
+  if (a.empty()) {
+    return std::string(b);
+  }
+  if (b.empty()) {
+    return std::string(a);
+  }
+  // Win32 actually provides two different APIs for combining paths; one of them
+  // has issues that could potentially lead to buffer overflow, and another is
+  // not supported in Windows 7, which is why we're doing it manually.
+  a = absl::StripSuffix(a, "/");
+  a = absl::StripSuffix(a, "\\");
+  return absl::StrCat(a, "\\", b);
+}
+#else
+std::string JoinPathImpl(absl::string_view a, absl::string_view b) {
+  if (a.empty()) {
+    return std::string(b);
+  }
+  if (b.empty()) {
+    return std::string(a);
+  }
+  return absl::StrCat(absl::StripSuffix(a, "/"), "/", b);
+}
+#endif  // defined(_WIN32)
+
+absl::optional<std::string> ReadFileContentsImpl(absl::string_view file) {
+  std::ifstream input_file(std::string{file}, std::ios::binary);
+  if (!input_file || !input_file.is_open()) {
+    return absl::nullopt;
+  }
+
+  input_file.seekg(0, std::ios_base::end);
+  auto file_size = input_file.tellg();
+  if (!input_file) {
+    return absl::nullopt;
+  }
+  input_file.seekg(0, std::ios_base::beg);
+
+  std::string output;
+  output.resize(file_size);
+  input_file.read(&output[0], file_size);
+  if (!input_file) {
+    return absl::nullopt;
+  }
+
+  return output;
+}
+
+#if defined(_WIN32)
+
+class ScopedDir {
+ public:
+  ScopedDir(HANDLE dir) : dir_(dir) {}
+  ~ScopedDir() {
+    if (dir_ != INVALID_HANDLE_VALUE) {
+      // The API documentation explicitly says that CloseHandle() should not be
+      // used on directory search handles.
+      FindClose(dir_);
+      dir_ = INVALID_HANDLE_VALUE;
+    }
+  }
+
+  HANDLE get() { return dir_; }
+
+ private:
+  HANDLE dir_;
+};
+
+bool EnumerateDirectoryImpl(absl::string_view path,
+                            std::vector<std::string>& directories,
+                            std::vector<std::string>& files) {
+  std::string path_owned(path);
+
+  // Explicitly check that the directory we are trying to search is in fact a
+  // directory.
+  DWORD attributes = GetFileAttributesA(path_owned.c_str());
+  if (attributes == INVALID_FILE_ATTRIBUTES) {
+    return false;
+  }
+  if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+    return false;
+  }
+
+  std::string search_path = JoinPathImpl(path, "*");
+  WIN32_FIND_DATAA file_data;
+  ScopedDir dir(FindFirstFileA(search_path.c_str(), &file_data));
+  if (dir.get() == INVALID_HANDLE_VALUE) {
+    return GetLastError() == ERROR_FILE_NOT_FOUND;
+  }
+  do {
+    std::string filename(file_data.cFileName);
+    if (filename == "." || filename == "..") {
+      continue;
+    }
+    if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+      directories.push_back(std::move(filename));
+    } else {
+      files.push_back(std::move(filename));
+    }
+  } while (FindNextFileA(dir.get(), &file_data));
+  return GetLastError() == ERROR_NO_MORE_FILES;
+}
+
+#else  // defined(_WIN32)
+
+class ScopedDir {
+ public:
+  ScopedDir(DIR* dir) : dir_(dir) {}
+  ~ScopedDir() {
+    if (dir_ != nullptr) {
+      closedir(dir_);
+      dir_ = nullptr;
+    }
+  }
+
+  DIR* get() { return dir_; }
+
+ private:
+  DIR* dir_;
+};
+
+bool EnumerateDirectoryImpl(absl::string_view path,
+                            std::vector<std::string>& directories,
+                            std::vector<std::string>& files) {
+  std::string path_owned(path);
+  ScopedDir dir(opendir(path_owned.c_str()));
+  if (dir.get() == nullptr) {
+    return false;
+  }
+
+  dirent* entry;
+  while ((entry = readdir(dir.get()))) {
+    const std::string filename(entry->d_name);
+    if (filename == "." || filename == "..") {
+      continue;
+    }
+
+    const std::string entry_path = JoinPathImpl(path, filename);
+    struct stat stat_entry;
+    if (stat(entry_path.c_str(), &stat_entry) != 0) {
+      return false;
+    }
+    if (S_ISREG(stat_entry.st_mode)) {
+      files.push_back(std::move(filename));
+    } else if (S_ISDIR(stat_entry.st_mode)) {
+      directories.push_back(std::move(filename));
+    }
+  }
+  return true;
+}
+
+#endif  // defined(_WIN32)
+
+}  // namespace quiche
diff --git a/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h b/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h
new file mode 100644
index 0000000..ad5ff1a
--- /dev/null
+++ b/common/platform/default/quiche_platform_impl/quiche_file_utils_impl.h
@@ -0,0 +1,26 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_
+#define QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+namespace quiche {
+
+std::string JoinPathImpl(absl::string_view a, absl::string_view b);
+
+absl::optional<std::string> ReadFileContentsImpl(absl::string_view file);
+
+bool EnumerateDirectoryImpl(absl::string_view path,
+                            std::vector<std::string>& directories,
+                            std::vector<std::string>& files);
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_PLATFORM_DEFAULT_QUICHE_PLATFORM_IMPL_QUICHE_FILE_UTILS_IMPL_H_
diff --git a/quic/platform/api/quic_file_utils.cc b/quic/platform/api/quic_file_utils.cc
deleted file mode 100644
index 5cb310b..0000000
--- a/quic/platform/api/quic_file_utils.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "quic/platform/api/quic_file_utils.h"
-
-#include "absl/strings/string_view.h"
-#include "net/quic/platform/impl/quic_file_utils_impl.h"
-
-namespace quic {
-
-// Traverses the directory |dirname| and retuns all of the files
-// it contains.
-std::vector<std::string> ReadFileContents(const std::string& dirname) {
-  return ReadFileContentsImpl(dirname);
-}
-
-// Reads the contents of |filename| as a string into |contents|.
-void ReadFileContents(absl::string_view filename, std::string* contents) {
-  ReadFileContentsImpl(filename, contents);
-}
-
-}  // namespace quic
diff --git a/quic/platform/api/quic_file_utils.h b/quic/platform/api/quic_file_utils.h
deleted file mode 100644
index a1f881f..0000000
--- a/quic/platform/api/quic_file_utils.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_
-#define QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_
-
-#include <string>
-#include <vector>
-
-#include "absl/strings/string_view.h"
-#include "quic/platform/api/quic_export.h"
-
-namespace quic {
-
-// Traverses the directory |dirname| and returns all of the files it contains.
-QUIC_EXPORT_PRIVATE std::vector<std::string> ReadFileContents(
-    const std::string& dirname);
-
-// Reads the contents of |filename| as a string into |contents|.
-QUIC_EXPORT_PRIVATE void ReadFileContents(absl::string_view filename,
-                                          std::string* contents);
-
-}  // namespace quic
-
-#endif  // QUICHE_QUIC_PLATFORM_API_QUIC_FILE_UTILS_H_
diff --git a/quic/test_tools/qpack/qpack_offline_decoder.cc b/quic/test_tools/qpack/qpack_offline_decoder.cc
index 0f681fe..ad7155a 100644
--- a/quic/test_tools/qpack/qpack_offline_decoder.cc
+++ b/quic/test_tools/qpack/qpack_offline_decoder.cc
@@ -35,9 +35,9 @@
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "quic/core/quic_types.h"
-#include "quic/platform/api/quic_file_utils.h"
 #include "quic/platform/api/quic_logging.h"
 #include "quic/test_tools/qpack/qpack_test_utils.h"
+#include "common/platform/api/quiche_file_utils.h"
 #include "common/quiche_endian.h"
 
 namespace quic {
@@ -69,8 +69,7 @@
 }
 
 void QpackOfflineDecoder::OnEncoderStreamError(
-    QuicErrorCode error_code,
-    absl::string_view error_message) {
+    QuicErrorCode error_code, absl::string_view error_message) {
   QUIC_LOG(ERROR) << "Encoder stream error: "
                   << QuicErrorCodeToString(error_code) << " " << error_message;
   encoder_stream_error_detected_ = true;
@@ -136,9 +135,10 @@
     absl::string_view input_filename) {
   // Store data in |input_data_storage|; use a absl::string_view to
   // efficiently keep track of remaining portion yet to be decoded.
-  std::string input_data_storage;
-  ReadFileContents(input_filename, &input_data_storage);
-  absl::string_view input_data(input_data_storage);
+  absl::optional<std::string> input_data_storage =
+      quiche::ReadFileContents(input_filename);
+  QUICHE_DCHECK(input_data_storage.has_value());
+  absl::string_view input_data(*input_data_storage);
 
   while (!input_data.empty()) {
     // Parse stream_id and length.
@@ -233,9 +233,10 @@
   // Store data in |expected_headers_data_storage|; use a
   // absl::string_view to efficiently keep track of remaining portion
   // yet to be decoded.
-  std::string expected_headers_data_storage;
-  ReadFileContents(expected_headers_filename, &expected_headers_data_storage);
-  absl::string_view expected_headers_data(expected_headers_data_storage);
+  absl::optional<std::string> expected_headers_data_storage =
+      quiche::ReadFileContents(expected_headers_filename);
+  QUICHE_DCHECK(expected_headers_data_storage.has_value());
+  absl::string_view expected_headers_data(*expected_headers_data_storage);
 
   while (!decoded_header_lists_.empty()) {
     spdy::Http2HeaderBlock decoded_header_list =
diff --git a/quic/tools/quic_memory_cache_backend.cc b/quic/tools/quic_memory_cache_backend.cc
index 120c2b7..58aa7a9 100644
--- a/quic/tools/quic_memory_cache_backend.cc
+++ b/quic/tools/quic_memory_cache_backend.cc
@@ -12,9 +12,9 @@
 #include "absl/strings/string_view.h"
 #include "quic/core/http/spdy_utils.h"
 #include "quic/platform/api/quic_bug_tracker.h"
-#include "quic/platform/api/quic_file_utils.h"
 #include "quic/platform/api/quic_logging.h"
 #include "quic/tools/web_transport_test_visitors.h"
+#include "common/platform/api/quiche_file_utils.h"
 #include "common/quiche_text_utils.h"
 
 using spdy::Http2HeaderBlock;
@@ -28,7 +28,14 @@
 QuicMemoryCacheBackend::ResourceFile::~ResourceFile() = default;
 
 void QuicMemoryCacheBackend::ResourceFile::Read() {
-  ReadFileContents(file_name_, &file_contents_);
+  absl::optional<std::string> maybe_file_contents =
+      quiche::ReadFileContents(file_name_);
+  if (!maybe_file_contents) {
+    QUIC_LOG(DFATAL) << "Failed to read file for the memory cache backend: "
+                     << file_name_;
+    return;
+  }
+  file_contents_ = *maybe_file_contents;
 
   // First read the headers.
   size_t start = 0;
@@ -139,8 +146,7 @@
 }
 
 const QuicBackendResponse* QuicMemoryCacheBackend::GetResponse(
-    absl::string_view host,
-    absl::string_view path) const {
+    absl::string_view host, absl::string_view path) const {
   QuicWriterMutexLock lock(&response_mutex_);
 
   auto it = responses_.find(GetKey(host, path));
@@ -178,11 +184,8 @@
 }
 
 void QuicMemoryCacheBackend::AddSimpleResponseWithServerPushResources(
-    absl::string_view host,
-    absl::string_view path,
-    int response_code,
-    absl::string_view body,
-    std::list<ServerPushInfo> push_resources) {
+    absl::string_view host, absl::string_view path, int response_code,
+    absl::string_view body, std::list<ServerPushInfo> push_resources) {
   AddSimpleResponse(host, path, response_code, body);
   MaybeAddServerPushResources(host, path, push_resources);
 }
@@ -213,10 +216,8 @@
 }
 
 void QuicMemoryCacheBackend::AddResponseWithEarlyHints(
-    absl::string_view host,
-    absl::string_view path,
-    spdy::Http2HeaderBlock response_headers,
-    absl::string_view response_body,
+    absl::string_view host, absl::string_view path,
+    spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
   AddResponseImpl(host, path, QuicBackendResponse::REGULAR_RESPONSE,
                   std::move(response_headers), response_body,
@@ -224,18 +225,15 @@
 }
 
 void QuicMemoryCacheBackend::AddSpecialResponse(
-    absl::string_view host,
-    absl::string_view path,
+    absl::string_view host, absl::string_view path,
     SpecialResponseType response_type) {
   AddResponseImpl(host, path, response_type, Http2HeaderBlock(), "",
                   Http2HeaderBlock(), std::vector<spdy::Http2HeaderBlock>());
 }
 
 void QuicMemoryCacheBackend::AddSpecialResponse(
-    absl::string_view host,
-    absl::string_view path,
-    spdy::Http2HeaderBlock response_headers,
-    absl::string_view response_body,
+    absl::string_view host, absl::string_view path,
+    spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
     SpecialResponseType response_type) {
   AddResponseImpl(host, path, response_type, std::move(response_headers),
                   response_body, Http2HeaderBlock(),
@@ -253,7 +251,12 @@
   QUIC_LOG(INFO)
       << "Attempting to initialize QuicMemoryCacheBackend from directory: "
       << cache_directory;
-  std::vector<std::string> files = ReadFileContents(cache_directory);
+  std::vector<std::string> files;
+  if (!quiche::EnumerateDirectoryRecursively(cache_directory, files)) {
+    QUIC_BUG(QuicMemoryCacheBackend unreadable directory)
+        << "Can't read QuicMemoryCacheBackend directory: " << cache_directory;
+    return false;
+  }
   std::list<std::unique_ptr<ResourceFile>> resource_files;
   for (const auto& filename : files) {
     std::unique_ptr<ResourceFile> resource_file(new ResourceFile(filename));
@@ -401,12 +404,9 @@
 }
 
 void QuicMemoryCacheBackend::AddResponseImpl(
-    absl::string_view host,
-    absl::string_view path,
-    SpecialResponseType response_type,
-    Http2HeaderBlock response_headers,
-    absl::string_view response_body,
-    Http2HeaderBlock response_trailers,
+    absl::string_view host, absl::string_view path,
+    SpecialResponseType response_type, Http2HeaderBlock response_headers,
+    absl::string_view response_body, Http2HeaderBlock response_trailers,
     const std::vector<spdy::Http2HeaderBlock>& early_hints) {
   QuicWriterMutexLock lock(&response_mutex_);
 
@@ -440,8 +440,7 @@
 }
 
 void QuicMemoryCacheBackend::MaybeAddServerPushResources(
-    absl::string_view request_host,
-    absl::string_view request_path,
+    absl::string_view request_host, absl::string_view request_path,
     std::list<ServerPushInfo> push_resources) {
   std::string request_url = GetKey(request_host, request_path);
 
@@ -480,8 +479,7 @@
 }
 
 bool QuicMemoryCacheBackend::PushResourceExistsInCache(
-    std::string original_request_url,
-    ServerPushInfo resource) {
+    std::string original_request_url, ServerPushInfo resource) {
   QuicWriterMutexLock lock(&response_mutex_);
   auto resource_range =
       server_push_resources_.equal_range(original_request_url);
diff --git a/quic/tools/quic_memory_cache_backend_test.cc b/quic/tools/quic_memory_cache_backend_test.cc
index 48d3df7..71f5dc5 100644
--- a/quic/tools/quic_memory_cache_backend_test.cc
+++ b/quic/tools/quic_memory_cache_backend_test.cc
@@ -4,11 +4,13 @@
 
 #include "quic/tools/quic_memory_cache_backend.h"
 
+#include <vector>
+
 #include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
-#include "quic/platform/api/quic_file_utils.h"
 #include "quic/platform/api/quic_test.h"
 #include "quic/tools/quic_backend_response.h"
+#include "common/platform/api/quiche_file_utils.h"
 
 namespace quic {
 namespace test {
@@ -20,8 +22,7 @@
 
 class QuicMemoryCacheBackendTest : public QuicTest {
  protected:
-  void CreateRequest(std::string host,
-                     std::string path,
+  void CreateRequest(std::string host, std::string path,
                      spdy::Http2HeaderBlock* headers) {
     (*headers)[":method"] = "GET";
     (*headers)[":path"] = path;
@@ -120,7 +121,9 @@
   // X-Original-Url header's value will be used.
   std::string dir;
   std::string path = "map.html";
-  for (const std::string& file : ReadFileContents(CacheDirectory())) {
+  std::vector<std::string> files;
+  ASSERT_TRUE(quiche::EnumerateDirectoryRecursively(CacheDirectory(), files));
+  for (const std::string& file : files) {
     if (absl::EndsWithIgnoreCase(file, "map.html")) {
       dir = file;
       dir.erase(dir.length() - path.length() - 1);