Revert "Delete rtc::Pathname"

This reverts commit 6b9dec0d16f2df59fa2820c5ec1341be52fb9f32.

Reason for revert: speculative revert for breaking internal projects

Original change's description:
> Delete rtc::Pathname
> 
> Bug: webrtc:6424
> Change-Id: Iec01dc5dd1426d4558983b828b67af872107d723
> Reviewed-on: https://webrtc-review.googlesource.com/c/108400
> Commit-Queue: Niels Moller <nisse@webrtc.org>
> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#25479}

TBR=kwiberg@webrtc.org,nisse@webrtc.org

Change-Id: I3129a81a1d8e36b3e6c67572410bdc478ec4d5e9
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: webrtc:6424
Reviewed-on: https://webrtc-review.googlesource.com/c/109201
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Commit-Queue: Qingsi Wang <qingsi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25490}
diff --git a/modules/audio_processing/test/conversational_speech/BUILD.gn b/modules/audio_processing/test/conversational_speech/BUILD.gn
index 551781b..721ebc7 100644
--- a/modules/audio_processing/test/conversational_speech/BUILD.gn
+++ b/modules/audio_processing/test/conversational_speech/BUILD.gn
@@ -51,7 +51,6 @@
     "../../../../common_audio",
     "../../../../rtc_base:checks",
     "../../../../rtc_base:rtc_base_approved",
-    "../../../../test:fileutils",
     "//third_party/abseil-cpp/absl/memory",
   ]
   visibility = [ ":*" ]  # Only targets in this file can depend on this.
