swift-corelibs-foundation: [SR-2429] Regression: HTTPURLResponse allHeaderFields is now case-sensitive

Previous ID SR-2429
Radar rdar://27925651
Original Reporter alexsporn (JIRA User)
Type Bug
Status Resolved
Resolution Done
Environment

Xcode Version 8.0 beta 6 (8S201h)

Additional Detail from JIRA
Votes 27
Component/s Foundation
Labels Bug
Assignee @airspeedswift
Priority Medium

md5: 2495a8cbf7aef0a067f006a363dbbfce

Issue Description:

HTTPURLResponse normalizes header names following RFC 2616 as described in https://developer.apple.com/reference/foundation/httpurlresponse/1417930-allheaderfields

Access to these header values is case-insensitive as described in the documentation. This is broken in the latest Xcode8 beta6. The access is now case-sensitive.

Additionally the “ETag” header name is normalized to “Etag” instead of “ETag” as described in RFC 2616

This works correctly on Xcode8 beta4

Steps to Reproduce:
Run following Swift code:

let url = URL(string: "http://www.apple.com")!

let headers = [
    "ETag" : "123456",
    "content-type": "application/json",
    ]

let urlResponse = HTTPURLResponse(url: url,
                                  statusCode: 200,
                                  httpVersion: nil,
                                  headerFields: headers)

print("ETag: \(urlResponse?.allHeaderFields["ETag"])")
print("Etag: \(urlResponse?.allHeaderFields["Etag"])")
print("ETAG: \(urlResponse?.allHeaderFields["ETAG"])")
print("etag: \(urlResponse?.allHeaderFields["etag"])")
print("content-type: \(urlResponse?.allHeaderFields["content-type"])")
print("Content-Type: \(urlResponse?.allHeaderFields["Content-Type"])")
print("CONTENT-TYPE: \(urlResponse?.allHeaderFields["CONTENT-TYPE"])")

Same code in Objective-C works as expected:

NSURL *url = [[NSURL alloc] initWithString:@"http://www.apple.com"];

NSDictionary *headers = @{
                          @"ETag": @"123456",
                          @"content-type": @"application/json",

                          };

NSURL *url = [[NSURL alloc] initWithString:@"http://www.apple.com"];

NSDictionary *headers = @{
                          @"ETag": @"123456",
                          @"content-type": @"application/json",

                          };

NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
                                                          statusCode:200
                                                         HTTPVersion:nil
                                                        headerFields:headers];

NSLog(@"Headers: %@", response.allHeaderFields);

NSLog(@"ETag: %@", response.allHeaderFields[@"ETag"]);
NSLog(@"Etag: %@", response.allHeaderFields[@"Etag"]);
NSLog(@"etag: %@", response.allHeaderFields[@"etag"]);
NSLog(@"ETAG: %@", response.allHeaderFields[@"ETAG"]);
NSLog(@"content-type: %@", response.allHeaderFields[@"content-type"]);
NSLog(@"Content-Type: %@", response.allHeaderFields[@"Content-Type"]);
NSLog(@"CONTENT-TYPE: %@", response.allHeaderFields[@"CONTENT-TYPE"]);

Expected Results:

Headers: Optional([AnyHashable("Content-Type"): application/json, AnyHashable("Etag"): 123456])
ETag: Optional(123456)
Etag: Optional(123456)
ETAG: Optional(123456)
etag: Optional(123456)
content-type: Optional(application/json)
Content-Type: Optional(application/json)
CONTENT-TYPE: Optional(application/json)

Actual Results:

Headers: Optional([AnyHashable("Content-Type"): application/json, AnyHashable("Etag"): 123456])
ETag: nil
Etag: Optional(123456)
ETAG: nil
etag: nil
content-type: nil
Content-Type: Optional(application/json)
CONTENT-TYPE: nil

Version:
Version 8.0 beta 6 (8S201h)

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 23 (18 by maintainers)

Most upvoted comments

Hi everyone,

There’s no way for us to keep HTTPURLResponse.allHeaderFields from being bridged to Swift as a case-sensitive dictionary, so this bug could only be fixed indirectly.

To provide a way to do a case-insensitive lookup from Swift, Foundation has added a HTTPURLResponse.value(forHTTPHeaderField:) method. The Swift documentation for HTTPURLResponse now recommends using this method instead of allHeaderFields to look up headers by name.

This new method is available in the OSes released with Swift 5.1 (macOS 10.15/iOS and tvOS 13.0/watchOS 6.0). To backwards deploy on older OSes, using (response.allHeaderFields as NSDictionary)[someHeaderName] is a supported workaround that is expected to continue working in future OSes. The method was not included in Swift 5.1 or 5.2’s Corelibs Foundation, but as of SR-12300, it’s now in nightly snapshots and will probably be in Swift 5.3’s Corelibs Foundation.

This bug has therefore been fixed by providing an alternate Swift-compatible API for the task. In the future, please use value(forHTTPHeaderField:) or, if necessary, the as NSDictionary workaround to look up headers case-insensitively.

Thanks!