From 7db8a3b14a42ac98798cab08c0bcc5f4c3ddf0db Mon Sep 17 00:00:00 2001 From: alhudz Date: Tue, 9 Jun 2026 16:19:41 +0530 Subject: [PATCH 1/3] bound array access in StoragePath url parsers --- FirebaseStorage/Sources/Internal/StoragePath.swift | 8 +++++--- FirebaseStorage/Tests/Unit/StoragePathTests.swift | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/FirebaseStorage/Sources/Internal/StoragePath.swift b/FirebaseStorage/Sources/Internal/StoragePath.swift index d27149af40e..d4f1bb76849 100644 --- a/FirebaseStorage/Sources/Internal/StoragePath.swift +++ b/FirebaseStorage/Sources/Internal/StoragePath.swift @@ -58,8 +58,10 @@ class StoragePath: NSCopying, Equatable { 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) + if let bucketName = splitStringArray.first { + let object = splitStringArray.count == 2 ? splitStringArray[1] : nil + return StoragePath(with: bucketName, object: object) + } } else if bucketObject.count > 0 { return StoragePath(with: String(bucketObject)) } @@ -88,7 +90,7 @@ class StoragePath: NSCopying, Equatable { } let bucketName = pathComponents[3] - guard pathComponents.count > 4 else { + guard pathComponents.count > 5 else { return StoragePath(with: bucketName) } // Construct object name diff --git a/FirebaseStorage/Tests/Unit/StoragePathTests.swift b/FirebaseStorage/Tests/Unit/StoragePathTests.swift index 3acc075d99e..f6562e7e398 100644 --- a/FirebaseStorage/Tests/Unit/StoragePathTests.swift +++ b/FirebaseStorage/Tests/Unit/StoragePathTests.swift @@ -84,10 +84,23 @@ class StoragePathTests: XCTestCase { XCTAssertEqual(path.object, "#hashtag/no/token") } + func testHTTPURLObjectMarkerWithoutObject() throws { + // "/o" object marker present but no object path; must not index past the + // path components. + let httpURL = "http://firebasestorage.googleapis.com/v0/b/bucket/o" + let path = try StoragePath.path(string: httpURL) + XCTAssertEqual(path.bucket, "bucket") + XCTAssertNil(path.object) + } + func testGSURIThrowsOnNoBucket() { XCTAssertThrowsError(try StoragePath.path(string: "gs://")) } + func testGSURIThrowsOnOnlySlashes() { + XCTAssertThrowsError(try StoragePath.path(string: "gs:///")) + } + func testHTTPURLThrowsOnNoBucket() { XCTAssertThrowsError(try StoragePath.path(string: "http://firebasestorage.googleapis.com/")) } From 77ffe029a0fc13aed6f6b5b0b43d7104a530f595 Mon Sep 17 00:00:00 2001 From: alhudz Date: Tue, 16 Jun 2026 14:42:37 +0530 Subject: [PATCH 2/3] validate /o marker and simplify object lookup in StoragePath parsers --- FirebaseStorage/Sources/Internal/StoragePath.swift | 6 +++++- FirebaseStorage/Tests/Unit/StoragePathTests.swift | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/FirebaseStorage/Sources/Internal/StoragePath.swift b/FirebaseStorage/Sources/Internal/StoragePath.swift index d4f1bb76849..77d43eb7cee 100644 --- a/FirebaseStorage/Sources/Internal/StoragePath.swift +++ b/FirebaseStorage/Sources/Internal/StoragePath.swift @@ -59,7 +59,7 @@ class StoragePath: NSCopying, Equatable { if bucketObject.contains("/") { let splitStringArray = bucketObject.split(separator: "/", maxSplits: 1).map(String.init) if let bucketName = splitStringArray.first { - let object = splitStringArray.count == 2 ? splitStringArray[1] : nil + let object = splitStringArray.dropFirst().first return StoragePath(with: bucketName, object: object) } } else if bucketObject.count > 0 { @@ -93,6 +93,10 @@ class StoragePath: NSCopying, Equatable { guard pathComponents.count > 5 else { return StoragePath(with: bucketName) } + guard pathComponents[4] == "o" else { + throw StoragePathError.storagePathError("Internal error: URL must be in the form of " + + "http[s]:///v0/b//o/[?token=signed_url_params]") + } // Construct object name var objectName = pathComponents[5] for i in 6 ..< pathComponents.count { diff --git a/FirebaseStorage/Tests/Unit/StoragePathTests.swift b/FirebaseStorage/Tests/Unit/StoragePathTests.swift index f6562e7e398..f9119867370 100644 --- a/FirebaseStorage/Tests/Unit/StoragePathTests.swift +++ b/FirebaseStorage/Tests/Unit/StoragePathTests.swift @@ -105,6 +105,13 @@ class StoragePathTests: XCTestCase { XCTAssertThrowsError(try StoragePath.path(string: "http://firebasestorage.googleapis.com/")) } + func testHTTPURLThrowsOnMissingObjectMarker() { + // A path segment in the "/o" marker position that isn't "o" is not a valid + // Storage URL and must throw rather than be parsed as an object. + let httpURL = "http://firebasestorage.googleapis.com/v0/b/bucket/x/path/to/object" + XCTAssertThrowsError(try StoragePath.path(string: httpURL)) + } + func testThrowsOnInvalidScheme() { let ftpURL = "ftp://firebasestorage.googleapis.com/v0/b/bucket/o/path/to/object" XCTAssertThrowsError(try StoragePath.path(string: ftpURL)) From d5f662466f0ef869bfea0e0520210514a48c81f9 Mon Sep 17 00:00:00 2001 From: Alhuda Khan Date: Mon, 29 Jun 2026 12:36:27 +0530 Subject: [PATCH 3/3] validate /o marker before the object-path guard in HTTP parser --- FirebaseStorage/Sources/Internal/StoragePath.swift | 5 ++++- FirebaseStorage/Tests/Unit/StoragePathTests.swift | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/FirebaseStorage/Sources/Internal/StoragePath.swift b/FirebaseStorage/Sources/Internal/StoragePath.swift index 77d43eb7cee..84e12dd893b 100644 --- a/FirebaseStorage/Sources/Internal/StoragePath.swift +++ b/FirebaseStorage/Sources/Internal/StoragePath.swift @@ -90,13 +90,16 @@ class StoragePath: NSCopying, Equatable { } let bucketName = pathComponents[3] - guard pathComponents.count > 5 else { + guard pathComponents.count > 4 else { return StoragePath(with: bucketName) } guard pathComponents[4] == "o" else { throw StoragePathError.storagePathError("Internal error: URL must be in the form of " + "http[s]:///v0/b//o/[?token=signed_url_params]") } + guard pathComponents.count > 5 else { + return StoragePath(with: bucketName) + } // Construct object name var objectName = pathComponents[5] for i in 6 ..< pathComponents.count { diff --git a/FirebaseStorage/Tests/Unit/StoragePathTests.swift b/FirebaseStorage/Tests/Unit/StoragePathTests.swift index f9119867370..d06dbdbb65c 100644 --- a/FirebaseStorage/Tests/Unit/StoragePathTests.swift +++ b/FirebaseStorage/Tests/Unit/StoragePathTests.swift @@ -110,6 +110,11 @@ class StoragePathTests: XCTestCase { // Storage URL and must throw rather than be parsed as an object. let httpURL = "http://firebasestorage.googleapis.com/v0/b/bucket/x/path/to/object" XCTAssertThrowsError(try StoragePath.path(string: httpURL)) + + // Same invalid marker but with no object path (exactly five components) must + // also throw rather than be parsed as a bucket-only path. + let shortHTTPURL = "http://firebasestorage.googleapis.com/v0/b/bucket/x" + XCTAssertThrowsError(try StoragePath.path(string: shortHTTPURL)) } func testThrowsOnInvalidScheme() {