diff --git a/modules/audio_processing/test/conversational_speech/mock_wavreader_factory.cc b/modules/audio_processing/test/conversational_speech/mock_wavreader_factory.cc
index a45e3bb..eb8e3be 100644
--- a/modules/audio_processing/test/conversational_speech/mock_wavreader_factory.cc
+++ b/modules/audio_processing/test/conversational_speech/mock_wavreader_factory.cc
@@ -12,6 +12,7 @@
 
 #include "modules/audio_processing/test/conversational_speech/mock_wavreader.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/pathutils.h"
 #include "test/gmock.h"
 
 namespace webrtc {
@@ -38,10 +39,9 @@
 std::unique_ptr<WavReaderInterface> MockWavReaderFactory::CreateMock(
     const std::string& filepath) {
   // Search the parameters corresponding to filepath.
-  size_t delimiter = filepath.find_last_of("/\\");  // Either windows or posix
-  std::string filename =
-      filepath.substr(delimiter == std::string::npos ? 0 : delimiter + 1);
-  const auto it = audiotrack_names_params_.find(filename);
+  const rtc::Pathname audiotrack_file_path(filepath);
+  const auto it =
+      audiotrack_names_params_.find(audiotrack_file_path.filename());
 
   // If not found, use default parameters.
   if (it == audiotrack_names_params_.end()) {
diff --git a/modules/audio_processing/test/conversational_speech/multiend_call.cc b/modules/audio_processing/test/conversational_speech/multiend_call.cc
index 4e2f54d..d633d90 100644
--- a/modules/audio_processing/test/conversational_speech/multiend_call.cc
+++ b/modules/audio_processing/test/conversational_speech/multiend_call.cc
@@ -14,7 +14,7 @@
 #include <iterator>
 
 #include "rtc_base/logging.h"
-#include "test/testsupport/fileutils.h"
+#include "rtc_base/pathutils.h"
 
 namespace webrtc {
 namespace test {
@@ -50,13 +50,13 @@
     if (it != audiotrack_readers_.end())
       continue;
 
-    const std::string audiotrack_file_path =
-        test::JoinFilename(audiotracks_path_, turn.audiotrack_file_name);
+    // Instance Pathname to retrieve the full path to the audiotrack file.
+    const rtc::Pathname audiotrack_file_path(audiotracks_path_,
+                                             turn.audiotrack_file_name);
 
     // Map the audiotrack file name to a new instance of WavReaderInterface.
     std::unique_ptr<WavReaderInterface> wavreader =
-        wavreader_abstract_factory_->Create(
-            test::JoinFilename(audiotracks_path_, turn.audiotrack_file_name));
+        wavreader_abstract_factory_->Create(audiotrack_file_path.pathname());
 
     if (sample_rate_hz_ == 0) {
       sample_rate_hz_ = wavreader->SampleRate();
diff --git a/modules/audio_processing/test/conversational_speech/simulator.cc b/modules/audio_processing/test/conversational_speech/simulator.cc
index c400499..c2fb780 100644
--- a/modules/audio_processing/test/conversational_speech/simulator.cc
+++ b/modules/audio_processing/test/conversational_speech/simulator.cc
@@ -25,7 +25,7 @@
 #include "rtc_base/constructormagic.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/numerics/safe_conversions.h"
-#include "test/testsupport/fileutils.h"
+#include "rtc_base/pathutils.h"
 
 namespace webrtc {
 namespace test {
@@ -46,20 +46,21 @@
 
   // Add near-end and far-end output paths into the map.
   for (const auto& speaker_name : speaker_names) {
-    const std::string near_end_path =
-        test::JoinFilename(output_path, "s_" + speaker_name + "-near_end.wav");
+    const rtc::Pathname near_end_path(output_path,
+                                      "s_" + speaker_name + "-near_end.wav");
     RTC_LOG(LS_VERBOSE) << "The near-end audio track will be created in "
-                        << near_end_path << ".";
+                        << near_end_path.pathname() << ".";
 
-    const std::string far_end_path =
-        test::JoinFilename(output_path, "s_" + speaker_name + "-far_end.wav");
+    const rtc::Pathname far_end_path(output_path,
+                                     "s_" + speaker_name + "-far_end.wav");
     RTC_LOG(LS_VERBOSE) << "The far-end audio track will be created in "
-                        << far_end_path << ".";
+                        << far_end_path.pathname() << ".";
 
     // Add to map.
     speaker_output_file_paths_map->emplace(
         std::piecewise_construct, std::forward_as_tuple(speaker_name),
-        std::forward_as_tuple(near_end_path, far_end_path));
+        std::forward_as_tuple(near_end_path.pathname(),
+                              far_end_path.pathname()));
   }
 
   return speaker_output_file_paths_map;
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
index 1453baf..3f02fb7 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -121,6 +121,8 @@
     "numerics/sample_counter.cc",
     "numerics/sample_counter.h",
     "onetimeevent.h",
+    "pathutils.cc",
+    "pathutils.h",
     "platform_file.cc",
     "platform_file.h",
     "race_checker.cc",
@@ -1103,6 +1105,7 @@
       "numerics/safe_minmax_unittest.cc",
       "numerics/sample_counter_unittest.cc",
       "onetimeevent_unittest.cc",
+      "pathutils_unittest.cc",
       "platform_file_unittest.cc",
       "platform_thread_unittest.cc",
       "random_unittest.cc",
diff --git a/rtc_base/filerotatingstream.cc b/rtc_base/filerotatingstream.cc
index f0a17bf..c28616d 100644
--- a/rtc_base/filerotatingstream.cc
+++ b/rtc_base/filerotatingstream.cc
@@ -18,6 +18,7 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/fileutils.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/pathutils.h"
 
 // Note: We use fprintf for logging in the write paths of this stream to avoid
 // infinite loops when logging.
@@ -55,6 +56,7 @@
       rotation_index_(0),
       current_bytes_written_(0),
       disable_buffering_(false) {
+  RTC_DCHECK(Filesystem::IsFolder(dir_path));
   switch (mode) {
     case kWrite: {
       file_names_.clear();
@@ -186,6 +188,7 @@
   *size = 0;
   size_t total_size = 0;
   for (auto file_name : file_names_) {
+    Pathname pathname(file_name);
     size_t file_size = 0;
     if (Filesystem::GetFileSize(file_name, &file_size)) {
       total_size += file_size;
@@ -304,14 +307,17 @@
   std::vector<std::string> files;
   // Iterate over the files in the directory.
   DirectoryIterator it;
-  if (!it.Iterate(dir_path_)) {
+  Pathname dir_path;
+  dir_path.SetFolder(dir_path_);
+  if (!it.Iterate(dir_path)) {
     return files;
   }
   do {
     std::string current_name = it.Name();
     if (current_name.size() && !it.IsDirectory() &&
         current_name.compare(0, file_prefix_.size(), file_prefix_) == 0) {
-      files.push_back(it.PathName());
+      Pathname path(dir_path_, current_name);
+      files.push_back(path.pathname());
     }
   } while (it.Next());
   return files;
@@ -328,7 +334,8 @@
   RTC_DCHECK_LT(1 + max_digits, buffer_size);
   std::snprintf(file_postfix, buffer_size, "_%0*zu", max_digits, index);
 
-  return dir_path_ + file_prefix_ + file_postfix;
+  Pathname file_path(dir_path_, file_prefix_ + file_postfix);
+  return file_path.pathname();
 }
 
 CallSessionFileRotatingStream::CallSessionFileRotatingStream(
diff --git a/rtc_base/filerotatingstream_unittest.cc b/rtc_base/filerotatingstream_unittest.cc
index 84bf45c..1905516 100644
--- a/rtc_base/filerotatingstream_unittest.cc
+++ b/rtc_base/filerotatingstream_unittest.cc
@@ -15,6 +15,7 @@
 #include "rtc_base/filerotatingstream.h"
 #include "rtc_base/fileutils.h"
 #include "rtc_base/gunit.h"
+#include "rtc_base/pathutils.h"
 #include "test/testsupport/fileutils.h"
 
 namespace rtc {
@@ -50,7 +51,7 @@
 
     // Append per-test output path in order to run within gtest parallel.
     dir_path_.append(dir_name);
-    dir_path_.append(webrtc::test::kPathDelimiter);
+    dir_path_.push_back(Pathname::DefaultFolderDelimiter());
     ASSERT_TRUE(webrtc::test::CreateDir(dir_path_));
     stream_.reset(new FileRotatingStream(dir_path_, file_prefix, max_file_size,
                                          num_log_files));
@@ -217,7 +218,7 @@
 
     // Append per-test output path in order to run within gtest parallel.
     dir_path_.append(dir_name);
-    dir_path_.append(webrtc::test::kPathDelimiter);
+    dir_path_.push_back(Pathname::DefaultFolderDelimiter());
     ASSERT_TRUE(webrtc::test::CreateDir(dir_path_));
     stream_.reset(
         new CallSessionFileRotatingStream(dir_path_, max_total_log_size));
diff --git a/rtc_base/fileutils.cc b/rtc_base/fileutils.cc
index 1086f3c..0adbbac 100644
--- a/rtc_base/fileutils.cc
+++ b/rtc_base/fileutils.cc
@@ -11,6 +11,7 @@
 #include "rtc_base/fileutils.h"
 
 #include "rtc_base/checks.h"
+#include "rtc_base/pathutils.h"
 
 #if defined(WEBRTC_WIN)
 #include "rtc_base/stringutils.h"  // for ToUtf16
@@ -57,12 +58,12 @@
 // Starts traversing a directory.
 // dir is the directory to traverse
 // returns true if the directory exists and is valid
-bool DirectoryIterator::Iterate(const std::string& dir) {
-  directory_ = dir;
+bool DirectoryIterator::Iterate(const Pathname& dir) {
+  directory_ = dir.pathname();
 #if defined(WEBRTC_WIN)
   if (handle_ != INVALID_HANDLE_VALUE)
     ::FindClose(handle_);
-  std::string d = dir + '*';
+  std::string d = dir.pathname() + '*';
   handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_);
   if (handle_ == INVALID_HANDLE_VALUE)
     return false;
@@ -76,7 +77,7 @@
   if (dirent_ == nullptr)
     return false;
 
-  if (::stat(PathName().c_str(), &stat_) != 0)
+  if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0)
     return false;
 #endif
   return true;
@@ -92,7 +93,7 @@
   if (dirent_ == nullptr)
     return false;
 
-  return ::stat(PathName().c_str(), &stat_) == 0;
+  return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0;
 #endif
 }
 
@@ -111,17 +112,7 @@
   return ToUtf8(data_.cFileName);
 #else
   RTC_DCHECK(dirent_);
-  return std::string(dirent_->d_name);
-#endif
-}
-
-// returns the name of the file currently pointed to
-std::string DirectoryIterator::PathName() const {
-#if defined(WEBRTC_WIN)
-  return directory_ + "\\" + ToUtf8(data_.cFileName);
-#else
-  RTC_DCHECK(dirent_);
-  return directory_ + "/" + dirent_->d_name;
+  return dirent_->d_name;
 #endif
 }
 
diff --git a/rtc_base/fileutils.h b/rtc_base/fileutils.h
index d85b6b1..deaf2e3 100644
--- a/rtc_base/fileutils.h
+++ b/rtc_base/fileutils.h
@@ -25,6 +25,8 @@
 
 namespace rtc {
 
+class Pathname;
+
 //////////////////////////
 // Directory Iterator   //
 //////////////////////////
@@ -42,25 +44,22 @@
   // Destructor
   virtual ~DirectoryIterator();
 
-  // Starts traversing a directory.
-  // |dir| is the directory to traverse.
-  // returns true if the directory exists and is valid.
-  // The iterator will point to the first entry in the directory.
-  virtual bool Iterate(const std::string& dir);
+  // Starts traversing a directory
+  // dir is the directory to traverse
+  // returns true if the directory exists and is valid
+  // The iterator will point to the first entry in the directory
+  virtual bool Iterate(const Pathname& path);
 
   // Advances to the next file
   // returns true if there were more files in the directory.
   virtual bool Next();
 
-  // Returns true if the file currently pointed to is a directory.
+  // returns true if the file currently pointed to is a directory
   virtual bool IsDirectory() const;
 
-  // Returns the name of the file currently pointed to.
+  // returns the name of the file currently pointed to
   virtual std::string Name() const;
 
-  // Returns complete name of the file, including directory part.
-  virtual std::string PathName() const;
-
  private:
   std::string directory_;
 #if defined(WEBRTC_WIN)
@@ -80,37 +79,42 @@
   // This will attempt to delete the path located at filename.
   // It DCHECKs and returns false if the path points to a folder or a
   // non-existent file.
-  virtual bool DeleteFile(const std::string& filename) = 0;
+  virtual bool DeleteFile(const Pathname& filename) = 0;
 
   // This moves a file from old_path to new_path, where "old_path" is a
   // plain file. This DCHECKs and returns false if old_path points to a
   // directory, and returns true if the function succeeds.
-  virtual bool MoveFile(const std::string& old_path,
-                        const std::string& new_path) = 0;
+  virtual bool MoveFile(const Pathname& old_path, const Pathname& new_path) = 0;
+
+  // Returns true if pathname refers to a directory
+  virtual bool IsFolder(const Pathname& pathname) = 0;
 
   // Returns true if pathname refers to a file
-  virtual bool IsFile(const std::string& pathname) = 0;
+  virtual bool IsFile(const Pathname& pathname) = 0;
 
   // Determines the size of the file indicated by path.
-  virtual bool GetFileSize(const std::string& path, size_t* size) = 0;
+  virtual bool GetFileSize(const Pathname& path, size_t* size) = 0;
 };
 
 class Filesystem {
  public:
-  static bool DeleteFile(const std::string& filename) {
+  static bool DeleteFile(const Pathname& filename) {
     return GetFilesystem()->DeleteFile(filename);
   }
 
-  static bool MoveFile(const std::string& old_path,
-                       const std::string& new_path) {
+  static bool MoveFile(const Pathname& old_path, const Pathname& new_path) {
     return GetFilesystem()->MoveFile(old_path, new_path);
   }
 
-  static bool IsFile(const std::string& pathname) {
+  static bool IsFolder(const Pathname& pathname) {
+    return GetFilesystem()->IsFolder(pathname);
+  }
+
+  static bool IsFile(const Pathname& pathname) {
     return GetFilesystem()->IsFile(pathname);
   }
 
-  static bool GetFileSize(const std::string& path, size_t* size) {
+  static bool GetFileSize(const Pathname& path, size_t* size) {
     return GetFilesystem()->GetFileSize(path, size);
   }
 
diff --git a/rtc_base/pathutils.cc b/rtc_base/pathutils.cc
new file mode 100644
index 0000000..0764671
--- /dev/null
+++ b/rtc_base/pathutils.cc
@@ -0,0 +1,154 @@
+/*
+ *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#if defined(WEBRTC_WIN)
+#include <windows.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#endif  // WEBRTC_WIN
+
+#include <string.h>  // for strchr
+
+#include "rtc_base/pathutils.h"
+
+namespace rtc {
+
+static const char EMPTY_STR[] = "";
+
+// EXT_DELIM separates a file basename from extension
+const char EXT_DELIM = '.';
+
+// FOLDER_DELIMS separate folder segments and the filename
+const char* const FOLDER_DELIMS = "/\\";
+
+// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform
+#ifdef WEBRTC_WIN
+const char DEFAULT_FOLDER_DELIM = '\\';
+#else  // !WEBRTC_WIN
+const char DEFAULT_FOLDER_DELIM = '/';
+#endif  // !WEBRTC_WIN
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa
+///////////////////////////////////////////////////////////////////////////////
+
+bool Pathname::IsFolderDelimiter(char ch) {
+  return (nullptr != ::strchr(FOLDER_DELIMS, ch));
+}
+
+char Pathname::DefaultFolderDelimiter() {
+  return DEFAULT_FOLDER_DELIM;
+}
+
+Pathname::Pathname()
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+}
+
+Pathname::Pathname(const Pathname&) = default;
+Pathname::Pathname(Pathname&&) = default;
+
+Pathname::Pathname(const std::string& pathname)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(pathname);
+}
+
+Pathname::Pathname(const std::string& folder, const std::string& filename)
+    : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+  SetPathname(folder, filename);
+}
+
+Pathname& Pathname::operator=(const Pathname&) = default;
+Pathname& Pathname::operator=(Pathname&&) = default;
+
+std::string Pathname::pathname() const {
+  std::string pathname(folder_);
+  pathname.append(basename_);
+  pathname.append(extension_);
+  if (pathname.empty()) {
+    // Instead of the empty pathname, return the current working directory.
+    pathname.push_back('.');
+    pathname.push_back(folder_delimiter_);
+  }
+  return pathname;
+}
+
+void Pathname::SetPathname(const std::string& pathname) {
+  std::string::size_type pos = pathname.find_last_of(FOLDER_DELIMS);
+  if (pos != std::string::npos) {
+    SetFolder(pathname.substr(0, pos + 1));
+    SetFilename(pathname.substr(pos + 1));
+  } else {
+    SetFolder(EMPTY_STR);
+    SetFilename(pathname);
+  }
+}
+
+void Pathname::SetPathname(const std::string& folder,
+                           const std::string& filename) {
+  SetFolder(folder);
+  SetFilename(filename);
+}
+
+void Pathname::SetFolder(const std::string& folder) {
+  folder_.assign(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+void Pathname::AppendFolder(const std::string& folder) {
+  folder_.append(folder);
+  // Ensure folder ends in a path delimiter
+  if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+    folder_.push_back(folder_delimiter_);
+  }
+}
+
+bool Pathname::SetBasename(const std::string& basename) {
+  if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) {
+    return false;
+  }
+  basename_.assign(basename);
+  return true;
+}
+
+bool Pathname::SetExtension(const std::string& extension) {
+  if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos ||
+    extension.find_first_of(EXT_DELIM, 1) != std::string::npos) {
+      return false;
+  }
+  extension_.assign(extension);
+  // Ensure extension begins with the extension delimiter
+  if (!extension_.empty() && (extension_[0] != EXT_DELIM)) {
+    extension_.insert(extension_.begin(), EXT_DELIM);
+  }
+  return true;
+}
+
+std::string Pathname::filename() const {
+  std::string filename(basename_);
+  filename.append(extension_);
+  return filename;
+}
+
+bool Pathname::SetFilename(const std::string& filename) {
+  std::string::size_type pos = filename.rfind(EXT_DELIM);
+  if ((pos == std::string::npos) || (pos == 0)) {
+    return SetExtension(EMPTY_STR) && SetBasename(filename);
+  } else {
+    return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos));
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace rtc
diff --git a/rtc_base/pathutils.h b/rtc_base/pathutils.h
new file mode 100644
index 0000000..59f2a4a
--- /dev/null
+++ b/rtc_base/pathutils.h
@@ -0,0 +1,79 @@
+/*
+ *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef RTC_BASE_PATHUTILS_H_
+#define RTC_BASE_PATHUTILS_H_
+
+#include <string>
+
+namespace rtc {
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa.
+//
+// To establish consistent terminology, a filename never contains a folder
+// component.  A folder never contains a filename.  A pathname may include
+// a folder and/or filename component.  Here are some examples:
+//
+//   pathname()      /home/john/example.txt
+//   folder()        /home/john/
+//   filename()                 example.txt
+//   parent_folder() /home/
+//   folder_name()         john/
+//   basename()                 example
+//   extension()                       .txt
+//
+// Basename may begin, end, and/or include periods, but no folder delimiters.
+// If extension exists, it consists of a period followed by zero or more
+// non-period/non-delimiter characters, and basename is non-empty.
+///////////////////////////////////////////////////////////////////////////////
+
+class Pathname {
+ public:
+  // Folder delimiters are slash and backslash
+  static bool IsFolderDelimiter(char ch);
+  static char DefaultFolderDelimiter();
+
+  Pathname();
+  Pathname(const Pathname&);
+  Pathname(Pathname&&);
+  Pathname(const std::string& pathname);
+  Pathname(const std::string& folder, const std::string& filename);
+
+  Pathname& operator=(const Pathname&);
+  Pathname& operator=(Pathname&&);
+
+  // Returns the folder and filename components.  If the pathname is empty,
+  // returns a string representing the current directory (as a relative path,
+  // i.e., ".").
+  std::string pathname() const;
+  void SetPathname(const std::string& pathname);
+  void SetPathname(const std::string& folder, const std::string& filename);
+
+  // SetFolder and AppendFolder will append a folder delimiter, if needed.
+  void SetFolder(const std::string& folder);
+  void AppendFolder(const std::string& folder);
+
+  bool SetBasename(const std::string& basename);
+
+  // SetExtension will prefix a period, if needed.
+  bool SetExtension(const std::string& extension);
+
+  std::string filename() const;
+  bool SetFilename(const std::string& filename);
+
+ private:
+  std::string folder_, basename_, extension_;
+  char folder_delimiter_;
+};
+
+}  // namespace rtc
+
+#endif  // RTC_BASE_PATHUTILS_H_
diff --git a/rtc_base/pathutils_unittest.cc b/rtc_base/pathutils_unittest.cc
new file mode 100644
index 0000000..fae4f0a
--- /dev/null
+++ b/rtc_base/pathutils_unittest.cc
@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2007 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "rtc_base/pathutils.h"
+#include "rtc_base/gunit.h"
+
+TEST(Pathname, ReturnsDotForEmptyPathname) {
+  const std::string kCWD =
+      std::string(".") + rtc::Pathname::DefaultFolderDelimiter();
+
+  rtc::Pathname path("/", "");
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(std::string("/"), path.pathname());
+
+  path.SetPathname("", "foo");
+  EXPECT_FALSE(path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(std::string("foo"), path.pathname());
+
+  path.SetPathname("", "");
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(kCWD, path.pathname());
+
+  path.SetPathname(kCWD, "");
+  EXPECT_TRUE (path.filename().empty());
+  EXPECT_FALSE(path.pathname().empty());
+  EXPECT_EQ(kCWD, path.pathname());
+}
diff --git a/rtc_base/unixfilesystem.cc b/rtc_base/unixfilesystem.cc
index ee9e3f0..2a941e2 100644
--- a/rtc_base/unixfilesystem.cc
+++ b/rtc_base/unixfilesystem.cc
@@ -38,6 +38,7 @@
 
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/pathutils.h"
 
 namespace rtc {
 
@@ -45,39 +46,47 @@
 
 UnixFilesystem::~UnixFilesystem() {}
 
-bool UnixFilesystem::DeleteFile(const std::string& filename) {
-  RTC_LOG(LS_INFO) << "Deleting file:" << filename;
+bool UnixFilesystem::DeleteFile(const Pathname& filename) {
+  RTC_LOG(LS_INFO) << "Deleting file:" << filename.pathname();
 
   if (!IsFile(filename)) {
     RTC_DCHECK(IsFile(filename));
     return false;
   }
-  return ::unlink(filename.c_str()) == 0;
+  return ::unlink(filename.pathname().c_str()) == 0;
 }
 
-bool UnixFilesystem::MoveFile(const std::string& old_path,
-                              const std::string& new_path) {
+bool UnixFilesystem::MoveFile(const Pathname& old_path,
+                              const Pathname& new_path) {
   if (!IsFile(old_path)) {
     RTC_DCHECK(IsFile(old_path));
     return false;
   }
-  RTC_LOG(LS_VERBOSE) << "Moving " << old_path << " to " << new_path;
-  if (rename(old_path.c_str(), new_path.c_str()) != 0) {
+  RTC_LOG(LS_VERBOSE) << "Moving " << old_path.pathname() << " to "
+                      << new_path.pathname();
+  if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
     return false;
   }
   return true;
 }
 
-bool UnixFilesystem::IsFile(const std::string& pathname) {
+bool UnixFilesystem::IsFolder(const Pathname& path) {
   struct stat st;
-  int res = ::stat(pathname.c_str(), &st);
+  if (stat(path.pathname().c_str(), &st) < 0)
+    return false;
+  return S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::IsFile(const Pathname& pathname) {
+  struct stat st;
+  int res = ::stat(pathname.pathname().c_str(), &st);
   // Treat symlinks, named pipes, etc. all as files.
   return res == 0 && !S_ISDIR(st.st_mode);
 }
 
-bool UnixFilesystem::GetFileSize(const std::string& pathname, size_t* size) {
+bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t* size) {
   struct stat st;
-  if (::stat(pathname.c_str(), &st) != 0)
+  if (::stat(pathname.pathname().c_str(), &st) != 0)
     return false;
   *size = st.st_size;
   return true;
diff --git a/rtc_base/unixfilesystem.h b/rtc_base/unixfilesystem.h
index 32af9b5..d95132c 100644
--- a/rtc_base/unixfilesystem.h
+++ b/rtc_base/unixfilesystem.h
@@ -14,6 +14,7 @@
 #include <stddef.h>
 
 #include "rtc_base/fileutils.h"
+#include "rtc_base/pathutils.h"
 
 namespace rtc {
 
@@ -24,18 +25,20 @@
 
   // This will attempt to delete the file located at filename.
   // It will fail with VERIY if you pass it a non-existant file, or a directory.
-  bool DeleteFile(const std::string& filename) override;
+  bool DeleteFile(const Pathname& filename) override;
 
   // This moves a file from old_path to new_path, where "file" can be a plain
   // file or directory, which will be moved recursively.
   // Returns true if function succeeds.
-  bool MoveFile(const std::string& old_path,
-                const std::string& new_path) override;
+  bool MoveFile(const Pathname& old_path, const Pathname& new_path) override;
+
+  // Returns true if a pathname is a directory
+  bool IsFolder(const Pathname& pathname) override;
 
   // Returns true of pathname represents an existing file
-  bool IsFile(const std::string& pathname) override;
+  bool IsFile(const Pathname& pathname) override;
 
-  bool GetFileSize(const std::string& path, size_t* size) override;
+  bool GetFileSize(const Pathname& path, size_t* size) override;
 };
 
 }  // namespace rtc
diff --git a/rtc_base/win32filesystem.cc b/rtc_base/win32filesystem.cc
index 5465a5c..cd43966 100644
--- a/rtc_base/win32filesystem.cc
+++ b/rtc_base/win32filesystem.cc
@@ -21,6 +21,7 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/fileutils.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/pathutils.h"
 #include "rtc_base/stream.h"
 #include "rtc_base/stringutils.h"
 
@@ -33,37 +34,48 @@
 
 namespace rtc {
 
-bool Win32Filesystem::DeleteFile(const std::string& filename) {
-  RTC_LOG(LS_INFO) << "Deleting file " << filename;
+bool Win32Filesystem::DeleteFile(const Pathname& filename) {
+  RTC_LOG(LS_INFO) << "Deleting file " << filename.pathname();
   if (!IsFile(filename)) {
     RTC_DCHECK(IsFile(filename));
     return false;
   }
-  return ::DeleteFile(ToUtf16(filename).c_str()) != 0;
+  return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
 }
 
-bool Win32Filesystem::MoveFile(const std::string& old_path,
-                               const std::string& new_path) {
+bool Win32Filesystem::MoveFile(const Pathname& old_path,
+                               const Pathname& new_path) {
   if (!IsFile(old_path)) {
     RTC_DCHECK(IsFile(old_path));
     return false;
   }
-  RTC_LOG(LS_INFO) << "Moving " << old_path << " to " << new_path;
-  return ::MoveFile(ToUtf16(old_path).c_str(), ToUtf16(new_path).c_str()) != 0;
+  RTC_LOG(LS_INFO) << "Moving " << old_path.pathname() << " to "
+                   << new_path.pathname();
+  return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+                    ToUtf16(new_path.pathname()).c_str()) != 0;
 }
 
-bool Win32Filesystem::IsFile(const std::string& path) {
+bool Win32Filesystem::IsFolder(const Pathname& path) {
   WIN32_FILE_ATTRIBUTE_DATA data = {0};
-  if (0 == ::GetFileAttributesEx(ToUtf16(path).c_str(), GetFileExInfoStandard,
-                                 &data))
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
+    return false;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
+         FILE_ATTRIBUTE_DIRECTORY;
+}
+
+bool Win32Filesystem::IsFile(const Pathname& path) {
+  WIN32_FILE_ATTRIBUTE_DATA data = {0};
+  if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+                                 GetFileExInfoStandard, &data))
     return false;
   return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
 }
 
-bool Win32Filesystem::GetFileSize(const std::string& pathname, size_t* size) {
+bool Win32Filesystem::GetFileSize(const Pathname& pathname, size_t* size) {
   WIN32_FILE_ATTRIBUTE_DATA data = {0};
-  if (::GetFileAttributesEx(ToUtf16(pathname).c_str(), GetFileExInfoStandard,
-                            &data) == 0)
+  if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
+                            GetFileExInfoStandard, &data) == 0)
     return false;
   *size = data.nFileSizeLow;
   return true;
diff --git a/rtc_base/win32filesystem.h b/rtc_base/win32filesystem.h
index 2c27f90..d26741e 100644
--- a/rtc_base/win32filesystem.h
+++ b/rtc_base/win32filesystem.h
@@ -19,19 +19,21 @@
  public:
   // This will attempt to delete the path located at filename.
   // If the path points to a folder, it will fail with VERIFY
-  bool DeleteFile(const std::string& filename) override;
+  bool DeleteFile(const Pathname& filename) override;
 
   // This moves a file from old_path to new_path. If the new path is on a
   // different volume than the old, it will attempt to copy and then delete
   // the folder
   // Returns true if the file is successfully moved
-  bool MoveFile(const std::string& old_path,
-                const std::string& new_path) override;
+  bool MoveFile(const Pathname& old_path, const Pathname& new_path) override;
+
+  // Returns true if a pathname is a directory
+  bool IsFolder(const Pathname& pathname) override;
 
   // Returns true if a file exists at path
-  bool IsFile(const std::string& path) override;
+  bool IsFile(const Pathname& path) override;
 
-  bool GetFileSize(const std::string& path, size_t* size) override;
+  bool GetFileSize(const Pathname& path, size_t* size) override;
 };
 
 }  // namespace rtc
diff --git a/test/testsupport/fileutils.cc b/test/testsupport/fileutils.cc
index a71d8f8..0eac8d5 100644
--- a/test/testsupport/fileutils.cc
+++ b/test/testsupport/fileutils.cc
@@ -64,12 +64,16 @@
 namespace webrtc {
 namespace test {
 
+namespace {
+
 #if defined(WEBRTC_WIN)
 const char* kPathDelimiter = "\\";
 #else
 const char* kPathDelimiter = "/";
 #endif
 
+}  // namespace
+
 std::string DirName(const std::string& path) {
   if (path.empty())
     return "";
diff --git a/test/testsupport/fileutils.h b/test/testsupport/fileutils.h
index 8a18698..693944e 100644
--- a/test/testsupport/fileutils.h
+++ b/test/testsupport/fileutils.h
@@ -25,9 +25,6 @@
 // to find the project root.
 extern const char* kCannotFindProjectRootDir;
 
-// Slash or backslash, depending on platform. NUL-terminated string.
-extern const char* kPathDelimiter;
-
 // Returns the absolute path to the output directory where log files and other
 // test artifacts should be put. The output directory is generally a directory
 // named "out" at the project root. This root is assumed to be two levels above
diff --git a/test/testsupport/fileutils_unittest.cc b/test/testsupport/fileutils_unittest.cc
index d39f468..c6dc86d 100644
--- a/test/testsupport/fileutils_unittest.cc
+++ b/test/testsupport/fileutils_unittest.cc
@@ -24,6 +24,9 @@
 
 #ifdef WIN32
 #define chdir _chdir
+static const char* kPathDelimiter = "\\";
+#else
+static const char* kPathDelimiter = "/";
 #endif
 
 using ::testing::EndsWith;
diff --git a/test/testsupport/test_artifacts_unittest.cc b/test/testsupport/test_artifacts_unittest.cc
index df02d27..267ea93 100644
--- a/test/testsupport/test_artifacts_unittest.cc
+++ b/test/testsupport/test_artifacts_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "rtc_base/file.h"
 #include "rtc_base/flags.h"
+#include "rtc_base/pathutils.h"
 #include "rtc_base/platform_file.h"
 #include "test/gtest.h"
 #include "test/testsupport/fileutils.h"
diff --git a/video/video_analyzer.cc b/video/video_analyzer.cc
index e4a3d56..6d16b1a 100644
--- a/video/video_analyzer.cc
+++ b/video/video_analyzer.cc
@@ -18,9 +18,9 @@
 #include "rtc_base/flags.h"
 #include "rtc_base/format_macros.h"
 #include "rtc_base/memory_usage.h"
+#include "rtc_base/pathutils.h"
 #include "system_wrappers/include/cpu_info.h"
 #include "test/call_test.h"
-#include "test/testsupport/fileutils.h"
 #include "test/testsupport/frame_writer.h"
 #include "test/testsupport/perf_test.h"
 #include "test/testsupport/test_artifacts.h"
@@ -607,7 +607,7 @@
     std::string output_dir;
     test::GetTestArtifactsDir(&output_dir);
     std::string output_path =
-        test::JoinFilename(output_dir, test_label_ + ".jpg");
+        rtc::Pathname(output_dir, test_label_ + ".jpg").pathname();
     RTC_LOG(LS_INFO) << "Saving worst frame to " << output_path;
     test::JpegFrameWriter frame_writer(output_path);
     RTC_CHECK(