Skip to content

Commit 2710ea8

Browse files
PM-8018: Fix error if self-hosted URL ends with a trailing slash (#734)
1 parent 072e62c commit 2710ea8

File tree

5 files changed

+49
-9
lines changed

5 files changed

+49
-9
lines changed

BitwardenShared/Core/Platform/Extensions/URL.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,18 @@ extension URL {
9393
}
9494

9595
/// Returns a sanitized version of the URL. This will add a https scheme to the URL if the
96-
/// scheme is missing.
96+
/// scheme is missing and remove a trailing slash.
9797
var sanitized: URL {
98-
guard absoluteString.starts(with: "https://") || absoluteString.starts(with: "http://") else {
99-
return URL(string: "https://" + absoluteString) ?? self
98+
let stringUrl = if absoluteString.hasSuffix("/") {
99+
String(absoluteString.dropLast())
100+
} else {
101+
absoluteString
102+
}
103+
104+
guard stringUrl.starts(with: "https://") || stringUrl.starts(with: "http://") else {
105+
return URL(string: "https://" + stringUrl) ?? self
100106
}
101-
return self
107+
return URL(string: stringUrl) ?? self
102108
}
103109

104110
// MARK: Methods

BitwardenShared/Core/Platform/Extensions/URLTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ class URLTests: BitwardenTestCase {
8282
)
8383
}
8484

85+
/// `sanitized` removes a trailing slash from the URL.
86+
func test_sanitized_trailingSlash() {
87+
XCTAssertEqual(
88+
URL(string: "https://bitwarden.com/")?.sanitized,
89+
URL(string: "https://bitwarden.com")
90+
)
91+
XCTAssertEqual(
92+
URL(string: "example.com/")?.sanitized,
93+
URL(string: "https://example.com")
94+
)
95+
}
96+
8597
/// `sanitized` returns the URL unchanged if it's valid and contains a scheme.
8698
func test_sanitized_validURL() {
8799
XCTAssertEqual(

BitwardenShared/Core/Platform/Models/Domain/EnvironmentUrlData.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ extension EnvironmentUrlData {
109109
// which includes the `#` symbol. Since the `#` character is a critical portion of these urls, we use String
110110
// concatenation to get around this limitation.
111111
if let baseUrl = webVault ?? base,
112-
let url = URL(string: baseUrl.absoluteString.appending("/#/\(additionalPath)")) {
112+
let url = URL(string: baseUrl.sanitized.absoluteString.appending("/#/\(additionalPath)")) {
113113
return url
114114
}
115115
return nil

BitwardenShared/Core/Platform/Models/Domain/EnvironmentUrls.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ extension EnvironmentUrls {
4343
///
4444
init(environmentUrlData: EnvironmentUrlData) {
4545
if let base = environmentUrlData.base {
46-
apiURL = base.appendingPathComponent("/api")
46+
apiURL = base.appendingPathComponent("api")
4747
baseURL = base
48-
eventsURL = base.appendingPathComponent("/events")
49-
iconsURL = base.appendingPathComponent("/icons")
50-
identityURL = base.appendingPathComponent("/identity")
48+
eventsURL = base.appendingPathComponent("events")
49+
iconsURL = base.appendingPathComponent("icons")
50+
identityURL = base.appendingPathComponent("identity")
5151
webVaultURL = base
5252
} else {
5353
apiURL = environmentUrlData.api ?? URL(string: "https://api.bitwarden.com")!

BitwardenShared/Core/Platform/Models/Domain/EnvironmentUrlsTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@ class EnvironmentUrlsTests: BitwardenTestCase {
2727
)
2828
}
2929

30+
/// `init(environmentUrlData:)` sets the URLs from the base URL which includes a trailing slash.
31+
func test_init_environmentUrlData_baseUrlWithTrailingSlash() {
32+
let subject = EnvironmentUrls(
33+
environmentUrlData: EnvironmentUrlData(base: URL(string: "https://example.com/")!)
34+
)
35+
XCTAssertEqual(
36+
subject,
37+
EnvironmentUrls(
38+
apiURL: URL(string: "https://example.com/api")!,
39+
baseURL: URL(string: "https://example.com/")!,
40+
eventsURL: URL(string: "https://example.com/events")!,
41+
iconsURL: URL(string: "https://example.com/icons")!,
42+
identityURL: URL(string: "https://example.com/identity")!,
43+
importItemsURL: URL(string: "https://example.com/#/tools/import")!,
44+
recoveryCodeURL: URL(string: "https://example.com/#/recover-2fa")!,
45+
sendShareURL: URL(string: "https://example.com/#/send")!,
46+
settingsURL: URL(string: "https://example.com/#/settings")!,
47+
webVaultURL: URL(string: "https://example.com/")!
48+
)
49+
)
50+
}
51+
3052
/// `init(environmentUrlData:)` sets the URLs based on the corresponding URL if there isn't a base URL.
3153
func test_init_environmentUrlData_custom() {
3254
let subject = EnvironmentUrls(

0 commit comments

Comments
 (0)