Skip to content

Commit f7a769b

Browse files
authored
Percent encode, URLEncodedForm using FoundationEssentials (#639)
* Add percentEncode/Decode from Foundation Essentials * Use percent encoding in URLEncodedForms * Use new percent encode/decode functions * Use Swift 6.0 ISO8601 format parser/style if available * Add NOTICE.txt * Remove URLEncoded DateDecodingStrategy.formatted * import FoundationEssentials in URLEncodedForm code * Copyright header * Remove breaking change
1 parent a4cc469 commit f7a769b

File tree

12 files changed

+555
-73
lines changed

12 files changed

+555
-73
lines changed

NOTICE.txt

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
The Hummingbird Project
2+
====================
3+
4+
Please visit the Hummingbird web site for more information:
5+
6+
* https://hummingbird.codes
7+
8+
Copyright 2024 The Hummingbird Project
9+
10+
The Hummingbird Project licenses this file to you under the Apache License,
11+
version 2.0 (the "License"); you may not use this file except in compliance
12+
with the License. You may obtain a copy of the License at:
13+
14+
https://www.apache.org/licenses/LICENSE-2.0
15+
16+
Unless required by applicable law or agreed to in writing, software
17+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19+
License for the specific language governing permissions and limitations
20+
under the License.
21+
22+
-------------------------------------------------------------------------------
23+
24+
This product contains code from swift-foundation.
25+
26+
* LICENSE (MIT):
27+
* https://github.com/swiftlang/swift-foundation/blob/main/LICENSE.md
28+
* HOMEPAGE:
29+
* https://github.com/swiftlang/swift-foundation/
30+

Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedForm.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
#if canImport(FoundationEssentials)
16+
import FoundationEssentials
17+
#else
1518
import Foundation
19+
#endif
1620

1721
internal enum URLEncodedForm {
1822
/// CodingKey used by URLEncodedFormEncoder and URLEncodedFormDecoder
@@ -43,15 +47,12 @@ internal enum URLEncodedForm {
4347
fileprivate static let `super` = Key(stringValue: "super")!
4448
}
4549

46-
/// ASCII characters that will not be percent encoded in URL encoded form data
47-
static let unreservedCharacters = CharacterSet(
48-
charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"
49-
)
50-
50+
#if compiler(<6.0)
5151
/// ISO8601 data formatter used throughout URL encoded form code
5252
static var iso8601Formatter: ISO8601DateFormatter {
5353
let formatter = ISO8601DateFormatter()
5454
formatter.formatOptions = .withInternetDateTime
5555
return formatter
5656
}
57+
#endif
5758
}

Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedFormDecoder.swift

+11-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Hummingbird server framework project
44
//
5-
// Copyright (c) 2021-2021 the Hummingbird authors
5+
// Copyright (c) 2021-2024 the Hummingbird authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -634,15 +634,17 @@ extension _URLEncodedFormDecoder {
634634
let seconds = try unbox(node, as: Double.self)
635635
return Date(timeIntervalSince1970: seconds)
636636
case .iso8601:
637-
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
638-
let dateString = try unbox(node, as: String.self)
639-
guard let date = URLEncodedForm.iso8601Formatter.date(from: dateString) else {
640-
throw DecodingError.dataCorrupted(.init(codingPath: self.codingPath, debugDescription: "Invalid date format"))
641-
}
642-
return date
643-
} else {
644-
preconditionFailure("ISO8601DateFormatter is unavailable on this platform")
637+
let dateString = try unbox(node, as: String.self)
638+
#if compiler(>=6.0)
639+
guard let date = try? Date(dateString, strategy: .iso8601) else {
640+
throw DecodingError.dataCorrupted(.init(codingPath: self.codingPath, debugDescription: "Invalid date format"))
645641
}
642+
#else
643+
guard let date = URLEncodedForm.iso8601Formatter.date(from: dateString) else {
644+
throw DecodingError.dataCorrupted(.init(codingPath: self.codingPath, debugDescription: "Invalid date format"))
645+
}
646+
#endif
647+
return date
646648
case .formatted(let formatter):
647649
let dateString = try unbox(node, as: String.self)
648650
guard let date = formatter.date(from: dateString) else {

Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedFormEncoder.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Hummingbird server framework project
44
//
5-
// Copyright (c) 2021-2021 the Hummingbird authors
5+
// Copyright (c) 2021-2024 the Hummingbird authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -330,11 +330,11 @@ extension _URLEncodedFormEncoder {
330330
case .secondsSince1970:
331331
try self.encode(Double(date.timeIntervalSince1970).description)
332332
case .iso8601:
333-
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
334-
try encode(URLEncodedForm.iso8601Formatter.string(from: date))
335-
} else {
336-
preconditionFailure("ISO8601DateFormatter is unavailable on this platform")
337-
}
333+
#if compiler(>=6.0)
334+
try self.encode(date.formatted(.iso8601))
335+
#else
336+
try self.encode(URLEncodedForm.iso8601Formatter.string(from: date))
337+
#endif
338338
case .formatted(let formatter):
339339
try self.encode(formatter.string(from: date))
340340
case .custom(let closure):

Sources/Hummingbird/Codable/URLEncodedForm/URLEncodedFormNode.swift

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Hummingbird server framework project
4+
//
5+
// Copyright (c) 2021-2024 the Hummingbird authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
115
/// Internal representation of URL encoded form data used by both encode and decode
216
enum URLEncodedFormNode: CustomStringConvertible, Equatable {
317
/// holds a value
@@ -30,7 +44,7 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {
3044
let node = Self.map(.init())
3145
for element in split {
3246
if let equals = element.firstIndex(of: "=") {
33-
let before = element[..<equals].removingPercentEncoding
47+
let before = element[..<equals].removingURLPercentEncoding()
3448
let afterEquals = element.index(after: equals)
3549
let after = element[afterEquals...].replacingOccurrences(of: "+", with: " ")
3650
guard let key = before else { throw Error.failedToDecode("Failed to percent decode \(element)") }
@@ -128,12 +142,12 @@ enum URLEncodedFormNode: CustomStringConvertible, Equatable {
128142
}
129143

130144
init?(percentEncoded value: String) {
131-
guard let value = value.removingPercentEncoding else { return nil }
145+
guard let value = value.removingURLPercentEncoding() else { return nil }
132146
self.value = value
133147
}
134148

135149
var percentEncoded: String {
136-
self.value.addingPercentEncoding(withAllowedCharacters: URLEncodedForm.unreservedCharacters) ?? self.value
150+
self.value.addingPercentEncoding(forURLComponent: .queryItem)
137151
}
138152

139153
static func == (lhs: URLEncodedFormNode.NodeValue, rhs: URLEncodedFormNode.NodeValue) -> Bool {

Sources/Hummingbird/Middleware/FileMiddleware.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ where Provider.FileAttributes: FileMiddlewareFileAttributes {
112112
}
113113

114114
// Remove percent encoding from URI path
115-
guard var path = request.uri.path.removingPercentEncoding else {
115+
guard var path = request.uri.path.removingURLPercentEncoding() else {
116116
throw HTTPError(.badRequest, message: "Invalid percent encoding in URL")
117117
}
118118

Sources/HummingbirdCore/Utils/HBParser.swift

+1-43
Original file line numberDiff line numberDiff line change
@@ -602,49 +602,7 @@ extension Parser {
602602

603603
/// percent decode UTF8
604604
public func percentDecode() -> String? {
605-
struct DecodeError: Swift.Error {}
606-
func _percentDecode(_ original: ArraySlice<UInt8>, _ bytes: UnsafeMutableBufferPointer<UInt8>) throws -> Int {
607-
var newIndex = 0
608-
var index = original.startIndex
609-
while index < (original.endIndex - 2) {
610-
// if we have found a percent sign
611-
if original[index] == 0x25 {
612-
let high = Self.asciiHexValues[Int(original[index + 1])]
613-
let low = Self.asciiHexValues[Int(original[index + 2])]
614-
index += 3
615-
if ((high | low) & 0x80) != 0 {
616-
throw DecodeError()
617-
}
618-
bytes[newIndex] = (high << 4) | low
619-
newIndex += 1
620-
} else {
621-
bytes[newIndex] = original[index]
622-
newIndex += 1
623-
index += 1
624-
}
625-
}
626-
while index < original.endIndex {
627-
bytes[newIndex] = original[index]
628-
newIndex += 1
629-
index += 1
630-
}
631-
return newIndex
632-
}
633-
guard self.index != self.range.endIndex else { return "" }
634-
do {
635-
if #available(macOS 11, macCatalyst 14.0, iOS 14.0, tvOS 14.0, *) {
636-
return try String(unsafeUninitializedCapacity: range.endIndex - index) { bytes -> Int in
637-
try _percentDecode(self.buffer[self.index..<range.endIndex], bytes)
638-
}
639-
} else {
640-
let newBuffer = try [UInt8](unsafeUninitializedCapacity: self.range.endIndex - self.index) { bytes, count in
641-
try count = _percentDecode(self.buffer[self.index..<self.range.endIndex], bytes)
642-
}
643-
return self.makeString(newBuffer)
644-
}
645-
} catch {
646-
return nil
647-
}
605+
String.removingURLPercentEncoding(utf8Buffer: self.buffer[self.index..<self.range.endIndex])
648606
}
649607
}
650608

0 commit comments

Comments
 (0)