@@ -3114,4 +3114,113 @@ class HTTPClientTests: XCTestCase {
3114
3114
3115
3115
XCTAssertNoThrow ( try client. execute ( request: request) . wait ( ) )
3116
3116
}
3117
+
3118
+ func testRejectsInvalidCharactersInHeaderFieldNames_http1( ) throws {
3119
+ try self . _rejectsInvalidCharactersInHeaderFieldNames ( mode: . http1_1( ssl: true ) )
3120
+ }
3121
+
3122
+ func testRejectsInvalidCharactersInHeaderFieldNames_http2( ) throws {
3123
+ try self . _rejectsInvalidCharactersInHeaderFieldNames ( mode: . http2( compress: false ) )
3124
+ }
3125
+
3126
+ private func _rejectsInvalidCharactersInHeaderFieldNames( mode: HTTPBin < HTTPBinHandler > . Mode ) throws {
3127
+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
3128
+ defer { XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) ) }
3129
+ let client = HTTPClient ( eventLoopGroupProvider: . shared( group) )
3130
+ defer { XCTAssertNoThrow ( try client. syncShutdown ( ) ) }
3131
+ let bin = HTTPBin ( mode)
3132
+ defer { XCTAssertNoThrow ( try bin. shutdown ( ) ) }
3133
+
3134
+ // The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
3135
+ // characters as the following:
3136
+ //
3137
+ // ```
3138
+ // field-name = token
3139
+ //
3140
+ // token = 1*tchar
3141
+ //
3142
+ // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
3143
+ // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
3144
+ // / DIGIT / ALPHA
3145
+ // ; any VCHAR, except delimiters
3146
+ let weirdAllowedFieldName = " !#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "
3147
+
3148
+ var request = try Request ( url: " \( self . defaultHTTPBinURLPrefix) get " )
3149
+ request. headers. add ( name: weirdAllowedFieldName, value: " present " )
3150
+
3151
+ // This should work fine.
3152
+ let response = try client. execute ( request: request) . wait ( )
3153
+ XCTAssertEqual ( response. status, . ok)
3154
+
3155
+ // Now, let's confirm all other bytes are rejected. We want to stay within the ASCII space as the HTTPHeaders type will forbid anything else.
3156
+ for byte in UInt8 ( 0 ) ... UInt8 ( 127 ) {
3157
+ // Skip bytes that we already believe are allowed.
3158
+ if weirdAllowedFieldName. utf8. contains ( byte) {
3159
+ continue
3160
+ }
3161
+ let forbiddenFieldName = weirdAllowedFieldName + String( decoding: [ byte] , as: UTF8 . self)
3162
+
3163
+ var request = try Request ( url: " \( self . defaultHTTPBinURLPrefix) get " )
3164
+ request. headers. add ( name: forbiddenFieldName, value: " present " )
3165
+
3166
+ XCTAssertThrowsError ( try client. execute ( request: request) . wait ( ) ) { error in
3167
+ XCTAssertEqual ( error as? HTTPClientError , . invalidHeaderFieldNames( [ forbiddenFieldName] ) )
3168
+ }
3169
+ }
3170
+ }
3171
+
3172
+ func testRejectsInvalidCharactersInHeaderFieldValues_http1( ) throws {
3173
+ try self . _rejectsInvalidCharactersInHeaderFieldValues ( mode: . http1_1( ssl: true ) )
3174
+ }
3175
+
3176
+ func testRejectsInvalidCharactersInHeaderFieldValues_http2( ) throws {
3177
+ try self . _rejectsInvalidCharactersInHeaderFieldValues ( mode: . http2( compress: false ) )
3178
+ }
3179
+
3180
+ private func _rejectsInvalidCharactersInHeaderFieldValues( mode: HTTPBin < HTTPBinHandler > . Mode ) throws {
3181
+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
3182
+ defer { XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) ) }
3183
+ let client = HTTPClient ( eventLoopGroupProvider: . shared( group) )
3184
+ defer { XCTAssertNoThrow ( try client. syncShutdown ( ) ) }
3185
+ let bin = HTTPBin ( mode)
3186
+ defer { XCTAssertNoThrow ( try bin. shutdown ( ) ) }
3187
+
3188
+ // We reject all ASCII control characters except HTAB and tolerate everything else.
3189
+ let weirdAllowedFieldValue = " ! \" \t #$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[ \\ ]^_`abcdefghijklmnopqrstuvwxyz{|}~ "
3190
+
3191
+ var request = try Request ( url: " \( self . defaultHTTPBinURLPrefix) get " )
3192
+ request. headers. add ( name: " Weird-Value " , value: weirdAllowedFieldValue)
3193
+
3194
+ // This should work fine.
3195
+ let response = try client. execute ( request: request) . wait ( )
3196
+ XCTAssertEqual ( response. status, . ok)
3197
+
3198
+ // Now, let's confirm all other bytes in the ASCII range ar rejected
3199
+ for byte in UInt8 ( 0 ) ... UInt8 ( 127 ) {
3200
+ // Skip bytes that we already believe are allowed.
3201
+ if weirdAllowedFieldValue. utf8. contains ( byte) {
3202
+ continue
3203
+ }
3204
+ let forbiddenFieldValue = weirdAllowedFieldValue + String( decoding: [ byte] , as: UTF8 . self)
3205
+
3206
+ var request = try Request ( url: " \( self . defaultHTTPBinURLPrefix) get " )
3207
+ request. headers. add ( name: " Weird-Value " , value: forbiddenFieldValue)
3208
+
3209
+ XCTAssertThrowsError ( try client. execute ( request: request) . wait ( ) ) { error in
3210
+ XCTAssertEqual ( error as? HTTPClientError , . invalidHeaderFieldValues( [ forbiddenFieldValue] ) )
3211
+ }
3212
+ }
3213
+
3214
+ // All the bytes outside the ASCII range are fine though.
3215
+ for byte in UInt8 ( 128 ) ... UInt8 ( 255 ) {
3216
+ let evenWeirderAllowedValue = weirdAllowedFieldValue + String( decoding: [ byte] , as: UTF8 . self)
3217
+
3218
+ var request = try Request ( url: " \( self . defaultHTTPBinURLPrefix) get " )
3219
+ request. headers. add ( name: " Weird-Value " , value: evenWeirderAllowedValue)
3220
+
3221
+ // This should work fine.
3222
+ let response = try client. execute ( request: request) . wait ( )
3223
+ XCTAssertEqual ( response. status, . ok)
3224
+ }
3225
+ }
3117
3226
}
0 commit comments