From e3f7eb31e4c98cb5a9d387ba42ddfc459a8a2b74 Mon Sep 17 00:00:00 2001 From: Yasura Dodo <11143310+yasuradodo@users.noreply.github.com> Date: Mon, 2 Oct 2023 09:31:59 +0200 Subject: [PATCH] Fix a crash bug at `Client.makeURL` (#1601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crash will happen when you type something unexpected instance URL. Example ```swift let server = "mstdn.jp/" var components = URLComponents() components.scheme = "https" components.host = server components.path = "/api/v1/instance" components.url! // 💥 error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x18c986650). ``` --- Packages/Env/Sources/Env/StreamWatcher.swift | 11 ++++-- Packages/Network/Sources/Network/Client.swift | 34 +++++++++++-------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Packages/Env/Sources/Env/StreamWatcher.swift b/Packages/Env/Sources/Env/StreamWatcher.swift index 0881fc2a..7e162c42 100644 --- a/Packages/Env/Sources/Env/StreamWatcher.swift +++ b/Packages/Env/Sources/Env/StreamWatcher.swift @@ -40,9 +40,14 @@ import Observation } private func connect() { - guard let client else { return } - task = client.makeWebSocketTask(endpoint: Streaming.streaming, instanceStreamingURL: instanceStreamingURL) - task?.resume() + guard let task = try? client?.makeWebSocketTask( + endpoint: Streaming.streaming, + instanceStreamingURL: instanceStreamingURL + ) else { + return + } + self.task = task + self.task?.resume() receiveMessage() } diff --git a/Packages/Network/Sources/Network/Client.swift b/Packages/Network/Sources/Network/Client.swift index f014e9c2..150d3504 100644 --- a/Packages/Network/Sources/Network/Client.swift +++ b/Packages/Network/Sources/Network/Client.swift @@ -18,6 +18,10 @@ import SwiftUI public enum Version: String, Sendable { case v1, v2 } + + public enum ClientError: Error { + case unexpectedRequest + } public enum OauthError: Error { case missingApp @@ -89,8 +93,7 @@ import SwiftUI private func makeURL(scheme: String = "https", endpoint: Endpoint, forceVersion: Version? = nil, - forceServer: String? = nil) -> URL - { + forceServer: String? = nil) throws -> URL { var components = URLComponents() components.scheme = scheme components.host = forceServer ?? server @@ -100,7 +103,10 @@ import SwiftUI components.path += "/api/\(forceVersion?.rawValue ?? version.rawValue)/\(endpoint.path())" } components.queryItems = endpoint.queryItems() - return components.url! + guard let url = components.url else { + throw ClientError.unexpectedRequest + } + return url } private func makeURLRequest(url: URL, endpoint: Endpoint, httpMethod: String) -> URLRequest { @@ -124,8 +130,8 @@ import SwiftUI return request } - private func makeGet(endpoint: Endpoint) -> URLRequest { - let url = makeURL(endpoint: endpoint) + private func makeGet(endpoint: Endpoint) throws -> URLRequest { + let url = try makeURL(endpoint: endpoint) return makeURLRequest(url: url, endpoint: endpoint, httpMethod: "GET") } @@ -134,7 +140,7 @@ import SwiftUI } public func getWithLink(endpoint: Endpoint) async throws -> (Entity, LinkHandler?) { - let (data, httpResponse) = try await urlSession.data(for: makeGet(endpoint: endpoint)) + let (data, httpResponse) = try await urlSession.data(for: try makeGet(endpoint: endpoint)) var linkHandler: LinkHandler? if let response = httpResponse as? HTTPURLResponse, let link = response.allHeaderFields["Link"] as? String @@ -150,14 +156,14 @@ import SwiftUI } public func post(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> HTTPURLResponse? { - let url = makeURL(endpoint: endpoint, forceVersion: forceVersion) + let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "POST") let (_, httpResponse) = try await urlSession.data(for: request) return httpResponse as? HTTPURLResponse } public func patch(endpoint: Endpoint) async throws -> HTTPURLResponse? { - let url = makeURL(endpoint: endpoint) + let url = try makeURL(endpoint: endpoint) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "PATCH") let (_, httpResponse) = try await urlSession.data(for: request) return httpResponse as? HTTPURLResponse @@ -168,7 +174,7 @@ import SwiftUI } public func delete(endpoint: Endpoint, forceVersion: Version? = nil) async throws -> HTTPURLResponse? { - let url = makeURL(endpoint: endpoint, forceVersion: forceVersion) + let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: "DELETE") let (_, httpResponse) = try await urlSession.data(for: request) return httpResponse as? HTTPURLResponse @@ -178,7 +184,7 @@ import SwiftUI method: String, forceVersion: Version? = nil) async throws -> Entity { - let url = makeURL(endpoint: endpoint, forceVersion: forceVersion) + let url = try makeURL(endpoint: endpoint, forceVersion: forceVersion) let request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method) let (data, httpResponse) = try await urlSession.data(for: request) logResponseOnError(httpResponse: httpResponse, data: data) @@ -198,7 +204,7 @@ import SwiftUI public func oauthURL() async throws -> URL { let app: InstanceApp = try await post(endpoint: Apps.registerApp) critical.withLock { $0.oauthApp = app } - return makeURL(endpoint: Oauth.authorize(clientId: app.clientId)) + return try makeURL(endpoint: Oauth.authorize(clientId: app.clientId)) } public func continueOauthFlow(url: URL) async throws -> OauthToken { @@ -217,8 +223,8 @@ import SwiftUI return token } - public func makeWebSocketTask(endpoint: Endpoint, instanceStreamingURL: URL?) -> URLSessionWebSocketTask { - let url = makeURL(scheme: "wss", endpoint: endpoint, forceServer: instanceStreamingURL?.host) + public func makeWebSocketTask(endpoint: Endpoint, instanceStreamingURL: URL?) throws -> URLSessionWebSocketTask { + let url = try makeURL(scheme: "wss", endpoint: endpoint, forceServer: instanceStreamingURL?.host) var subprotocols: [String] = [] if let oauthToken = critical.withLock({ $0.oauthToken }) { subprotocols.append(oauthToken.accessToken) @@ -233,7 +239,7 @@ import SwiftUI filename: String, data: Data) async throws -> Entity { - let url = makeURL(endpoint: endpoint, forceVersion: version) + let url = try makeURL(endpoint: endpoint, forceVersion: version) var request = makeURLRequest(url: url, endpoint: endpoint, httpMethod: method) let boundary = UUID().uuidString request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")