// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import SignalCoreKit // TODO: Rework to _not_ extend NSMutableURLRequest. @objcMembers public class TSRequest: NSMutableURLRequest { public var isUDRequest: Bool = false public var shouldHaveAuthorizationHeaders: Bool = true /// If true, an HTTP 401 will trigger a follow up request to see if the account is deregistered. /// If it is, the account will be marked as de-registered. /// /// - Warning: This only applies to REST requests. We handle HTTP 403 errors /// (*not* HTTP 401) for web sockets during the initial handshake, not /// during the processing for individual requests. public var shouldCheckDeregisteredOn401: Bool = false public let parameters: [String: Any] public init(url: URL) { parameters = [:] super.init( url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: OWSRequestFactory.textSecureHTTPTimeOut ) } public init(url: URL, method: String, parameters: [String: Any]?) { owsAssertDebug(method.isEmpty.negated) self.parameters = parameters ?? [:] super.init( url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: OWSRequestFactory.textSecureHTTPTimeOut ) self.httpMethod = method } @objc(requestWithUrl:method:parameters:) public static func request(url: URL, method: String, paramters: [String: Any]?) -> TSRequest { return TSRequest(url: url, method: method, parameters: paramters) } @available(*, unavailable) public override init(url: URL, cachePolicy: NSURLRequest.CachePolicy, timeoutInterval: TimeInterval) { fatalError() } public required init?(coder: NSCoder) { fatalError("init(coder:) unavailable") } // MARK: - Authorization private let authLock = UnfairLock() private var _authUsername: String? public var authUsername: String? { get { owsAssertDebug(shouldHaveAuthorizationHeaders) return authLock.withLock { let result = _authUsername ?? DependenciesBridge.shared.tsAccountManager.storedServerUsernameWithMaybeTransaction owsAssertDebug(result.isEmptyOrNil.negated) return result } } set { owsAssertDebug(shouldHaveAuthorizationHeaders) authLock.withLock { _authUsername = newValue } } } private var _authPassword: String? public var authPassword: String? { get { owsAssertDebug(shouldHaveAuthorizationHeaders) return authLock.withLock { let result = _authPassword ?? DependenciesBridge.shared.tsAccountManager.storedServerAuthTokenWithMaybeTransaction owsAssertDebug(result.isEmptyOrNil.negated) return result } } set { owsAssertDebug(shouldHaveAuthorizationHeaders) authLock.withLock { _authPassword = newValue } } } public enum RedactionStrategy { case none /// Error responses must be separately handled case redactURLForSuccessResponses(replacementString: String = "[REDACTED]") } private var redactionStrategy = RedactionStrategy.none public func applyRedactionStrategy(_ strategy: RedactionStrategy) { self.redactionStrategy = strategy } public func objc_applySuccessResponsesURLRedactionStrategy() { self.applyRedactionStrategy(.redactURLForSuccessResponses()) } public override var description: String { switch redactionStrategy { case .none: return "{ \(self.httpMethod): \(String(describing: self.url)) }" case .redactURLForSuccessResponses(let replacementString): return "{ \(self.httpMethod): \(replacementString) }" } } }