Skip to content

Commit 19fc6aa

Browse files
feat: make invalid host fail-fast
1 parent 4a7db28 commit 19fc6aa

File tree

4 files changed

+73
-13
lines changed

4 files changed

+73
-13
lines changed

Example/HCaptcha_Tests/Core/HCaptcha__Config__Tests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,51 @@ class HCaptcha__Config__Tests: XCTestCase {
139139
let actual = config!.actualEndpoint.absoluteString
140140
XCTAssertTrue(actual.contains("pat=off"))
141141
}
142+
143+
func test__Host_Validation__Valid_Hostnames() {
144+
// Test valid hostnames
145+
let validHosts = [
146+
"example.com",
147+
"subdomain.example.com",
148+
"localhost",
149+
"127.0.0.1",
150+
"api.hcaptcha.com",
151+
"test-host.com",
152+
"host_with_underscores.com"
153+
]
154+
155+
for host in validHosts {
156+
do {
157+
let config = try HCaptchaConfig(host: host)
158+
XCTAssertEqual(config.host, host)
159+
} catch {
160+
XCTFail("Valid host '\(host)' should not throw error: \(error)")
161+
}
162+
}
163+
}
164+
165+
func test__Host_Validation__Invalid_URLs() {
166+
// Test invalid URLs that should throw error
167+
let invalidHosts = [
168+
"https://example.com",
169+
"http://api.hcaptcha.com",
170+
"ftp://test.com",
171+
"example.com/path",
172+
"api.hcaptcha.com/api",
173+
"https://example.com/path",
174+
"http://localhost:8080",
175+
"example.com:8080/path"
176+
]
177+
178+
for host in invalidHosts {
179+
do {
180+
_ = try HCaptchaConfig(host: host)
181+
XCTFail("Invalid host '\(host)' should throw HCaptchaError.invalidHostFormat")
182+
} catch let error as HCaptchaError {
183+
XCTAssertEqual(error, HCaptchaError.invalidHostFormat)
184+
} catch {
185+
XCTFail("HCaptchaError.invalidHostFormat should be thrown, but got: \(error)")
186+
}
187+
}
188+
}
142189
}

Example/HCaptcha_Tests/Helpers/HCaptchaError+Equatable.swift

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ extension HCaptchaError: Equatable {
2020
(.sessionTimeout, .sessionTimeout),
2121
(.rateLimit, .rateLimit),
2222
(.invalidCustomTheme, .invalidCustomTheme),
23-
(.networkError, .networkError):
23+
(.networkError, .networkError),
24+
(.invalidHostFormat, .invalidHostFormat):
2425
return true
2526
case (.unexpected(let lhe as NSError), .unexpected(let rhe as NSError)):
2627
return lhe == rhe
@@ -30,17 +31,19 @@ extension HCaptchaError: Equatable {
3031
}
3132

3233
static func random() -> HCaptchaError {
33-
switch arc4random_uniform(7) {
34-
case 0: return .htmlLoadError
35-
case 1: return .apiKeyNotFound
36-
case 2: return .baseURLNotFound
37-
case 3: return .wrongMessageFormat
38-
case 4: return .failedSetup
39-
case 5: return .sessionTimeout
40-
case 6: return .rateLimit
41-
case 7: return .invalidCustomTheme
42-
case 8: return .networkError
43-
default: return .unexpected(NSError())
44-
}
34+
let cases: [HCaptchaError] = [
35+
.htmlLoadError,
36+
.apiKeyNotFound,
37+
.baseURLNotFound,
38+
.wrongMessageFormat,
39+
.failedSetup,
40+
.sessionTimeout,
41+
.rateLimit,
42+
.invalidCustomTheme,
43+
.invalidHostFormat,
44+
.networkError,
45+
.unexpected(NSError())
46+
]
47+
return cases.randomElement()!
4548
}
4649
}

HCaptcha/Classes/HCaptchaConfig.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ struct HCaptchaConfig: CustomDebugStringConvertible {
234234
}
235235
}
236236

237+
if let host = host, host.localizedStandardContains("/") {
238+
throw HCaptchaError.invalidHostFormat
239+
}
240+
237241
self.html = html
238242
self.apiKey = apiKey
239243
self.passiveApiKey = passiveApiKey ?? false

HCaptcha/Classes/HCaptchaError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public enum HCaptchaError: Error, CustomStringConvertible {
4444
/// Invalid custom theme passed
4545
case invalidCustomTheme
4646

47+
/// Invalid host format - host should be a hostname only, not a full URL
48+
case invalidHostFormat
49+
4750
public static func == (lhs: HCaptchaError, rhs: HCaptchaError) -> Bool {
4851
return lhs.description == rhs.description
4952
}
@@ -86,6 +89,9 @@ public enum HCaptchaError: Error, CustomStringConvertible {
8689

8790
case .invalidCustomTheme:
8891
return "Invalid JSON or JSObject as customTheme"
92+
93+
case .invalidHostFormat:
94+
return "Host parameter should be a hostname only (e.g., 'example.com'), not a full URL"
8995
}
9096
}
9197
}

0 commit comments

Comments
 (0)