From 92ca5688e903cf87add918a030a58b778da0fff7 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Thu, 29 Jun 2023 14:41:06 +0330 Subject: [PATCH 1/5] add `HTTPHeaders.first(name:)` --- Sources/AWSLambdaEvents/Utils/HTTP.swift | 60 +++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/Sources/AWSLambdaEvents/Utils/HTTP.swift b/Sources/AWSLambdaEvents/Utils/HTTP.swift index fd8054e..2ef3d0d 100644 --- a/Sources/AWSLambdaEvents/Utils/HTTP.swift +++ b/Sources/AWSLambdaEvents/Utils/HTTP.swift @@ -12,11 +12,69 @@ // //===----------------------------------------------------------------------===// -// MARK: HTTPMethod +// MARK: HTTPHeaders public typealias HTTPHeaders = [String: String] public typealias HTTPMultiValueHeaders = [String: [String]] +extension HTTPHeaders { + /// Retrieves the first value for a given header-field / dictionary-key (`name`) from the block. + /// This method uses case-insensitive comparisons. + /// + /// - Parameter name: The header field name whose first value should be retrieved. + /// - Returns: The first value for the header field name. + public func first(name: String) -> String? { + guard !self.isEmpty else { + return nil + } + + return self.first { header in header.0.isEqualCaseInsensitiveASCIIBytes(to: name) }?.1 + } +} + +extension String { + internal func isEqualCaseInsensitiveASCIIBytes(to: String) -> Bool { + return self.utf8.compareCaseInsensitiveASCIIBytes(to: to.utf8) + } +} + +extension Sequence where Self.Element == UInt8 { + /// Compares the collection of `UInt8`s to a case insensitive collection. + /// + /// This collection could be get from applying the `UTF8View` + /// property on the string protocol. + /// + /// - Parameter bytes: The string constant in the form of a collection of `UInt8` + /// - Returns: Whether the collection contains **EXACTLY** this array or no, but by ignoring case. + internal func compareCaseInsensitiveASCIIBytes(to: T) -> Bool + where T.Element == UInt8 { + // fast path: we can get the underlying bytes of both + let maybeMaybeResult = self.withContiguousStorageIfAvailable { lhsBuffer -> Bool? in + to.withContiguousStorageIfAvailable { rhsBuffer in + if lhsBuffer.count != rhsBuffer.count { + return false + } + + for idx in 0 ..< lhsBuffer.count { + // let's hope this gets vectorised ;) + if lhsBuffer[idx] & 0xdf != rhsBuffer[idx] & 0xdf { + return false + } + } + return true + } + } + + if let maybeResult = maybeMaybeResult, let result = maybeResult { + return result + } else { + return self.elementsEqual(to, by: {return ($0 & 0xdf) == ($1 & 0xdf)}) + } + } +} + +// MARK: HTTPMethod + public struct HTTPMethod: RawRepresentable, Equatable { public var rawValue: String From 9c9bff0c35d692888b7afc807d83cc4b907cf817 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Thu, 29 Jun 2023 14:59:03 +0330 Subject: [PATCH 2/5] use explicit `UTF8View` instead of generic sequence of bytes --- Sources/AWSLambdaEvents/Utils/HTTP.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AWSLambdaEvents/Utils/HTTP.swift b/Sources/AWSLambdaEvents/Utils/HTTP.swift index 2ef3d0d..ae943bd 100644 --- a/Sources/AWSLambdaEvents/Utils/HTTP.swift +++ b/Sources/AWSLambdaEvents/Utils/HTTP.swift @@ -38,7 +38,7 @@ extension String { } } -extension Sequence where Self.Element == UInt8 { +extension String.UTF8View { /// Compares the collection of `UInt8`s to a case insensitive collection. /// /// This collection could be get from applying the `UTF8View` From 9619553c5c7d2aa6b48c9c0adfb44b4fc21476b8 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Thu, 29 Jun 2023 16:00:16 +0330 Subject: [PATCH 3/5] add tests --- .../Utils/HTTPHeadersTests.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift diff --git a/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift b/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift new file mode 100644 index 0000000..50a9730 --- /dev/null +++ b/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AWSLambdaEvents +import XCTest + +class HTTPHeadersTests: XCTestCase { + func first() throws { + let headers: HTTPHeaders = [ + ":method": "GET", + "foo": "bar", + "custom-key": "value-1,value-2" + ] + + XCTAssertEqual(headers.first(name: ":method"), "GET") + XCTAssertEqual(headers.first(name: "Foo"), "bar") + XCTAssertEqual(headers.first(name: "custom-key"), "value-1,value-2") + XCTAssertNil(headers.first(name: "not-present")) + } +} From a366cebbc7b26bb73cddd76c7b8cef4326725483 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Fri, 30 Jun 2023 06:19:57 +0330 Subject: [PATCH 4/5] fix soundness issues --- Sources/AWSLambdaEvents/Utils/HTTP.swift | 9 ++++----- Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/AWSLambdaEvents/Utils/HTTP.swift b/Sources/AWSLambdaEvents/Utils/HTTP.swift index ae943bd..c9901c2 100644 --- a/Sources/AWSLambdaEvents/Utils/HTTP.swift +++ b/Sources/AWSLambdaEvents/Utils/HTTP.swift @@ -34,7 +34,7 @@ extension HTTPHeaders { extension String { internal func isEqualCaseInsensitiveASCIIBytes(to: String) -> Bool { - return self.utf8.compareCaseInsensitiveASCIIBytes(to: to.utf8) + self.utf8.compareCaseInsensitiveASCIIBytes(to: to.utf8) } } @@ -46,8 +46,7 @@ extension String.UTF8View { /// /// - Parameter bytes: The string constant in the form of a collection of `UInt8` /// - Returns: Whether the collection contains **EXACTLY** this array or no, but by ignoring case. - internal func compareCaseInsensitiveASCIIBytes(to: T) -> Bool - where T.Element == UInt8 { + internal func compareCaseInsensitiveASCIIBytes(to: String.UTF8View ) -> Bool { // fast path: we can get the underlying bytes of both let maybeMaybeResult = self.withContiguousStorageIfAvailable { lhsBuffer -> Bool? in to.withContiguousStorageIfAvailable { rhsBuffer in @@ -57,7 +56,7 @@ extension String.UTF8View { for idx in 0 ..< lhsBuffer.count { // let's hope this gets vectorised ;) - if lhsBuffer[idx] & 0xdf != rhsBuffer[idx] & 0xdf { + if lhsBuffer[idx] & 0xDF != rhsBuffer[idx] & 0xDF { return false } } @@ -68,7 +67,7 @@ extension String.UTF8View { if let maybeResult = maybeMaybeResult, let result = maybeResult { return result } else { - return self.elementsEqual(to, by: {return ($0 & 0xdf) == ($1 & 0xdf)}) + return self.elementsEqual(to, by: { ($0 & 0xDF) == ($1 & 0xDF) }) } } } diff --git a/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift b/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift index 50a9730..dd5a4bc 100644 --- a/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift +++ b/Tests/AWSLambdaEventsTests/Utils/HTTPHeadersTests.swift @@ -20,7 +20,7 @@ class HTTPHeadersTests: XCTestCase { let headers: HTTPHeaders = [ ":method": "GET", "foo": "bar", - "custom-key": "value-1,value-2" + "custom-key": "value-1,value-2", ] XCTAssertEqual(headers.first(name: ":method"), "GET") From f524a9ef015d721e68237914c91f3b26a4031839 Mon Sep 17 00:00:00 2001 From: Mahdi Bahrami Date: Fri, 30 Jun 2023 06:20:24 +0330 Subject: [PATCH 5/5] remove extra space --- Sources/AWSLambdaEvents/Utils/HTTP.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AWSLambdaEvents/Utils/HTTP.swift b/Sources/AWSLambdaEvents/Utils/HTTP.swift index c9901c2..39d4939 100644 --- a/Sources/AWSLambdaEvents/Utils/HTTP.swift +++ b/Sources/AWSLambdaEvents/Utils/HTTP.swift @@ -46,7 +46,7 @@ extension String.UTF8View { /// /// - Parameter bytes: The string constant in the form of a collection of `UInt8` /// - Returns: Whether the collection contains **EXACTLY** this array or no, but by ignoring case. - internal func compareCaseInsensitiveASCIIBytes(to: String.UTF8View ) -> Bool { + internal func compareCaseInsensitiveASCIIBytes(to: String.UTF8View) -> Bool { // fast path: we can get the underlying bytes of both let maybeMaybeResult = self.withContiguousStorageIfAvailable { lhsBuffer -> Bool? in to.withContiguousStorageIfAvailable { rhsBuffer in