Skip to content

Commit e44a816

Browse files
authored
Merge branch 'main' into mutex
2 parents 32a95f2 + 4f278a0 commit e44a816

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

Sources/HTTPTypesFoundation/HTTPRequest+URL.swift

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import CoreFoundation
1615
import Foundation
1716
import HTTPTypes
1817

18+
#if canImport(CoreFoundation)
19+
import CoreFoundation
20+
#endif // canImport(CoreFoundation)
21+
1922
extension HTTPRequest {
2023
/// The URL of the request synthesized from the scheme, authority, and path pseudo header
2124
/// fields.
@@ -80,6 +83,7 @@ extension URL {
8083
buffer.append(contentsOf: authority)
8184
buffer.append(contentsOf: path)
8285

86+
#if canImport(CoreFoundation)
8387
if let url = buffer.withUnsafeBytes({ buffer in
8488
CFURLCreateAbsoluteURLWithBytes(
8589
kCFAllocatorDefault,
@@ -94,9 +98,14 @@ extension URL {
9498
} else {
9599
return nil
96100
}
101+
#else // canImport(CoreFoundation)
102+
// This initializer does not preserve WHATWG URLs
103+
self.init(string: String(decoding: buffer, as: UTF8.self))
104+
#endif // canImport(CoreFoundation)
97105
}
98106

99107
fileprivate var httpRequestComponents: (scheme: [UInt8], authority: [UInt8]?, path: [UInt8]) {
108+
#if canImport(CoreFoundation)
100109
// CFURL parser based on byte ranges does not unnecessarily percent-encode WHATWG URL
101110
let url = unsafeBitCast(self.absoluteURL as NSURL, to: CFURL.self)
102111
let length = CFURLGetBytes(url, nil, 0)
@@ -133,11 +142,11 @@ extension URL {
133142
let requestPathRange = unionRange(pathRange, queryRange)
134143
if pathRange.length == 0 {
135144
if requestPathRange.length == 0 {
136-
path = Array("/".utf8)
145+
path = [UInt8(ascii: "/")]
137146
} else {
138147
let pathBuffer = bufferSlice(requestPathRange)
139148
path = [UInt8](unsafeUninitializedCapacity: pathBuffer.count + 1) { buffer, initializedCount in
140-
buffer[0] = 0x2F
149+
buffer[0] = UInt8(ascii: "/")
141150
UnsafeMutableRawBufferPointer(UnsafeMutableBufferPointer(rebasing: buffer[1...])).copyMemory(
142151
from: UnsafeRawBufferPointer(pathBuffer)
143152
)
@@ -149,5 +158,52 @@ extension URL {
149158
}
150159
return (scheme, authority, path)
151160
}
161+
#else // canImport(CoreFoundation)
162+
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
163+
let urlString = components.string
164+
else {
165+
fatalError("Invalid URL")
166+
}
167+
168+
guard let schemeRange = components.rangeOfScheme else {
169+
fatalError("Schemeless URL is not supported")
170+
}
171+
let scheme = Array(urlString[schemeRange].utf8)
172+
173+
let authority: [UInt8]?
174+
if let hostRange = components.rangeOfHost {
175+
let authorityRange =
176+
if let portRange = components.rangeOfPort {
177+
hostRange.lowerBound..<portRange.upperBound
178+
} else {
179+
hostRange
180+
}
181+
authority = Array(urlString[authorityRange].utf8)
182+
} else {
183+
authority = nil
184+
}
185+
186+
let pathRange = components.rangeOfPath
187+
let queryRange = components.rangeOfQuery
188+
let requestPathRange: Range<String.Index>?
189+
if let lowerBound = pathRange?.lowerBound ?? queryRange?.lowerBound,
190+
let upperBound = queryRange?.upperBound ?? pathRange?.upperBound
191+
{
192+
requestPathRange = lowerBound..<upperBound
193+
} else {
194+
requestPathRange = nil
195+
}
196+
let path: [UInt8]
197+
if let pathRange, !pathRange.isEmpty, let requestPathRange {
198+
path = Array(urlString[requestPathRange].utf8)
199+
} else {
200+
if let requestPathRange, !requestPathRange.isEmpty {
201+
path = [UInt8(ascii: "/")] + Array(urlString[requestPathRange].utf8)
202+
} else {
203+
path = [UInt8(ascii: "/")]
204+
}
205+
}
206+
return (scheme, authority, path)
207+
#endif // canImport(CoreFoundation)
152208
}
153209
}

Tests/HTTPTypesFoundationTests/HTTPTypesFoundationTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,40 @@ final class HTTPTypesFoundationTests: XCTestCase {
4949
let request5 = HTTPRequest(url: URL(string: "data:,Hello%2C%20World%21")!)
5050
XCTAssertEqual(request5.scheme, "data")
5151
XCTAssertNil(request5.authority)
52+
#if canImport(CoreFoundation)
5253
XCTAssertEqual(request5.path, "/")
54+
#else // canImport(CoreFoundation)
55+
XCTAssertEqual(request5.path, ",Hello%2C%20World%21")
56+
#endif // canImport(CoreFoundation)
5357
XCTAssertNil(request5.url)
5458
}
5559

60+
func testRequestURLAuthorityParsing() {
61+
let request1 = HTTPRequest(url: URL(string: "https://[::1]")!)
62+
XCTAssertEqual(request1.scheme, "https")
63+
XCTAssertEqual(request1.authority, "[::1]")
64+
XCTAssertEqual(request1.path, "/")
65+
XCTAssertEqual(request1.url?.absoluteString, "https://[::1]/")
66+
67+
let request2 = HTTPRequest(url: URL(string: "https://[::1]:443")!)
68+
XCTAssertEqual(request2.scheme, "https")
69+
XCTAssertEqual(request2.authority, "[::1]:443")
70+
XCTAssertEqual(request2.path, "/")
71+
XCTAssertEqual(request2.url?.absoluteString, "https://[::1]:443/")
72+
73+
let request3 = HTTPRequest(url: URL(string: "https://127.0.0.1")!)
74+
XCTAssertEqual(request3.scheme, "https")
75+
XCTAssertEqual(request3.authority, "127.0.0.1")
76+
XCTAssertEqual(request3.path, "/")
77+
XCTAssertEqual(request3.url?.absoluteString, "https://127.0.0.1/")
78+
79+
let request4 = HTTPRequest(url: URL(string: "https://127.0.0.1:443")!)
80+
XCTAssertEqual(request4.scheme, "https")
81+
XCTAssertEqual(request4.authority, "127.0.0.1:443")
82+
XCTAssertEqual(request4.path, "/")
83+
XCTAssertEqual(request4.url?.absoluteString, "https://127.0.0.1:443/")
84+
}
85+
5686
func testRequestToFoundation() throws {
5787
let request = HTTPRequest(
5888
method: .get,

0 commit comments

Comments
 (0)