Skip to content

Commit 28d8171

Browse files
authored
Ensure user-provieded metadata has lowercase keys (#30)
Motivation: HTTP/2 requires header field names to be lowercased. Metadata keys should be lowercased automatically. Modifications: Lowercase user-provided metadata keys. Results: Fewer bugs
1 parent 928d356 commit 28d8171

File tree

4 files changed

+61
-8
lines changed

4 files changed

+61
-8
lines changed

Sources/GRPCNIOTransportCore/GRPCStreamStateMachine.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,8 @@ extension GRPCStreamStateMachine {
659659
}
660660

661661
for metadataPair in customMetadata {
662-
headers.add(name: metadataPair.key, value: metadataPair.value.encoded())
662+
// Lowercase the field names for user-provided metadata.
663+
headers.add(name: metadataPair.key.lowercased(), value: metadataPair.value.encoded())
663664
}
664665

665666
return headers
@@ -1248,7 +1249,8 @@ extension GRPCStreamStateMachine {
12481249
}
12491250

12501251
for metadataPair in customMetadata {
1251-
headers.add(name: metadataPair.key, value: metadataPair.value.encoded())
1252+
// Lowercase the field names for user-provided metadata.
1253+
headers.add(name: metadataPair.key.lowercased(), value: metadataPair.value.encoded())
12521254
}
12531255
}
12541256

@@ -1827,7 +1829,8 @@ extension HPACKHeaders {
18271829
}
18281830

18291831
for (key, value) in metadata {
1830-
trailers.add(name: key, value: value.encoded())
1832+
// Lowercase the field names for user-provided metadata.
1833+
trailers.add(name: key.lowercased(), value: value.encoded())
18311834
}
18321835
}
18331836
}

Tests/GRPCNIOTransportHTTP2Tests/ControlMessages.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ struct ControlInput: Codable {
6464
/// removed (":path" should become "echo-path").
6565
var echoMetadataInTrailers: Bool = false
6666

67+
/// Key-value pairs to add to the initial metadata.
68+
var initialMetadataToAdd: [String: String] = [:]
69+
70+
/// Key-value pairs to add to the trailing metadata.
71+
var trailingMetadataToAdd: [String: String] = [:]
72+
6773
struct Status: Codable {
6874
var code: GRPCCore.Status.Code
6975
var message: String

Tests/GRPCNIOTransportHTTP2Tests/ControlService.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ extension ControlService {
102102

103103
// Check if the request is for a trailers-only response.
104104
if let status = message.status, message.isTrailersOnly {
105-
let trailers = message.echoMetadataInTrailers ? request.metadata.echo() : [:]
105+
var trailers = message.echoMetadataInTrailers ? request.metadata.echo() : [:]
106+
for (key, value) in message.trailingMetadataToAdd {
107+
trailers.addString(value, forKey: key)
108+
}
106109
let code = Status.Code(rawValue: status.code.rawValue).flatMap { RPCError.Code($0) }
107110

108111
if let code = code {
@@ -117,7 +120,10 @@ extension ControlService {
117120
}
118121

119122
// Not a trailers-only response. Should the metadata be echo'd back?
120-
let metadata = message.echoMetadataInHeaders ? request.metadata.echo() : [:]
123+
var metadata = message.echoMetadataInHeaders ? request.metadata.echo() : [:]
124+
for (key, value) in message.initialMetadataToAdd {
125+
metadata.addString(value, forKey: key)
126+
}
121127

122128
// The iterator needs to be transferred into the response. This is okay: we won't touch the
123129
// iterator again from the current concurrency domain.
@@ -174,10 +180,13 @@ extension ControlService {
174180

175181
// Check whether the RPC should be finished (i.e. the input `hasStatus`).
176182
guard let status = input.status else {
177-
if input.echoMetadataInTrailers {
183+
if input.echoMetadataInTrailers || !input.trailingMetadataToAdd.isEmpty {
178184
// There was no status in the input, but echo metadata in trailers was set. This is an
179185
// implicit 'ok' status.
180-
let trailers = input.echoMetadataInTrailers ? metadata.echo() : [:]
186+
var trailers = input.echoMetadataInTrailers ? metadata.echo() : [:]
187+
for (key, value) in input.trailingMetadataToAdd {
188+
trailers.addString(value, forKey: key)
189+
}
181190
return .return(trailers)
182191
} else {
183192
// No status, and not echoing back metadata. Continue consuming the input stream.
@@ -186,7 +195,10 @@ extension ControlService {
186195
}
187196

188197
// Build the trailers.
189-
let trailers = input.echoMetadataInTrailers ? metadata.echo() : [:]
198+
var trailers = input.echoMetadataInTrailers ? metadata.echo() : [:]
199+
for (key, value) in input.trailingMetadataToAdd {
200+
trailers.addString(value, forKey: key)
201+
}
190202

191203
if status.code == .ok {
192204
return .return(trailers)

Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,38 @@ final class HTTP2TransportTests: XCTestCase {
14461446
}
14471447
}
14481448
}
1449+
1450+
func testUppercaseClientMetadataKey() async throws {
1451+
try await self.forEachTransportPair { control, _, _ in
1452+
let request = ClientRequest<ControlInput>(
1453+
message: .with {
1454+
$0.echoMetadataInHeaders = true
1455+
$0.numberOfMessages = 1
1456+
},
1457+
metadata: ["UPPERCASE-KEY": "value"]
1458+
)
1459+
try await control.unary(request: request) { response in
1460+
// Keys will be lowercase before being sent over the wire.
1461+
XCTAssertEqual(Array(response.metadata["echo-uppercase-key"]), ["value"])
1462+
}
1463+
}
1464+
}
1465+
1466+
func testUppercaseServerMetadataKey() async throws {
1467+
try await self.forEachTransportPair { control, _, _ in
1468+
let request = ClientRequest<ControlInput>(
1469+
message: .with {
1470+
$0.initialMetadataToAdd["UPPERCASE-KEY"] = "initial"
1471+
$0.trailingMetadataToAdd["UPPERCASE-KEY"] = "trailing"
1472+
$0.numberOfMessages = 1
1473+
}
1474+
)
1475+
try await control.unary(request: request) { response in
1476+
XCTAssertEqual(Array(response.metadata["uppercase-key"]), ["initial"])
1477+
XCTAssertEqual(Array(response.trailingMetadata["uppercase-key"]), ["trailing"])
1478+
}
1479+
}
1480+
}
14491481
}
14501482

14511483
extension [HTTP2TransportTests.Transport] {

0 commit comments

Comments
 (0)