TM-SGNL-iOS/SignalServiceKit/Network/OWSURLBuilderUtil.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

82 lines
2.7 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public class OWSURLBuilderUtil {
/// Joins a URL path/query/fragment chunk with a base URL.
///
/// Unlike `URL(string:relativeTo:)`, this method will *always* prefer the
/// scheme, host, and port of `baseUrl`. In addition, it will always clear
/// the `user` and `password` components of the URL.
///
/// - Parameters:
/// - urlString:
/// A chunk of a URL, such as "v1/endpoint?name=value". The scheme &
/// host typically aren't present.
///
/// - overrideUrlScheme:
/// A scheme to use instead of the one provided by `baseUrl`. This is
/// typically used when creating a web socket.
///
/// - baseUrl:
/// The URL from which scheme, host, and port are extracted.
class func joinUrl(urlString: String, overrideUrlScheme: String? = nil, baseUrl: URL?) -> URL? {
guard var finalComponents = URLComponents(string: urlString) else {
owsFailDebug("Could not rewrite URL.")
return nil
}
// Never set these.
finalComponents.user = nil
finalComponents.password = nil
if let baseUrl {
// Use scheme, host, and port from baseUrl.
finalComponents.scheme = baseUrl.scheme
finalComponents.host = baseUrl.host
finalComponents.port = baseUrl.port
// But join the two paths together.
finalComponents.path = baseUrl.path.appending(urlPathComponent: finalComponents.path)
}
if let overrideUrlScheme {
// If an explicit scheme is provided, prefer that to baseUrl's scheme.
finalComponents.scheme = overrideUrlScheme
}
// Note that query & fragment are left untouched.
guard let finalUrl = finalComponents.url else {
owsFailDebug("Could not rewrite URL.")
return nil
}
return finalUrl
}
}
private extension String {
/// Joins two URL path components with a "/".
///
/// This method is similar to `NSString.appendingPathComponent`, but there's
/// two subtle yet important differences.
///
/// 1. If `self` is the empty string, the result will start with a "/".
///
/// 2. If `urlPathComponent` ends with "/", the result will end with "/".
func appending(urlPathComponent: String) -> String {
var prefix = self[...]
while prefix.last == "/" {
prefix = prefix.dropLast()
}
var suffix = urlPathComponent[...]
while suffix.first == "/" {
suffix = suffix.dropFirst()
}
return "\(prefix)/\(suffix)"
}
}