From 9211d9f2e328e704eb3ab19a1eca72d7e4c83455 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 13 Mar 2024 19:39:10 +1000 Subject: [PATCH] Path: Add CreateFileURL() --- src/common-tests/path_tests.cpp | 12 +++- src/common/file_system.cpp | 108 ++++++++++++++++++++++++++++++++ src/common/path.h | 9 +++ src/core/game_list.cpp | 8 +-- src/util/http_downloader.cpp | 70 --------------------- src/util/http_downloader.h | 2 - 6 files changed, 132 insertions(+), 77 deletions(-) diff --git a/src/common-tests/path_tests.cpp b/src/common-tests/path_tests.cpp index 03b307e40..1af1a6cea 100644 --- a/src/common-tests/path_tests.cpp +++ b/src/common-tests/path_tests.cpp @@ -255,4 +255,14 @@ TEST(Path, RealPath) #endif } -#endif \ No newline at end of file +#endif + +TEST(Path, CreateFileURL) +{ +#ifdef _WIN32 + ASSERT_EQ(Path::CreateFileURL("C:\\foo\\bar"), "file:///C:/foo/bar"); + ASSERT_EQ(Path::CreateFileURL("\\\\server\\share\\file.txt"), "file://server/share/file.txt"); +#else + ASSERT_EQ(Path::CreateFileURL("/foo/bar"), "file:///foo/bar"); +#endif +} diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 0a89022a0..e22039dcf 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -745,6 +745,114 @@ std::string Path::Combine(const std::string_view& base, const std::string_view& return ret; } +std::string Path::URLEncode(std::string_view str) +{ + std::string ret; + ret.reserve(str.length() + ((str.length() + 3) / 4) * 3); + + for (size_t i = 0, l = str.size(); i < l; i++) + { + const char c = str[i]; + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') + { + ret.push_back(c); + } + else + { + ret.push_back('%'); + + const unsigned char n1 = static_cast(c) >> 4; + const unsigned char n2 = static_cast(c) & 0x0F; + ret.push_back((n1 >= 10) ? ('a' + (n1 - 10)) : ('0' + n1)); + ret.push_back((n2 >= 10) ? ('a' + (n2 - 10)) : ('0' + n2)); + } + } + + return ret; +} + +std::string Path::URLDecode(std::string_view str) +{ + std::string ret; + ret.reserve(str.length()); + + for (size_t i = 0, l = str.size(); i < l; i++) + { + const char c = str[i]; + if (c == '+') + { + ret.push_back(c); + } + else if (c == '%') + { + if ((i + 2) >= str.length()) + break; + + const char clower = str[i + 1]; + const char cupper = str[i + 2]; + const unsigned char lower = + (clower >= '0' && clower <= '9') ? + static_cast(clower - '0') : + ((clower >= 'a' && clower <= 'f') ? + static_cast(clower - 'a') : + ((clower >= 'A' && clower <= 'F') ? static_cast(clower - 'A') : 0)); + const unsigned char upper = + (cupper >= '0' && cupper <= '9') ? + static_cast(cupper - '0') : + ((cupper >= 'a' && cupper <= 'f') ? + static_cast(cupper - 'a') : + ((cupper >= 'A' && cupper <= 'F') ? static_cast(cupper - 'A') : 0)); + const char dch = static_cast(lower | (upper << 4)); + ret.push_back(dch); + } + else + { + ret.push_back(c); + } + } + + return std::string(str); +} + +std::string Path::CreateFileURL(std::string_view path) +{ + DebugAssert(IsAbsolute(path)); + + std::string ret; + ret.reserve(path.length() + 10); + ret.append("file://"); + + const std::vector components = SplitNativePath(path); + Assert(!components.empty()); + + const std::string_view& first = components.front(); +#ifdef _WIN32 + // Windows doesn't urlencode the drive letter. + // UNC paths should be omit the leading slash. + if (first.starts_with("\\\\")) + { + // file://hostname/... + ret.append(first.substr(2)); + } + else + { + // file:///c:/... + fmt::format_to(std::back_inserter(ret), "/{}", first); + } +#else + // Don't append a leading slash for the first component. + ret.append(first); +#endif + + for (size_t comp = 1; comp < components.size(); comp++) + { + fmt::format_to(std::back_inserter(ret), "/{}", URLEncode(components[comp])); + } + + return ret; +} + std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error* error) { #ifdef _WIN32 diff --git a/src/common/path.h b/src/common/path.h index 12d864719..dff7a04eb 100644 --- a/src/common/path.h +++ b/src/common/path.h @@ -71,4 +71,13 @@ std::string JoinWindowsPath(const std::vector& components); /// Splits a path into its components, only handling native separators. std::vector SplitNativePath(const std::string_view& path); std::string JoinNativePath(const std::vector& components); + +/// URL encodes the specified string. +std::string URLEncode(std::string_view str); + +/// Decodes the specified escaped string. +std::string URLDecode(std::string_view str); + +/// Returns a URL for a given path. The path should be absolute. +std::string CreateFileURL(std::string_view path); } // namespace Path diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 598c7c6c6..8b249c7e6 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1153,14 +1153,14 @@ bool GameList::DownloadCovers(const std::vector& url_templates, boo { std::string url(url_template); if (has_title) - StringUtil::ReplaceAll(&url, "${title}", HTTPDownloader::URLEncode(entry.title)); + StringUtil::ReplaceAll(&url, "${title}", Path::URLEncode(entry.title)); if (has_file_title) { std::string display_name(FileSystem::GetDisplayNameFromPath(entry.path)); - StringUtil::ReplaceAll(&url, "${filetitle}", HTTPDownloader::URLEncode(Path::GetFileTitle(display_name))); + StringUtil::ReplaceAll(&url, "${filetitle}", Path::URLEncode(Path::GetFileTitle(display_name))); } if (has_serial) - StringUtil::ReplaceAll(&url, "${serial}", HTTPDownloader::URLEncode(entry.serial)); + StringUtil::ReplaceAll(&url, "${serial}", Path::URLEncode(entry.serial)); download_urls.emplace_back(entry.path, std::move(url)); } @@ -1201,7 +1201,7 @@ bool GameList::DownloadCovers(const std::vector& url_templates, boo } // we could actually do a few in parallel here... - std::string filename(HTTPDownloader::URLDecode(url)); + std::string filename = Path::URLDecode(url); downloader->CreateRequest( std::move(url), [use_serial, &save_callback, entry_path = std::move(entry_path), filename = std::move(filename)]( s32 status_code, const std::string& content_type, HTTPDownloader::Request::Data data) { diff --git a/src/util/http_downloader.cpp b/src/util/http_downloader.cpp index d24cfe2b9..479bc4cdb 100644 --- a/src/util/http_downloader.cpp +++ b/src/util/http_downloader.cpp @@ -230,76 +230,6 @@ bool HTTPDownloader::HasAnyRequests() return !m_pending_http_requests.empty(); } -std::string HTTPDownloader::URLEncode(const std::string_view& str) -{ - std::string ret; - ret.reserve(str.length() + ((str.length() + 3) / 4) * 3); - - for (size_t i = 0, l = str.size(); i < l; i++) - { - const char c = str[i]; - if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || - c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') - { - ret.push_back(c); - } - else - { - ret.push_back('%'); - - const unsigned char n1 = static_cast(c) >> 4; - const unsigned char n2 = static_cast(c) & 0x0F; - ret.push_back((n1 >= 10) ? ('a' + (n1 - 10)) : ('0' + n1)); - ret.push_back((n2 >= 10) ? ('a' + (n2 - 10)) : ('0' + n2)); - } - } - - return ret; -} - -std::string HTTPDownloader::URLDecode(const std::string_view& str) -{ - std::string ret; - ret.reserve(str.length()); - - for (size_t i = 0, l = str.size(); i < l; i++) - { - const char c = str[i]; - if (c == '+') - { - ret.push_back(c); - } - else if (c == '%') - { - if ((i + 2) >= str.length()) - break; - - const char clower = str[i + 1]; - const char cupper = str[i + 2]; - const unsigned char lower = - (clower >= '0' && clower <= '9') ? - static_cast(clower - '0') : - ((clower >= 'a' && clower <= 'f') ? - static_cast(clower - 'a') : - ((clower >= 'A' && clower <= 'F') ? static_cast(clower - 'A') : 0)); - const unsigned char upper = - (cupper >= '0' && cupper <= '9') ? - static_cast(cupper - '0') : - ((cupper >= 'a' && cupper <= 'f') ? - static_cast(cupper - 'a') : - ((cupper >= 'A' && cupper <= 'F') ? static_cast(cupper - 'A') : 0)); - const char dch = static_cast(lower | (upper << 4)); - ret.push_back(dch); - } - else - { - ret.push_back(c); - } - } - - return std::string(str); -} - std::string HTTPDownloader::GetExtensionForContentType(const std::string& content_type) { // Based on https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types diff --git a/src/util/http_downloader.h b/src/util/http_downloader.h index 45e5b6d24..6302641e6 100644 --- a/src/util/http_downloader.h +++ b/src/util/http_downloader.h @@ -65,8 +65,6 @@ public: virtual ~HTTPDownloader(); static std::unique_ptr Create(std::string user_agent = DEFAULT_USER_AGENT); - static std::string URLEncode(const std::string_view& str); - static std::string URLDecode(const std::string_view& str); static std::string GetExtensionForContentType(const std::string& content_type); void SetTimeout(float timeout);