diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a7aeec9d5f..85dc9077ff 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -268,8 +268,6 @@ 34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */; }; 34480B551FD0A7A400BC14EF /* DebugLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B4D1FD0A7A300BC14EF /* DebugLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */; }; - 34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 34480B591FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B511FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m */; }; 34480B5B1FD0A7E300BC14EF /* SignalMessaging-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 34480B5A1FD0A7E300BC14EF /* SignalMessaging-Prefix.pch */; }; 344A761124B366F4009D69A5 /* FlagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344A761024B366F4009D69A5 /* FlagsViewController.swift */; }; 344A761324B36C8C009D69A5 /* TestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344A761224B36C8C009D69A5 /* TestingViewController.swift */; }; @@ -1394,6 +1392,7 @@ F9613CDE2981F15700894B55 /* SqliteUtilTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9613CDD2981F15700894B55 /* SqliteUtilTest.swift */; }; F962B38A293F9F1F00765BD8 /* CRC32.swift in Sources */ = {isa = PBXBuildFile; fileRef = F962B389293F9F1F00765BD8 /* CRC32.swift */; }; F962B38C293F9F9F00765BD8 /* CRC32Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = F962B38B293F9F9F00765BD8 /* CRC32Test.swift */; }; + F962FF4929AD0C7C00AFA397 /* OWSScrubbingLogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F962FF4829AD0C7C00AFA397 /* OWSScrubbingLogFormatter.swift */; }; F963164B291AE06C00218FB7 /* OWSScrubbingLogFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F963164A291AE06C00218FB7 /* OWSScrubbingLogFormatterTest.swift */; }; F963F816292D1B5B007DBBBD /* UIButton+SignalUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F963F815292D1B5B007DBBBD /* UIButton+SignalUI.swift */; }; F963F818292D7E53007DBBBD /* FormattedNumberField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F963F817292D7E53007DBBBD /* FormattedNumberField.swift */; }; @@ -2525,8 +2524,6 @@ 34480B381FD092E300BC14EF /* SignalShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Prefix.pch"; sourceTree = ""; }; 34480B4D1FD0A7A300BC14EF /* DebugLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugLogger.h; sourceTree = ""; }; 34480B4E1FD0A7A300BC14EF /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = ""; }; - 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScrubbingLogFormatter.h; sourceTree = ""; }; - 34480B511FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSScrubbingLogFormatter.m; sourceTree = ""; }; 34480B5A1FD0A7E300BC14EF /* SignalMessaging-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalMessaging-Prefix.pch"; sourceTree = ""; }; 344A761024B366F4009D69A5 /* FlagsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlagsViewController.swift; sourceTree = ""; }; 344A761224B36C8C009D69A5 /* TestingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestingViewController.swift; sourceTree = ""; }; @@ -3910,6 +3907,7 @@ F9613CDD2981F15700894B55 /* SqliteUtilTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SqliteUtilTest.swift; sourceTree = ""; }; F962B389293F9F1F00765BD8 /* CRC32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRC32.swift; sourceTree = ""; }; F962B38B293F9F9F00765BD8 /* CRC32Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRC32Test.swift; sourceTree = ""; }; + F962FF4829AD0C7C00AFA397 /* OWSScrubbingLogFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSScrubbingLogFormatter.swift; sourceTree = ""; }; F963164A291AE06C00218FB7 /* OWSScrubbingLogFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSScrubbingLogFormatterTest.swift; sourceTree = ""; }; F963F815292D1B5B007DBBBD /* UIButton+SignalUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+SignalUI.swift"; sourceTree = ""; }; F963F817292D7E53007DBBBD /* FormattedNumberField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedNumberField.swift; sourceTree = ""; }; @@ -5138,8 +5136,7 @@ 346129371FD1B47200532771 /* OWSPreferences.h */, 346129381FD1B47200532771 /* OWSPreferences.m */, 34641E172088D7E900E2EDE5 /* OWSScreenLock.swift */, - 34480B4F1FD0A7A300BC14EF /* OWSScrubbingLogFormatter.h */, - 34480B511FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m */, + F962FF4829AD0C7C00AFA397 /* OWSScrubbingLogFormatter.swift */, 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */, 3406D31D25DBF70400885B14 /* RefreshEvent.swift */, 45360B8C1F9521F800FA666C /* Searcher.swift */, @@ -9143,7 +9140,6 @@ 3464450E22B7F93600A957B1 /* OWSOrphanDataCleaner.h in Headers */, 346129391FD1B47300532771 /* OWSPreferences.h in Headers */, 346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */, - 34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */, 34074F62203D0CBE004596AE /* OWSSounds.h in Headers */, 34612A061FD7238600532771 /* OWSSyncManager.h in Headers */, 34480B5B1FD0A7E300BC14EF /* SignalMessaging-Prefix.pch in Headers */, @@ -10683,7 +10679,7 @@ 346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */, 3470249E2385B6360078D72C /* OWSProfileManager.swift in Sources */, 34641E182088D7E900E2EDE5 /* OWSScreenLock.swift in Sources */, - 34480B591FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m in Sources */, + F962FF4929AD0C7C00AFA397 /* OWSScrubbingLogFormatter.swift in Sources */, 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */, 34612A071FD7238600532771 /* OWSSyncManager.m in Sources */, 885C35502370DFD50004BA35 /* OWSSyncManager.swift in Sources */, diff --git a/Signal/test/util/OWSScrubbingLogFormatterTest.swift b/Signal/test/util/OWSScrubbingLogFormatterTest.swift index b0355873a5..c920635e49 100644 --- a/Signal/test/util/OWSScrubbingLogFormatterTest.swift +++ b/Signal/test/util/OWSScrubbingLogFormatterTest.swift @@ -5,7 +5,9 @@ import XCTest import CocoaLumberjack -import Signal +import SignalCoreKit +import SignalServiceKit +@testable import SignalMessaging final class OWSScrubbingLogFormatterTest: XCTestCase { private var formatter: OWSScrubbingLogFormatter { OWSScrubbingLogFormatter() } @@ -38,6 +40,18 @@ final class OWSScrubbingLogFormatterTest: XCTestCase { rawMessage.substring(from: datePrefixLength) } + func testAttachmentPathScrubbed() { + let testCases: [String] = [ + "/Attachments/", + "/foo/bar/Attachments/abc123.txt", + "Something /foo/bar/Attachments/abc123.txt Something" + ] + + for testCase in testCases { + XCTAssertEqual(format(testCase), "[ REDACTED_CONTAINS_USER_PATH ]") + } + } + func testDataScrubbed_preformatted() { let testCases: [String: String] = [ "<01>": "[ REDACTED_DATA:01... ]", @@ -156,6 +170,51 @@ final class OWSScrubbingLogFormatterTest: XCTestCase { } } + func testGroupIdScrubbed() { + for _ in 1...100 { + let groupIdCount = Bool.random() ? kGroupIdLengthV1 : kGroupIdLengthV2 + let groupId = Randomness.generateRandomBytes(groupIdCount) + let groupIdString = TSGroupThread.defaultThreadId(forGroupId: groupId) + + let expectedOutput = "Hello [ REDACTED_GROUP_ID:...\(groupIdString.suffix(2)) ]!" + + let result = format("Hello \(groupIdString)!") + + XCTAssertTrue( + result.contains(expectedOutput), + "Failed to redact group ID: \(groupIdString). Result was \(result)" + ) + } + } + + func testThingsThatLookLikeGroupIdNotScrubbed() { + let forbiddenBase64Lengths = Set([ + kGroupIdLengthV1.base64Length, + kGroupIdLengthV2.base64Length + ]) + + for _ in 1...100 { + let fakeGroupIdCount: Int32 = { + while true { + let result = Int32.random(in: 1...(kGroupIdLengthV2 * 2)) + if !forbiddenBase64Lengths.contains(result.base64Length) { + return result + } + } + }() + let fakeGroupId = Randomness.generateRandomBytes(fakeGroupIdCount) + let fakeGroupIdString = TSGroupThread.defaultThreadId(forGroupId: fakeGroupId) + let input = "Hello \(fakeGroupIdString)!" + + let result = format(input) + XCTAssertEqual( + stripDate(fromRawMessage: result), + input, + "Should not be affected" + ) + } + } + func testNotScrubbed() { let input = "Some unfiltered string" let result = format(input) @@ -265,3 +324,7 @@ final class OWSScrubbingLogFormatterTest: XCTestCase { } } } + +private extension Int32 { + var base64Length: Int32 { Int32(4 * ceil(Double(self) / 3)) } +} diff --git a/SignalMessaging/SignalMessaging.h b/SignalMessaging/SignalMessaging.h index 8b6a484584..f710e416f8 100644 --- a/SignalMessaging/SignalMessaging.h +++ b/SignalMessaging/SignalMessaging.h @@ -21,7 +21,6 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[]; #import #import #import -#import #import #import #import diff --git a/SignalMessaging/utils/DebugLogger.m b/SignalMessaging/utils/DebugLogger.m index a673f45d61..1909594611 100644 --- a/SignalMessaging/utils/DebugLogger.m +++ b/SignalMessaging/utils/DebugLogger.m @@ -5,10 +5,10 @@ #import "DebugLogger.h" #import "OWSPreferences.h" -#import "OWSScrubbingLogFormatter.h" #import #import #import +#import #import #import #import diff --git a/SignalMessaging/utils/OWSScrubbingLogFormatter.h b/SignalMessaging/utils/OWSScrubbingLogFormatter.h deleted file mode 100644 index a7c2b46767..0000000000 --- a/SignalMessaging/utils/OWSScrubbingLogFormatter.h +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright 2014 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSScrubbingLogFormatter : DDLogFileFormatterDefault - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/OWSScrubbingLogFormatter.m b/SignalMessaging/utils/OWSScrubbingLogFormatter.m deleted file mode 100644 index f266534c68..0000000000 --- a/SignalMessaging/utils/OWSScrubbingLogFormatter.m +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright 2016 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -#import "OWSScrubbingLogFormatter.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSScrubbingLogFormatter - -- (NSRegularExpression *)phoneRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSError *error; - regex = [NSRegularExpression regularExpressionWithPattern:@"\\+\\d{7,12}(\\d{3})" - options:0 - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSRegularExpression *)uuidRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Example: AF112388-9F3D-4EBA-A321-CCE01BA2C85D - NSError *error; - regex = - [NSRegularExpression regularExpressionWithPattern: - @"[\\da-f]{8}\\-[\\da-f]{4}\\-[\\da-f]{4}\\-[\\da-f]{4}\\-[\\da-f]{9}([\\da-f]{3})" - options:NSRegularExpressionCaseInsensitive - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSRegularExpression *)dataRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSError *error; - regex = [NSRegularExpression regularExpressionWithPattern:@"<([\\da-f]{2})[\\da-f]{0,6}( [\\da-f]{2,8})*>" - options:NSRegularExpressionCaseInsensitive - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSRegularExpression *)ios13DataRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSError *error; - regex = [NSRegularExpression - regularExpressionWithPattern:@"\\{length = \\d+, bytes = 0x([\\da-f]{2})[\\.\\da-f ]*\\}" - options:NSRegularExpressionCaseInsensitive - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSRegularExpression *)ipV4AddressRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // NOTE: The group matches the last quad of the IPv4 address. - NSError *error; - regex = [NSRegularExpression regularExpressionWithPattern:@"\\d+\\.\\d+\\.\\d+\\.(\\d+)" - options:NSRegularExpressionCaseInsensitive - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSRegularExpression *)longHexRegex -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Any hex string of 14 chars (7 bytes) or more. - // Example: A321CCE01BA2C85D - NSError *error; - regex = [NSRegularExpression regularExpressionWithPattern:@"[\\da-f]{11,}([\\da-f]{3})" - options:NSRegularExpressionCaseInsensitive - error:&error]; - if (error || !regex) { - OWSFail(@"could not compile regular expression: %@", error); - } - }); - return regex; -} - -- (NSString *__nullable)formatLogMessage:(DDLogMessage *)logMessage -{ - NSString *logString = [super formatLogMessage:logMessage]; - - NSRegularExpression *phoneRegex = self.phoneRegex; - logString = [phoneRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_PHONE_NUMBER:xxx$1 ]"]; - - NSRegularExpression *uuidRegex = self.uuidRegex; - logString = [uuidRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_UUID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx$1 ]"]; - - // We capture only the first two characters of the hex string for logging. - // example log line: "Called someFunction with nsData: <01234567 89abcdef>" - // scrubbed output: "Called someFunction with nsData: [ REDACTED_DATA:01 ]" - NSRegularExpression *dataRegex = self.dataRegex; - logString = [dataRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_DATA:$1... ]"]; - - // On iOS 13, when built with the 13 SDK, NSData's description has changed - // and needs to be scrubbed specifically. - // example log line: "Called someFunction with nsData: {length = 8, bytes = 0x0123456789abcdef}" - // scrubbed output: "Called someFunction with nsData: [ REDACTED_DATA:96 ]" - NSRegularExpression *ios13DataRegex = self.ios13DataRegex; - logString = [ios13DataRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_DATA:$1... ]"]; - - NSRegularExpression *ipV4AddressRegex = self.ipV4AddressRegex; - logString = [ipV4AddressRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_IPV4_ADDRESS:...$1 ]"]; - - NSRegularExpression *longHexRegex = self.longHexRegex; - logString = [longHexRegex stringByReplacingMatchesInString:logString - options:0 - range:NSMakeRange(0, [logString length]) - withTemplate:@"[ REDACTED_HEX:...$1 ]"]; - - return logString; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/OWSScrubbingLogFormatter.swift b/SignalMessaging/utils/OWSScrubbingLogFormatter.swift new file mode 100644 index 0000000000..7fc0c1ccd0 --- /dev/null +++ b/SignalMessaging/utils/OWSScrubbingLogFormatter.swift @@ -0,0 +1,116 @@ +// +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import Foundation +import CocoaLumberjack +import SignalServiceKit + +@objc +class OWSScrubbingLogFormatter: DDLogFileFormatterDefault { + private struct Replacement { + let regex: NSRegularExpression + let replacementTemplate: String + + init( + pattern: String, + options: NSRegularExpression.Options = [], + replacementTemplate: String + ) { + do { + self.regex = try .init(pattern: pattern, options: options) + } catch { + owsFail("Could not compile regular expression: \(error)") + } + self.replacementTemplate = replacementTemplate + } + + init(groupIdLength: Int32) { + let prefix = TSGroupThread.groupThreadUniqueIdPrefix + let groupIdBase64StringLength = groupIdLength.base64Length + + let unredactedSize = 2 + let redactedSize = groupIdBase64StringLength - unredactedSize + + // This assertion exists to prevent someone from updating the values and forgetting to + // update things here. + owsAssert( + prefix == "g" && + groupIdBase64StringLength >= (unredactedSize + 1) && + groupIdBase64StringLength <= 100 + ) + + let base64Pattern = "A-Za-z0-9+/=" + let base64Char = "[\(base64Pattern)]" + let notBase64Char = "[^\(base64Pattern)]" + self.init( + pattern: "(^|\(notBase64Char))\(prefix)\(base64Char){\(redactedSize)}(\(base64Char){\(unredactedSize)})($|\(notBase64Char))", + replacementTemplate: "$1[ REDACTED_GROUP_ID:...$2 ]$3" + ) + } + } + + private let replacements: [Replacement] = [ + .init( + pattern: "\\+\\d{7,12}(\\d{3})", + replacementTemplate: "[ REDACTED_PHONE_NUMBER:xxx$1 ]" + ), + // It's important to redact GV2 IDs first because they're longer. If the shorter IDs were + // first, we'd be left with a bunch of partially-redacted GV2 IDs. + .init(groupIdLength: kGroupIdLengthV2), + .init(groupIdLength: kGroupIdLengthV1), + .init( + pattern: "[\\da-f]{8}\\-[\\da-f]{4}\\-[\\da-f]{4}\\-[\\da-f]{4}\\-[\\da-f]{9}([\\da-f]{3})", + options: .caseInsensitive, + replacementTemplate: "[ REDACTED_UUID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx$1 ]" + ), + .init( + pattern: "<([\\da-f]{2})[\\da-f]{0,6}( [\\da-f]{2,8})*>", + options: .caseInsensitive, + replacementTemplate: "[ REDACTED_DATA:$1... ]" + ), + // On iOS 13, when built with the 13 SDK, NSData's description has changed and needs to be + // scrubbed specifically. + // example log line: "Called someFunction with nsData: {length = 8, bytes = 0x0123456789abcdef}" + // scrubbed output: "Called someFunction with nsData: [ REDACTED_DATA:96 ]" + .init( + pattern: "\\{length = \\d+, bytes = 0x([\\da-f]{2})[\\.\\da-f ]*\\}", + options: .caseInsensitive, + replacementTemplate: "[ REDACTED_DATA:$1... ]" + ), + .init( + pattern: "\\d+\\.\\d+\\.\\d+\\.(\\d+)", + replacementTemplate: "[ REDACTED_IPV4_ADDRESS:...$1 ]" + ), + .init( + pattern: "[\\da-f]{11,}([\\da-f]{3})", + options: .caseInsensitive, + replacementTemplate: "[ REDACTED_HEX:...$1 ]" + ) + ] + + public override func format(message: DDLogMessage) -> String? { + guard var logString = super.format(message: message) else { + return nil + } + + if logString.contains("/Attachments/") { + return "[ REDACTED_CONTAINS_USER_PATH ]" + } + + for replacement in replacements { + logString = replacement.regex.stringByReplacingMatches( + in: logString, + range: logString.entireRange, + withTemplate: replacement.replacementTemplate + ) + } + + return logString + } +} + +private extension Int32 { + var base64Length: Int { Int(4 * ceil(Double(self) / 3)) } +} diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread+OWS.swift b/SignalServiceKit/src/Contacts/Threads/TSGroupThread+OWS.swift index 3cc2e8b318..eba53aedf4 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread+OWS.swift +++ b/SignalServiceKit/src/Contacts/Threads/TSGroupThread+OWS.swift @@ -38,7 +38,7 @@ public extension TSGroupThread { groupMembership.isLocalUserFullMemberAndAdministrator } - private static let groupThreadUniqueIdPrefix = "g" + public static let groupThreadUniqueIdPrefix = "g" private static let uniqueIdMappingStore = SDSKeyValueStore(collection: "TSGroupThread.uniqueIdMappingStore")