Skip to content

Commit f43c554

Browse files
committed
Add default headers. Support gzip, deflate encoding.
Accept: */* Accept-Encoding: deflate, gzip Connection: keep-alive User-Agent: TestFoundation (unknown version) curl/7.43.0
1 parent afbc6bd commit f43c554

File tree

5 files changed

+175
-22
lines changed

5 files changed

+175
-22
lines changed

CoreFoundation/URL.subproj/CFURLSessionInterface.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,18 @@ CFURLSessionPoll const CFURLSessionPollOut = { CURL_POLL_OUT };
714714
CFURLSessionPoll const CFURLSessionPollInOut = { CURL_POLL_INOUT };
715715
CFURLSessionPoll const CFURLSessionPollRemove = { CURL_POLL_REMOVE };
716716

717-
char *CFURLSessionCurlVersion(void) {
717+
char *CFURLSessionCurlVersionString(void) {
718718
return curl_version();
719719
}
720+
CFURLSessionCurlVersion CFURLSessionCurlVersionInfo(void) {
721+
curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
722+
CFURLSessionCurlVersion v = {
723+
info->version_num >> 16 & 0xff,
724+
info->version_num >> 8 & 0xff,
725+
info->version_num & 0xff,
726+
};
727+
return v;
728+
}
720729

721730

722731
int const CFURLSessionWriteFuncPause = CURL_WRITEFUNC_PAUSE;

CoreFoundation/URL.subproj/CFURLSessionInterface.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,13 @@ CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolALL; // CURLPROTO_ALL
673673

674674
CF_EXPORT size_t const CFURLSessionMaxWriteSize; // CURL_MAX_WRITE_SIZE
675675

676-
CF_EXPORT char * _Nonnull CFURLSessionCurlVersion(void);
676+
CF_EXPORT char * _Nonnull CFURLSessionCurlVersionString(void);
677+
typedef struct CFURLSessionCurlVersion {
678+
int major;
679+
int minor;
680+
int patch;
681+
} CFURLSessionCurlVersion;
682+
CF_EXPORT CFURLSessionCurlVersion CFURLSessionCurlVersionInfo(void);
677683

678684

679685
CF_EXPORT int const CFURLSessionWriteFuncPause;

Foundation/NSURLSession/EasyHandle.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,6 @@ extension NSURLSessionTask.EasyHandle {
217217
// We need to retain the list for as long as the rawHandle is in use.
218218
headerList = list
219219
}
220-
/// Disable HTTP content decoding
221-
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_CONTENT_DECODING.html
222-
func setDisableContentDecoding() {
223-
//TODO: Darwin Foundation appears to automatically handle content decoding (gzip) ?!?
224-
try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHTTP_CONTENT_DECODING, 0).asError()
225-
}
226220
/// Wait for pipelining/multiplexing
227221
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_PIPEWAIT.html
228222
func setWaitForPipeliningAndMultiplexing(flag: Bool) {
@@ -241,7 +235,20 @@ extension NSURLSessionTask.EasyHandle {
241235
let w = 1 + max(0, min(255, Int(round(weight * 255))))
242236
try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionPIPEWAIT, w).asError()
243237
}
244-
238+
/// Enable automatic decompression of HTTP downloads
239+
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html
240+
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_CONTENT_DECODING.html
241+
func setAutomaticBodyDecompression(flag: Bool) {
242+
if flag {
243+
"".withCString {
244+
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionACCEPT_ENCODING, UnsafeMutablePointer($0)).asError()
245+
}
246+
try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHTTP_CONTENT_DECODING, 1).asError()
247+
} else {
248+
try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionACCEPT_ENCODING, nil).asError()
249+
try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHTTP_CONTENT_DECODING, 0).asError()
250+
}
251+
}
245252
/// Set request method
246253
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html
247254
func setRequestMethod(method: String) {

Foundation/NSURLSession/NSURLSessionTask.swift

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -541,23 +541,97 @@ private extension NSURLSessionTask {
541541

542542
// HTTP Options:
543543
easyHandle.setFollowLocation(false)
544-
//TODO: Fix headers
545-
var headers: [String] = []
546-
if case .none = body {
547-
} else {
548-
headers.append("Expect:")
549-
}
550-
easyHandle.setCustomHeaders(headers)
551-
easyHandle.setDisableContentDecoding()
544+
easyHandle.setCustomHeaders(curlHeaders(for: request))
552545
easyHandle.setWaitForPipeliningAndMultiplexing(true)
553546
easyHandle.setStreamWeight(priority)
547+
easyHandle.setAutomaticBodyDecompression(true)
554548
easyHandle.setRequestMethod(request.HTTPMethod ?? "GET")
555549
if request.HTTPMethod == "HEAD" {
556550
easyHandle.setNoBody(true)
557551
}
558552
}
559553
}
560554

555+
private extension NSURLSessionTask {
556+
/// These are a list of headers that should be passed to libcurl.
557+
///
558+
/// Headers will be returned as `Accept: text/html` strings for
559+
/// setting fields, `Accept:` for disabling the libcurl default header, or
560+
/// `Accept;` for a header with no content. This is the format that libcurl
561+
/// expects.
562+
///
563+
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
564+
func curlHeaders(for request: NSURLRequest) -> [String] {
565+
var result: [String] = []
566+
var names = Set<String>()
567+
if let hh = currentRequest?.allHTTPHeaderFields {
568+
hh.forEach {
569+
let name = $0.0.lowercased()
570+
guard !names.contains(name) else { return }
571+
names.insert(name)
572+
573+
if $0.1.isEmpty {
574+
result.append($0.0 + ";")
575+
} else {
576+
result.append($0.0 + ": " + $0.1)
577+
}
578+
}
579+
}
580+
curlHeadersToSet.forEach {
581+
let name = $0.0.lowercased()
582+
guard !names.contains(name) else { return }
583+
names.insert(name)
584+
585+
if $0.1.isEmpty {
586+
result.append($0.0 + ";")
587+
} else {
588+
result.append($0.0 + ": " + $0.1)
589+
}
590+
}
591+
curlHeadersToRemove.forEach {
592+
let name = $0.lowercased()
593+
guard !names.contains(name) else { return }
594+
names.insert(name)
595+
result.append($0 + ":")
596+
}
597+
return result
598+
}
599+
/// Any header values that should be passed to libcurl
600+
///
601+
/// These will only be set if not already part of the request.
602+
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
603+
var curlHeadersToSet: [(String,String)] {
604+
var result = [("Connection", "keep-alive"),
605+
("User-Agent", userAgentString),
606+
]
607+
if let language = NSLocale.currentLocale().objectForKey(NSLocaleLanguageCode) as? String {
608+
result.append(("Accept-Language", language))
609+
}
610+
return result
611+
}
612+
/// Any header values that should be removed from the ones set by libcurl
613+
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
614+
var curlHeadersToRemove: [String] {
615+
if case .none = body {
616+
return []
617+
} else {
618+
return ["Expect"]
619+
}
620+
}
621+
}
622+
623+
private var userAgentString: String = {
624+
// Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)"
625+
let info = NSProcessInfo.processInfo()
626+
let name = info.processName
627+
let curlVersion = CFURLSessionCurlVersionInfo()
628+
//TODO: Should probably use sysctl(3) to get these:
629+
// kern.ostype: Darwin
630+
// kern.osrelease: 15.4.0
631+
//TODO: Use NSBundle to get the version number?
632+
return "\(name) (unknown version) curl/\(curlVersion.major).\(curlVersion.minor).\(curlVersion.patch)"
633+
}()
634+
561635
private func errorCodeFromFileSystemError(error: ErrorProtocol) -> Int {
562636
func fromCocoaErrorCode(code: Int) -> Int {
563637
switch code {

TestFoundation/TestNSURLSession.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class TestNSURLSession : XCTestCase {
194194
("test_functional_serverClosingConnection", test_functional_serverClosingConnection),
195195
("test_functional_delegate_GET_withRedirect", test_functional_delegate_GET_withRedirect),
196196
("test_functional_completionHandler_GET", test_functional_completionHandler_GET),
197+
("test_functional_GET_compressedData", test_functional_GET_compressedData),
197198
("test_functional_POST_fromData", test_functional_POST_fromData),
198199
("test_functional_POST_fromFile", test_functional_POST_fromFile),
199200
("test_functional_POST_fromFileThatDoesNotExist", test_functional_POST_fromFileThatDoesNotExist),
@@ -467,6 +468,57 @@ extension TestNSURLSession {
467468
}
468469
}
469470

471+
func test_functional_GET_compressedData() {
472+
var baseURL: NSURL?
473+
guard let (server, _baseURL) = createServer(handler: { request in
474+
switch request.URI.path! {
475+
case "/hello":
476+
let bytes = ContiguousArray<UInt8>([0x1f, 0x8b, 0x08, 0x08, 0xa3, 0x7b, 0x05, 0x57,
477+
0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x2e, 0x74,
478+
0x78, 0x74, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9,
479+
0x57, 0x08, 0x2e, 0xcf, 0x4c, 0x2b, 0x51, 0x04,
480+
0x00, 0xef, 0xc5, 0x65, 0x17, 0x0c, 0x00, 0x00,
481+
0x00])
482+
let body = bytes.withUnsafeBufferPointer { return dispatch_data_create($0.baseAddress, $0.count, nil, nil) }
483+
let fields = [("Content-Type", "text/plain"), ("Content-Encoding", "gzip")]
484+
return HTTPResponse(statusCode: 200, additionalHeaderFields: fields, bodyData: body)
485+
default:
486+
XCTFail("Unexpected URI \(request.URI.absoluteString)")
487+
return HTTPResponse(statusCode: 500, additionalHeaderFields: [], body: "")
488+
}
489+
}) else { XCTFail(); return }
490+
baseURL = _baseURL
491+
withExtendedLifetime(server) {
492+
let delegate = TaskDelegate()
493+
let sut = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: delegate, delegateQueue: queue)
494+
withExtendedLifetime(sut) {
495+
let URL = NSURL(string: "/hello", relativeToURL: baseURL)!
496+
497+
let completionExpectation = expectationWithDescription("Task did complete")
498+
let task = sut.dataTaskWithURL(URL, completionHandler: {
499+
(data, response, error) in
500+
501+
XCTAssertNil(error)
502+
if let d = data, let s = String(data: d, encoding: NSUTF8StringEncoding) {
503+
XCTAssertEqual(s, "Hello Swift!")
504+
} else {
505+
XCTFail()
506+
}
507+
guard let r = response as? NSHTTPURLResponse else { XCTFail(); return }
508+
XCTAssertEqual(r.URL?.absoluteString, URL.absoluteString)
509+
XCTAssertEqual(r.statusCode, 200)
510+
XCTAssertEqual(r.MIMEType, "text/plain")
511+
512+
completionExpectation.fulfill()
513+
})
514+
515+
task.resume()
516+
waitForExpectationsWithTimeout(1, handler: nil)
517+
}
518+
}
519+
}
520+
521+
470522
//MARK: Upload
471523
//
472524
// NSURLSessionUploadTask
@@ -649,11 +701,12 @@ extension TestNSURLSession {
649701
let port = Int(baseURL?.port ?? 0)
650702
let expected = ["Host": "\(baseURL?.host ?? ""):\(String(port))",
651703
"Accept": "*/*",
652-
"Accept-Language": "en-us",
653-
"Accept-Encoding": "gzip, deflate",
704+
//"Accept-Language": "en",
705+
"Accept-Encoding": "deflate, gzip",
706+
"Connection": "keep-alive",
654707
]
655708
AssertHeaderFieldsEqual(headers, expected)
656-
AssertHeaderFieldNamesEqual(headers, ["Host", "Accept", "Accept-Language", "Accept-Encoding", "Connection", "User-Agent"])
709+
AssertHeaderFieldNamesEqual(headers, ["Host", "Accept", /*"Accept-Language",*/ "Accept-Encoding", "Connection", "User-Agent"])
657710
}) else { XCTFail(); return }
658711
baseURL = _baseURL
659712
with(server: server) { (session, delegate) in
@@ -669,19 +722,23 @@ extension TestNSURLSession {
669722
XCTAssertEqual(task.state, NSURLSessionTaskState.Completed)
670723
}
671724
}
725+
672726
}
673727

674728
private extension HTTPResponse {
675-
init(statusCode: Int, additionalHeaderFields: [(String, String)], body: String) {
729+
init(statusCode: Int, additionalHeaderFields: [(String, String)], bodyData: dispatch_data_t) {
676730
var headerFields = additionalHeaderFields
677731
// This simple server does not support persistent connections.
678732
// C.f. RFC 2616 section 8.1.2.1
679733
headerFields.append(("Connection", "close"))
680734
// https://tools.ietf.org/html/rfc2616#section-14.13
681-
let bodyData = dispatchData(body)
682735
headerFields.append(("Content-Length", String(dispatch_data_get_size(bodyData))))
683736
self.init(statusCode: statusCode, headerFields: headerFields, body: bodyData)
684737
}
738+
init(statusCode: Int, additionalHeaderFields: [(String, String)], body: String) {
739+
let bodyData = dispatchData(body)
740+
self.init(statusCode: statusCode, additionalHeaderFields: additionalHeaderFields, bodyData: bodyData)
741+
}
685742
}
686743

687744
/// Encodes the string as UTF-8 into a dispatch_data_t without copying memory.

0 commit comments

Comments
 (0)