From 8ffc556a977fb2a2e4a67b23ca53eaf9b06575e4 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 08:38:16 -0400 Subject: [PATCH 01/37] use insensitive key Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 39 ++++++++++++++++++++++------ test/swift/HeadersBuilderTests.swift | 12 +++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 0217597bd0..73e06b56ba 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,14 +3,37 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - return name == "host" || kRestrictedPrefixes.contains { name.hasPrefix($0) } + return name.lowercased() == "host" || kRestrictedPrefixes.contains { name.lowercased().hasPrefix($0) } } /// Base builder class used to construct `Headers` instances. /// See `{Request|Response}HeadersBuilder` for usage. @objcMembers public class HeadersBuilder: NSObject { - private(set) var headers: [String: [String]] + + struct CaseInsensitiveKey: Hashable { + let name: String + lazy var lowercasedName: String = { self.name.lowercased() }() + + static func == (lhs: CaseInsensitiveKey, rhs: CaseInsensitiveKey) -> Bool { + var lhs = lhs, rhs = rhs + return lhs.lowercasedName == rhs.lowercasedName + } + + func hash(into hasher: inout Hasher) -> Int { + hasher.combine(self.name.hashValue) + return hasher.finalize() + } + } + + private(set) var _headers: [CaseInsensitiveKey: [String]] + + var headers: [String: [String]] { + return Dictionary(uniqueKeysWithValues: _headers.map { key, value in + var k = key + return (k.lowercasedName, value) + }) + } /// Append a value to the header name. /// @@ -24,7 +47,7 @@ public class HeadersBuilder: NSObject { return self } - self.headers[name, default: []].append(value) + self._headers[CaseInsensitiveKey(name: name), default: []].append(value) return self } @@ -40,7 +63,7 @@ public class HeadersBuilder: NSObject { return self } - self.headers[name] = value + self._headers[CaseInsensitiveKey(name: name)] = value return self } @@ -55,7 +78,7 @@ public class HeadersBuilder: NSObject { return self } - self.headers[name] = nil + self._headers[CaseInsensitiveKey(name: name)] = nil return self } @@ -69,14 +92,14 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult func internalSet(name: String, value: [String]) -> Self { - self.headers[name] = value + self._headers[CaseInsensitiveKey(name: name)] = value return self } // Only explicitly implemented to work around a swiftinterface issue in Swift 5.1. This can be // removed once envoy is only built with Swift 5.2+ public override init() { - self.headers = [:] + self._headers = [:] super.init() } @@ -84,7 +107,7 @@ public class HeadersBuilder: NSObject { /// /// - parameter headers: The headers with which to start. required init(headers: [String: [String]]) { - self.headers = headers + self._headers = Dictionary(uniqueKeysWithValues: headers.map { key, value in (CaseInsensitiveKey(name: key), value) }) super.init() } } diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index bf8990026b..129210d8ee 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -78,4 +78,16 @@ final class HeadersBuilderTests: XCTestCase { let headers2 = RequestHeadersBuilder(headers: ["x-foo": ["123"], "x-bar": ["abc"]]).build() XCTAssert(headers1 !== headers2) } + + // func testInitializationIsCaseSensitiveButDoesIgnoresKeysCasingWhenLookingForDuplicates() { + // let headers = HeadersBuilder(headers: ["fOo": ["abc"], "foo": ["123"]]) + // XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers) + // } + + func testInitializationIsCaseInsensitiveOperation() { + let headers = HeadersBuilder(headers: ["foo": ["123"], "fOo": ["abc"]]) + headers.set(name: "fOo", value: ["abd"]) + headers.set(name: "foo", value: ["123"]) + XCTAssertEqual(["foo": ["abc", "123"]], headers.headers) + } } From c3c90155c4e73fbaf7372c0a54a0cfb5cba8bbf2 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 10:04:37 -0400 Subject: [PATCH 02/37] Make HeadersBuilder case insensitive Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 73 +++++++++++++++++++--------- test/swift/HeadersBuilderTests.swift | 38 ++++++++++----- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 73e06b56ba..e2ade1d1ba 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,35 +3,42 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - return name.lowercased() == "host" || kRestrictedPrefixes.contains { name.lowercased().hasPrefix($0) } + return name == "host" || kRestrictedPrefixes.contains { name.lowercased().hasPrefix($0) } } /// Base builder class used to construct `Headers` instances. +/// It preserves the original casing of headers and enforces a case +/// insensitive look up and setting of headers. /// See `{Request|Response}HeadersBuilder` for usage. @objcMembers public class HeadersBuilder: NSObject { + struct KeyValuesPair { + private(set) var key: String + private(set) var values: [String] - struct CaseInsensitiveKey: Hashable { - let name: String - lazy var lowercasedName: String = { self.name.lowercased() }() + init(key: String, values: [String] = []) { + self.key = key + self.values = values + } + + mutating func appendValue(_ value: String) { + self.values.append(value) + } - static func == (lhs: CaseInsensitiveKey, rhs: CaseInsensitiveKey) -> Bool { - var lhs = lhs, rhs = rhs - return lhs.lowercasedName == rhs.lowercasedName + mutating func appendValues(_ values: [String]) { + self.values.append(contentsOf: values) } - func hash(into hasher: inout Hasher) -> Int { - hasher.combine(self.name.hashValue) - return hasher.finalize() + mutating func setValue(_ value: String) { + self.values = [value] } } - private(set) var _headers: [CaseInsensitiveKey: [String]] + private var _headers: [String: KeyValuesPair] var headers: [String: [String]] { - return Dictionary(uniqueKeysWithValues: _headers.map { key, value in - var k = key - return (k.lowercasedName, value) + return Dictionary(uniqueKeysWithValues: _headers.map { _, value in + return (value.key, value.values) }) } @@ -43,11 +50,12 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func add(name: String, value: String) -> Self { - if isRestrictedHeader(name: name) { + let lowercasedName = name.lowercased() + if isRestrictedHeader(name: lowercasedName) { return self } - self._headers[CaseInsensitiveKey(name: name), default: []].append(value) + self._headers[lowercasedName, default: KeyValuesPair(key: name)].appendValue(value) return self } @@ -59,11 +67,12 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func set(name: String, value: [String]) -> Self { - if isRestrictedHeader(name: name) { + let lowercasedName = name.lowercased() + if isRestrictedHeader(name: lowercasedName) { return self } - self._headers[CaseInsensitiveKey(name: name)] = value + self._headers[lowercasedName] = KeyValuesPair(key: name, values: value) return self } @@ -74,11 +83,12 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func remove(name: String) -> Self { - if isRestrictedHeader(name: name) { + let lowercasedName = name.lowercased() + if isRestrictedHeader(name: lowercasedName) { return self } - self._headers[CaseInsensitiveKey(name: name)] = nil + self._headers[lowercasedName] = nil return self } @@ -92,7 +102,7 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult func internalSet(name: String, value: [String]) -> Self { - self._headers[CaseInsensitiveKey(name: name)] = value + self._headers[name.lowercased()] = KeyValuesPair(key: name, values: value) return self } @@ -107,7 +117,26 @@ public class HeadersBuilder: NSObject { /// /// - parameter headers: The headers with which to start. required init(headers: [String: [String]]) { - self._headers = Dictionary(uniqueKeysWithValues: headers.map { key, value in (CaseInsensitiveKey(name: key), value) }) + var processedHeaders = [String: KeyValuesPair]() + for (name, values) in headers { + let lowercasedName = name.lowercased() + /// Dictionaries in Swift are unordered collections. We process headers with keys + /// that are the same when lowercased in an alphabetical order to avoid a situation + /// in which the result of the initialization is underministic i.e., we want + /// "[A: ["1"]", "a: ["2"]]" headers to be always converted to ["A": ["1", "2"]] and + /// never to "a": ["2", "1"]. + /// + /// If a given header name already exists in the processed headers map, check + /// if the currently processed header name is before the existing header name as + /// determined by an alphabetical order. + if let existing = processedHeaders[lowercasedName], existing.key > name { + processedHeaders[lowercasedName] = KeyValuesPair(key: name, values: values + existing.values) + } else { + processedHeaders[lowercasedName, default: KeyValuesPair(key: name)].appendValues(values) + } + } + + self._headers = processedHeaders super.init() } } diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index 129210d8ee..08c1540809 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -40,6 +40,32 @@ final class HeadersBuilderTests: XCTestCase { XCTAssertEqual(["x-foo": ["abc"]], headers) } + func testInitializationIsCaseInsensitivePreservesCasingAndProcessesConflictingHeadersInAlphabeticalOrder() { + let headers = HeadersBuilder(headers: ["a": ["456"], "A": ["123"]]) + XCTAssertEqual(["A": ["123", "456"]], headers.headers) + } + + func testAddingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { + let headers = HeadersBuilder(headers: [:]) + headers.add(name: "fOo", value: "abc") + headers.add(name: "foo", value: "123") + XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers) + } + + func testSettingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { + let headers = HeadersBuilder(headers: [:]) + headers.set(name: "foo", value: ["123"]) + headers.set(name: "fOo", value: ["abc"]) + XCTAssertEqual(["fOo": ["abc"]], headers.headers) + } + + func testRemovingHeaderIsCaseInsensitive() { + let headers = HeadersBuilder(headers: [:]) + headers.set(name: "foo", value: ["123"]) + headers.remove(name: "fOo") + XCTAssertEqual([:], headers.headers) + } + func testRestrictedHeadersAreNotSettable() { let headers = RequestHeadersBuilder(method: .get, authority: "example.com", path: "/") .add(name: "host", value: "example.com") @@ -78,16 +104,4 @@ final class HeadersBuilderTests: XCTestCase { let headers2 = RequestHeadersBuilder(headers: ["x-foo": ["123"], "x-bar": ["abc"]]).build() XCTAssert(headers1 !== headers2) } - - // func testInitializationIsCaseSensitiveButDoesIgnoresKeysCasingWhenLookingForDuplicates() { - // let headers = HeadersBuilder(headers: ["fOo": ["abc"], "foo": ["123"]]) - // XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers) - // } - - func testInitializationIsCaseInsensitiveOperation() { - let headers = HeadersBuilder(headers: ["foo": ["123"], "fOo": ["abc"]]) - headers.set(name: "fOo", value: ["abd"]) - headers.set(name: "foo", value: ["123"]) - XCTAssertEqual(["foo": ["abc", "123"]], headers.headers) - } } From 8137392332fa1d210192043331d750c30801e6bd Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 10:07:07 -0400 Subject: [PATCH 03/37] remove unnecessary call Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index e2ade1d1ba..188ff8d431 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,7 +3,7 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - return name == "host" || kRestrictedPrefixes.contains { name.lowercased().hasPrefix($0) } + return name == "host" || kRestrictedPrefixes.contains { name.hasPrefix($0) } } /// Base builder class used to construct `Headers` instances. From 15f722b222cb7b1638074d700abc1e357d100f9e Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 10:56:49 -0400 Subject: [PATCH 04/37] Update code Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 11 ++++++----- library/swift/RequestHeadersBuilder.swift | 2 +- library/swift/RequestTrailersBuilder.swift | 2 +- library/swift/ResponseHeadersBuilder.swift | 2 +- library/swift/ResponseTrailersBuilder.swift | 2 +- .../swift/grpc/GRPCRequestHeadersBuilder.swift | 2 +- test/swift/HeadersBuilderTests.swift | 18 +++++++++--------- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 188ff8d431..7c359d2c5d 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -36,10 +36,10 @@ public class HeadersBuilder: NSObject { private var _headers: [String: KeyValuesPair] - var headers: [String: [String]] { - return Dictionary(uniqueKeysWithValues: _headers.map { _, value in - return (value.key, value.values) - }) + func headers() -> [String: [String]] { + return Dictionary(uniqueKeysWithValues: self._headers.map { _, value in + return (value.key, value.values) + }) } /// Append a value to the header name. @@ -117,6 +117,7 @@ public class HeadersBuilder: NSObject { /// /// - parameter headers: The headers with which to start. required init(headers: [String: [String]]) { + var processedHeaders = [String: KeyValuesPair]() for (name, values) in headers { let lowercasedName = name.lowercased() @@ -145,6 +146,6 @@ public class HeadersBuilder: NSObject { extension HeadersBuilder { public override func isEqual(_ object: Any?) -> Bool { - return (object as? Self)?.headers == self.headers + return (object as? Self)?.headers() == self.headers() } } diff --git a/library/swift/RequestHeadersBuilder.swift b/library/swift/RequestHeadersBuilder.swift index d36a1b318d..8b4d64cde7 100644 --- a/library/swift/RequestHeadersBuilder.swift +++ b/library/swift/RequestHeadersBuilder.swift @@ -52,6 +52,6 @@ public final class RequestHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of request headers. public func build() -> RequestHeaders { - return RequestHeaders(headers: self.headers) + return RequestHeaders(headers: self.headers()) } } diff --git a/library/swift/RequestTrailersBuilder.swift b/library/swift/RequestTrailersBuilder.swift index b5b855aa00..a521549f2c 100644 --- a/library/swift/RequestTrailersBuilder.swift +++ b/library/swift/RequestTrailersBuilder.swift @@ -12,6 +12,6 @@ public final class RequestTrailersBuilder: HeadersBuilder { /// /// - returns: New instance of request trailers. public func build() -> RequestTrailers { - return RequestTrailers(headers: self.headers) + return RequestTrailers(headers: self.headers()) } } diff --git a/library/swift/ResponseHeadersBuilder.swift b/library/swift/ResponseHeadersBuilder.swift index 56c213d191..970a75ec93 100644 --- a/library/swift/ResponseHeadersBuilder.swift +++ b/library/swift/ResponseHeadersBuilder.swift @@ -23,6 +23,6 @@ public final class ResponseHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of response headers. public func build() -> ResponseHeaders { - return ResponseHeaders(headers: self.headers) + return ResponseHeaders(headers: self.headers()) } } diff --git a/library/swift/ResponseTrailersBuilder.swift b/library/swift/ResponseTrailersBuilder.swift index 34d4a0358d..3e8832e405 100644 --- a/library/swift/ResponseTrailersBuilder.swift +++ b/library/swift/ResponseTrailersBuilder.swift @@ -12,6 +12,6 @@ public final class ResponseTrailersBuilder: HeadersBuilder { /// /// - returns: New instance of response trailers. public func build() -> ResponseTrailers { - return ResponseTrailers(headers: self.headers) + return ResponseTrailers(headers: self.headers()) } } diff --git a/library/swift/grpc/GRPCRequestHeadersBuilder.swift b/library/swift/grpc/GRPCRequestHeadersBuilder.swift index 177ddf9003..6f14926a86 100644 --- a/library/swift/grpc/GRPCRequestHeadersBuilder.swift +++ b/library/swift/grpc/GRPCRequestHeadersBuilder.swift @@ -40,6 +40,6 @@ public final class GRPCRequestHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of request headers. public func build() -> GRPCRequestHeaders { - return GRPCRequestHeaders(headers: self.headers) + return GRPCRequestHeaders(headers: self.headers()) } } diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index 08c1540809..38871a1214 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -10,7 +10,7 @@ final class HeadersBuilderTests: XCTestCase { let headers = HeadersBuilder(headers: [:]) .add(name: "x-foo", value: "1") .add(name: "x-foo", value: "2") - .headers + .headers() XCTAssertEqual(["1", "2"], headers["x-foo"]) } @@ -19,7 +19,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "x-foo", value: "1") .add(name: "x-foo", value: "2") .remove(name: "x-foo") - .headers + .headers() XCTAssertNil(headers["x-foo"]) } @@ -28,7 +28,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "x-foo", value: "123") .add(name: "x-bar", value: "abc") .remove(name: "x-foo") - .headers + .headers() XCTAssertEqual(["x-bar": ["abc"]], headers) } @@ -36,34 +36,34 @@ final class HeadersBuilderTests: XCTestCase { let headers = HeadersBuilder(headers: [:]) .add(name: "x-foo", value: "123") .set(name: "x-foo", value: ["abc"]) - .headers + .headers() XCTAssertEqual(["x-foo": ["abc"]], headers) } func testInitializationIsCaseInsensitivePreservesCasingAndProcessesConflictingHeadersInAlphabeticalOrder() { let headers = HeadersBuilder(headers: ["a": ["456"], "A": ["123"]]) - XCTAssertEqual(["A": ["123", "456"]], headers.headers) + XCTAssertEqual(["A": ["123", "456"]], headers.headers()) } func testAddingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { let headers = HeadersBuilder(headers: [:]) headers.add(name: "fOo", value: "abc") headers.add(name: "foo", value: "123") - XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers) + XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers()) } func testSettingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.set(name: "fOo", value: ["abc"]) - XCTAssertEqual(["fOo": ["abc"]], headers.headers) + XCTAssertEqual(["fOo": ["abc"]], headers.headers()) } func testRemovingHeaderIsCaseInsensitive() { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.remove(name: "fOo") - XCTAssertEqual([:], headers.headers) + XCTAssertEqual([:], headers.headers()) } func testRestrictedHeadersAreNotSettable() { @@ -71,7 +71,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "host", value: "example.com") .set(name: ":scheme", value: ["http"]) .set(name: ":path", value: ["/nope"]) - .headers + .headers() let expected = [ ":authority": ["example.com"], ":path": ["/"], From 3576622670d6873c00e6d55245388c412c0851e5 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 11:13:35 -0400 Subject: [PATCH 05/37] Fix formatting Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 4 ++-- test/swift/HeadersBuilderTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 7c359d2c5d..6e21328234 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -38,7 +38,7 @@ public class HeadersBuilder: NSObject { func headers() -> [String: [String]] { return Dictionary(uniqueKeysWithValues: self._headers.map { _, value in - return (value.key, value.values) + return (value.key, value.values) }) } @@ -122,7 +122,7 @@ public class HeadersBuilder: NSObject { for (name, values) in headers { let lowercasedName = name.lowercased() /// Dictionaries in Swift are unordered collections. We process headers with keys - /// that are the same when lowercased in an alphabetical order to avoid a situation + /// that are the same when lowercased in an alphabetical order to avoid a situation /// in which the result of the initialization is underministic i.e., we want /// "[A: ["1"]", "a: ["2"]]" headers to be always converted to ["A": ["1", "2"]] and /// never to "a": ["2", "1"]. diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index 38871a1214..dfc1cb807d 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -56,14 +56,14 @@ final class HeadersBuilderTests: XCTestCase { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.set(name: "fOo", value: ["abc"]) - XCTAssertEqual(["fOo": ["abc"]], headers.headers()) + XCTAssertEqual(["fOo": ["abc"]], headers.headers()) } func testRemovingHeaderIsCaseInsensitive() { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.remove(name: "fOo") - XCTAssertEqual([:], headers.headers()) + XCTAssertEqual([:], headers.headers()) } func testRestrictedHeadersAreNotSettable() { From f7538f2a07dcaa079f76ea8a3f6336dfee08dd4b Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 11:15:31 -0400 Subject: [PATCH 06/37] update version history Signed-off-by: Rafal Augustyniak --- docs/root/intro/version_history.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 70ad8b26f9..c3f8cd2eab 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,6 +24,7 @@ Bugfixes: - Android: update Kotlin standard libraries to 1.6.21 (:issue:`#2256 <2256>`) - fix bug where finalStreamIntel was not consistently set on cancel (:issue:`#2285 <2285>`) - iOS: fix termination crash in ProvisionalDispatcher (:issue:`#2059 <2059>`) +- iOS: make headers lookup in HeadersBuilder case insensitive (:issue:`#2383 <2383>`) Features: From 516a1fc66ef5ae924b2e4658ab38b9284d7c6331 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 11:21:02 -0400 Subject: [PATCH 07/37] Lint fixes Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 4 ++-- test/swift/HeadersBuilderTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 6e21328234..4d496dfd9b 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -117,7 +117,6 @@ public class HeadersBuilder: NSObject { /// /// - parameter headers: The headers with which to start. required init(headers: [String: [String]]) { - var processedHeaders = [String: KeyValuesPair]() for (name, values) in headers { let lowercasedName = name.lowercased() @@ -131,7 +130,8 @@ public class HeadersBuilder: NSObject { /// if the currently processed header name is before the existing header name as /// determined by an alphabetical order. if let existing = processedHeaders[lowercasedName], existing.key > name { - processedHeaders[lowercasedName] = KeyValuesPair(key: name, values: values + existing.values) + processedHeaders[lowercasedName] = + KeyValuesPair(key: name, values: values + existing.values) } else { processedHeaders[lowercasedName, default: KeyValuesPair(key: name)].appendValues(values) } diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index dfc1cb807d..efa2b793c0 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -40,7 +40,7 @@ final class HeadersBuilderTests: XCTestCase { XCTAssertEqual(["x-foo": ["abc"]], headers) } - func testInitializationIsCaseInsensitivePreservesCasingAndProcessesConflictingHeadersInAlphabeticalOrder() { + func testInitializationIsCaseInsensitivePreservesCasingAndProcessesInAlphabeticalOrder() { let headers = HeadersBuilder(headers: ["a": ["456"], "A": ["123"]]) XCTAssertEqual(["A": ["123", "456"]], headers.headers()) } From 42897f2e233d6e16e9a24bb2eaffdf2856925368 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 11:23:36 -0400 Subject: [PATCH 08/37] add doc Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 4d496dfd9b..8e62d3ea04 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -36,6 +36,7 @@ public class HeadersBuilder: NSObject { private var _headers: [String: KeyValuesPair] + /// Creates headers map. func headers() -> [String: [String]] { return Dictionary(uniqueKeysWithValues: self._headers.map { _, value in return (value.key, value.values) From 0e328a09203cae6168a7afb583e8afcaaada820a Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 11:43:55 -0400 Subject: [PATCH 09/37] fix casing Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 8e62d3ea04..0c594a7188 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -7,8 +7,8 @@ private func isRestrictedHeader(name: String) -> Bool { } /// Base builder class used to construct `Headers` instances. -/// It preserves the original casing of headers and enforces a case -/// insensitive look up and setting of headers. +/// It preserves the original casing of headers and enforces +/// a case-insensitive look up and setting of headers. /// See `{Request|Response}HeadersBuilder` for usage. @objcMembers public class HeadersBuilder: NSObject { From 8430f4f8250a94fb17fba70d35245047e129bffd Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 13:08:29 -0400 Subject: [PATCH 10/37] fix typo Signed-off-by: Rafal Augustyniak --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 2e59c4b29e..cf8232a2ca 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,7 +24,7 @@ Bugfixes: - Android: update Kotlin standard libraries to 1.6.21 (:issue:`#2256 <2256>`) - fix bug where finalStreamIntel was not consistently set on cancel (:issue:`#2285 <2285>`) - iOS: fix termination crash in ProvisionalDispatcher (:issue:`#2059 <2059>`) -- iOS: make headers lookup in HeadersBuilder case insensitive (:issue:`#2383 <2383>`) +- iOS: make headers lookup in HeadersBuilder case-insensitive (:issue:`#2383 <2383>`) - iOS: use correct DNS resolver when using C++ config builder (:issue: `#2378 <2378 >`) Features: From 86ce3fc6d9fc3596089b33ac6b9277a6ce6f5482 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Thu, 23 Jun 2022 13:08:54 -0400 Subject: [PATCH 11/37] Improve version history Signed-off-by: Rafal Augustyniak --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index cf8232a2ca..378cddf47f 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,7 +24,7 @@ Bugfixes: - Android: update Kotlin standard libraries to 1.6.21 (:issue:`#2256 <2256>`) - fix bug where finalStreamIntel was not consistently set on cancel (:issue:`#2285 <2285>`) - iOS: fix termination crash in ProvisionalDispatcher (:issue:`#2059 <2059>`) -- iOS: make headers lookup in HeadersBuilder case-insensitive (:issue:`#2383 <2383>`) +- iOS: make headers lookup in ``HeadersBuilder`` case-insensitive (:issue:`#2383 <2383>`) - iOS: use correct DNS resolver when using C++ config builder (:issue: `#2378 <2378 >`) Features: From 37c6e5cbcd26b53891c8568ec0af8fc9256bdbfd Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Fri, 24 Jun 2022 18:09:21 -0400 Subject: [PATCH 12/37] Make Headers case-insensitive too Signed-off-by: Rafal Augustyniak --- docs/root/intro/version_history.rst | 2 +- library/swift/BUILD | 1 + library/swift/Headers.swift | 31 +++-- library/swift/HeadersBuilder.swift | 93 +++++---------- library/swift/HeadersContainer.swift | 111 ++++++++++++++++++ library/swift/RequestHeaders.swift | 2 +- library/swift/RequestHeadersBuilder.swift | 2 +- library/swift/RequestTrailers.swift | 2 +- library/swift/RequestTrailersBuilder.swift | 4 +- library/swift/ResponseHeaders.swift | 2 +- library/swift/ResponseHeadersBuilder.swift | 4 +- library/swift/ResponseTrailers.swift | 2 +- library/swift/ResponseTrailersBuilder.swift | 4 +- library/swift/Stream.swift | 4 +- library/swift/filters/Filter.swift | 28 ++--- library/swift/grpc/GRPCRequestHeaders.swift | 2 +- .../grpc/GRPCRequestHeadersBuilder.swift | 2 +- library/swift/mocks/MockStream.swift | 4 +- test/swift/BUILD | 1 + test/swift/HeadersBuilderTests.swift | 18 +-- test/swift/HeadersContainerTests.swift | 75 ++++++++++++ 21 files changed, 278 insertions(+), 116 deletions(-) create mode 100644 library/swift/HeadersContainer.swift create mode 100644 test/swift/HeadersContainerTests.swift diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 378cddf47f..2fdf016ac9 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,7 +24,7 @@ Bugfixes: - Android: update Kotlin standard libraries to 1.6.21 (:issue:`#2256 <2256>`) - fix bug where finalStreamIntel was not consistently set on cancel (:issue:`#2285 <2285>`) - iOS: fix termination crash in ProvisionalDispatcher (:issue:`#2059 <2059>`) -- iOS: make headers lookup in ``HeadersBuilder`` case-insensitive (:issue:`#2383 <2383>`) +- iOS: make headers lookup in ``HeadersBuilder`` and ``Headers`` case-insensitive (:issue:`#2383 <2383>`) - iOS: use correct DNS resolver when using C++ config builder (:issue: `#2378 <2378 >`) Features: diff --git a/library/swift/BUILD b/library/swift/BUILD index 3fcdd55f08..6bbc9cb69d 100644 --- a/library/swift/BUILD +++ b/library/swift/BUILD @@ -17,6 +17,7 @@ swift_library( "FinalStreamIntel.swift", "Headers.swift", "HeadersBuilder.swift", + "HeadersContainer.swift", "KeyValueStore.swift", "LogLevel.swift", "NetworkMonitoringMode.swift", diff --git a/library/swift/Headers.swift b/library/swift/Headers.swift index 094a039294..0c93008411 100644 --- a/library/swift/Headers.swift +++ b/library/swift/Headers.swift @@ -4,38 +4,51 @@ import Foundation /// To instantiate new instances, see `{Request|Response}HeadersBuilder`. @objcMembers public class Headers: NSObject { - let headers: [String: [String]] + let container: HeadersContainer /// Get the value for the provided header name. /// - /// - parameter name: Header name for which to get the current value. + /// - parameter name: The case-insensitive header name for which to + /// get the current value. + /// /// /// - returns: The current headers specified for the provided name. public func value(forName name: String) -> [String]? { - return self.headers[name] + return self.container.value(forName: name) } /// Accessor for all underlying headers as a map. /// /// - returns: The underlying headers. public func allHeaders() -> [String: [String]] { - return self.headers + return self.container.allHeaders() } /// Internal initializer used by builders. /// - /// - parameter headers: Headers to set. - required init(headers: [String: [String]]) { - self.headers = headers + /// - parameter container: Headers to set. + required init(container: HeadersContainer) { + self.container = container super.init() } + + /// Inialize the receiver with a given headers map. + /// + /// - parameter headers: The headers map to use. + convenience init(headers: [String: [String]]) { + self.init(container: HeadersContainer(headers: headers)) + } + + override convenience init() { + self.init(headers: [:]) + } } // MARK: - Equatable extension Headers { public override func isEqual(_ object: Any?) -> Bool { - return (object as? Self)?.headers == self.headers + return (object as? Self)?.container == self.container } } @@ -43,6 +56,6 @@ extension Headers { extension Headers { public override var description: String { - return "\(type(of: self)) \(self.headers.description)" + return "\(type(of: self)) \(self.container.description)" } } diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 0c594a7188..d0cde1a1d7 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,7 +3,8 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - return name == "host" || kRestrictedPrefixes.contains { name.hasPrefix($0) } + let lowercasedName = name.lowercased() + return lowercasedName == "host" || kRestrictedPrefixes.contains { lowercasedName.hasPrefix($0) } } /// Base builder class used to construct `Headers` instances. @@ -12,36 +13,7 @@ private func isRestrictedHeader(name: String) -> Bool { /// See `{Request|Response}HeadersBuilder` for usage. @objcMembers public class HeadersBuilder: NSObject { - struct KeyValuesPair { - private(set) var key: String - private(set) var values: [String] - - init(key: String, values: [String] = []) { - self.key = key - self.values = values - } - - mutating func appendValue(_ value: String) { - self.values.append(value) - } - - mutating func appendValues(_ values: [String]) { - self.values.append(contentsOf: values) - } - - mutating func setValue(_ value: String) { - self.values = [value] - } - } - - private var _headers: [String: KeyValuesPair] - - /// Creates headers map. - func headers() -> [String: [String]] { - return Dictionary(uniqueKeysWithValues: self._headers.map { _, value in - return (value.key, value.values) - }) - } + private(set) var container: HeadersContainer /// Append a value to the header name. /// @@ -51,12 +23,11 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func add(name: String, value: String) -> Self { - let lowercasedName = name.lowercased() - if isRestrictedHeader(name: lowercasedName) { + if isRestrictedHeader(name: name) { return self } - self._headers[lowercasedName, default: KeyValuesPair(key: name)].appendValue(value) + self.container.add(name: name, value: value) return self } @@ -68,12 +39,11 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func set(name: String, value: [String]) -> Self { - let lowercasedName = name.lowercased() - if isRestrictedHeader(name: lowercasedName) { + if isRestrictedHeader(name: name) { return self } - self._headers[lowercasedName] = KeyValuesPair(key: name, values: value) + self.container.set(name: name, value: value) return self } @@ -84,12 +54,11 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult public func remove(name: String) -> Self { - let lowercasedName = name.lowercased() - if isRestrictedHeader(name: lowercasedName) { + if isRestrictedHeader(name: name) { return self } - self._headers[lowercasedName] = nil + self.container.set(name: name, value: nil) return self } @@ -103,42 +72,34 @@ public class HeadersBuilder: NSObject { /// - returns: This builder. @discardableResult func internalSet(name: String, value: [String]) -> Self { - self._headers[name.lowercased()] = KeyValuesPair(key: name, values: value) + self.container.set(name: name, value: value) return self } + func allHeaders() -> [String: [String]] { + return self.container.allHeaders() + } + // Only explicitly implemented to work around a swiftinterface issue in Swift 5.1. This can be // removed once envoy is only built with Swift 5.2+ public override init() { - self._headers = [:] + self.container = HeadersContainer() super.init() } - /// Initialize a new builder. Subclasses should provide their own public convenience initializers. + // Initialize a new builder using the provided headers container. /// - /// - parameter headers: The headers with which to start. - required init(headers: [String: [String]]) { - var processedHeaders = [String: KeyValuesPair]() - for (name, values) in headers { - let lowercasedName = name.lowercased() - /// Dictionaries in Swift are unordered collections. We process headers with keys - /// that are the same when lowercased in an alphabetical order to avoid a situation - /// in which the result of the initialization is underministic i.e., we want - /// "[A: ["1"]", "a: ["2"]]" headers to be always converted to ["A": ["1", "2"]] and - /// never to "a": ["2", "1"]. - /// - /// If a given header name already exists in the processed headers map, check - /// if the currently processed header name is before the existing header name as - /// determined by an alphabetical order. - if let existing = processedHeaders[lowercasedName], existing.key > name { - processedHeaders[lowercasedName] = - KeyValuesPair(key: name, values: values + existing.values) - } else { - processedHeaders[lowercasedName, default: KeyValuesPair(key: name)].appendValues(values) - } - } + /// - parameter: The headers container to initialize the receiver with. + init(container: HeadersContainer) { + self.container = container + super.init() + } - self._headers = processedHeaders + // Initialize a new builder. Subclasses should provide their own public convenience initializers. + // + // - parameter headers: The headers with which to start. + init(headers: [String: [String]]) { + self.container = HeadersContainer(headers: headers) super.init() } } @@ -147,6 +108,6 @@ public class HeadersBuilder: NSObject { extension HeadersBuilder { public override func isEqual(_ object: Any?) -> Bool { - return (object as? Self)?.headers() == self.headers() + return (object as? Self)?.container == self.container } } diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift new file mode 100644 index 0000000000..d7df6464dc --- /dev/null +++ b/library/swift/HeadersContainer.swift @@ -0,0 +1,111 @@ +/// The container which manages the underlying headers map. +/// It maintains the original casing of passed header names. +/// It treats headers names as case-insensitive for the purpose +/// of headers lookup and header name conflict resolutions. +struct HeadersContainer: Equatable { + private var headers: [String: Header] + + // Represents a headers name together with all of its values. + // It preserves the original casing of the header name. + struct Header: Equatable { + private(set) var name: String + private(set) var value: [String] + + init(name: String, value: [String] = []) { + self.name = name + self.value = value + } + + mutating func addValue(_ value: [String]) { + self.value.append(contentsOf: value) + } + + mutating func addValue(_ value: String) { + self.value.append(value) + } + } + + /// Initialize a new instance of the receiver using the provided headers map. + /// + /// - parameter headers: The headers map. + init(headers: [String: [String]]) { + var underlyingHeaders = [String: Header]() + for (name, value) in headers { + let lowercasedName = name.lowercased() + /// Dictionaries in Swift are unordered collections. Process headers with names + /// that are the same when lowercased in an alphabetical order to avoid a situation + /// in which the result of the initialization is underministic i.e., we want + /// "[A: ["1"]", "a: ["2"]]" headers to be always converted to ["A": ["1", "2"]] and + /// never to "a": ["2", "1"]. + /// + /// If a given header name already exists in the processed headers map, check + /// if the currently processed header name is before the existing header name as + /// determined by an alphabetical order. + if let existing = underlyingHeaders[lowercasedName], existing.name > name { + underlyingHeaders[lowercasedName] = + Header(name: name, value: value + existing.value) + } else { + let `default` = Header(name: underlyingHeaders[lowercasedName]?.name ?? name) + underlyingHeaders[lowercasedName, default: `default`].addValue(value) + } + } + self.headers = underlyingHeaders + } + + /// Initialize an empty headers container. + init() { + self.headers = [:] + } + + /// Add a value to a header with a given name. + /// + /// - parameter name: The name of the header. For the purpose of headers lookup + /// and header name conflict resolution, the name of the header + /// is considered to be case-insensitive. + /// - parameter value: The value to add. + mutating func add(name: String, value: String) { + self.headers[name.lowercased(), default: Header(name: name)].addValue(value) + } + + /// Add values to a header with a given name. + /// + /// - parameter name: The name of the header. + /// - parameter values: The values to add. + mutating func add(name: String, value: [String]) { + self.headers[name.lowercased(), default: Header(name: name)].addValue(value) + } + + /// Set the value of a given header. + /// + /// - parameter name: The name of the header. + /// - parameter value: The value to set the header value to. + mutating func set(name: String, value: [String]?) { + guard let value = value else { + self.headers[name.lowercased()] = nil + return + } + self.headers[name.lowercased()] = Header(name: name, value: value) + } + + /// Returns the value of a given header. + /// + /// - parameter name: The name of the header to return the value for. + /// + /// - returns: The value associated with a given header. + func value(forName name: String) -> [String]? { + return self.headers[name.lowercased()]?.value + } + + /// The list of all headers stored by the receiver. + func allHeaders() -> [String: [String]] { + Dictionary(uniqueKeysWithValues: self.headers.map { _, value in + return (value.name, value.value) + }) + } +} + +extension HeadersContainer: CustomStringConvertible { + var description: String { + return self.headers.description + } +} diff --git a/library/swift/RequestHeaders.swift b/library/swift/RequestHeaders.swift index e9e8e10ff1..8da6194e10 100644 --- a/library/swift/RequestHeaders.swift +++ b/library/swift/RequestHeaders.swift @@ -29,6 +29,6 @@ public class RequestHeaders: Headers { /// /// - returns: The new builder. public func toRequestHeadersBuilder() -> RequestHeadersBuilder { - return RequestHeadersBuilder(headers: self.headers) + return RequestHeadersBuilder(container: self.container) } } diff --git a/library/swift/RequestHeadersBuilder.swift b/library/swift/RequestHeadersBuilder.swift index 8b4d64cde7..54b8652adf 100644 --- a/library/swift/RequestHeadersBuilder.swift +++ b/library/swift/RequestHeadersBuilder.swift @@ -52,6 +52,6 @@ public final class RequestHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of request headers. public func build() -> RequestHeaders { - return RequestHeaders(headers: self.headers()) + return RequestHeaders(container: self.container) } } diff --git a/library/swift/RequestTrailers.swift b/library/swift/RequestTrailers.swift index a525c6acc6..0df0760f82 100644 --- a/library/swift/RequestTrailers.swift +++ b/library/swift/RequestTrailers.swift @@ -7,6 +7,6 @@ public final class RequestTrailers: Trailers { /// /// - returns: The new builder. public func toRequestTrailersBuilder() -> RequestTrailersBuilder { - return RequestTrailersBuilder(headers: self.headers) + return RequestTrailersBuilder(container: self.container) } } diff --git a/library/swift/RequestTrailersBuilder.swift b/library/swift/RequestTrailersBuilder.swift index a521549f2c..e146a21372 100644 --- a/library/swift/RequestTrailersBuilder.swift +++ b/library/swift/RequestTrailersBuilder.swift @@ -5,13 +5,13 @@ import Foundation public final class RequestTrailersBuilder: HeadersBuilder { /// Initialize a new instance of the builder. public override convenience init() { - self.init(headers: [:]) + self.init(container: HeadersContainer(headers: [:])) } /// Build the request trailers using the current builder. /// /// - returns: New instance of request trailers. public func build() -> RequestTrailers { - return RequestTrailers(headers: self.headers()) + return RequestTrailers(container: self.container) } } diff --git a/library/swift/ResponseHeaders.swift b/library/swift/ResponseHeaders.swift index a2ca41a133..ce24f6a880 100644 --- a/library/swift/ResponseHeaders.swift +++ b/library/swift/ResponseHeaders.swift @@ -11,6 +11,6 @@ public final class ResponseHeaders: Headers { /// /// - returns: The new builder. public func toResponseHeadersBuilder() -> ResponseHeadersBuilder { - return ResponseHeadersBuilder(headers: self.headers) + return ResponseHeadersBuilder(container: self.container) } } diff --git a/library/swift/ResponseHeadersBuilder.swift b/library/swift/ResponseHeadersBuilder.swift index 970a75ec93..fc62d605a2 100644 --- a/library/swift/ResponseHeadersBuilder.swift +++ b/library/swift/ResponseHeadersBuilder.swift @@ -5,7 +5,7 @@ import Foundation public final class ResponseHeadersBuilder: HeadersBuilder { /// Initialize a new instance of the builder. public override convenience init() { - self.init(headers: [:]) + self.init(container: HeadersContainer(headers: [:])) } /// Add an HTTP status to the response headers. @@ -23,6 +23,6 @@ public final class ResponseHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of response headers. public func build() -> ResponseHeaders { - return ResponseHeaders(headers: self.headers()) + return ResponseHeaders(container: self.container) } } diff --git a/library/swift/ResponseTrailers.swift b/library/swift/ResponseTrailers.swift index 2b25a8b45e..c3e0571904 100644 --- a/library/swift/ResponseTrailers.swift +++ b/library/swift/ResponseTrailers.swift @@ -7,6 +7,6 @@ public final class ResponseTrailers: Trailers { /// /// - returns: The new builder. public func toResponseTrailersBuilder() -> ResponseTrailersBuilder { - return ResponseTrailersBuilder(headers: self.headers) + return ResponseTrailersBuilder(container: self.container) } } diff --git a/library/swift/ResponseTrailersBuilder.swift b/library/swift/ResponseTrailersBuilder.swift index 3e8832e405..6d02fbbf0e 100644 --- a/library/swift/ResponseTrailersBuilder.swift +++ b/library/swift/ResponseTrailersBuilder.swift @@ -5,13 +5,13 @@ import Foundation public final class ResponseTrailersBuilder: HeadersBuilder { /// Initialize a new instance of the builder. public override convenience init() { - self.init(headers: [:]) + self.init(container: HeadersContainer()) } /// Build the response trailers using the current builder. /// /// - returns: New instance of response trailers. public func build() -> ResponseTrailers { - return ResponseTrailers(headers: self.headers()) + return ResponseTrailers(container: self.container) } } diff --git a/library/swift/Stream.swift b/library/swift/Stream.swift index a9b416e6f9..0a0e8ddc0a 100644 --- a/library/swift/Stream.swift +++ b/library/swift/Stream.swift @@ -26,7 +26,7 @@ public class Stream: NSObject { /// - returns: This stream, for chaining syntax. @discardableResult public func sendHeaders(_ headers: RequestHeaders, endStream: Bool) -> Stream { - self.underlyingStream.sendHeaders(headers.headers, close: endStream) + self.underlyingStream.sendHeaders(headers.allHeaders(), close: endStream) return self } @@ -57,7 +57,7 @@ public class Stream: NSObject { /// /// - parameter trailers: Trailers with which to close the stream. public func close(trailers: RequestTrailers) { - self.underlyingStream.sendTrailers(trailers.headers) + self.underlyingStream.sendTrailers(trailers.allHeaders()) } /// Close the stream with a data frame. diff --git a/library/swift/filters/Filter.swift b/library/swift/filters/Filter.swift index ebd87a5024..8835fbc078 100644 --- a/library/swift/filters/Filter.swift +++ b/library/swift/filters/Filter.swift @@ -27,7 +27,7 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let headers): - return [kEnvoyFilterHeadersStatusContinue, headers.headers] + return [kEnvoyFilterHeadersStatusContinue, headers.allHeaders()] case .stopIteration: return [kEnvoyFilterHeadersStatusStopIteration, NSNull()] } @@ -44,7 +44,7 @@ extension EnvoyHTTPFilter { case .stopIterationNoBuffer: return [kEnvoyFilterDataStatusStopIterationNoBuffer, NSNull()] case .resumeIteration(let headers, let data): - return [kEnvoyFilterDataStatusResumeIteration, data, headers?.headers as Any] + return [kEnvoyFilterDataStatusResumeIteration, data, headers?.allHeaders() as Any] } } @@ -53,14 +53,14 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let trailers): - return [kEnvoyFilterTrailersStatusContinue, trailers.headers] + return [kEnvoyFilterTrailersStatusContinue, trailers.allHeaders()] case .stopIteration: return [kEnvoyFilterTrailersStatusStopIteration, NSNull()] case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterTrailersStatusResumeIteration, - trailers.headers, - headers?.headers as Any, + trailers.allHeaders(), + headers?.allHeaders() as Any, data as Any, ] } @@ -74,7 +74,7 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let headers): - return [kEnvoyFilterHeadersStatusContinue, headers.headers] + return [kEnvoyFilterHeadersStatusContinue, headers.allHeaders()] case .stopIteration: return [kEnvoyFilterHeadersStatusStopIteration, NSNull()] } @@ -91,7 +91,7 @@ extension EnvoyHTTPFilter { case .stopIterationNoBuffer: return [kEnvoyFilterDataStatusStopIterationNoBuffer, NSNull()] case .resumeIteration(let headers, let data): - return [kEnvoyFilterDataStatusResumeIteration, data, headers?.headers as Any] + return [kEnvoyFilterDataStatusResumeIteration, data, headers?.allHeaders() as Any] } } @@ -100,14 +100,14 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let trailers): - return [kEnvoyFilterTrailersStatusContinue, trailers.headers] + return [kEnvoyFilterTrailersStatusContinue, trailers.allHeaders()] case .stopIteration: return [kEnvoyFilterTrailersStatusStopIteration, NSNull()] case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterTrailersStatusResumeIteration, - trailers.headers, - headers?.headers as Any, + trailers.allHeaders(), + headers?.allHeaders() as Any, data as Any, ] } @@ -146,9 +146,9 @@ extension EnvoyHTTPFilter { case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterResumeStatusResumeIteration, - headers?.headers as Any, + headers?.allHeaders() as Any, data as Any, - trailers?.headers as Any, + trailers?.allHeaders() as Any, ] } } @@ -172,9 +172,9 @@ extension EnvoyHTTPFilter { case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterResumeStatusResumeIteration, - headers?.headers as Any, + headers?.allHeaders() as Any, data as Any, - trailers?.headers as Any, + trailers?.allHeaders() as Any, ] } } diff --git a/library/swift/grpc/GRPCRequestHeaders.swift b/library/swift/grpc/GRPCRequestHeaders.swift index a5c589b5f8..cdf55cca19 100644 --- a/library/swift/grpc/GRPCRequestHeaders.swift +++ b/library/swift/grpc/GRPCRequestHeaders.swift @@ -7,6 +7,6 @@ public final class GRPCRequestHeaders: RequestHeaders { /// /// - returns: The new builder. public func toGRPCRequestHeadersBuilder() -> GRPCRequestHeadersBuilder { - return GRPCRequestHeadersBuilder(headers: self.headers) + return GRPCRequestHeadersBuilder(container: self.container) } } diff --git a/library/swift/grpc/GRPCRequestHeadersBuilder.swift b/library/swift/grpc/GRPCRequestHeadersBuilder.swift index 6f14926a86..0f3fc8ba1b 100644 --- a/library/swift/grpc/GRPCRequestHeadersBuilder.swift +++ b/library/swift/grpc/GRPCRequestHeadersBuilder.swift @@ -40,6 +40,6 @@ public final class GRPCRequestHeadersBuilder: HeadersBuilder { /// /// - returns: New instance of request headers. public func build() -> GRPCRequestHeaders { - return GRPCRequestHeaders(headers: self.headers()) + return GRPCRequestHeaders(container: self.container) } } diff --git a/library/swift/mocks/MockStream.swift b/library/swift/mocks/MockStream.swift index 8aa331f03c..d383d62d78 100644 --- a/library/swift/mocks/MockStream.swift +++ b/library/swift/mocks/MockStream.swift @@ -52,7 +52,7 @@ public final class MockStream: Stream { /// - parameter headers: Response headers to receive. /// - parameter endStream: Whether this is a headers-only response. public func receiveHeaders(_ headers: ResponseHeaders, endStream: Bool) { - self.mockStream.callbacks.onHeaders(headers.headers, endStream, EnvoyStreamIntel()) + self.mockStream.callbacks.onHeaders(headers.allHeaders(), endStream, EnvoyStreamIntel()) } /// Simulate response data coming back over the stream. @@ -67,7 +67,7 @@ public final class MockStream: Stream { /// /// - parameter trailers: Response trailers to receive. public func receiveTrailers(_ trailers: ResponseTrailers) { - self.mockStream.callbacks.onTrailers(trailers.headers, EnvoyStreamIntel()) + self.mockStream.callbacks.onTrailers(trailers.allHeaders(), EnvoyStreamIntel()) } /// Simulate the stream receiving a cancellation signal from Envoy. diff --git a/test/swift/BUILD b/test/swift/BUILD index 5f98ee480e..1839c45e50 100644 --- a/test/swift/BUILD +++ b/test/swift/BUILD @@ -9,6 +9,7 @@ envoy_mobile_swift_test( "GRPCRequestHeadersBuilderTests.swift", "GRPCStreamTests.swift", "HeadersBuilderTests.swift", + "HeadersContainerTests.swift", "PulseClientImplTests.swift", "RequestHeadersBuilderTests.swift", "ResponseHeadersTests.swift", diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index efa2b793c0..4d08c0c4af 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -10,7 +10,7 @@ final class HeadersBuilderTests: XCTestCase { let headers = HeadersBuilder(headers: [:]) .add(name: "x-foo", value: "1") .add(name: "x-foo", value: "2") - .headers() + .allHeaders() XCTAssertEqual(["1", "2"], headers["x-foo"]) } @@ -19,7 +19,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "x-foo", value: "1") .add(name: "x-foo", value: "2") .remove(name: "x-foo") - .headers() + .allHeaders() XCTAssertNil(headers["x-foo"]) } @@ -28,7 +28,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "x-foo", value: "123") .add(name: "x-bar", value: "abc") .remove(name: "x-foo") - .headers() + .allHeaders() XCTAssertEqual(["x-bar": ["abc"]], headers) } @@ -36,34 +36,34 @@ final class HeadersBuilderTests: XCTestCase { let headers = HeadersBuilder(headers: [:]) .add(name: "x-foo", value: "123") .set(name: "x-foo", value: ["abc"]) - .headers() + .allHeaders() XCTAssertEqual(["x-foo": ["abc"]], headers) } func testInitializationIsCaseInsensitivePreservesCasingAndProcessesInAlphabeticalOrder() { let headers = HeadersBuilder(headers: ["a": ["456"], "A": ["123"]]) - XCTAssertEqual(["A": ["123", "456"]], headers.headers()) + XCTAssertEqual(["A": ["123", "456"]], headers.allHeaders()) } func testAddingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { let headers = HeadersBuilder(headers: [:]) headers.add(name: "fOo", value: "abc") headers.add(name: "foo", value: "123") - XCTAssertEqual(["fOo": ["abc", "123"]], headers.headers()) + XCTAssertEqual(["fOo": ["abc", "123"]], headers.allHeaders()) } func testSettingHeaderIsCaseInsensitiveAndHeaderCasingIsPreserved() { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.set(name: "fOo", value: ["abc"]) - XCTAssertEqual(["fOo": ["abc"]], headers.headers()) + XCTAssertEqual(["fOo": ["abc"]], headers.allHeaders()) } func testRemovingHeaderIsCaseInsensitive() { let headers = HeadersBuilder(headers: [:]) headers.set(name: "foo", value: ["123"]) headers.remove(name: "fOo") - XCTAssertEqual([:], headers.headers()) + XCTAssertEqual([:], headers.allHeaders()) } func testRestrictedHeadersAreNotSettable() { @@ -71,7 +71,7 @@ final class HeadersBuilderTests: XCTestCase { .add(name: "host", value: "example.com") .set(name: ":scheme", value: ["http"]) .set(name: ":path", value: ["/nope"]) - .headers() + .allHeaders() let expected = [ ":authority": ["example.com"], ":path": ["/"], diff --git a/test/swift/HeadersContainerTests.swift b/test/swift/HeadersContainerTests.swift new file mode 100644 index 0000000000..1d918a5130 --- /dev/null +++ b/test/swift/HeadersContainerTests.swift @@ -0,0 +1,75 @@ +@testable import Envoy +import XCTest + +final class HeadersContainerTests: XCTestCase { + func testInitializationPreservesAllHeadersFromInputHeadersMap() { + let container = HeadersContainer(headers: ["a": ["456"], "b": ["123"]]) + XCTAssertEqual(["a": ["456"], "b": ["123"]], container.allHeaders()) + } + + func testInitializationIsCaseInsensitivePreservesCasingAndProcessesInAlphabeticalOrder() { + let container = HeadersContainer(headers: ["a": ["456"], "A": ["123"]]) + XCTAssertEqual(["A": ["123", "456"]], container.allHeaders()) + } + + func testAddingHeaderValueAddsToListOfHeader() { + var container = HeadersContainer() + container.add(name: "x-foo", value: "1") + container.add(name: "x-foo", value: "2") + + XCTAssertEqual(["1", "2"], container.value(forName: "x-foo")) + } + + func testAddingHeaderValueIsCaseInsensitiveAndPreservesHeaderNameCasing() { + var container = HeadersContainer() + container.add(name: "x-FOO", value: "1") + container.add(name: "x-foo", value: "2") + + XCTAssertEqual(["1", "2"], container.value(forName: "x-foo")) + XCTAssertEqual(["x-FOO": ["1", "2"]], container.allHeaders()) + } + + func testAddingHeadersValuesAddsToListOfHeaders() { + var container = HeadersContainer() + container.add(name: "x-foo", value: ["1", "2"]) + container.add(name: "x-foo", value: ["3", "4"]) + + XCTAssertEqual(["1", "2", "3", "4"], container.value(forName: "x-foo")) + } + + func testAddingHeaderValuesIsCaseInsensitiveAndPreservesHeaderNameCasing() { + var container = HeadersContainer() + container.add(name: "x-FOO", value: ["1", "2"]) + container.add(name: "x-foo", value: ["3", "4"]) + + XCTAssertEqual(["1", "2", "3", "4"], container.value(forName: "x-foo")) + XCTAssertEqual(["x-FOO": ["1", "2", "3", "4"]], container.allHeaders()) + } + + func testSettingHeaderToNilRemovesAllOfItsValues() { + var container = HeadersContainer() + container.add(name: "x-foo", value: "1") + container.add(name: "x-foo", value: "2") + container.set(name: "x-foo", value: nil) + + XCTAssertNil(container.value(forName: "x-foo")) + } + + func testSettingHeaderToNilPerformsCaseInsensitiveHeaderNameLookup() { + var container = HeadersContainer() + container.add(name: "x-FOO", value: "1") + container.add(name: "x-foo", value: "2") + container.set(name: "x-foo", value: nil) + + XCTAssertNil(container.value(forName: "x-foo")) + } + + func testLookupIsCaseInsensitive() { + var container = HeadersContainer() + container.add(name: "x-FOO", value: "1") + + XCTAssertEqual(["1"], container.value(forName: "x-foo")) + XCTAssertEqual(["1"], container.value(forName: "x-fOo")) + XCTAssertEqual(["1"], container.value(forName: "x-FOO")) + } +} \ No newline at end of file From 35c0536dc719650335917f39e24ec7ada69b0934 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Fri, 24 Jun 2022 18:11:22 -0400 Subject: [PATCH 13/37] lint fixes Signed-off-by: Rafal Augustyniak --- library/swift/Headers.swift | 2 +- test/swift/HeadersContainerTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/swift/Headers.swift b/library/swift/Headers.swift index 0c93008411..9aff6e5b79 100644 --- a/library/swift/Headers.swift +++ b/library/swift/Headers.swift @@ -10,7 +10,7 @@ public class Headers: NSObject { /// /// - parameter name: The case-insensitive header name for which to /// get the current value. - /// + /// /// /// - returns: The current headers specified for the provided name. public func value(forName name: String) -> [String]? { diff --git a/test/swift/HeadersContainerTests.swift b/test/swift/HeadersContainerTests.swift index 1d918a5130..a915e98b79 100644 --- a/test/swift/HeadersContainerTests.swift +++ b/test/swift/HeadersContainerTests.swift @@ -72,4 +72,4 @@ final class HeadersContainerTests: XCTestCase { XCTAssertEqual(["1"], container.value(forName: "x-fOo")) XCTAssertEqual(["1"], container.value(forName: "x-FOO")) } -} \ No newline at end of file +} From 1fd993826a88b79032ce83b0a5b5bf996a0e2e16 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Fri, 24 Jun 2022 18:12:42 -0400 Subject: [PATCH 14/37] swiftlint fix Signed-off-by: Rafal Augustyniak --- library/swift/HeadersContainer.swift | 4 ++-- test/swift/HeadersContainerTests.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index d7df6464dc..d89f6125e8 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -98,8 +98,8 @@ struct HeadersContainer: Equatable { /// The list of all headers stored by the receiver. func allHeaders() -> [String: [String]] { - Dictionary(uniqueKeysWithValues: self.headers.map { _, value in - return (value.name, value.value) + return Dictionary(uniqueKeysWithValues: self.headers.map { _, value in + return (value.name, value.value) }) } } diff --git a/test/swift/HeadersContainerTests.swift b/test/swift/HeadersContainerTests.swift index a915e98b79..394fc38421 100644 --- a/test/swift/HeadersContainerTests.swift +++ b/test/swift/HeadersContainerTests.swift @@ -13,7 +13,7 @@ final class HeadersContainerTests: XCTestCase { } func testAddingHeaderValueAddsToListOfHeader() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-foo", value: "1") container.add(name: "x-foo", value: "2") @@ -21,7 +21,7 @@ final class HeadersContainerTests: XCTestCase { } func testAddingHeaderValueIsCaseInsensitiveAndPreservesHeaderNameCasing() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-FOO", value: "1") container.add(name: "x-foo", value: "2") @@ -30,7 +30,7 @@ final class HeadersContainerTests: XCTestCase { } func testAddingHeadersValuesAddsToListOfHeaders() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-foo", value: ["1", "2"]) container.add(name: "x-foo", value: ["3", "4"]) @@ -38,7 +38,7 @@ final class HeadersContainerTests: XCTestCase { } func testAddingHeaderValuesIsCaseInsensitiveAndPreservesHeaderNameCasing() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-FOO", value: ["1", "2"]) container.add(name: "x-foo", value: ["3", "4"]) @@ -56,7 +56,7 @@ final class HeadersContainerTests: XCTestCase { } func testSettingHeaderToNilPerformsCaseInsensitiveHeaderNameLookup() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-FOO", value: "1") container.add(name: "x-foo", value: "2") container.set(name: "x-foo", value: nil) @@ -65,7 +65,7 @@ final class HeadersContainerTests: XCTestCase { } func testLookupIsCaseInsensitive() { - var container = HeadersContainer() + var container = HeadersContainer() container.add(name: "x-FOO", value: "1") XCTAssertEqual(["1"], container.value(forName: "x-foo")) From 6389322f5fb40484a56ea7ea4891e042900f7aa7 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Fri, 24 Jun 2022 21:51:56 -0400 Subject: [PATCH 15/37] doc string fix Signed-off-by: Rafal Augustyniak --- library/swift/HeadersContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index d89f6125e8..9c26efcbba 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -70,7 +70,7 @@ struct HeadersContainer: Equatable { /// Add values to a header with a given name. /// /// - parameter name: The name of the header. - /// - parameter values: The values to add. + /// - parameter value: The values to add. mutating func add(name: String, value: [String]) { self.headers[name.lowercased(), default: Header(name: name)].addValue(value) } From 3ac922cf0791f80f1429407eb67e172a8287e71b Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Sat, 25 Jun 2022 00:10:03 -0400 Subject: [PATCH 16/37] remove method Signed-off-by: Rafal Augustyniak --- library/swift/HeadersContainer.swift | 10 +--------- test/swift/HeadersContainerTests.swift | 19 +++++++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index 9c26efcbba..5a16fcdb3c 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -20,7 +20,7 @@ struct HeadersContainer: Equatable { self.value.append(contentsOf: value) } - mutating func addValue(_ value: String) { + mutating func addValue(_ value: String) { self.value.append(value) } } @@ -67,14 +67,6 @@ struct HeadersContainer: Equatable { self.headers[name.lowercased(), default: Header(name: name)].addValue(value) } - /// Add values to a header with a given name. - /// - /// - parameter name: The name of the header. - /// - parameter value: The values to add. - mutating func add(name: String, value: [String]) { - self.headers[name.lowercased(), default: Header(name: name)].addValue(value) - } - /// Set the value of a given header. /// /// - parameter name: The name of the header. diff --git a/test/swift/HeadersContainerTests.swift b/test/swift/HeadersContainerTests.swift index 394fc38421..d7ecd3b976 100644 --- a/test/swift/HeadersContainerTests.swift +++ b/test/swift/HeadersContainerTests.swift @@ -12,7 +12,7 @@ final class HeadersContainerTests: XCTestCase { XCTAssertEqual(["A": ["123", "456"]], container.allHeaders()) } - func testAddingHeaderValueAddsToListOfHeader() { + func testAddingHeaderValueAddsToListOfHeaders() { var container = HeadersContainer() container.add(name: "x-foo", value: "1") container.add(name: "x-foo", value: "2") @@ -29,21 +29,20 @@ final class HeadersContainerTests: XCTestCase { XCTAssertEqual(["x-FOO": ["1", "2"]], container.allHeaders()) } - func testAddingHeadersValuesAddsToListOfHeaders() { + func testSettingHeaderAddsToListOfHeaders() { var container = HeadersContainer() - container.add(name: "x-foo", value: ["1", "2"]) - container.add(name: "x-foo", value: ["3", "4"]) + container.set(name: "x-foo", value: ["abc"]) - XCTAssertEqual(["1", "2", "3", "4"], container.value(forName: "x-foo")) + XCTAssertEqual(["abc"], container.value(forName: "x-foo")) } - func testAddingHeaderValuesIsCaseInsensitiveAndPreservesHeaderNameCasing() { + func testSettingHeaderOverridesPreviousHeaderValues() { var container = HeadersContainer() - container.add(name: "x-FOO", value: ["1", "2"]) - container.add(name: "x-foo", value: ["3", "4"]) + container.add(name: "x-FOO", value: "1") + container.add(name: "x-foo", value: "2") + container.set(name: "x-foo", value: ["3"]) - XCTAssertEqual(["1", "2", "3", "4"], container.value(forName: "x-foo")) - XCTAssertEqual(["x-FOO": ["1", "2", "3", "4"]], container.allHeaders()) + XCTAssertEqual(["3"], container.value(forName: "x-foo")) } func testSettingHeaderToNilRemovesAllOfItsValues() { From 6d6caf2566f87e5835e2319f555d5bde988be18a Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 08:49:21 -0400 Subject: [PATCH 17/37] small tweaks to docs and description property Signed-off-by: Rafal Augustyniak --- library/swift/Headers.swift | 2 +- library/swift/HeadersContainer.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/swift/Headers.swift b/library/swift/Headers.swift index 9aff6e5b79..f383b2f080 100644 --- a/library/swift/Headers.swift +++ b/library/swift/Headers.swift @@ -56,6 +56,6 @@ extension Headers { extension Headers { public override var description: String { - return "\(type(of: self)) \(self.container.description)" + return "\(type(of: self)) \(self.allHeaders())" } } diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index 5a16fcdb3c..dcf4defa12 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -1,7 +1,7 @@ /// The container which manages the underlying headers map. /// It maintains the original casing of passed header names. /// It treats headers names as case-insensitive for the purpose -/// of headers lookup and header name conflict resolutions. +/// of header lookups and header name conflict resolutions. struct HeadersContainer: Equatable { private var headers: [String: Header] From cc401343d51b534fcbeaa086c7122302ab64186a Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 09:04:40 -0400 Subject: [PATCH 18/37] simplify code Signed-off-by: Rafal Augustyniak --- library/swift/HeadersContainer.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index dcf4defa12..8de0291ab4 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -41,12 +41,16 @@ struct HeadersContainer: Equatable { /// If a given header name already exists in the processed headers map, check /// if the currently processed header name is before the existing header name as /// determined by an alphabetical order. - if let existing = underlyingHeaders[lowercasedName], existing.name > name { + guard let existingHeader = underlyingHeaders[lowercasedName] else { + underlyingHeaders[lowercasedName] = Header(name: name, value: value) + continue + } + + if existingHeader.name > name { underlyingHeaders[lowercasedName] = - Header(name: name, value: value + existing.value) + Header(name: name, value: value + existingHeader.value) } else { - let `default` = Header(name: underlyingHeaders[lowercasedName]?.name ?? name) - underlyingHeaders[lowercasedName, default: `default`].addValue(value) + underlyingHeaders[lowercasedName]?.addValue(value) } } self.headers = underlyingHeaders From eac2d9db00b39267796f33ba060d00ae4116b599 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 10:00:09 -0400 Subject: [PATCH 19/37] Doc updates Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 2 +- library/swift/HeadersContainer.swift | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 11679e4fff..b9544b9693 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -89,7 +89,7 @@ public class HeadersBuilder: NSObject { // Initialize a new builder using the provided headers container. /// - /// - parameter: The headers container to initialize the receiver with. + /// - parameter container: The headers container to initialize the receiver with. init(container: HeadersContainer) { self.container = container super.init() diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index 8de0291ab4..a7cdf4e099 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -83,16 +83,19 @@ struct HeadersContainer: Equatable { self.headers[name.lowercased()] = Header(name: name, value: value) } - /// Returns the value of a given header. + /// Get the value for the provided header name. /// - /// - parameter name: The name of the header to return the value for. + /// - parameter name: The case-insensitive header name for which to + /// get the current value. /// /// - returns: The value associated with a given header. func value(forName name: String) -> [String]? { return self.headers[name.lowercased()]?.value } - /// The list of all headers stored by the receiver. + /// Return all underlying headers. + /// + /// - returns: The underlying headers. func allHeaders() -> [String: [String]] { return Dictionary(uniqueKeysWithValues: self.headers.map { _, value in return (value.name, value.value) From 302db00097643051aa6b38e02c01b5cca2deaa89 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 10:00:23 -0400 Subject: [PATCH 20/37] get rid of todo Signed-off-by: Rafal Augustyniak --- examples/swift/async_await/ContentView.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/swift/async_await/ContentView.swift b/examples/swift/async_await/ContentView.swift index 2502352c82..bf1b938a4c 100644 --- a/examples/swift/async_await/ContentView.swift +++ b/examples/swift/async_await/ContentView.swift @@ -74,10 +74,8 @@ private extension StreamClient { .setOnResponseHeaders { headers, _, _ in let allHeaders = headers.allHeaders() - if allHeaders[":status"]?.first == "200", - // TODO(jpsim): Expose an API that enforces case-insensitive lookups - let contentLengthValue = allHeaders["Content-Length"] ?? - allHeaders["content-length"], + if headers.value(forName: ":status")?.first == "200", + let contentLengthValue = headers.value(forName: "content-length"), let firstContentLength = contentLengthValue.first, let contentLengthInt = Int64(firstContentLength) { From 9069668063d09b058db7837e931dc713c0bc72c8 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 10:46:12 -0400 Subject: [PATCH 21/37] use case-insensitive comparison Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index b9544b9693..c6139d4cc6 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,8 +3,10 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - let lowercasedName = name.lowercased() - return lowercasedName == "host" || kRestrictedPrefixes.contains { lowercasedName.hasPrefix($0) } + let isHostHeader = name.range(of: "host", options: [.caseInsensitive]) != nil + let hasRestrictedPrefix = kRestrictedPrefixes + .contains { name.range(of: $0, options: [.caseInsensitive, .anchored]) != nil } + return isHostHeader || hasRestrictedPrefix } /// Base builder class used to construct `Headers` instances. From 15cb3621fbf2bd302bc316a53860cfe808eae847 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 12:02:32 -0400 Subject: [PATCH 22/37] fix case insensitive comparison and add tests Signed-off-by: Rafal Augustyniak --- library/swift/HeadersBuilder.swift | 2 +- .../io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt | 2 ++ test/swift/HeadersBuilderTests.swift | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index c6139d4cc6..0e29a65089 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -3,7 +3,7 @@ import Foundation private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { - let isHostHeader = name.range(of: "host", options: [.caseInsensitive]) != nil + let isHostHeader = name.caseInsensitiveCompare("host") == .orderedSame let hasRestrictedPrefix = kRestrictedPrefixes .contains { name.range(of: $0, options: [.caseInsensitive, .anchored]) != nil } return isHostHeader || hasRestrictedPrefix diff --git a/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt b/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt index 7fb43c3c59..88d0b0f5f1 100644 --- a/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt +++ b/test/kotlin/io/envoyproxy/envoymobile/RequestHeadersBuilderTest.kt @@ -100,11 +100,13 @@ class RequestHeadersBuilderTest { .add(":x-foo", "123") .add("x-envoy-mobile-foo", "abc") .add("host", "example.com") + .add("hostWithSuffix", "foo.bar") .build() assertThat(headers.allHeaders()).doesNotContainKey(":x-foo") assertThat(headers.allHeaders()).doesNotContainKey("x-envoy-mobile-foo") assertThat(headers.allHeaders()).doesNotContainKey("host") + assertThat(headers.value("hostWithSuffix")).containsExactly("foo.bar") } @Test diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index 4d08c0c4af..4bf5fdcf30 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -69,6 +69,7 @@ final class HeadersBuilderTests: XCTestCase { func testRestrictedHeadersAreNotSettable() { let headers = RequestHeadersBuilder(method: .get, authority: "example.com", path: "/") .add(name: "host", value: "example.com") + .add(name: "hostWithSuffix", value: "foo.bar") .set(name: ":scheme", value: ["http"]) .set(name: ":path", value: ["/nope"]) .allHeaders() @@ -77,6 +78,7 @@ final class HeadersBuilderTests: XCTestCase { ":path": ["/"], ":method": ["GET"], ":scheme": ["https"], + "hostWithSuffix": ["foo.bar"] ] XCTAssertEqual(expected, headers) } From ca4441ad5b26e874e6b594c4b3aa0eeb6d2a7b75 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 12:08:51 -0400 Subject: [PATCH 23/37] lint fixes Signed-off-by: Rafal Augustyniak --- test/swift/HeadersBuilderTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/swift/HeadersBuilderTests.swift b/test/swift/HeadersBuilderTests.swift index 4bf5fdcf30..a92185ad25 100644 --- a/test/swift/HeadersBuilderTests.swift +++ b/test/swift/HeadersBuilderTests.swift @@ -78,7 +78,7 @@ final class HeadersBuilderTests: XCTestCase { ":path": ["/"], ":method": ["GET"], ":scheme": ["https"], - "hostWithSuffix": ["foo.bar"] + "hostWithSuffix": ["foo.bar"], ] XCTAssertEqual(expected, headers) } From fe79cb3702c3d83531516d8f9b170e920fd3b83e Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 13:12:05 -0400 Subject: [PATCH 24/37] cr comments Signed-off-by: Rafal Augustyniak --- library/swift/Headers.swift | 18 ++++++++++----- library/swift/Stream.swift | 4 ++-- library/swift/filters/Filter.swift | 34 ++++++++++++++++------------ library/swift/mocks/MockStream.swift | 5 ++-- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/library/swift/Headers.swift b/library/swift/Headers.swift index f383b2f080..779b57b940 100644 --- a/library/swift/Headers.swift +++ b/library/swift/Headers.swift @@ -8,19 +8,25 @@ public class Headers: NSObject { /// Get the value for the provided header name. /// - /// - parameter name: The case-insensitive header name for which to - /// get the current value. + /// - note: The lookup for a header name is a case-insensitive operation. /// + /// - parameter name: Header name for which to get the current value. /// /// - returns: The current headers specified for the provided name. public func value(forName name: String) -> [String]? { return self.container.value(forName: name) } - /// Accessor for all underlying headers as a map. + /// Accessor for all underlying case-sensitive headers. When possible, + /// use case-insensitive accessors instead. /// - /// - returns: The underlying headers. - public func allHeaders() -> [String: [String]] { + /// - warning: It's discouraged to use this dictionary for equality + /// key-based lookups as this may lead to issues with headers + /// that do not follow expected casing i.e., "Content-Length" + /// instead of "content-length". + /// + /// - returns: The underlying case-sensitive headers. + public func caseSensitiveHeaders() -> [String: [String]] { return self.container.allHeaders() } @@ -56,6 +62,6 @@ extension Headers { extension Headers { public override var description: String { - return "\(type(of: self)) \(self.allHeaders())" + return "\(type(of: self)) \(self.caseSensitiveHeaders())" } } diff --git a/library/swift/Stream.swift b/library/swift/Stream.swift index 0a0e8ddc0a..8be28f14cb 100644 --- a/library/swift/Stream.swift +++ b/library/swift/Stream.swift @@ -26,7 +26,7 @@ public class Stream: NSObject { /// - returns: This stream, for chaining syntax. @discardableResult public func sendHeaders(_ headers: RequestHeaders, endStream: Bool) -> Stream { - self.underlyingStream.sendHeaders(headers.allHeaders(), close: endStream) + self.underlyingStream.sendHeaders(headers.caseSensitiveHeaders(), close: endStream) return self } @@ -57,7 +57,7 @@ public class Stream: NSObject { /// /// - parameter trailers: Trailers with which to close the stream. public func close(trailers: RequestTrailers) { - self.underlyingStream.sendTrailers(trailers.allHeaders()) + self.underlyingStream.sendTrailers(trailers.caseSensitiveHeaders()) } /// Close the stream with a data frame. diff --git a/library/swift/filters/Filter.swift b/library/swift/filters/Filter.swift index 8835fbc078..4bce1feafd 100644 --- a/library/swift/filters/Filter.swift +++ b/library/swift/filters/Filter.swift @@ -27,7 +27,7 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let headers): - return [kEnvoyFilterHeadersStatusContinue, headers.allHeaders()] + return [kEnvoyFilterHeadersStatusContinue, headers.caseSensitiveHeaders()] case .stopIteration: return [kEnvoyFilterHeadersStatusStopIteration, NSNull()] } @@ -44,7 +44,10 @@ extension EnvoyHTTPFilter { case .stopIterationNoBuffer: return [kEnvoyFilterDataStatusStopIterationNoBuffer, NSNull()] case .resumeIteration(let headers, let data): - return [kEnvoyFilterDataStatusResumeIteration, data, headers?.allHeaders() as Any] + return [ + kEnvoyFilterDataStatusResumeIteration, data, + headers?.caseSensitiveHeaders() as Any, + ] } } @@ -53,14 +56,14 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let trailers): - return [kEnvoyFilterTrailersStatusContinue, trailers.allHeaders()] + return [kEnvoyFilterTrailersStatusContinue, trailers.caseSensitiveHeaders()] case .stopIteration: return [kEnvoyFilterTrailersStatusStopIteration, NSNull()] case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterTrailersStatusResumeIteration, - trailers.allHeaders(), - headers?.allHeaders() as Any, + trailers.caseSensitiveHeaders(), + headers?.caseSensitiveHeaders() as Any, data as Any, ] } @@ -74,7 +77,7 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let headers): - return [kEnvoyFilterHeadersStatusContinue, headers.allHeaders()] + return [kEnvoyFilterHeadersStatusContinue, headers.caseSensitiveHeaders()] case .stopIteration: return [kEnvoyFilterHeadersStatusStopIteration, NSNull()] } @@ -91,7 +94,10 @@ extension EnvoyHTTPFilter { case .stopIterationNoBuffer: return [kEnvoyFilterDataStatusStopIterationNoBuffer, NSNull()] case .resumeIteration(let headers, let data): - return [kEnvoyFilterDataStatusResumeIteration, data, headers?.allHeaders() as Any] + return [ + kEnvoyFilterDataStatusResumeIteration, data, + headers?.caseSensitiveHeaders() as Any, + ] } } @@ -100,14 +106,14 @@ extension EnvoyHTTPFilter { streamIntel: StreamIntel(streamIntel)) switch result { case .continue(let trailers): - return [kEnvoyFilterTrailersStatusContinue, trailers.allHeaders()] + return [kEnvoyFilterTrailersStatusContinue, trailers.caseSensitiveHeaders()] case .stopIteration: return [kEnvoyFilterTrailersStatusStopIteration, NSNull()] case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterTrailersStatusResumeIteration, - trailers.allHeaders(), - headers?.allHeaders() as Any, + trailers.caseSensitiveHeaders(), + headers?.caseSensitiveHeaders() as Any, data as Any, ] } @@ -146,9 +152,9 @@ extension EnvoyHTTPFilter { case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterResumeStatusResumeIteration, - headers?.allHeaders() as Any, + headers?.caseSensitiveHeaders() as Any, data as Any, - trailers?.allHeaders() as Any, + trailers?.caseSensitiveHeaders() as Any, ] } } @@ -172,9 +178,9 @@ extension EnvoyHTTPFilter { case .resumeIteration(let headers, let data, let trailers): return [ kEnvoyFilterResumeStatusResumeIteration, - headers?.allHeaders() as Any, + headers?.caseSensitiveHeaders() as Any, data as Any, - trailers?.allHeaders() as Any, + trailers?.caseSensitiveHeaders() as Any, ] } } diff --git a/library/swift/mocks/MockStream.swift b/library/swift/mocks/MockStream.swift index d383d62d78..4bbd3fcadb 100644 --- a/library/swift/mocks/MockStream.swift +++ b/library/swift/mocks/MockStream.swift @@ -52,7 +52,8 @@ public final class MockStream: Stream { /// - parameter headers: Response headers to receive. /// - parameter endStream: Whether this is a headers-only response. public func receiveHeaders(_ headers: ResponseHeaders, endStream: Bool) { - self.mockStream.callbacks.onHeaders(headers.allHeaders(), endStream, EnvoyStreamIntel()) + self.mockStream.callbacks.onHeaders(headers.caseSensitiveHeaders(), endStream, + EnvoyStreamIntel()) } /// Simulate response data coming back over the stream. @@ -67,7 +68,7 @@ public final class MockStream: Stream { /// /// - parameter trailers: Response trailers to receive. public func receiveTrailers(_ trailers: ResponseTrailers) { - self.mockStream.callbacks.onTrailers(trailers.allHeaders(), EnvoyStreamIntel()) + self.mockStream.callbacks.onTrailers(trailers.caseSensitiveHeaders(), EnvoyStreamIntel()) } /// Simulate the stream receiving a cancellation signal from Envoy. From a80ef6a2d6d676fc6b487d1335a91727df3348b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Augustyniak?= Date: Tue, 28 Jun 2022 13:13:00 -0400 Subject: [PATCH 25/37] Update library/swift/HeadersBuilder.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JP Simard Signed-off-by: Rafał Augustyniak --- library/swift/HeadersBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 0e29a65089..2851187bba 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -11,7 +11,7 @@ private func isRestrictedHeader(name: String) -> Bool { /// Base builder class used to construct `Headers` instances. /// It preserves the original casing of headers and enforces -/// a case-insensitive look up and setting of headers. +/// a case-insensitive lookup and setting of headers. /// See `{Request|Response}HeadersBuilder` for usage. @objcMembers public class HeadersBuilder: NSObject { From 1a4d9150f5345d64c7be7302c38fdfb34b7c44f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Augustyniak?= Date: Tue, 28 Jun 2022 13:13:12 -0400 Subject: [PATCH 26/37] Update library/swift/HeadersContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JP Simard Signed-off-by: Rafał Augustyniak --- library/swift/HeadersContainer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index a7cdf4e099..fcecc61a68 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -5,8 +5,8 @@ struct HeadersContainer: Equatable { private var headers: [String: Header] - // Represents a headers name together with all of its values. - // It preserves the original casing of the header name. + /// Represents a headers name together with all of its values. + /// It preserves the original casing of the header name. struct Header: Equatable { private(set) var name: String private(set) var value: [String] From 2a5085a0dd632d7f5a4787e8f00bb8d706bc40d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Augustyniak?= Date: Tue, 28 Jun 2022 13:13:22 -0400 Subject: [PATCH 27/37] Update library/swift/HeadersContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JP Simard Signed-off-by: Rafał Augustyniak --- library/swift/HeadersContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/swift/HeadersContainer.swift b/library/swift/HeadersContainer.swift index fcecc61a68..2f2e631348 100644 --- a/library/swift/HeadersContainer.swift +++ b/library/swift/HeadersContainer.swift @@ -34,7 +34,7 @@ struct HeadersContainer: Equatable { let lowercasedName = name.lowercased() /// Dictionaries in Swift are unordered collections. Process headers with names /// that are the same when lowercased in an alphabetical order to avoid a situation - /// in which the result of the initialization is underministic i.e., we want + /// in which the result of the initialization is non-derministic i.e., we want /// "[A: ["1"]", "a: ["2"]]" headers to be always converted to ["A": ["1", "2"]] and /// never to "a": ["2", "1"]. /// From b9d1dd9ab5ca6f26eba8d6f64857bc36c9c161e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Augustyniak?= Date: Tue, 28 Jun 2022 13:20:10 -0400 Subject: [PATCH 28/37] Update library/swift/HeadersBuilder.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JP Simard Signed-off-by: Rafał Augustyniak --- library/swift/HeadersBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/swift/HeadersBuilder.swift b/library/swift/HeadersBuilder.swift index 2851187bba..b245f3aee5 100644 --- a/library/swift/HeadersBuilder.swift +++ b/library/swift/HeadersBuilder.swift @@ -4,7 +4,7 @@ private let kRestrictedPrefixes = [":", "x-envoy-mobile"] private func isRestrictedHeader(name: String) -> Bool { let isHostHeader = name.caseInsensitiveCompare("host") == .orderedSame - let hasRestrictedPrefix = kRestrictedPrefixes + lazy var hasRestrictedPrefix = kRestrictedPrefixes .contains { name.range(of: $0, options: [.caseInsensitive, .anchored]) != nil } return isHostHeader || hasRestrictedPrefix } From 75a586df0f47b99758da704028cec8f729155b85 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 13:25:15 -0400 Subject: [PATCH 29/37] update version history Signed-off-by: Rafal Augustyniak --- docs/root/intro/version_history.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 2fdf016ac9..819ccd3e9c 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -24,7 +24,7 @@ Bugfixes: - Android: update Kotlin standard libraries to 1.6.21 (:issue:`#2256 <2256>`) - fix bug where finalStreamIntel was not consistently set on cancel (:issue:`#2285 <2285>`) - iOS: fix termination crash in ProvisionalDispatcher (:issue:`#2059 <2059>`) -- iOS: make headers lookup in ``HeadersBuilder`` and ``Headers`` case-insensitive (:issue:`#2383 <2383>`) +- iOS: make headers lookup in ``HeadersBuilder`` and ``Headers`` case-insensitive. Rename ``allHeaders`` method to ``caseSensitiveHeaders``. (:issue:`#2383 <2383>`) - iOS: use correct DNS resolver when using C++ config builder (:issue: `#2378 <2378 >`) Features: From 7295362d1efea3a930383d283ff3d2b9eb5448f6 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 14:37:43 -0400 Subject: [PATCH 30/37] change used method Signed-off-by: Rafal Augustyniak --- examples/swift/async_await/ContentView.swift | 2 -- examples/swift/hello_world/ViewController.swift | 2 +- test/swift/apps/experimental/ViewController.swift | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/swift/async_await/ContentView.swift b/examples/swift/async_await/ContentView.swift index bf1b938a4c..8b8ade46bc 100644 --- a/examples/swift/async_await/ContentView.swift +++ b/examples/swift/async_await/ContentView.swift @@ -72,8 +72,6 @@ private extension StreamClient { let stream = self .newStreamPrototype() .setOnResponseHeaders { headers, _, _ in - let allHeaders = headers.allHeaders() - if headers.value(forName: ":status")?.first == "200", let contentLengthValue = headers.value(forName: "content-length"), let firstContentLength = contentLengthValue.first, diff --git a/examples/swift/hello_world/ViewController.swift b/examples/swift/hello_world/ViewController.swift index 8676c01002..f9f998e65d 100644 --- a/examples/swift/hello_world/ViewController.swift +++ b/examples/swift/hello_world/ViewController.swift @@ -70,7 +70,7 @@ final class ViewController: UITableViewController { let statusCode = headers.httpStatus.map(String.init) ?? "nil" let message = "received headers with status \(statusCode)" - let headerMessage = headers.allHeaders() + let headerMessage = headers.caseSensitiveHeaders() .filter { kFilteredHeaders.contains($0.key) } .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") diff --git a/test/swift/apps/experimental/ViewController.swift b/test/swift/apps/experimental/ViewController.swift index 59644895ed..02f043617a 100644 --- a/test/swift/apps/experimental/ViewController.swift +++ b/test/swift/apps/experimental/ViewController.swift @@ -72,7 +72,7 @@ final class ViewController: UITableViewController { let statusCode = headers.httpStatus.map(String.init) ?? "nil" let message = "received headers with status \(statusCode)" - let headerMessage = headers.allHeaders() + let headerMessage = headers.caseSensitiveHeaders() .filter { kFilteredHeaders.contains($0.key) } .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") From 723c76ff46e76f78de210a0d318a50cfc3003132 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 14:50:30 -0400 Subject: [PATCH 31/37] update example app Signed-off-by: Rafal Augustyniak --- test/swift/apps/baseline/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/swift/apps/baseline/ViewController.swift b/test/swift/apps/baseline/ViewController.swift index 8676c01002..f9f998e65d 100644 --- a/test/swift/apps/baseline/ViewController.swift +++ b/test/swift/apps/baseline/ViewController.swift @@ -70,7 +70,7 @@ final class ViewController: UITableViewController { let statusCode = headers.httpStatus.map(String.init) ?? "nil" let message = "received headers with status \(statusCode)" - let headerMessage = headers.allHeaders() + let headerMessage = headers.caseSensitiveHeaders() .filter { kFilteredHeaders.contains($0.key) } .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") From 079a01a8b62988bc21152d16fa0965626b5aae8e Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 15:16:32 -0400 Subject: [PATCH 32/37] update example app Signed-off-by: Rafal Augustyniak --- examples/swift/async_await/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swift/async_await/ContentView.swift b/examples/swift/async_await/ContentView.swift index 8b8ade46bc..0931c531bc 100644 --- a/examples/swift/async_await/ContentView.swift +++ b/examples/swift/async_await/ContentView.swift @@ -83,7 +83,7 @@ private extension StreamClient { return } - let headerMessage = allHeaders + let headerMessage = caseSensitiveHeaders .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") From 62885af9da93ff5b5792f8f2cbb7f1823012d341 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 15:30:49 -0400 Subject: [PATCH 33/37] update example app Signed-off-by: Rafal Augustyniak --- examples/swift/async_await/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/swift/async_await/ContentView.swift b/examples/swift/async_await/ContentView.swift index 0931c531bc..e510e8d0b8 100644 --- a/examples/swift/async_await/ContentView.swift +++ b/examples/swift/async_await/ContentView.swift @@ -83,7 +83,7 @@ private extension StreamClient { return } - let headerMessage = caseSensitiveHeaders + let headerMessage = headers.caseSensitiveHeaders .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") From 702bc24d51c109d1507267bdb277e64730ad6b7b Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 15:53:43 -0400 Subject: [PATCH 34/37] update example apps Signed-off-by: Rafal Augustyniak --- examples/objective-c/hello_world/ViewController.m | 4 ++-- examples/swift/async_await/ContentView.swift | 2 +- test/swift/apps/experimental/ViewController.swift | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/objective-c/hello_world/ViewController.m b/examples/objective-c/hello_world/ViewController.m index 3a989e42cf..69e16bc5d4 100644 --- a/examples/objective-c/hello_world/ViewController.m +++ b/examples/objective-c/hello_world/ViewController.m @@ -90,9 +90,9 @@ - (void)performRequest { NSString *message = [NSString stringWithFormat:@"received headers with status %i", statusCode]; NSMutableString *headerMessage = [NSMutableString new]; - for (NSString *name in headers.allHeaders) { + for (NSString *name in headers.caseSensitiveHeaders()) { if ([self.filteredHeaders containsObject:name]) { - NSArray *values = headers.allHeaders[name]; + NSArray *values = headers.caseSensitiveHeaders()[name]; NSString *joined = [values componentsJoinedByString:@", "]; NSString *pair = [NSString stringWithFormat:@"%@: %@\n", name, joined]; [headerMessage appendString:pair]; diff --git a/examples/swift/async_await/ContentView.swift b/examples/swift/async_await/ContentView.swift index e510e8d0b8..6772ecbf5c 100644 --- a/examples/swift/async_await/ContentView.swift +++ b/examples/swift/async_await/ContentView.swift @@ -83,7 +83,7 @@ private extension StreamClient { return } - let headerMessage = headers.caseSensitiveHeaders + let headerMessage = headers.caseSensitiveHeaders() .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") diff --git a/test/swift/apps/experimental/ViewController.swift b/test/swift/apps/experimental/ViewController.swift index 02f043617a..f2e8fe2f94 100644 --- a/test/swift/apps/experimental/ViewController.swift +++ b/test/swift/apps/experimental/ViewController.swift @@ -72,9 +72,14 @@ final class ViewController: UITableViewController { let statusCode = headers.httpStatus.map(String.init) ?? "nil" let message = "received headers with status \(statusCode)" - let headerMessage = headers.caseSensitiveHeaders() - .filter { kFilteredHeaders.contains($0.key) } - .map { "\($0.key): \($0.value.joined(separator: ", "))" } + let headerMessage = kFilteredHeaders + .compactMap { name in + guard let value = headers.value(forName: $0) else { + return nil + } + + return "\($0.key): \($0.value.joined(separator: ", "))" + } .joined(separator: "\n") NSLog(message) From d3bd1ea913db9de227005ebc047f5cefa541d4c4 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 15:57:12 -0400 Subject: [PATCH 35/37] update example apps Signed-off-by: Rafal Augustyniak --- test/swift/apps/experimental/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/swift/apps/experimental/ViewController.swift b/test/swift/apps/experimental/ViewController.swift index f2e8fe2f94..a6141ffb8d 100644 --- a/test/swift/apps/experimental/ViewController.swift +++ b/test/swift/apps/experimental/ViewController.swift @@ -73,7 +73,7 @@ final class ViewController: UITableViewController { let message = "received headers with status \(statusCode)" let headerMessage = kFilteredHeaders - .compactMap { name in + .compactMap { name in guard let value = headers.value(forName: $0) else { return nil } From d45fa9f8fbf2567126aab1327cf4339f43409b0e Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 15:57:50 -0400 Subject: [PATCH 36/37] update example apps Signed-off-by: Rafal Augustyniak --- test/swift/apps/experimental/ViewController.swift | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/swift/apps/experimental/ViewController.swift b/test/swift/apps/experimental/ViewController.swift index a6141ffb8d..02f043617a 100644 --- a/test/swift/apps/experimental/ViewController.swift +++ b/test/swift/apps/experimental/ViewController.swift @@ -72,14 +72,9 @@ final class ViewController: UITableViewController { let statusCode = headers.httpStatus.map(String.init) ?? "nil" let message = "received headers with status \(statusCode)" - let headerMessage = kFilteredHeaders - .compactMap { name in - guard let value = headers.value(forName: $0) else { - return nil - } - - return "\($0.key): \($0.value.joined(separator: ", "))" - } + let headerMessage = headers.caseSensitiveHeaders() + .filter { kFilteredHeaders.contains($0.key) } + .map { "\($0.key): \($0.value.joined(separator: ", "))" } .joined(separator: "\n") NSLog(message) From 42b38bf8ee16f7267774bb580717b7b487108a46 Mon Sep 17 00:00:00 2001 From: Rafal Augustyniak Date: Tue, 28 Jun 2022 16:21:14 -0400 Subject: [PATCH 37/37] update example apps Signed-off-by: Rafal Augustyniak --- examples/objective-c/hello_world/ViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/objective-c/hello_world/ViewController.m b/examples/objective-c/hello_world/ViewController.m index 69e16bc5d4..d01dd8a750 100644 --- a/examples/objective-c/hello_world/ViewController.m +++ b/examples/objective-c/hello_world/ViewController.m @@ -90,9 +90,9 @@ - (void)performRequest { NSString *message = [NSString stringWithFormat:@"received headers with status %i", statusCode]; NSMutableString *headerMessage = [NSMutableString new]; - for (NSString *name in headers.caseSensitiveHeaders()) { + for (NSString *name in headers.caseSensitiveHeaders) { if ([self.filteredHeaders containsObject:name]) { - NSArray *values = headers.caseSensitiveHeaders()[name]; + NSArray *values = headers.caseSensitiveHeaders[name]; NSString *joined = [values componentsJoinedByString:@", "]; NSString *pair = [NSString stringWithFormat:@"%@: %@\n", name, joined]; [headerMessage appendString:pair];