tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

file_path.cpp (7949B)


      1 // Copyright (c) 2012 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 // This is a partial implementation of Chromium's source file
      6 // base/file/file_path.cc.
      7 
      8 #include "base/files/file_path.h"
      9 
     10 namespace base {
     11 
     12 using StringType = FilePath::StringType;
     13 using StringPieceType = FilePath::StringPieceType;
     14 
     15 namespace {
     16 
     17 const FilePath::CharType kStringTerminator = FILE_PATH_LITERAL('\0');
     18 
     19 // If this FilePath contains a drive letter specification, returns the
     20 // position of the last character of the drive letter specification,
     21 // otherwise returns npos.  This can only be true on Windows, when a pathname
     22 // begins with a letter followed by a colon.  On other platforms, this always
     23 // returns npos.
     24 StringPieceType::size_type FindDriveLetter(StringPieceType path) {
     25 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
     26  // This is dependent on an ASCII-based character set, but that's a
     27  // reasonable assumption.  iswalpha can be too inclusive here.
     28  if (path.length() >= 2 && path[1] == L':' &&
     29      ((path[0] >= L'A' && path[0] <= L'Z') ||
     30       (path[0] >= L'a' && path[0] <= L'z'))) {
     31    return 1;
     32  }
     33 #endif  // FILE_PATH_USES_DRIVE_LETTERS
     34  return StringType::npos;
     35 }
     36 
     37 bool IsPathAbsolute(StringPieceType path) {
     38 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
     39  StringType::size_type letter = FindDriveLetter(path);
     40  if (letter != StringType::npos) {
     41    // Look for a separator right after the drive specification.
     42    return path.length() > letter + 1 &&
     43        FilePath::IsSeparator(path[letter + 1]);
     44  }
     45  // Look for a pair of leading separators.
     46  return path.length() > 1 &&
     47      FilePath::IsSeparator(path[0]) && FilePath::IsSeparator(path[1]);
     48 #else  // FILE_PATH_USES_DRIVE_LETTERS
     49  // Look for a separator in the first position.
     50  return path.length() > 0 && FilePath::IsSeparator(path[0]);
     51 #endif  // FILE_PATH_USES_DRIVE_LETTERS
     52 }
     53 
     54 }  // namespace
     55 
     56 FilePath::FilePath() = default;
     57 
     58 FilePath::FilePath(const FilePath& that) = default;
     59 FilePath::FilePath(FilePath&& that) noexcept = default;
     60 
     61 FilePath::FilePath(StringPieceType path) : path_(path) {
     62  StringType::size_type nul_pos = path_.find(kStringTerminator);
     63  if (nul_pos != StringType::npos)
     64    path_.erase(nul_pos, StringType::npos);
     65 }
     66 
     67 FilePath::~FilePath() = default;
     68 
     69 FilePath& FilePath::operator=(const FilePath& that) = default;
     70 
     71 FilePath& FilePath::operator=(FilePath&& that) noexcept = default;
     72 
     73 // static
     74 bool FilePath::IsSeparator(CharType character) {
     75  for (size_t i = 0; i < kSeparatorsLength - 1; ++i) {
     76    if (character == kSeparators[i]) {
     77      return true;
     78    }
     79  }
     80 
     81  return false;
     82 }
     83 
     84 // libgen's dirname and basename aren't guaranteed to be thread-safe and aren't
     85 // guaranteed to not modify their input strings, and in fact are implemented
     86 // differently in this regard on different platforms.  Don't use them, but
     87 // adhere to their behavior.
     88 FilePath FilePath::DirName() const {
     89  FilePath new_path(path_);
     90  new_path.StripTrailingSeparatorsInternal();
     91 
     92  // The drive letter, if any, always needs to remain in the output.  If there
     93  // is no drive letter, as will always be the case on platforms which do not
     94  // support drive letters, letter will be npos, or -1, so the comparisons and
     95  // resizes below using letter will still be valid.
     96  StringType::size_type letter = FindDriveLetter(new_path.path_);
     97 
     98  StringType::size_type last_separator =
     99      new_path.path_.find_last_of(kSeparators, StringType::npos,
    100                                  kSeparatorsLength - 1);
    101  if (last_separator == StringType::npos) {
    102    // path_ is in the current directory.
    103    new_path.path_.resize(letter + 1);
    104  } else if (last_separator == letter + 1) {
    105    // path_ is in the root directory.
    106    new_path.path_.resize(letter + 2);
    107  } else if (last_separator == letter + 2 &&
    108             IsSeparator(new_path.path_[letter + 1])) {
    109    // path_ is in "//" (possibly with a drive letter); leave the double
    110    // separator intact indicating alternate root.
    111    new_path.path_.resize(letter + 3);
    112  } else if (last_separator != 0) {
    113    // path_ is somewhere else, trim the basename.
    114    new_path.path_.resize(last_separator);
    115  }
    116 
    117  new_path.StripTrailingSeparatorsInternal();
    118  if (!new_path.path_.length())
    119    new_path.path_ = kCurrentDirectory;
    120 
    121  return new_path;
    122 }
    123 
    124 FilePath FilePath::BaseName() const {
    125  FilePath new_path(path_);
    126  new_path.StripTrailingSeparatorsInternal();
    127 
    128  // The drive letter, if any, is always stripped.
    129  StringType::size_type letter = FindDriveLetter(new_path.path_);
    130  if (letter != StringType::npos) {
    131    new_path.path_.erase(0, letter + 1);
    132  }
    133 
    134  // Keep everything after the final separator, but if the pathname is only
    135  // one character and it's a separator, leave it alone.
    136  StringType::size_type last_separator =
    137      new_path.path_.find_last_of(kSeparators, StringType::npos,
    138                                  kSeparatorsLength - 1);
    139  if (last_separator != StringType::npos &&
    140      last_separator < new_path.path_.length() - 1) {
    141    new_path.path_.erase(0, last_separator + 1);
    142  }
    143 
    144  return new_path;
    145 }
    146 
    147 FilePath FilePath::Append(StringPieceType component) const {
    148  StringPieceType appended = component;
    149  StringType without_nuls;
    150 
    151  StringType::size_type nul_pos = component.find(kStringTerminator);
    152  if (nul_pos != StringPieceType::npos) {
    153    without_nuls = StringType(component.substr(0, nul_pos));
    154    appended = StringPieceType(without_nuls);
    155  }
    156 
    157  DCHECK(!IsPathAbsolute(appended));
    158 
    159  if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
    160    // Append normally doesn't do any normalization, but as a special case,
    161    // when appending to kCurrentDirectory, just return a new path for the
    162    // component argument.  Appending component to kCurrentDirectory would
    163    // serve no purpose other than needlessly lengthening the path, and
    164    // it's likely in practice to wind up with FilePath objects containing
    165    // only kCurrentDirectory when calling DirName on a single relative path
    166    // component.
    167    return FilePath(appended);
    168  }
    169 
    170  FilePath new_path(path_);
    171  new_path.StripTrailingSeparatorsInternal();
    172 
    173  // Don't append a separator if the path is empty (indicating the current
    174  // directory) or if the path component is empty (indicating nothing to
    175  // append).
    176  if (!appended.empty() && !new_path.path_.empty()) {
    177    // Don't append a separator if the path still ends with a trailing
    178    // separator after stripping (indicating the root directory).
    179    if (!IsSeparator(new_path.path_.back())) {
    180      // Don't append a separator if the path is just a drive letter.
    181      if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
    182        new_path.path_.append(1, kSeparators[0]);
    183      }
    184    }
    185  }
    186 
    187  new_path.path_.append(appended.data(), appended.size());
    188  return new_path;
    189 }
    190 
    191 FilePath FilePath::Append(const FilePath& component) const {
    192  return Append(component.value());
    193 }
    194 
    195 void FilePath::StripTrailingSeparatorsInternal() {
    196  // If there is no drive letter, start will be 1, which will prevent stripping
    197  // the leading separator if there is only one separator.  If there is a drive
    198  // letter, start will be set appropriately to prevent stripping the first
    199  // separator following the drive letter, if a separator immediately follows
    200  // the drive letter.
    201  StringType::size_type start = FindDriveLetter(path_) + 2;
    202 
    203  StringType::size_type last_stripped = StringType::npos;
    204  for (StringType::size_type pos = path_.length();
    205       pos > start && IsSeparator(path_[pos - 1]);
    206       --pos) {
    207    // If the string only has two separators and they're at the beginning,
    208    // don't strip them, unless the string began with more than two separators.
    209    if (pos != start + 1 || last_stripped == start + 2 ||
    210        !IsSeparator(path_[start - 1])) {
    211      path_.resize(pos - 1);
    212      last_stripped = pos;
    213    }
    214  }
    215 }
    216 
    217 } // namespace base