diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 2f0ee5626..6f8b67e72 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -402,6 +402,7 @@ C809AEE7291ECFD000BFDBE1 /* NSDataAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C809AEE6291ECFD000BFDBE1 /* NSDataAdditions.mm */; }; C82B30312953337B0001BD6E /* NSDataAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C809AEE6291ECFD000BFDBE1 /* NSDataAdditions.mm */; }; C841A28129197724009F18E8 /* NSKeyedUnarchiverAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */; }; + C843FC8429C51B9400491854 /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = C843FC8329C51B9400491854 /* utils.mm */; }; C86BCD9928228A9600F45599 /* SparkleProxy.mm in Sources */ = {isa = PBXBuildFile; fileRef = C86BCD9828228A9600F45599 /* SparkleProxy.mm */; }; C87369652809984200573C90 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C87369642809984200573C90 /* UserNotifications.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C8748D8A29891EA100D9E979 /* suffixes_dafsa.h in Headers */ = {isa = PBXBuildFile; fileRef = C8748D8929891EA100D9E979 /* suffixes_dafsa.h */; }; @@ -1207,6 +1208,7 @@ C81E411127F5BABD00652F56 /* CocoaCompatibility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaCompatibility.h; sourceTree = ""; }; C841A27F29197724009F18E8 /* NSKeyedUnarchiverAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSKeyedUnarchiverAdditions.h; sourceTree = ""; }; C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSKeyedUnarchiverAdditions.mm; sourceTree = ""; }; + C843FC8329C51B9400491854 /* utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = utils.mm; sourceTree = ""; }; C86BCD9828228A9600F45599 /* SparkleProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SparkleProxy.mm; sourceTree = ""; }; C87369642809984200573C90 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; C8748D8929891EA100D9E979 /* suffixes_dafsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = suffixes_dafsa.h; path = "third-party/suffixes_dafsa.h"; sourceTree = SOURCE_ROOT; }; @@ -1793,6 +1795,7 @@ BEFC1DF30C07861A00B0BB3C /* port-forwarding-upnp.h */, BEFC1DF20C07861A00B0BB3C /* utils.cc */, BEFC1DF10C07861A00B0BB3C /* utils.h */, + C843FC8329C51B9400491854 /* utils.mm */, A25BFD63167BED3B0039D1AA /* variant-benc.cc */, A25BFD64167BED3B0039D1AA /* variant-common.h */, A25BFD65167BED3B0039D1AA /* variant-json.cc */, @@ -3082,6 +3085,7 @@ A220EC5B118C8A060022B4BE /* tr-lpd.cc in Sources */, C1FEE57A1C3223CC00D62832 /* watchdir.cc in Sources */, A23547E211CD0B090046EAE6 /* cache.cc in Sources */, + C843FC8429C51B9400491854 /* utils.mm in Sources */, A284214412DA663E00FBDDBB /* tr-udp.cc in Sources */, C17740D5273A002C00E455D2 /* web-utils.cc in Sources */, A2679294130E00A000CB7464 /* tr-utp.cc in Sources */, diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 3620805b1..6fc5d26da 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -150,6 +150,7 @@ target_sources(${TR_NAME} utils-ev.h utils.cc utils.h + utils.mm variant-benc.cc variant-common.h variant-converters.cc @@ -198,6 +199,7 @@ tr_allow_compile_if( watchdir-kqueue.cc [=[[APPLE]]=] tr-assert.mm + utils.mm [=[[NOT APPLE]]=] tr-assert.cc [=[[WIN32]]=] diff --git a/libtransmission/magnet-metainfo.h b/libtransmission/magnet-metainfo.h index 0cbf7169d..196a391b2 100644 --- a/libtransmission/magnet-metainfo.h +++ b/libtransmission/magnet-metainfo.h @@ -14,6 +14,7 @@ #include "announce-list.h" #include "tr-strbuf.h" // tr_urlbuf +#include "utils.h" // tr_strv_convert_utf8() struct tr_error; struct tr_variant; @@ -69,7 +70,7 @@ public: void setName(std::string_view name) { - name_ = name; + name_ = tr_strv_convert_utf8(name); } void addWebseed(std::string_view webseed); diff --git a/libtransmission/torrent-metainfo.cc b/libtransmission/torrent-metainfo.cc index fe3ca41f0..15929d180 100644 --- a/libtransmission/torrent-metainfo.cc +++ b/libtransmission/torrent-metainfo.cc @@ -329,11 +329,11 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler + +#include +#include + +#include "utils.h" + +// macOS implementation of tr_strv_convert_utf8() that autodetects the encoding. +// This replaces the generic implementation of the function in utils.cc. + +std::string tr_strv_convert_utf8(std::string_view sv) +{ + // UTF-8 encoding + char const* validUTF8 = [[NSString alloc] initWithBytes:std::data(sv) length:std::size(sv) encoding:NSUTF8StringEncoding].UTF8String; + if (validUTF8) + { + return std::string(validUTF8); + } + + // autodetection of the encoding (#3434) + NSString* convertedString; + NSStringEncoding stringEncoding = [NSString + stringEncodingForData:[NSData dataWithBytes:std::data(sv) length:std::size(sv)] + encodingOptions:@{ + // We disallow lossy conversion, and will leave it to `utf8::unchecked::replace_invalid`. + NSStringEncodingDetectionAllowLossyKey : @NO, + // We only set the likely language. + // If we were to set suggested encodings, then whatever is listed first would take precedence on all others, making for instance kCFStringEncodingDOSJapanese (cp932) and kCFStringEncodingDOSRussian (cp866) taking priority on each other. + NSStringEncodingDetectionLikelyLanguageKey : NSLocale.currentLocale.languageCode + } + convertedString:&convertedString + usedLossyConversion:nil]; + if (stringEncoding) + { + validUTF8 = convertedString.UTF8String; + if (validUTF8) + { + return std::string(validUTF8); + } + } + + // invalid encoding + return tr_strv_replace_invalid(sv); +} diff --git a/tests/libtransmission/utils-test.cc b/tests/libtransmission/utils-test.cc index 1f7097f33..ae13eb1af 100644 --- a/tests/libtransmission/utils-test.cc +++ b/tests/libtransmission/utils-test.cc @@ -151,15 +151,15 @@ TEST_F(UtilsTest, strvReplaceInvalid) EXPECT_EQ(out, tr_strv_replace_invalid(out)); } -TEST_F(UtilsTest, strvReplaceInvalidFuzz) +TEST_F(UtilsTest, strvConvertUtf8Fuzz) { auto buf = std::vector{}; for (size_t i = 0; i < 1000; ++i) { buf.resize(tr_rand_int(4096U)); tr_rand_buffer(std::data(buf), std::size(buf)); - auto const out = tr_strv_replace_invalid({ std::data(buf), std::size(buf) }); - EXPECT_EQ(out, tr_strv_replace_invalid(out)); + auto const out = tr_strv_convert_utf8({ std::data(buf), std::size(buf) }); + EXPECT_EQ(out, tr_strv_convert_utf8(out)); } }