Split file path processing off into it's own static class and implement validation.

PiperOrigin-RevId: 485890871
This commit is contained in:
John Carroll
2022-11-03 09:18:34 -07:00
committed by Copybara-Service
parent fa983e304a
commit 180b76cf84
8 changed files with 1220 additions and 856 deletions
@@ -22,6 +22,7 @@
#include "connections/implementation/offline_frames.h"
#include "connections/implementation/proto/offline_wire_formats.pb.h"
#include "internal/platform/implementation/platform.h"
#include "internal/platform/logging.h"
namespace location {
namespace nearby {
@@ -334,26 +335,29 @@ Exception EnsureValidBandwidthUpgradeNegotiationFrame(
}
bool CheckForIllegalCharacters(std::string toBeValidated,
std::vector<std::string> illegalPatterns) {
const absl::string_view illegalPatterns[],
size_t illegalPatternsSize) {
if (toBeValidated.empty()) {
return false;
}
CHECK_GT(illegalPatterns.size(), 0);
CHECK_GT(illegalPatternsSize, 0);
return std::any_of(illegalPatterns.begin(), illegalPatterns.end(),
[&toBeValidated](const auto& s) {
size_t found = toBeValidated.find(s);
if (found != std::string::npos) {
// TODO(jfcarroll): Find a way to log messages
// here.
// NEARBY_LOGS(ERROR)
// << "Illegal character sequence found: \""
// << toBeValidated[found] << "\"";
return true;
}
return false;
});
size_t found = 0;
for (int index = 0; index < illegalPatternsSize; index++) {
found = toBeValidated.find(std::string(illegalPatterns[index]));
if (found != std::string::npos) {
// TODO(jfcarroll): Find a way to issue a log statement here.
// Currently, this breaks the fuzzer, as a logging dep is not
// included for it in the BUILD file.
// NEARBY_LOGS(ERROR) << "In path " << toBeValidated
// << " found illegal character/pattern "
// << illegalPatterns[index];
return true;
}
}
return false;
}
} // namespace
@@ -391,7 +395,8 @@ Exception EnsureValidOfflineFrame(const OfflineFrame& offline_frame) {
.payload_transfer()
.payload_header()
.file_name(),
kIllegalFileNamePatterns)) {
kIllegalFileNamePatterns,
kIllegalFileNamePatternsSize)) {
return {Exception::kIllegalCharacters};
}
}
@@ -403,7 +408,8 @@ Exception EnsureValidOfflineFrame(const OfflineFrame& offline_frame) {
.payload_transfer()
.payload_header()
.parent_folder(),
kIllegalParentFolderPatterns)) {
kIllegalParentFolderPatterns,
kIllegalParentFolderPatternsSize)) {
return {Exception::kIllegalCharacters};
}
}
@@ -15,6 +15,10 @@
#ifndef CORE_INTERNAL_OFFLINE_FRAMES_VALIDATOR_H_
#define CORE_INTERNAL_OFFLINE_FRAMES_VALIDATOR_H_
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "connections/implementation/proto/offline_wire_formats.pb.h"
#include "internal/platform/exception.h"
@@ -23,22 +27,15 @@ namespace nearby {
namespace connections {
namespace parser {
#ifdef NEARBY_CHROMIUM
const std::vector<std::string> kIllegalFileNamePatterns{
"/", "\\", "?", "*", "\"", "<", ">",
"|", ":", "..", "\n", "\r", "\t", "\f"};
constexpr absl::string_view kIllegalFileNamePatterns[] = {":", "/", "\\"};
const std::vector<std::string> kIllegalParentFolderPatterns{
"\\", "?", "*", "\"", "<", ">", "|", ":", "..", "\n", "\r", "\t", "\f"};
#else
const std::vector<std::string> kIllegalFileNamePatterns{
"/", "\\", "?", "*", "\"", "<", ">", "|", "[",
"]", ":", ",", ";", "..", "\n", "\r", "\t", "\f"};
constexpr absl::string_view kIllegalParentFolderPatterns[] = {":", ".."};
const std::vector<std::string> kIllegalParentFolderPatterns{
"\\", "?", "*", "\"", "<", ">", "|", "[", "]",
":", ",", ";", "..", "\n", "\r", "\t", "\f"};
#endif
const size_t kIllegalFileNamePatternsSize =
sizeof(kIllegalFileNamePatterns) / sizeof(*kIllegalFileNamePatterns);
const size_t kIllegalParentFolderPatternsSize =
sizeof(kIllegalParentFolderPatterns) /
sizeof(*kIllegalParentFolderPatterns);
Exception EnsureValidOfflineFrame(const OfflineFrame& offline_frame);
@@ -61,6 +61,7 @@ cc_library(
"condition_variable.h",
"executor.h",
"file.h",
"file_path.h",
"mutex.h",
"scheduled_executor.h",
"server_sync.h",
@@ -125,6 +126,7 @@ cc_library(
"bluetooth_classic_socket.cc",
"executor.cc",
"file.cc",
"file_path.cc",
"platform.cc",
"scheduled_executor.cc",
"submittable_executor.cc",
@@ -196,7 +198,7 @@ cc_test(
"count_down_latch_test.cc",
"crypto_test.cc",
"executor_test.cc",
"platform_test.cc",
"file_path_test.cc",
"scheduled_executor_test.cc",
"submittable_executor_test.cc",
"thread_pool_test.cc",
@@ -0,0 +1,264 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/file_path.h"
#include <windows.h>
#include <winver.h>
#include <PathCch.h>
#include <knownfolders.h>
#include <psapi.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "internal/platform/implementation/windows/utils.h"
#include "internal/platform/logging.h"
namespace location {
namespace nearby {
namespace windows {
const wchar_t* kUpOneLevel = L"/..";
constexpr wchar_t kPathDelimiter = L'/';
constexpr wchar_t kReplacementChar = L'_';
constexpr wchar_t kForwardSlash = L'/';
constexpr wchar_t kBackSlash = L'\\';
wchar_t const* kForbiddenPathNames[] = {
L"CON", L"PRN", L"AUX", L"NUL", L"COM1", L"COM2", L"COM3", L"COM4",
L"COM5", L"COM6", L"COM7", L"COM8", L"COM9", L"LPT1", L"LPT2", L"LPT3",
L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9"};
std::wstring FilePath::GetDownloadPath(std::wstring parent_folder,
std::wstring file_name) {
return CreateOutputFileWithRename(
GetDownloadPathInternal(parent_folder, file_name));
}
std::wstring FilePath::GetDownloadPathInternal(std::wstring parent_folder,
std::wstring file_name) {
PWSTR basePath;
// Retrieves the full path of a known folder identified by the folder's
// KNOWNFOLDERID.
// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
SHGetKnownFolderPath(
/*rfid=*/FOLDERID_Downloads,
/*dwFlags=*/0,
/*hToken=*/nullptr,
/*ppszPath=*/&basePath);
std::wstring wide_path(basePath);
std::replace(wide_path.begin(), wide_path.end(), kBackSlash, kForwardSlash);
// If parent_folder starts with a \\ or /, then strip it
while (!parent_folder.empty() && (*parent_folder.begin() == kBackSlash ||
*parent_folder.begin() == kForwardSlash)) {
parent_folder.erase(0, 1);
}
// If parent_folder ends with a \\ or /, then strip it
while (!parent_folder.empty() && (*parent_folder.rbegin() == kBackSlash ||
*parent_folder.rbegin() == kForwardSlash)) {
parent_folder.erase(parent_folder.size() - 1, 1);
}
// If file_name starts with a \\, then strip it
while (!file_name.empty() && (*file_name.begin() == kBackSlash ||
*file_name.begin() == kForwardSlash)) {
file_name.erase(0, 1);
}
// If file_name ends with a \\, then strip it
while (!file_name.empty() && (*file_name.rbegin() == kBackSlash ||
*file_name.rbegin() == kForwardSlash)) {
file_name.erase(file_name.size() - 1, 1);
}
CoTaskMemFree(basePath);
std::wstring path;
if (parent_folder.empty()) {
path =
file_name.empty() ? wide_path : wide_path + kForwardSlash + file_name;
} else {
path = file_name.empty() ? wide_path + kForwardSlash + parent_folder
: wide_path + kForwardSlash + parent_folder +
kForwardSlash + file_name;
}
// Convert to UTF8 format.
return path;
}
// If the file already exists we add " (x)", where x is an incrementing number,
// starting at 1, using the next non-existing number, to the file name, just
// before the first dot, or at the end if no dot. The absolute path is returned.
std::wstring FilePath::CreateOutputFileWithRename(std::wstring path) {
std::wstring sanitized_path(path);
// Replace any \\ with /
std::replace(sanitized_path.begin(), sanitized_path.end(), kBackSlash,
kForwardSlash);
// Remove any /..'s
SanitizePath(sanitized_path);
auto last_delimiter = sanitized_path.find_last_of(kPathDelimiter);
std::wstring folder(sanitized_path.substr(0, last_delimiter));
std::wstring file_name(sanitized_path.substr(last_delimiter));
// Locate the last dot
auto first = file_name.find_last_of('.');
if (first == std::string::npos) {
first = file_name.size();
}
// Break the string at the dot.
auto file_name1 = file_name.substr(0, first);
auto file_name2 = file_name.substr(first);
// Construct the target file name
std::wstring target(sanitized_path);
std::fstream file;
// Open file as std::wstring
file.open(target, std::fstream::binary | std::fstream::in);
// While we successfully open the file, keep incrementing the count.
int count = 0;
while (!(file.rdstate() & std::ifstream::failbit)) {
file.close();
target = (folder + file_name1 + L" (" + std::to_wstring(++count) + L")" +
file_name2);
file.clear();
file.open(target, std::fstream::binary | std::fstream::in);
}
if (count > 0) {
NEARBY_LOGS(INFO) << "Renamed " << wstring_to_string(path) << " to "
<< wstring_to_string(target);
}
// The above leaves the file open, so close it.
file.close();
return target;
}
std::wstring FilePath::MutateForbiddenPathElements(std::wstring& str) {
std::vector<std::wstring> path_elements;
std::wstring::iterator pos = str.begin();
std::wstring::iterator last = str.begin();
while (pos != str.end()) {
last = pos;
pos = std::find(pos, str.end(), kPathDelimiter);
if (pos != str.end()) {
std::wstring path_element = std::wstring(last, pos);
if (path_element.length() > 0) path_elements.push_back(path_element);
last = ++pos;
}
}
std::wstring lastToken = std::wstring(last, pos);
if (lastToken.length() > 0) path_elements.push_back(lastToken);
std::wstring processed_path;
for (auto& path_element : path_elements) {
auto tmp_path_element = path_element;
std::transform(tmp_path_element.begin(), tmp_path_element.end(),
tmp_path_element.begin(),
[](wchar_t c) { return std::toupper(c); });
std::vector<std::wstring> forbidden(std::begin(kForbiddenPathNames),
std::end(kForbiddenPathNames));
while (std::find(forbidden.begin(), forbidden.end(), tmp_path_element) !=
forbidden.end()) {
tmp_path_element.insert(tmp_path_element.begin(), kReplacementChar);
NEARBY_LOGS(INFO) << "Renamed path element "
<< wstring_to_string(path_element) << " to "
<< wstring_to_string(tmp_path_element);
path_element.insert(path_element.begin(), kReplacementChar);
}
processed_path += path_element;
if (&path_element != &path_elements.back()) {
processed_path += kPathDelimiter;
}
}
return processed_path;
}
void FilePath::SanitizePath(std::wstring& path) {
size_t pos = std::wstring::npos;
// Search for the substring in string in a loop until nothing is found
while ((pos = path.find(kUpOneLevel)) != std::string::npos) {
// If found then erase it from string
path.erase(pos, wcslen(kUpOneLevel));
}
path = MutateForbiddenPathElements(path);
ReplaceInvalidCharacters(path);
}
char kIllegalFileCharacters[] = {'?', '*', '\'', '<', '>', '|', ':'};
void FilePath::ReplaceInvalidCharacters(std::wstring& path) {
auto it = path.begin();
it += 2; // Skip the 'C:' or any other drive specifier
for (; it != path.end(); it++) {
// If 0 < character < 32, it's illegal, replace it
if (*it > 0 && *it < 32) {
NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path)
<< " replaced \'" << std::string(1, *it) << "\' with \'"
<< std::string(1, kReplacementChar);
*it = kReplacementChar;
}
for (auto illegal_character : kIllegalFileCharacters) {
if (*it == illegal_character) {
NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path)
<< " replaced \'" << std::string(1, *it)
<< "\' with \'" << std::string(1, kReplacementChar);
*it = kReplacementChar;
}
}
}
}
} // namespace windows
} // namespace nearby
} // namespace location
@@ -0,0 +1,49 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_FILE_PATH_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_FILE_PATH_H_
#include <string>
#include "absl/strings/string_view.h"
namespace location {
namespace nearby {
namespace windows {
class FilePath {
public:
static std::wstring GetDownloadPath(std::wstring parent_folder,
std::wstring file_name);
private:
// If the file already exists we add " (x)", where x is an incrementing
// number, starting at 1, using the next non-existing number, to the
// file name, just before the first dot, or at the end if no dot. The
// absolute path is returned.
static std::wstring CreateOutputFileWithRename(std::wstring path);
static void ReplaceInvalidCharacters(std::wstring& path);
static void SanitizePath(std::wstring& path);
static std::wstring MutateForbiddenPathElements(std::wstring& str);
static std::wstring GetDownloadPathInternal(std::wstring parent_folder,
std::wstring file_name);
};
} // namespace windows
} // namespace nearby
} // namespace location
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_FILE_PATH_H_
@@ -0,0 +1,858 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/file_path.h"
#include <windows.h>
#include <knownfolders.h>
#include <shlobj.h>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace windows {
namespace {
const wchar_t* kIllegalPathNames[] = {
L"CON", L"PRN", L"AUX", L"NUL", L"COM1", L"COM2", L"COM3", L"COM4",
L"COM5", L"COM6", L"COM7", L"COM8", L"COM9", L"LPT1", L"LPT2", L"LPT3",
L"LPT4", L"LPT5", L"LPT6", L"LPT7", L"LPT8", L"LPT9"};
const wchar_t* kFileName(L"increment_file_test.txt");
const wchar_t* kFirstIterationFileName(L"/increment_file_test (1).txt");
const wchar_t* kSecondIterationFileName(L"/increment_file_test (2).txt");
const wchar_t* kThirdIterationFileName(L"/increment_file_test (3).txt");
const wchar_t* kNoDotsFileName(L"incrementfiletesttxt");
const wchar_t* kOneIterationNoDotsFileName(L"/incrementfiletesttxt (1)");
const wchar_t* kMultipleDotsFileName(L"increment.file.test.txt");
const wchar_t* kOneIterationMultipleDotsFileName(
L"/increment.file.test (1).txt");
const wchar_t* kImmediateEscape(L"../");
const wchar_t* kLongEscapeBackSlash(L"..\\test\\..\\..\\test");
const wchar_t* kTwoLevelFolder(L"/test/test");
const wchar_t* kLongEscapeSlash(L"../test/../../test");
const wchar_t* kLongEscapeMixedSlash(L"../test\\..\\../test");
const wchar_t* kLongEscapeEndingEscape(L"../test/../../test/..");
const wchar_t* kLongEscapeEndingEscapeWithSlash(
L"../test/../../test/../../../");
} // namespace
// Can't run on google 3, I presume the SHGetKnownFolderPath
// fails.
class FilePathTests : public testing::Test {
protected:
// You can define per-test set-up logic as usual.
FilePathTests() {
PWSTR basePath;
SHGetKnownFolderPath(
FOLDERID_Downloads, // rfid: A reference to the KNOWNFOLDERID that
// identifies the folder.
0, // dwFlags: Flags that specify special retrieval
// options.
nullptr, // hToken: An access token that represents a
// particular user.
&basePath); // ppszPath: When this method returns, contains
// the address of a pointer to a
// null-terminated Unicode string that
// specifies the path of the known
// folder. The calling process is
// responsible for freeing this resource
// once it is no longer needed by
// calling CoTaskMemFree, whether
// SHGetKnownFolderPath succeeds or not.
// size_t bufferSize;
// wcstombs_s(&bufferSize, nullptr, 0, basePath, 0);
// default_download_path_.resize(bufferSize - 1, '\0');
// wcstombs_s(&bufferSize, default_download_path_.data(), bufferSize,
// basePath,
// _TRUNCATE);
default_download_path_ = basePath;
std::replace(default_download_path_.begin(), default_download_path_.end(),
L'\\', L'/');
}
std::wstring default_download_path_;
};
TEST_F(FilePathTests, GetDownloadPathWithEmptyStringArguments\
ShouldReturnBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_);
} // NOLINT false lint error here
TEST_F(FilePathTests, GetDownloadPathWithSlashParent\
FolderArgumentsShouldReturnBaseDownloadPath) {
std::wstring parent_folder(L"/");
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_);
} // NOLINT false lint error here
TEST_F(FilePathTests, GetDownloadPathWithBackslashParent\
FolderArgumentsShouldReturnBaseDownloadPath) {
std::wstring parent_folder(L"\\");
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_);
} // NOLINT false lint error here
TEST_F(FilePathTests, GetDownloadPathWithAttemptToEscape\
UsersDownloadFolderShouldReturnDownloadPathNotEscapingUsersDownloadFolder) {
std::wstring parent_folder(kImmediateEscape);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_);
}
TEST_F(FilePathTests, GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithBackslashShouldReturnDownloadPath\
NotEscapingUsersDownloadFolder) {
std::wstring parent_folder(kLongEscapeBackSlash);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder);
}
TEST_F(FilePathTests, GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderShouldReturnDownloadPathNotEscapingUsers\
DownloadFolder) {
std::wstring parent_folder(kLongEscapeSlash);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder);
}
TEST_F(FilePathTests, GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithMixedSlashShouldReturnDownloadPath\
NotEscapingUsersDownloadFolder) {
std::wstring parent_folder(kLongEscapeMixedSlash);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder);
}
TEST_F(FilePathTests, GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithEndingEscapeShouldReturnDownload\
PathNotEscapingUsersDownloadFolder) {
std::wstring parent_folder(kLongEscapeEndingEscape);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder);
}
TEST_F(FilePathTests, GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithEndingSlashShouldReturnDownloadPathNot\
EscapingUsersDownloadFolder) {
std::wstring parent_folder(kLongEscapeEndingEscapeWithSlash);
std::wstring file_name(L"");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder);
}
TEST_F(FilePathTests, GetDownloadPathWithSlashFileName\
ArgumentsShouldReturnBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"/");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, default_download_path_);
}
TEST_F(FilePathTests, GetDownloadPathWithBackslashFile\
NameArgumentsShouldReturnBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"\\");
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
auto result_size = actual.size();
auto default_size = default_download_path_.size();
EXPECT_EQ(actual, default_download_path_);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
ShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder");
std::wstring file_name(L"");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
StartingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"/test_parent_folder");
std::wstring file_name(L"");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
StartingWithBackslashArgumentsShouldReturnParentFolderAppendedToBase\
DownloadPath) {
std::wstring parent_folder(L"\\test_parent_folder");
std::wstring file_name(L"");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
EndingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder/");
std::wstring file_name(L"");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
EndingWithBackslashArguments\
ShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder\\");
std::wstring file_name(L"");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithFileName\
BeginningWithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"/test_file_name.name");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithFileName\
BeginningWithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"\\test_file_name.name");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_file_name.name";
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, path.str().c_str());
}
TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\
WithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"test_file_name.name/");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\
WithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"");
std::wstring file_name(L"test_file_name.name\\");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolderAnd\
FileNameArgumentsShould\
ReturnParentFolderAndFileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder");
std::wstring file_name(L"test_file_name.name");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithParentFolder\
EndingWithBackslashAndFileNameArgumentsShouldReturnParentFolderAndFileName\
AppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder\\");
std::wstring file_name(L"test_file_name.name");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPathWithFileName\
StartingWithBackslashAndParentFolderArgumentsShouldReturnParentFolderAnd\
FileNameAppendedToBaseDownloadPath) {
std::wstring parent_folder(L"test_parent_folder");
std::wstring file_name(L"\\test_file_name.name");
std::wstringstream path(L"");
path << default_download_path_ << L"/" << "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::wstring expected = path.str();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalPathComponent\
ReturnsComponentWithUnderbarPrepended) {
for (auto illegal_file_name : kIllegalPathNames) {
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/_" + std::wstring(illegal_file_name) +
std::wstring(L"/") + std::wstring(kFileName));
auto actual(FilePath::GetDownloadPath(illegal_file_name, kFileName));
EXPECT_EQ(actual, expected);
}
}
TEST_F(FilePathTests, GetDownloadPath_IllegalPathComponentInLowerCase\
ReturnsComponentWithUnderbarPrepended) {
for (auto illegal_file_name : kIllegalPathNames) {
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/_" + std::wstring(_wcslwr(
(std::wstring(illegal_file_name)).data())));
expected.append(L"/" + std::wstring(kFileName));
auto actual(FilePath::GetDownloadPath(
std::wstring(_wcslwr((std::wstring(illegal_file_name)).data())),
kFileName));
EXPECT_EQ(actual, expected);
}
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameInLowerCase\
ReturnsFileNameWithUnderbarPrepended) {
for (auto illegal_file_name : kIllegalPathNames) {
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/_" + std::wstring(_wcslwr(
(std::wstring(illegal_file_name)).data())));
auto actual(FilePath::GetDownloadPath(
parent_folder,
std::wstring(_wcslwr((std::wstring(illegal_file_name)).data()))));
EXPECT_EQ(actual, expected);
}
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacters\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x05,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test\x5Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_LowestIllegalFileNameCharacter\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x01,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test\x1Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_HighestIllegalFileNameCharacter\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x1f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test\x1fTest");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterQuestionMark\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test?Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterAsterisk\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test*Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterLessThan\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test<Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterGreaterThan\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test>Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterVerticalBar\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test|Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterColon\
ReturnsFileNameWithUnderbarSubstituted) {
// char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f,
// 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 };
auto illegal_character_sequence(L"Test:Test");
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/Test_Test");
auto actual(FilePath::GetDownloadPath(
parent_folder, std::wstring(illegal_character_sequence)));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_FileDoesntExist\
ReturnsFileWithPassedName) {
std::wstring file_name(kFileName);
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(L"/");
expected.append(file_name);
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(FilePathTests, GetDownloadPath_FileExistsReturns\
FileWithIncrementedName) {
std::wstring file_name(kFileName);
std::wstring renamed_file_name(kFirstIterationFileName);
std::wstring parent_folder(L"");
std::wstring output_file_path(default_download_path_);
output_file_path.append(L"/");
output_file_path.append(file_name);
std::wstring expected(default_download_path_);
expected += renamed_file_name;
std::wifstream input_file;
std::wofstream output_file;
output_file.open(output_file_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
// Remove the file and check that it is removed
// File 1
_wremove(output_file_path.c_str());
input_file.open(output_file_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(FilePathTests, GetDownloadPath_MultipleFilesExist\
ReturnsNextIncrementedFileName) {
std::ofstream output_file;
std::ifstream input_file;
std::wstring file_name(kFileName);
std::wstring first_renamed_file_name(kFirstIterationFileName);
std::wstring second_renamed_file_name(kSecondIterationFileName);
std::wstring parent_folder(L"");
std::wstring expected(default_download_path_);
expected.append(second_renamed_file_name.c_str());
std::wstring output_file1_path(default_download_path_);
output_file1_path.append(L"/" + file_name);
std::wstring output_file2_path(default_download_path_);
output_file2_path.append(first_renamed_file_name);
// Create the test files
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
output_file.clear();
output_file.open(output_file2_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
// Remove the test files and check that it is removed
// File 1
_wremove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// File 2
_wremove(output_file2_path.c_str());
input_file.clear();
input_file.open(output_file2_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(FilePathTests, GetDownloadPath_FileNameContains\
MultipleDotsReturnsIncrementBeforeFirstDot) {
std::ifstream input_file;
std::ofstream output_file;
std::wstring file_name(kMultipleDotsFileName);
std::wstring renamed_file_name(kOneIterationMultipleDotsFileName);
std::wstring parent_folder(L"");
std::wstring output_file1_path(default_download_path_);
output_file1_path.append(L"/" + file_name);
std::wstring output_file2_path(default_download_path_);
output_file2_path.append(renamed_file_name);
std::wstring expected(default_download_path_);
expected.append(renamed_file_name);
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
_wremove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(FilePathTests, GetDownloadPath_FileNameContainsNo\
DotsReturnsWithIncrementAtEnd) {
std::ifstream input_file;
std::ofstream output_file;
std::wstring file_name(kNoDotsFileName);
std::wstring renamed_file_name(kOneIterationNoDotsFileName);
std::wstring parent_folder(L"");
std::wstring output_file1_path(default_download_path_);
output_file1_path.append(L"/" + file_name);
std::wstring output_file2_path(default_download_path_);
output_file2_path.append(L"/" + renamed_file_name);
std::wstring expected(default_download_path_);
expected.append(renamed_file_name);
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
_wremove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(FilePathTests, GetDownloadPath_FileNameExistsWith\
AHoleBetweenRenamedFiles) {
std::ifstream input_file;
std::ofstream output_file;
std::wstring file_name(kFileName);
std::wstring file_name1(kFirstIterationFileName);
std::wstring file_name2(kSecondIterationFileName);
std::wstring file_name3(kThirdIterationFileName);
std::wstring parent_folder(L"");
// Create the path for the original file name
std::wstring output_file_path(default_download_path_);
output_file_path.append(
L"/" +
file_name); // Original file name example: "increment_file_test.txt"
// Create the path for the first iteration of the original file name
std::wstring output_file1_path(default_download_path_);
output_file1_path.append(file_name1); // First iteration on original file
// name example:
// "increment_file_test (1).txt"
// Create the path for the third iteration of the original file name
std::wstring output_file3_path(default_download_path_);
output_file3_path.append(
file_name3); // Third iteration on original file
// name example: "increment_file_test (3).txt"
// Create the expected result which is the second iteration of the original
// file name
std::wstring expected(default_download_path_);
expected.append(file_name2); // Second iteration on original file name
// example: "increment_file_test (2).txt"
// Create the original file
output_file.open(output_file_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// Create the first iteration of the original file
output_file.clear();
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// Create the third iteration of the original file
output_file.clear();
output_file.open(output_file3_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// This should return the second iteration of the original file
auto actual(FilePath::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
// Delete the original file
_wremove(output_file_path.c_str());
input_file.open(output_file_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// Delete the first iteration of the original file
input_file.clear(); // Reset the input_file state
_wremove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// Delete the third iteration of the original file
input_file.clear(); // Reset the input_file state
_wremove(output_file3_path.c_str());
input_file.open(output_file3_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
} // namespace windows
} // namespace nearby
} // namespace location
@@ -44,6 +44,7 @@
#include "internal/platform/implementation/windows/condition_variable.h"
#include "internal/platform/implementation/windows/executor.h"
#include "internal/platform/implementation/windows/file.h"
#include "internal/platform/implementation/windows/file_path.h"
#include "internal/platform/implementation/windows/future.h"
#include "internal/platform/implementation/windows/listenable_future.h"
#include "internal/platform/implementation/windows/log_message.h"
@@ -64,126 +65,6 @@ namespace nearby {
namespace api {
namespace {
constexpr absl::string_view kUpOneLevel("/..");
std::string GetDownloadPathInternal(absl::string_view parent_folder,
absl::string_view file_name) {
// Parent_folder and file_name are in UTF8 encoding, we should use wide char
// to handle path in windows to avoid encoding issues.
PWSTR basePath;
SHGetKnownFolderPath(
/*rfid=*/FOLDERID_Downloads,
/*dwFlags=*/0,
/*hToken=*/nullptr,
/*ppszPath=*/&basePath);
std::wstring wide_path(basePath);
std::wstring parent_folder_path =
windows::string_to_wstring(std::string(parent_folder));
std::replace(wide_path.begin(), wide_path.end(), L'\\', L'/');
// If parent_folder starts with a \\ or /, then strip it
while (!parent_folder_path.empty() && (*parent_folder_path.begin() == L'\\' ||
*parent_folder_path.begin() == L'/')) {
parent_folder_path.erase(0, 1);
}
// If parent_folder ends with a \\ or /, then strip it
while (!parent_folder_path.empty() &&
(*parent_folder_path.rbegin() == L'\\' ||
*parent_folder_path.rbegin() == L'/')) {
parent_folder_path.erase(parent_folder_path.size() - 1, 1);
}
std::wstring file_name_path =
windows::string_to_wstring(std::string(file_name));
// If file_name starts with a \\, then strip it
while (!file_name_path.empty() && (*file_name_path.begin() == L'\\' ||
*file_name_path.begin() == L'/')) {
file_name_path.erase(0, 1);
}
// If file_name ends with a \\, then strip it
while (!file_name_path.empty() && (*file_name_path.rbegin() == L'\\' ||
*file_name_path.rbegin() == L'/')) {
file_name_path.erase(file_name_path.size() - 1, 1);
}
CoTaskMemFree(basePath);
std::wstring path;
if (parent_folder_path.empty()) {
path =
file_name_path.empty() ? wide_path : wide_path + L"/" + file_name_path;
} else {
path = file_name_path.empty() ? parent_folder_path
: parent_folder_path + L"/" + file_name_path;
}
// Convert to UTF8 format.
return windows::wstring_to_string(path);
}
void SanitizePath(std::string& path) {
size_t pos = std::string::npos;
// Search for the substring in string in a loop until nothing is found
while ((pos = path.find(kUpOneLevel.data())) != std::string::npos) {
// If found then erase it from string
path.erase(pos, kUpOneLevel.size());
}
}
// If the file already exists we add " (x)", where x is an incrementing number,
// starting at 1, using the next non-existing number, to the file name, just
// before the first dot, or at the end if no dot. The absolute path is returned.
std::string CreateOutputFileWithRename(absl::string_view path) {
// Remove any /..
std::string sanitized_path(path);
std::replace(sanitized_path.begin(), sanitized_path.end(), '\\', '/');
SanitizePath(sanitized_path);
auto last_separator = sanitized_path.find_last_of('/');
std::string folder(sanitized_path.substr(0, last_separator));
std::string file_name(sanitized_path.substr(last_separator));
int count = 0;
// Locate the last dot
auto first = file_name.find_last_of('.');
if (first == std::string::npos) {
first = file_name.size();
}
// Break the string at the dot.
auto file_name1 = file_name.substr(0, first);
auto file_name2 = file_name.substr(first);
// Construct the target file name
std::wstring target(windows::string_to_wstring(sanitized_path));
std::wfstream file;
file.open(target, std::fstream::binary | std::fstream::in);
// While we successfully open the file, keep incrementing the count.
while (!(file.rdstate() & std::ifstream::failbit)) {
file.close();
#undef StrCat
target = windows::string_to_wstring(
absl::StrCat(folder, file_name1, " (", ++count, ")", file_name2));
file.clear();
file.open(target, std::fstream::binary | std::fstream::in);
}
// The above leaves the file open, so close it.
file.close();
return windows::wstring_to_string(target);
}
std::string GetApplicationName(DWORD pid) {
HANDLE handle =
@@ -210,32 +91,24 @@ std::string GetApplicationName(DWORD pid) {
0, just_the_file_name_and_ext.find_last_of('.'));
}
bool FolderExists(const std::string& folder_name) {
DWORD ftyp = GetFileAttributesA(folder_name.c_str());
if (ftyp == INVALID_FILE_ATTRIBUTES) {
return false; // something is wrong with your path!
}
if (ftyp & FILE_ATTRIBUTE_DIRECTORY) {
return true;
} // this is a directory!
return false; // this is not a directory!
}
} // namespace
std::string ImplementationPlatform::GetDownloadPath(
absl::string_view parent_folder, absl::string_view file_name) {
return CreateOutputFileWithRename(
GetDownloadPathInternal(parent_folder, file_name));
auto parent = windows::string_to_wstring(std::string(parent_folder));
auto file = windows::string_to_wstring(std::string(file_name));
return windows::wstring_to_string(
windows::FilePath::GetDownloadPath(parent, file));
}
std::string ImplementationPlatform::GetDownloadPath(
absl::string_view file_name) {
std::string fake_parent_path;
return GetDownloadPathInternal(fake_parent_path, file_name);
std::wstring fake_parent_path;
auto file = windows::string_to_wstring(std::string(file_name));
return windows::wstring_to_string(
windows::FilePath::GetDownloadPath(fake_parent_path, file));
}
std::string ImplementationPlatform::GetAppDataPath(
@@ -327,6 +200,7 @@ std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(
std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(
absl::string_view file_path) {
// TODO(jfcarroll): the following code should probably be moved to FilePath
std::string path(file_path);
std::string folder_path = path.substr(0, path.find_last_of('/'));
@@ -1,686 +0,0 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/platform.h"
#include <windows.h>
#include <knownfolders.h>
#include <shlobj.h>
#include <string>
#include <fstream>
#include "gtest/gtest.h"
namespace location::nearby::windows {
namespace {
constexpr absl::string_view kFileName("/increment_file_test.txt");
constexpr absl::string_view kFirstIterationFileName(
"/increment_file_test (1).txt");
constexpr absl::string_view kSecondIterationFileName(
"/increment_file_test (2).txt");
constexpr absl::string_view kThirdIterationFileName(
"/increment_file_test (3).txt");
constexpr absl::string_view kNoDotsFileName("/incrementfiletesttxt");
constexpr absl::string_view kOneIterationNoDotsFileName(
"/incrementfiletesttxt (1)");
constexpr absl::string_view kMultipleDotsFileName("/increment.file.test.txt");
constexpr absl::string_view kOneIterationMultipleDotsFileName(
"/increment.file.test (1).txt");
constexpr absl::string_view kImmediateEscape("../");
constexpr absl::string_view kLongEscapeBackSlash("..\\test\\..\\..\\test");
constexpr absl::string_view kTwoLevelFolder("/test/test");
constexpr absl::string_view kLongEscapeSlash("../test/../../test");
constexpr absl::string_view kLongEscapeMixedSlash("../test\\..\\../test");
constexpr absl::string_view kLongEscapeEndingEscape("../test/../../test/..");
constexpr absl::string_view kLongEscapeEndingEscapeWithSlash(
"../test/../../test/../../../");
} // namespace
using ::location::nearby::api::ImplementationPlatform;
// Can't run on google 3, I presume the SHGetKnownFolderPath
// fails.
class ImplementationPlatformTests : public testing::Test {
protected:
// You can define per-test set-up logic as usual.
ImplementationPlatformTests() {
PWSTR basePath;
SHGetKnownFolderPath(
FOLDERID_Downloads, // rfid: A reference to the KNOWNFOLDERID that
// identifies the folder.
0, // dwFlags: Flags that specify special retrieval
// options.
NULL, // hToken: An access token that represents a
// particular user.
&basePath); // ppszPath: When this method returns, contains
// the address of a pointer to a
// null-terminated Unicode string that
// specifies the path of the known
// folder. The calling process is
// responsible for freeing this resource
// once it is no longer needed by
// calling CoTaskMemFree, whether
// SHGetKnownFolderPath succeeds or not.
size_t bufferSize;
wcstombs_s(&bufferSize, NULL, 0, basePath, 0);
default_download_path_.resize(bufferSize - 1, '\0');
wcstombs_s(&bufferSize, default_download_path_.data(), bufferSize, basePath,
_TRUNCATE);
std::replace(default_download_path_.begin(), default_download_path_.end(),
'\\', '/');
}
std::string default_download_path_;
};
TEST_F(ImplementationPlatformTests,
DISABLED_GetDownloadPathWithEmptyStringArguments\
ShouldReturnBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_);
} // NOLINT false lint error here
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithSlashParent\
FolderArgumentsShouldReturnBaseDownloadPath) {
std::string parent_folder("/");
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithBackslashParent\
FolderArgumentsShouldReturnBaseDownloadPath) {
std::string parent_folder("\\");
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithAttemptToEscape\
UsersDownloadFolderShouldReturnDownloadPathNotEscapingUsersDownloadFolder) {
std::string parent_folder(kImmediateEscape);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithBackslashShouldReturnDownloadPath\
NotEscapingUsersDownloadFolder) {
std::string parent_folder(kLongEscapeBackSlash);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_ + kTwoLevelFolder.data());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderShouldReturnDownloadPathNotEscapingUsers\
DownloadFolder) {
std::string parent_folder(kLongEscapeSlash);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_ + kTwoLevelFolder.data());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithMixedSlashShouldReturnDownloadPath\
NotEscapingUsersDownloadFolder) {
std::string parent_folder(kLongEscapeMixedSlash);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_ + kTwoLevelFolder.data());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithEndingEscapeShouldReturnDownload\
PathNotEscapingUsersDownloadFolder) {
std::string parent_folder(kLongEscapeEndingEscape);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_ + kTwoLevelFolder.data());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithMultiple\
AttemptsToEscapeUsersDownloadFolderWithEndingSlashShouldReturnDownloadPathNot\
EscapingUsersDownloadFolder) {
std::string parent_folder(kLongEscapeEndingEscapeWithSlash);
std::string file_name("");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_ + kTwoLevelFolder.data());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithSlashFileName\
ArgumentsShouldReturnBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("/");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, default_download_path_);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithBackslashFile\
NameArgumentsShouldReturnBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("\\");
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
auto result_size = result.size();
auto default_size = default_download_path_.size();
EXPECT_EQ(result, default_download_path_);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
ShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder");
std::string file_name("");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
StartingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::string parent_folder("/test_parent_folder");
std::string file_name("");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
StartingWithBackslashArgumentsShouldReturnParentFolderAppendedToBase\
DownloadPath) {
std::string parent_folder("\\test_parent_folder");
std::string file_name("");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
EndingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder/");
std::string file_name("");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
EndingWithBackslashArguments\
ShouldReturnParentFolderAppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder\\");
std::string file_name("");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithFileName\
BeginningWithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("/test_file_name.name");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithFileName\
BeginningWithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("\\test_file_name.name");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_file_name.name";
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, path.str().c_str());
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithFileNameEnding\
WithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("test_file_name.name/");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithFileNameEnding\
WithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) {
std::string parent_folder("");
std::string file_name("test_file_name.name\\");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolderAnd\
FileNameArgumentsShould\
ReturnParentFolderAndFileNameAppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder");
std::string file_name("test_file_name.name");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithParentFolder\
EndingWithBackslashAndFileNameArgumentsShouldReturnParentFolderAndFileName\
AppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder\\");
std::string file_name("test_file_name.name");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPathWithFileName\
StartingWithBackslashAndParentFolderArgumentsShouldReturnParentFolderAnd\
FileNameAppendedToBaseDownloadPath) {
std::string parent_folder("test_parent_folder");
std::string file_name("\\test_file_name.name");
std::stringstream path("");
path << default_download_path_ << "/"
<< "test_parent_folder"
<< "/"
<< "test_file_name.name";
std::string expected = path.str();
auto result =
ImplementationPlatform::GetDownloadPath(parent_folder, file_name);
EXPECT_EQ(result, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_FileDoesntExist\
ReturnsFileWithPassedName) {
std::string file_name(kFileName);
std::string parent_folder("");
std::string expected(default_download_path_);
expected.append(file_name.c_str());
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_FileExistsReturns\
FileWithIncrementedName) {
std::string file_name(kFileName);
std::string renamed_file_name(kFirstIterationFileName);
std::string parent_folder("");
std::string output_file_path(default_download_path_);
output_file_path.append(file_name);
std::string expected(default_download_path_);
expected.append(renamed_file_name.c_str());
std::ifstream input_file;
std::ofstream output_file;
output_file.open(output_file_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(actual, expected);
// Remove the file and check that it is removed
// File 1
std::remove(output_file_path.c_str());
input_file.open(output_file_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_MultipleFilesExist\
ReturnsNextIncrementedFileName) {
std::ofstream output_file;
std::ifstream input_file;
std::string file_name(kFileName);
std::string first_renamed_file_name(kFirstIterationFileName);
std::string second_renamed_file_name(kSecondIterationFileName);
std::string parent_folder("");
std::string expected(default_download_path_);
expected.append(second_renamed_file_name.c_str());
std::string output_file1_path(default_download_path_);
output_file1_path.append(file_name);
std::string output_file2_path(default_download_path_);
output_file2_path.append(first_renamed_file_name);
// Create the test files
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
output_file.clear();
output_file.open(output_file2_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
// Remove the test files and check that it is removed
// File 1
std::remove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// File 2
std::remove(output_file2_path.c_str());
input_file.clear();
input_file.open(output_file2_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_FileNameContains\
MultipleDotsReturnsIncrementBeforeFirstDot) {
std::ifstream input_file;
std::ofstream output_file;
std::string file_name(kMultipleDotsFileName);
std::string renamed_file_name(kOneIterationMultipleDotsFileName);
std::string parent_folder("");
std::string output_file1_path(default_download_path_);
output_file1_path.append(file_name);
std::string output_file2_path(default_download_path_);
output_file2_path.append(renamed_file_name);
std::string expected(default_download_path_);
expected.append(renamed_file_name);
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
std::remove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_FileNameContainsNo\
DotsReturnsWithIncrementAtEnd) {
std::ifstream input_file;
std::ofstream output_file;
std::string file_name(kNoDotsFileName);
std::string renamed_file_name(kOneIterationNoDotsFileName);
std::string parent_folder("");
std::string output_file1_path(default_download_path_);
output_file1_path.append(file_name);
std::string output_file2_path(default_download_path_);
output_file2_path.append(renamed_file_name);
std::string expected(default_download_path_);
expected.append(renamed_file_name);
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
std::remove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
TEST_F(ImplementationPlatformTests, DISABLED_GetDownloadPath_FileNameExistsWith\
AHoleBetweenRenamedFiles) {
std::ifstream input_file;
std::ofstream output_file;
std::string file_name(kFileName);
std::string file_name1(kFirstIterationFileName);
std::string file_name2(kSecondIterationFileName);
std::string file_name3(kThirdIterationFileName);
std::string parent_folder("");
// Create the path for the original file name
std::string output_file_path(default_download_path_);
output_file_path.append(
file_name); // Original file name example: "increment_file_test.txt"
// Create the path for the first iteration of the original file name
std::string output_file1_path(default_download_path_);
output_file1_path.append(file_name1); // First iteration on original file
// name example:
// "increment_file_test (1).txt"
// Create the path for the third iteration of the original file name
std::string output_file3_path(default_download_path_);
output_file3_path.append(
file_name3); // Third iteration on original file
// name example: "increment_file_test (3).txt"
// Create the expected result which is the second iteration of the original
// file name
std::string expected(default_download_path_);
expected.append(file_name2); // Second iteration on original file name
// example: "increment_file_test (2).txt"
// Create the original file
output_file.open(output_file_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// Create the first iteration of the original file
output_file.clear();
output_file.open(output_file1_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// Create the third iteration of the original file
output_file.clear();
output_file.open(output_file3_path,
std::ofstream::binary | std::ofstream::out);
ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit);
output_file.close();
// This should return the second iteration of the original file
std::string actual(
ImplementationPlatform::GetDownloadPath(parent_folder, file_name));
EXPECT_EQ(expected, actual);
// Delete the original file
std::remove(output_file_path.c_str());
input_file.open(output_file_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// Delete the first iteration of the original file
input_file.clear(); // Reset the input_file state
std::remove(output_file1_path.c_str());
input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
// Delete the third iteration of the original file
input_file.clear(); // Reset the input_file state
std::remove(output_file3_path.c_str());
input_file.open(output_file3_path, std::ifstream::binary | std::ifstream::in);
ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit);
}
} // namespace location::nearby::windows