mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
203 lines
7.2 KiB
Swift
203 lines
7.2 KiB
Swift
// Copyright 2022 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import Foundation
|
|
|
|
enum StoragePathError: Error {
|
|
case storagePathError(String)
|
|
}
|
|
|
|
/**
|
|
* Represents a path in GCS, which can be represented as: gs://bucket/path/to/object
|
|
* or http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object?token=<12345>
|
|
* This class also includes helper methods to parse those URI/Ls, as well as to
|
|
* add and remove path segments.
|
|
*/
|
|
internal class StoragePath: NSCopying, Equatable {
|
|
// MARK: Class methods
|
|
|
|
/**
|
|
* Parses a generic string (representing some URI or URL) and returns the appropriate path.
|
|
* @param string String which is parsed into a path.
|
|
* @return Returns an instance of StoragePath.
|
|
* @throws Throws an error if the string is not a valid gs:// URI or http[s]:// URL.
|
|
*/
|
|
static func path(string: String) throws -> StoragePath {
|
|
if string.hasPrefix("gs://") {
|
|
// "gs://bucket/path/to/object"
|
|
return try path(GSURI: string)
|
|
} else if string.hasPrefix("http://") || string.hasPrefix("https://") {
|
|
// "http[s]://firebasestorage.googleapis.com/bucket/path/to/object?signed_url_params"
|
|
return try path(HTTPURL: string)
|
|
} else {
|
|
// Invalid scheme, throw an error!
|
|
throw StoragePathError.storagePathError("Internal error: URL scheme must be one " +
|
|
"of gs://, http://, or https://")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses a gs://bucket/path/to/object URI into a GCS path.
|
|
* @param aURIString gs:// URI which is parsed into a path.
|
|
* @return Returns an instance of StoragePath or nil if one can't be created.
|
|
* @throws Throws an error if the string is not a valid gs:// URI.
|
|
*/
|
|
internal static func path(GSURI aURIString: String) throws -> StoragePath {
|
|
if aURIString.starts(with: "gs://") {
|
|
let bucketObject = aURIString.dropFirst("gs://".count)
|
|
if bucketObject.contains("/") {
|
|
let splitStringArray = bucketObject.split(separator: "/", maxSplits: 1).map(String.init)
|
|
let object = splitStringArray.count == 2 ? splitStringArray[1] : nil
|
|
return StoragePath(with: splitStringArray[0], object: object)
|
|
} else if bucketObject.count > 0 {
|
|
return StoragePath(with: String(bucketObject))
|
|
}
|
|
}
|
|
throw StoragePathError.storagePathError("Internal error: URI must be in the form of " +
|
|
"gs://<bucket>/<path/to/object>")
|
|
}
|
|
|
|
/**
|
|
* Parses a http[s]://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object...?token=<12345>
|
|
* URL into a GCS path.
|
|
* @param aURLString http[s]:// URL which is parsed into a path.
|
|
* string which is parsed into a path.
|
|
* @return Returns an instance of StoragePath or nil if one can't be created.
|
|
* @throws Throws an error if the string is not a valid http[s]:// URL.
|
|
*/
|
|
private static func path(HTTPURL aURLString: String) throws -> StoragePath {
|
|
let httpsURL = URL(string: aURLString)
|
|
let pathComponents = httpsURL?.pathComponents
|
|
guard let pathComponents = pathComponents,
|
|
pathComponents.count >= 4,
|
|
pathComponents[1] == "v0",
|
|
pathComponents[2] == "b" else {
|
|
throw StoragePathError.storagePathError("Internal error: URL must be in the form of " +
|
|
"http[s]://<host>/v0/b/<bucket>/o/<path/to/object>[?token=signed_url_params]")
|
|
}
|
|
let bucketName = pathComponents[3]
|
|
|
|
guard pathComponents.count > 4 else {
|
|
return StoragePath(with: bucketName)
|
|
}
|
|
// Construct object name
|
|
var objectName = pathComponents[5]
|
|
for i in 6 ..< pathComponents.count {
|
|
objectName = "\(objectName)/\(pathComponents[i])"
|
|
}
|
|
return StoragePath(with: bucketName, object: objectName)
|
|
}
|
|
|
|
// Removes leading and trailing slashes, and compresses multiple slashes
|
|
// to create a canonical representation.
|
|
// Example: /foo//bar///baz//// -> foo/bar/baz
|
|
private static func standardizedPathForString(_ string: String) -> String {
|
|
var output = string
|
|
while true {
|
|
let newOutput = output.replacingOccurrences(of: "//", with: "/")
|
|
if newOutput == output {
|
|
break
|
|
}
|
|
output = newOutput
|
|
}
|
|
return output.trimmingCharacters(in: ["/"])
|
|
}
|
|
|
|
// MARK: - Internal Implementations
|
|
|
|
/**
|
|
* The GCS bucket in the path.
|
|
*/
|
|
internal let bucket: String
|
|
|
|
/**
|
|
* The GCS object in the path.
|
|
*/
|
|
internal let object: String?
|
|
|
|
/**
|
|
* Constructs an StoragePath object that represents the given bucket and object.
|
|
* @param bucket The name of the bucket.
|
|
* @param object The name of the object.
|
|
* @return An instance of StoragePath representing the @a bucket and @a object.
|
|
*/
|
|
internal init(with bucket: String,
|
|
object: String? = nil) {
|
|
self.bucket = bucket
|
|
if let object = object {
|
|
self.object = StoragePath.standardizedPathForString(object)
|
|
} else {
|
|
self.object = nil
|
|
}
|
|
}
|
|
|
|
static func == (lhs: StoragePath, rhs: StoragePath) -> Bool {
|
|
return lhs.bucket == rhs.bucket && lhs.object == rhs.object
|
|
}
|
|
|
|
internal func copy(with zone: NSZone? = nil) -> Any {
|
|
return StoragePath(with: bucket, object: object)
|
|
}
|
|
|
|
/**
|
|
* Creates a new path based off of the current path and a string appended to it.
|
|
* Note that all slashes are compressed to a single slash, and leading and trailing slashes
|
|
* are removed.
|
|
* @param path String to append to the current path.
|
|
* @return Returns a new instance of StoragePath with the new path appended.
|
|
*/
|
|
internal func child(_ path: String) -> StoragePath {
|
|
if path.count == 0 {
|
|
return copy() as! StoragePath
|
|
}
|
|
var childObject: String
|
|
if let object = object as? NSString {
|
|
childObject = object.appendingPathComponent(path)
|
|
} else {
|
|
childObject = path
|
|
}
|
|
return StoragePath(with: bucket, object: childObject)
|
|
}
|
|
|
|
/**
|
|
* Creates a new path based off of the current path with the last path segment removed.
|
|
* @return Returns a new instance of StoragePath pointing to the parent path,
|
|
* or nil if the current path points to the root.
|
|
*/
|
|
internal func parent() -> StoragePath? {
|
|
guard let object = object,
|
|
object.count > 0 else {
|
|
return nil
|
|
}
|
|
let parentObject = (object as NSString).deletingLastPathComponent
|
|
return StoragePath(with: bucket, object: parentObject)
|
|
}
|
|
|
|
/**
|
|
* Creates a new path based off of the root of the bucket.
|
|
* @return Returns a new instance of StoragePath pointing to the root of the bucket.
|
|
*/
|
|
internal func root() -> StoragePath {
|
|
return StoragePath(with: bucket)
|
|
}
|
|
|
|
/**
|
|
* Returns a GS URI representing the current path.
|
|
* @return Returns a gs://bucket/path/to/object URI representing the current path.
|
|
*/
|
|
internal func stringValue() -> String {
|
|
return "gs://\(bucket)/\(object ?? "")"
|
|
}
|
|
}
|