Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Network/OWSURLBuilderUtil.swift
1 views
//
// 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)"
    }
}