// This file Copyright © Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include // for std::sort, std::transform #include // std::array #include // DBL_DIG #include // std::from_chars() #include #include // SIZE_MAX #include // getenv() #include #include #include #include // for std::back_inserter #include #include #include #include #include // std::runtime_error #include #include #include #include #include #ifdef _WIN32 #include /* Sleep(), GetEnvironmentVariable() */ #include /* htonl(), ntohl() */ #include /* CommandLineToArgv() */ #else #include #endif #include #include #include #include "libtransmission/env.h" #include "libtransmission/mime-types.h" #include "libtransmission/serializer.h" #include "libtransmission/string-utils.h" #include "libtransmission/tr-assert.h" #include "libtransmission/tr-strbuf.h" #include "libtransmission/types.h" #include "libtransmission/utils.h" #include "libtransmission/values.h" using namespace std::literals; using namespace tr::Values; time_t tr::detail::tr_time::current_time = {}; // --- namespace tr::Values { // default values; can be overridden by client apps Config::Units Config::memory{ Config::Base::Kibi, "B"sv, "KiB"sv, "MiB"sv, "GiB"sv, "TiB"sv }; Config::Units Config::speed{ Config::Base::Kilo, "B/s"sv, "kB/s"sv, "MB/s"sv, "GB/s"sv, "TB/s"sv }; Config::Units Config::storage{ Config::Base::Kilo, "B"sv, "kB"sv, "MB"sv, "GB"sv, "TB"sv }; } // namespace tr::Values // --- #if defined(_WIN32) && defined(__clang_analyzer__) // See https://github.com/llvm/llvm-project/issues/44701 #define WORKAROUND_CLANG_TIDY_GH44701 #endif std::optional tr_locale_set_global(char const* locale_name) noexcept { #ifndef WORKAROUND_CLANG_TIDY_GH44701 try #endif { return tr_locale_set_global(std::locale{ locale_name }); } #ifndef WORKAROUND_CLANG_TIDY_GH44701 catch (std::runtime_error const&) { return {}; } #endif } std::optional tr_locale_set_global(std::locale const& locale) noexcept { #ifndef WORKAROUND_CLANG_TIDY_GH44701 try #endif { auto old_locale = std::locale::global(locale); std::cout.imbue(std::locale{}); std::cerr.imbue(std::locale{}); return old_locale; } #ifndef WORKAROUND_CLANG_TIDY_GH44701 catch (std::exception const&) { return {}; } #endif } // --- uint64_t tr_time_msec() { return std::chrono::system_clock::now().time_since_epoch() / 1ms; } // --- double tr_getRatio(uint64_t numerator, uint64_t denominator) { if (denominator > 0) { return numerator / static_cast(denominator); } if (numerator > 0) { return TR_RATIO_INF; } return TR_RATIO_NA; } #ifdef _WIN32 namespace { namespace tr_main_win32_impl { std::optional> win32MakeUtf8Argv() { int argc = 0; auto argv = std::vector{}; if (wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); wargv != nullptr) { for (int i = 0; i < argc; ++i) { if (wargv[i] == nullptr) { break; } auto str = tr_win32_native_to_utf8(wargv[i]); if (std::empty(str)) { break; } argv.emplace_back(std::move(str)); } LocalFree(reinterpret_cast(wargv)); } if (static_cast(std::size(argv)) == argc) { return argv; } return {}; } } // namespace tr_main_win32_impl } // namespace int tr_main_win32(int argc, char** argv, int (*real_main)(int, char**)) { using namespace tr_main_win32_impl; SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); // build an argv from GetCommandLineW + CommandLineToArgvW if (auto argv_strs = win32MakeUtf8Argv(); argv_strs) { auto argv_cstrs = std::vector{}; argv_cstrs.reserve(std::size(*argv_strs)); std::transform( std::begin(*argv_strs), std::end(*argv_strs), std::back_inserter(argv_cstrs), [](auto& str) { return std::data(str); }); argv_cstrs.push_back(nullptr); // argv is nullptr-terminated return (*real_main)(std::size(*argv_strs), std::data(argv_cstrs)); } return (*real_main)(argc, argv); } #endif // --- namespace { namespace tr_num_parse_range_impl { struct number_range { int low; int high; }; /** * This should be a single number (ex. "6") or a range (ex. "6-9"). * Anything else is an error and will return failure. */ bool parseNumberSection(std::string_view str, number_range& range) { auto constexpr Delimiter = "-"sv; auto const first = tr_num_parse(str, &str); if (!first) { return false; } range.low = range.high = *first; if (std::empty(str)) { return true; } if (!tr_strv_starts_with(str, Delimiter)) { return false; } str.remove_prefix(std::size(Delimiter)); auto const second = tr_num_parse(str); if (!second) { return false; } range.high = *second; return true; } } // namespace tr_num_parse_range_impl } // namespace /** * Given a string like "1-4" or "1-4,6,9,14-51", this allocates and returns an * array of setmeCount ints of all the values in the array. * For example, "5-8" will return [ 5, 6, 7, 8 ] and setmeCount will be 4. * If a fragment of the string can't be parsed, nullptr is returned. */ std::vector tr_num_parse_range(std::string_view str) { using namespace tr_num_parse_range_impl; auto values = std::vector{}; auto token = std::string_view{}; auto range = number_range{}; while (tr_strv_sep(&str, &token, ',') && parseNumberSection(token, range)) { for (auto i = range.low; i <= range.high; ++i) { values.emplace_back(i); } } std::ranges::sort(values); auto const [erase_first, erase_last] = std::ranges::unique(values); values.erase(erase_first, erase_last); return values; } // --- double tr_truncd(double x, int decimal_places) { auto buf = std::array{}; auto const [out, len] = fmt::format_to_n(std::data(buf), std::size(buf) - 1, "{:.{}f}", x, DBL_DIG); *out = '\0'; if (auto* const pt = strchr(std::data(buf), '.'); pt != nullptr) { pt[decimal_places != 0 ? decimal_places + 1 : 0] = '\0'; } return tr_num_parse(std::data(buf)).value_or(0.0); } std::string tr_strpercent(double x) { if (x < 5.0) { return fmt::format("{:.2Lf}", tr_truncd(x, 2)); } if (x < 100.0) { return fmt::format("{:.1Lf}", tr_truncd(x, 1)); } return fmt::format("{:.0Lf}", x); } std::string tr_strratio(double ratio, std::string_view const none, std::string_view const infinity) { if ((int)ratio == TR_RATIO_NA) { return std::string{ none }; } if ((int)ratio == TR_RATIO_INF) { return std::string{ infinity }; } return tr_strpercent(ratio); } // --- // --- uint64_t tr_htonll(uint64_t hostlonglong) { #ifdef HAVE_HTONLL return htonll(hostlonglong); #else /* fallback code by bdonlan at https://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */ union { std::array lx; uint64_t llx; } u = {}; u.lx[0] = htonl(hostlonglong >> 32); u.lx[1] = htonl(hostlonglong & 0xFFFFFFFFULL); return u.llx; #endif } uint64_t tr_ntohll(uint64_t netlonglong) { #ifdef HAVE_NTOHLL return ntohll(netlonglong); #else /* fallback code by bdonlan at https://stackoverflow.com/questions/809902/64-bit-ntohl-in-c/875505#875505 */ union { std::array lx; uint64_t llx; } u = {}; u.llx = netlonglong; return ((uint64_t)ntohl(u.lx[0]) << 32) | (uint64_t)ntohl(u.lx[1]); #endif } // --- namespace { namespace tr_net_init_impl { class tr_net_init_mgr { private: tr_net_init_mgr() { // try to init curl with default settings (currently ssl support + win32 sockets) // but if that fails, we need to init win32 sockets as a bare minimum if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { curl_global_init(CURL_GLOBAL_WIN32); } } public: tr_net_init_mgr(tr_net_init_mgr const&) = delete; tr_net_init_mgr(tr_net_init_mgr&&) = delete; tr_net_init_mgr& operator=(tr_net_init_mgr const&) = delete; tr_net_init_mgr& operator=(tr_net_init_mgr&&) = delete; ~tr_net_init_mgr() { curl_global_cleanup(); } static void create() { if (!instance) { instance = std::unique_ptr{ new tr_net_init_mgr }; } } private: static std::unique_ptr instance; }; std::unique_ptr tr_net_init_mgr::instance; } // namespace tr_net_init_impl } // namespace void tr_lib_init() { static auto once = std::once_flag{}; std::call_once( once, [] { tr_net_init_impl::tr_net_init_mgr::create(); tr::serializer::Converters::ensure_default_converters(); }); } // --- mime-type std::string_view tr_get_mime_type_for_filename(std::string_view filename) { static auto constexpr Project = [](mime_type_suffix const& entry) { return entry.suffix; }; if (auto const pos = filename.rfind('.'); pos != std::string_view::npos) { auto const suffix_lc = tr_strlower(filename.substr(pos + 1)); auto const it = std::ranges::lower_bound(MimeTypeSuffixes, suffix_lc, {}, Project); if (it != std::end(MimeTypeSuffixes) && suffix_lc == it->suffix) { std::string_view mime_type = it->mime_type; // https://github.com/transmission/transmission/issues/5965#issuecomment-1704421231 // An mp4 file's correct mime-type depends on the codecs used in the file, // which we have no way of inspecting and which might not be downloaded yet. // Let's use `video/mp4` since that's by far the most common use case for torrents. if (mime_type == "application/mp4") { mime_type = "video/mp4"; } return mime_type; } } // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types // application/octet-stream is the default value. // An unknown file type should use this type. auto constexpr Fallback = "application/octet-stream"sv; return Fallback; } // --- tr_num_parse() template [[nodiscard]] std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base) { auto val = T{}; auto const* const begin_ch = std::data(str); auto const* const end_ch = begin_ch + std::size(str); auto const result = std::from_chars(begin_ch, end_ch, val, base); if (result.ec != std::errc{}) { return std::nullopt; } if (remainder != nullptr) { *remainder = str; remainder->remove_prefix(result.ptr - std::data(str)); } return val; } template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template std::optional tr_num_parse(std::string_view str, std::string_view* remainder, int base); template [[nodiscard]] std::optional tr_num_parse(std::string_view str, std::string_view* remainder) { auto const* const begin_ch = std::data(str); auto const* const end_ch = begin_ch + std::size(str); auto val = T{}; auto const result = fast_float::from_chars(begin_ch, end_ch, val); if (result.ec != std::errc{}) { return std::nullopt; } if (remainder != nullptr) { *remainder = str; remainder->remove_prefix(result.ptr - std::data(str)); } return val; } template std::optional tr_num_parse(std::string_view sv, std::string_view* remainder);