Skip to content

Commit a842db4

Browse files
authored
(149532884) Revert strict IPv6 validation in URL (#1258)
1 parent 5156883 commit a842db4

File tree

2 files changed

+7
-45
lines changed

2 files changed

+7
-45
lines changed

Sources/FoundationEssentials/URL/URLParser.swift

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -435,41 +435,6 @@ internal struct RFC3986Parser {
435435
return validate(string: password, component: .password, percentEncodingAllowed: percentEncodingAllowed)
436436
}
437437

438-
private static func isIPvFuture(_ innerHost: some StringProtocol) -> Bool {
439-
// precondition: IP-literal == "[" innerHost [ "%25" zoneID ] "]"
440-
var iter = innerHost.utf8.makeIterator()
441-
guard iter.next() == UInt8(ascii: "v") else { return false }
442-
guard let second = iter.next(), second.isValidHexDigit else { return false }
443-
while let next = iter.next() {
444-
if next.isValidHexDigit { continue }
445-
if next == ._dot { return true }
446-
return false
447-
}
448-
return false
449-
}
450-
451-
/// Only checks that the characters are allowed in an IPv6 address.
452-
/// Does not validate the format of the IPv6 address.
453-
private static func validateIPv6Address(_ address: some StringProtocol) -> Bool {
454-
let isValid = address.utf8.withContiguousStorageIfAvailable {
455-
$0.allSatisfy { $0.isValidHexDigit || $0 == UInt8(ascii: ":") || $0 == UInt8(ascii: ".") }
456-
}
457-
if let isValid {
458-
return isValid
459-
}
460-
#if FOUNDATION_FRAMEWORK
461-
if let fastCharacters = address._ns._fastCharacterContents() {
462-
let charsBuffer = UnsafeBufferPointer(start: fastCharacters, count: address._ns.length)
463-
return charsBuffer.allSatisfy {
464-
guard $0 < 128 else { return false }
465-
let v = UInt8($0)
466-
return v.isValidHexDigit || v == UInt8(ascii: ":") || v == UInt8(ascii: ".")
467-
}
468-
}
469-
#endif
470-
return address.utf8.allSatisfy { $0.isValidHexDigit || $0 == UInt8(ascii: ":") || $0 == UInt8(ascii: ".") }
471-
}
472-
473438
/// Validates an IP-literal host string that has leading and trailing brackets.
474439
/// If the host string contains a zone ID delimiter "%", this must be percent encoded to "%25" to be valid.
475440
/// The zone ID may contain any `reg_name` characters, including percent-encoding.
@@ -483,11 +448,7 @@ internal struct RFC3986Parser {
483448

484449
guard let percentIndex = utf8.firstIndex(of: UInt8(ascii: "%")) else {
485450
// There is no zoneID, so the whole innerHost must be the IP-literal address.
486-
if isIPvFuture(innerHost) {
487-
return validate(string: innerHost, component: .hostIPvFuture, percentEncodingAllowed: false)
488-
} else {
489-
return validateIPv6Address(innerHost)
490-
}
451+
return validate(string: innerHost, component: .hostIPvFuture, percentEncodingAllowed: false)
491452
}
492453

493454
// The first "%" in an IP-literal must be the zone ID delimiter.
@@ -503,11 +464,7 @@ internal struct RFC3986Parser {
503464
return false
504465
}
505466

506-
if isIPvFuture(innerHost) {
507-
return validate(string: innerHost[..<percentIndex], component: .hostIPvFuture, percentEncodingAllowed: false) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
508-
} else {
509-
return validateIPv6Address(innerHost[..<percentIndex]) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
510-
}
467+
return validate(string: innerHost[..<percentIndex], component: .hostIPvFuture, percentEncodingAllowed: false) && validate(string: innerHost[innerHost.index(after: twoAfterIndex)...], component: .hostZoneID)
511468
}
512469

513470
private static func validate(host: some StringProtocol, knownIPLiteral: Bool = false) -> Bool {

Tests/FoundationEssentialsTests/URLTests.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,11 @@ final class URLTests : XCTestCase {
884884
XCTAssertEqual(url.host, "fe80::a%100%CustomZone")
885885
XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25100%25CustomZone")
886886
XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%100%CustomZone")
887+
888+
// Make sure an IP-literal with invalid characters `{` and `}`
889+
// returns `nil` even if we can percent-encode the zone-ID.
890+
let invalid = URL(string: "http://[{Invalid}%100%EncodableZone]")
891+
XCTAssertNil(invalid)
887892
}
888893

889894
#if !os(Windows)

0 commit comments

Comments
 (0)