// 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/linux/file_path.h" #include #include #include #include #include #include #include "absl/strings/str_cat.h" #include "internal/platform/implementation/linux/dbus.h" #include "internal/platform/implementation/linux/device_info.h" #include "internal/platform/implementation/linux/utils.h" #include "internal/platform/logging.h" namespace nearby { namespace linux { 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'\\'; std::wstring FilePath::GetCustomSavePath(std::wstring parent_folder, std::wstring file_name) { std::wstring path; path += parent_folder + kPathDelimiter + file_name; return CreateOutputFileWithRename(path); } 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) { DeviceInfo info = DeviceInfo(linux::getSystemBusConnection()); auto nearby_path = info.GetDownloadPath(); std::optional download_path = !nearby_path.IsEmpty() ? std::optional( std::filesystem::path(nearby_path.ToString())) : std::nullopt; std::string base_path; if (!download_path) { const char* home = std::getenv("HOME"); if (home == nullptr) { base_path = "/tmp/Downloads"; // fallback for test environments } else { base_path = home; base_path.append("/Downloads"); } } else { base_path = download_path->string(); } std::wstring wide_path = string_to_wstring(base_path); // 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); } 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; std::wstring file_name; if (last_delimiter == std::wstring::npos) { folder = L""; file_name = sanitized_path; } else { folder = sanitized_path.substr(0, last_delimiter); file_name = sanitized_path.substr(last_delimiter + 1); } // 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(wstring_to_string(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(wstring_to_string(target), std::fstream::binary | std::fstream::in); } if (count > 0) { LOG(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) { // There are no forbidden paths in Linux return str; } 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)); } ReplaceInvalidCharacters(path); } // Legit the only illegal character in Linux char kIllegalFileCharacters[] = {'/'}; void FilePath::ReplaceInvalidCharacters(std::wstring& path) { for (auto& character : path) { // If 0 < character < 32, it's illegal, replace it if (character > 0 && character < 32) { LOG(INFO) << "In path " << wstring_to_string(path) << " replaced \'" << std::string(1, character) << "\' with \'" << std::string(1, kReplacementChar); character = kReplacementChar; } for (auto illegal_character : kIllegalFileCharacters) { if (character == illegal_character) { LOG(INFO) << "In path " << wstring_to_string(path) << " replaced \'" << std::string(1, character) << "\' with \'" << std::string(1, kReplacementChar); character = kReplacementChar; } } } } } // namespace linux } // namespace nearby