From deefb1e77fe7a8f6138e5f3e42a7390c16774012 Mon Sep 17 00:00:00 2001 From: Pushkar N Kulkarni Date: Fri, 19 Aug 2016 15:53:47 +0530 Subject: [PATCH] Updating PR 299 (NSURLSession) to work with the latest Foundation Also includes support for downloadTask and uploadTask with Data and a completion handler. The original PR 299 doesn't build on Ubuntu 14.04, due to an older level of libcurl. This rework removes some of that code (it does not affect any functionality). Updated to use the Dispatch 3.0 API. Includes basic tests with external URLs. --- .../Base.subproj/ForSwiftFoundationOnly.h | 1 + .../URL.subproj/CFURLSessionInterface.c | 609 ++++++++ .../URL.subproj/CFURLSessionInterface.h | 605 ++++++++ Foundation/NSData.swift | 4 +- Foundation/NSURLSession.swift | 805 ----------- Foundation/NSURLSession/Configuration.swift | 137 ++ Foundation/NSURLSession/EasyHandle.swift | 615 +++++++++ Foundation/NSURLSession/HTTPBodySource.swift | 244 ++++ Foundation/NSURLSession/HTTPMessage.swift | 359 +++++ Foundation/NSURLSession/MultiHandle.swift | 448 ++++++ Foundation/NSURLSession/NSURLSession.swift | 519 +++++++ .../NSURLSessionConfiguration.swift | 211 +++ .../NSURLSession/NSURLSessionDelegate.swift | 284 ++++ .../NSURLSession/NSURLSessionTask.swift | 1222 +++++++++++++++++ Foundation/NSURLSession/TaskRegistry.swift | 95 ++ Foundation/NSURLSession/TransferState.swift | 137 ++ Foundation/NSURLSession/libcurlHelpers.swift | 50 + TestFoundation/TestNSURLSession.swift | 269 ++++ TestFoundation/main.swift | 1 + build.py | 27 +- 20 files changed, 5830 insertions(+), 812 deletions(-) create mode 100644 CoreFoundation/URL.subproj/CFURLSessionInterface.c create mode 100644 CoreFoundation/URL.subproj/CFURLSessionInterface.h delete mode 100644 Foundation/NSURLSession.swift create mode 100644 Foundation/NSURLSession/Configuration.swift create mode 100644 Foundation/NSURLSession/EasyHandle.swift create mode 100644 Foundation/NSURLSession/HTTPBodySource.swift create mode 100644 Foundation/NSURLSession/HTTPMessage.swift create mode 100644 Foundation/NSURLSession/MultiHandle.swift create mode 100644 Foundation/NSURLSession/NSURLSession.swift create mode 100644 Foundation/NSURLSession/NSURLSessionConfiguration.swift create mode 100644 Foundation/NSURLSession/NSURLSessionDelegate.swift create mode 100644 Foundation/NSURLSession/NSURLSessionTask.swift create mode 100644 Foundation/NSURLSession/TaskRegistry.swift create mode 100644 Foundation/NSURLSession/TransferState.swift create mode 100644 Foundation/NSURLSession/libcurlHelpers.swift create mode 100644 TestFoundation/TestNSURLSession.swift diff --git a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h index 7b7ac23645..f180eb8783 100644 --- a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h +++ b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/CoreFoundation/URL.subproj/CFURLSessionInterface.c b/CoreFoundation/URL.subproj/CFURLSessionInterface.c new file mode 100644 index 0000000000..f44f44d59d --- /dev/null +++ b/CoreFoundation/URL.subproj/CFURLSessionInterface.c @@ -0,0 +1,609 @@ +//===-- CoreFoundation/URL/CFURLSessionInterface.c - Very brief description -----------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains wrappes / helpers to import libcurl into Swift. +/// It is used to implement the NSURLSession API. +/// +/// - SeeAlso: CFURLSessionInterface.h +/// +//===----------------------------------------------------------------------===// + +#include "CFURLSessionInterface.h" +#include + +FILE* aa = NULL; +CURL * gcurl = NULL; + +static CFURLSessionEasyCode MakeEasyCode(CURLcode value) { + return (CFURLSessionEasyCode) { value }; +} +static CFURLSessionMultiCode MakeMultiCode(CURLMcode value) { + return (CFURLSessionMultiCode) { value }; +} + + +CFURLSessionEasyHandle _Nonnull CFURLSessionEasyHandleInit() { + return curl_easy_init(); +} +void CFURLSessionEasyHandleDeinit(CFURLSessionEasyHandle _Nonnull handle) { + curl_easy_cleanup(handle); +} +CFURLSessionEasyCode CFURLSessionEasyHandleSetPauseState(CFURLSessionEasyHandle _Nonnull handle, int send, int receive) { + int bitmask = 0 | (send ? CURLPAUSE_SEND : CURLPAUSE_SEND_CONT) | (receive ? CURLPAUSE_RECV : CURLPAUSE_RECV_CONT); + return MakeEasyCode(curl_easy_pause(handle, bitmask)); +} + +CFURLSessionMultiHandle _Nonnull CFURLSessionMultiHandleInit() { + return curl_multi_init(); +} +CFURLSessionMultiCode CFURLSessionMultiHandleDeinit(CFURLSessionMultiHandle _Nonnull handle) { + return MakeMultiCode(curl_multi_cleanup(handle)); +} +CFURLSessionMultiCode CFURLSessionMultiHandleAddHandle(CFURLSessionMultiHandle _Nonnull handle, CFURLSessionEasyHandle _Nonnull curl) { + return MakeMultiCode(curl_multi_add_handle(handle, curl)); +} +CFURLSessionMultiCode CFURLSessionMultiHandleRemoveHandle(CFURLSessionMultiHandle _Nonnull handle, CFURLSessionEasyHandle _Nonnull curl) { + return MakeMultiCode(curl_multi_remove_handle(handle, curl)); +} +CFURLSessionMultiCode CFURLSessionMultiHandleAssign(CFURLSessionMultiHandle _Nonnull handle, CFURLSession_socket_t socket, void * _Nullable sockp) { + return MakeMultiCode(curl_multi_assign(handle, socket, sockp)); +} +CFURLSessionMultiCode CFURLSessionMultiHandleAction(CFURLSessionMultiHandle _Nonnull handle, CFURLSession_socket_t socket, int bitmask, int * _Nonnull running_handles) +{ + return MakeMultiCode(curl_multi_socket_action(handle, socket, bitmask, running_handles)); +} +CFURLSessionMultiHandleInfo CFURLSessionMultiHandleInfoRead(CFURLSessionMultiHandle _Nonnull handle, int * _Nonnull msgs_in_queue) { + CFURLSessionMultiHandleInfo info = {}; + CURLMsg *msg = curl_multi_info_read(handle, msgs_in_queue); + if (msg == NULL) { + return info; + } + if (msg->msg != CURLMSG_DONE) { + return info; + } + info.resultCode = MakeEasyCode(msg->data.result); + info.easyHandle = msg->easy_handle; + return info; +} + +CFURLSessionEasyCode CFURLSession_easy_setopt_ptr(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, void *_Nullable a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_int(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, long a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_int64(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int64_t a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} + +CFURLSessionEasyCode CFURLSession_easy_setopt_wc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, size_t(*_Nonnull a)(char *_Nonnull, size_t, size_t, void *_Nullable)) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_dc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int(*_Nonnull a)(CFURLSessionEasyHandle _Nonnull handle, int type, char *_Nonnull data, size_t size, void *_Nullable userptr)) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_sc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionSocketOptionCallback * _Nullable a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} +CFURLSessionEasyCode CFURLSession_easy_setopt_seek(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionSeekCallback * _Nullable a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} + +CFURLSessionEasyCode CFURLSession_easy_setopt_tc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionTransferInfoCallback * _Nullable a) { + return MakeEasyCode(curl_easy_setopt(curl, option.value, a)); +} + +CFURLSessionEasyCode CFURLSession_easy_getinfo_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, long *_Nonnull a) { + return MakeEasyCode(curl_easy_getinfo(curl, info.value, a)); +} + +CFURLSessionEasyCode CFURLSession_easy_getinfo_double(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, double *_Nonnull a) { + return MakeEasyCode(curl_easy_getinfo(curl, info.value, a)); +} + +CFURLSessionEasyCode CFURLSession_easy_getinfo_charp(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, char *_Nullable*_Nonnull a) { + return MakeEasyCode(curl_easy_getinfo(curl, info.value, a)); +} + +CFURLSessionMultiCode CFURLSession_multi_setopt_ptr(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, void *_Nullable a) { + return MakeMultiCode(curl_multi_setopt(multi_handle, option.value, a)); +} +CFURLSessionMultiCode CFURLSession_multi_setopt_l(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, long a) { + return MakeMultiCode(curl_multi_setopt(multi_handle, option.value, a)); +} +CFURLSessionMultiCode CFURLSession_multi_setopt_sf(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, int (*_Nonnull a)(CFURLSessionEasyHandle _Nonnull, CFURLSession_socket_t, int, void *_Nullable, void *_Nullable)) { + return MakeMultiCode(curl_multi_setopt(multi_handle, option.value, a)); +} +CFURLSessionMultiCode CFURLSession_multi_setopt_tf(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, int (*_Nonnull a)(CFURLSessionMultiHandle _Nonnull, long, void *_Nullable)) { + return MakeMultiCode(curl_multi_setopt(multi_handle, option.value, a)); +} + +CFURLSessionEasyCode CFURLSessionInit(void) { + return MakeEasyCode(curl_global_init(CURL_GLOBAL_SSL)); +} + + +CFURLSessionEasyCode const CFURLSessionEasyCodeOK = { CURLE_OK }; +CFURLSessionEasyCode const CFURLSessionEasyCodeUNSUPPORTED_PROTOCOL = { CURLE_UNSUPPORTED_PROTOCOL }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFAILED_INIT = { CURLE_FAILED_INIT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeURL_MALFORMAT = { CURLE_URL_MALFORMAT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeNOT_BUILT_IN = { CURLE_NOT_BUILT_IN }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_RESOLVE_PROXY = { CURLE_COULDNT_RESOLVE_PROXY }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_RESOLVE_HOST = { CURLE_COULDNT_RESOLVE_HOST }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_CONNECT = { CURLE_COULDNT_CONNECT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_SERVER_REPLY = { CURLE_FTP_WEIRD_SERVER_REPLY }; +CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_ACCESS_DENIED = { CURLE_REMOTE_ACCESS_DENIED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_ACCEPT_FAILED = { CURLE_FTP_ACCEPT_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_PASS_REPLY = { CURLE_FTP_WEIRD_PASS_REPLY }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_ACCEPT_TIMEOUT = { CURLE_FTP_ACCEPT_TIMEOUT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_PASV_REPLY = { CURLE_FTP_WEIRD_PASV_REPLY }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_227_FORMAT = { CURLE_FTP_WEIRD_227_FORMAT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_CANT_GET_HOST = { CURLE_FTP_CANT_GET_HOST }; +//CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP2 = { CURLE_HTTP2 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_SET_TYPE = { CURLE_FTP_COULDNT_SET_TYPE }; +CFURLSessionEasyCode const CFURLSessionEasyCodePARTIAL_FILE = { CURLE_PARTIAL_FILE }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_RETR_FILE = { CURLE_FTP_COULDNT_RETR_FILE }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE20 = { CURLE_OBSOLETE20 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeQUOTE_ERROR = { CURLE_QUOTE_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP_RETURNED_ERROR = { CURLE_HTTP_RETURNED_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeWRITE_ERROR = { CURLE_WRITE_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE24 = { CURLE_OBSOLETE24 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeUPLOAD_FAILED = { CURLE_UPLOAD_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeREAD_ERROR = { CURLE_READ_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOUT_OF_MEMORY = { CURLE_OUT_OF_MEMORY }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOPERATION_TIMEDOUT = { CURLE_OPERATION_TIMEDOUT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE29 = { CURLE_OBSOLETE29 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_PORT_FAILED = { CURLE_FTP_PORT_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_USE_REST = { CURLE_FTP_COULDNT_USE_REST }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE32 = { CURLE_OBSOLETE32 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeRANGE_ERROR = { CURLE_RANGE_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP_POST_ERROR = { CURLE_HTTP_POST_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CONNECT_ERROR = { CURLE_SSL_CONNECT_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_DOWNLOAD_RESUME = { CURLE_BAD_DOWNLOAD_RESUME }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFILE_COULDNT_READ_FILE = { CURLE_FILE_COULDNT_READ_FILE }; +CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_CANNOT_BIND = { CURLE_LDAP_CANNOT_BIND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_SEARCH_FAILED = { CURLE_LDAP_SEARCH_FAILED }; +//CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE40 = { CURLE_OBSOLETE40 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFUNCTION_NOT_FOUND = { CURLE_FUNCTION_NOT_FOUND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeABORTED_BY_CALLBACK = { CURLE_ABORTED_BY_CALLBACK }; +CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_FUNCTION_ARGUMENT = { CURLE_BAD_FUNCTION_ARGUMENT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE44 = { CURLE_OBSOLETE44 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeINTERFACE_FAILED = { CURLE_INTERFACE_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE46 = { CURLE_OBSOLETE46 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTOO_MANY_REDIRECTS = { CURLE_TOO_MANY_REDIRECTS }; +CFURLSessionEasyCode const CFURLSessionEasyCodeUNKNOWN_OPTION = { CURLE_UNKNOWN_OPTION }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTELNET_OPTION_SYNTAX = { CURLE_TELNET_OPTION_SYNTAX }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE50 = { CURLE_OBSOLETE50 }; +CFURLSessionEasyCode const CFURLSessionEasyCodePEER_FAILED_VERIFICATION = { CURLE_PEER_FAILED_VERIFICATION }; +CFURLSessionEasyCode const CFURLSessionEasyCodeGOT_NOTHING = { CURLE_GOT_NOTHING }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_NOTFOUND = { CURLE_SSL_ENGINE_NOTFOUND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_SETFAILED = { CURLE_SSL_ENGINE_SETFAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSEND_ERROR = { CURLE_SEND_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeRECV_ERROR = { CURLE_RECV_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE57 = { CURLE_OBSOLETE57 }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CERTPROBLEM = { CURLE_SSL_CERTPROBLEM }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CIPHER = { CURLE_SSL_CIPHER }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CACERT = { CURLE_SSL_CACERT }; +CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_CONTENT_ENCODING = { CURLE_BAD_CONTENT_ENCODING }; +CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_INVALID_URL = { CURLE_LDAP_INVALID_URL }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFILESIZE_EXCEEDED = { CURLE_FILESIZE_EXCEEDED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeUSE_SSL_FAILED = { CURLE_USE_SSL_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSEND_FAIL_REWIND = { CURLE_SEND_FAIL_REWIND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_INITFAILED = { CURLE_SSL_ENGINE_INITFAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeLOGIN_DENIED = { CURLE_LOGIN_DENIED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_NOTFOUND = { CURLE_TFTP_NOTFOUND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_PERM = { CURLE_TFTP_PERM }; +CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_DISK_FULL = { CURLE_REMOTE_DISK_FULL }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_ILLEGAL = { CURLE_TFTP_ILLEGAL }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_UNKNOWNID = { CURLE_TFTP_UNKNOWNID }; +CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_FILE_EXISTS = { CURLE_REMOTE_FILE_EXISTS }; +CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_NOSUCHUSER = { CURLE_TFTP_NOSUCHUSER }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCONV_FAILED = { CURLE_CONV_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCONV_REQD = { CURLE_CONV_REQD }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CACERT_BADFILE = { CURLE_SSL_CACERT_BADFILE }; +CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_FILE_NOT_FOUND = { CURLE_REMOTE_FILE_NOT_FOUND }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSH = { CURLE_SSH }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_SHUTDOWN_FAILED = { CURLE_SSL_SHUTDOWN_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeAGAIN = { CURLE_AGAIN }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CRL_BADFILE = { CURLE_SSL_CRL_BADFILE }; +CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ISSUER_ERROR = { CURLE_SSL_ISSUER_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_PRET_FAILED = { CURLE_FTP_PRET_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeRTSP_CSEQ_ERROR = { CURLE_RTSP_CSEQ_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeRTSP_SESSION_ERROR = { CURLE_RTSP_SESSION_ERROR }; +CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_BAD_FILE_LIST = { CURLE_FTP_BAD_FILE_LIST }; +CFURLSessionEasyCode const CFURLSessionEasyCodeCHUNK_FAILED = { CURLE_CHUNK_FAILED }; +CFURLSessionEasyCode const CFURLSessionEasyCodeNO_CONNECTION_AVAILABLE = { CURLE_NO_CONNECTION_AVAILABLE }; +//CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_PINNEDPUBKEYNOTMATCH = { CURLE_SSL_PINNEDPUBKEYNOTMATCH }; +//CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_INVALIDCERTSTATUS = { CURLE_SSL_INVALIDCERTSTATUS }; + + +CFURLSessionProtocol const CFURLSessionProtocolHTTP = CURLPROTO_HTTP; +CFURLSessionProtocol const CFURLSessionProtocolHTTPS = CURLPROTO_HTTPS; +CFURLSessionProtocol const CFURLSessionProtocolFTP = CURLPROTO_FTP; +CFURLSessionProtocol const CFURLSessionProtocolFTPS = CURLPROTO_FTPS; +CFURLSessionProtocol const CFURLSessionProtocolSCP = CURLPROTO_SCP; +CFURLSessionProtocol const CFURLSessionProtocolSFTP = CURLPROTO_SFTP; +CFURLSessionProtocol const CFURLSessionProtocolTELNET = CURLPROTO_TELNET; +CFURLSessionProtocol const CFURLSessionProtocolLDAP = CURLPROTO_LDAP; +CFURLSessionProtocol const CFURLSessionProtocolLDAPS = CURLPROTO_LDAPS; +CFURLSessionProtocol const CFURLSessionProtocolDICT = CURLPROTO_DICT; +CFURLSessionProtocol const CFURLSessionProtocolFILE = CURLPROTO_FILE; +CFURLSessionProtocol const CFURLSessionProtocolTFTP = CURLPROTO_TFTP; +CFURLSessionProtocol const CFURLSessionProtocolIMAP = CURLPROTO_IMAP; +CFURLSessionProtocol const CFURLSessionProtocolIMAPS = CURLPROTO_IMAPS; +CFURLSessionProtocol const CFURLSessionProtocolPOP3 = CURLPROTO_POP3; +CFURLSessionProtocol const CFURLSessionProtocolPOP3S = CURLPROTO_POP3S; +CFURLSessionProtocol const CFURLSessionProtocolSMTP = CURLPROTO_SMTP; +CFURLSessionProtocol const CFURLSessionProtocolSMTPS = CURLPROTO_SMTPS; +CFURLSessionProtocol const CFURLSessionProtocolRTSP = CURLPROTO_RTSP; +CFURLSessionProtocol const CFURLSessionProtocolRTMP = CURLPROTO_RTMP; +CFURLSessionProtocol const CFURLSessionProtocolRTMPT = CURLPROTO_RTMPT; +CFURLSessionProtocol const CFURLSessionProtocolRTMPE = CURLPROTO_RTMPE; +CFURLSessionProtocol const CFURLSessionProtocolRTMPTE = CURLPROTO_RTMPTE; +CFURLSessionProtocol const CFURLSessionProtocolRTMPS = CURLPROTO_RTMPS; +CFURLSessionProtocol const CFURLSessionProtocolRTMPTS = CURLPROTO_RTMPTS; +CFURLSessionProtocol const CFURLSessionProtocolGOPHER = CURLPROTO_GOPHER; +//CFURLSessionProtocol const CFURLSessionProtocolSMB = CURLPROTO_SMB; +//CFURLSessionProtocol const CFURLSessionProtocolSMBS = CURLPROTO_SMBS; +CFURLSessionProtocol const CFURLSessionProtocolALL = CURLPROTO_ALL; + + +size_t const CFURLSessionMaxWriteSize = CURL_MAX_WRITE_SIZE; + + +CFURLSessionOption const CFURLSessionOptionWRITEDATA = { CURLOPT_WRITEDATA }; +CFURLSessionOption const CFURLSessionOptionURL = { CURLOPT_URL }; +CFURLSessionOption const CFURLSessionOptionPORT = { CURLOPT_PORT }; +CFURLSessionOption const CFURLSessionOptionPROXY = { CURLOPT_PROXY }; +CFURLSessionOption const CFURLSessionOptionUSERPWD = { CURLOPT_USERPWD }; +CFURLSessionOption const CFURLSessionOptionPROXYUSERPWD = { CURLOPT_PROXYUSERPWD }; +CFURLSessionOption const CFURLSessionOptionRANGE = { CURLOPT_RANGE }; +CFURLSessionOption const CFURLSessionOptionREADDATA = { CURLOPT_READDATA }; +CFURLSessionOption const CFURLSessionOptionERRORBUFFER = { CURLOPT_ERRORBUFFER }; +CFURLSessionOption const CFURLSessionOptionWRITEFUNCTION = { CURLOPT_WRITEFUNCTION }; +CFURLSessionOption const CFURLSessionOptionREADFUNCTION = { CURLOPT_READFUNCTION }; +CFURLSessionOption const CFURLSessionOptionTIMEOUT = { CURLOPT_TIMEOUT }; +CFURLSessionOption const CFURLSessionOptionINFILESIZE = { CURLOPT_INFILESIZE }; +CFURLSessionOption const CFURLSessionOptionPOSTFIELDS = { CURLOPT_POSTFIELDS }; +CFURLSessionOption const CFURLSessionOptionREFERER = { CURLOPT_REFERER }; +CFURLSessionOption const CFURLSessionOptionFTPPORT = { CURLOPT_FTPPORT }; +CFURLSessionOption const CFURLSessionOptionUSERAGENT = { CURLOPT_USERAGENT }; +CFURLSessionOption const CFURLSessionOptionLOW_SPEED_LIMIT = { CURLOPT_LOW_SPEED_LIMIT }; +CFURLSessionOption const CFURLSessionOptionLOW_SPEED_TIME = { CURLOPT_LOW_SPEED_TIME }; +CFURLSessionOption const CFURLSessionOptionRESUME_FROM = { CURLOPT_RESUME_FROM }; +CFURLSessionOption const CFURLSessionOptionCOOKIE = { CURLOPT_COOKIE }; +CFURLSessionOption const CFURLSessionOptionHTTPHEADER = { CURLOPT_HTTPHEADER }; +CFURLSessionOption const CFURLSessionOptionHTTPPOST = { CURLOPT_HTTPPOST }; +CFURLSessionOption const CFURLSessionOptionSSLCERT = { CURLOPT_SSLCERT }; +CFURLSessionOption const CFURLSessionOptionKEYPASSWD = { CURLOPT_KEYPASSWD }; +CFURLSessionOption const CFURLSessionOptionCRLF = { CURLOPT_CRLF }; +CFURLSessionOption const CFURLSessionOptionQUOTE = { CURLOPT_QUOTE }; +CFURLSessionOption const CFURLSessionOptionHEADERDATA = { CURLOPT_HEADERDATA }; +CFURLSessionOption const CFURLSessionOptionCOOKIEFILE = { CURLOPT_COOKIEFILE }; +CFURLSessionOption const CFURLSessionOptionSSLVERSION = { CURLOPT_SSLVERSION }; +CFURLSessionOption const CFURLSessionOptionTIMECONDITION = { CURLOPT_TIMECONDITION }; +CFURLSessionOption const CFURLSessionOptionTIMEVALUE = { CURLOPT_TIMEVALUE }; +CFURLSessionOption const CFURLSessionOptionCUSTOMREQUEST = { CURLOPT_CUSTOMREQUEST }; +CFURLSessionOption const CFURLSessionOptionSTDERR = { CURLOPT_STDERR }; +CFURLSessionOption const CFURLSessionOptionPOSTQUOTE = { CURLOPT_POSTQUOTE }; +/*CFURLSessionOption const CFURLSessionOptionOBSOLETE40 = { CURLOPT_OBSOLETE40 };*/ +CFURLSessionOption const CFURLSessionOptionVERBOSE = { CURLOPT_VERBOSE }; +CFURLSessionOption const CFURLSessionOptionHEADER = { CURLOPT_HEADER }; +CFURLSessionOption const CFURLSessionOptionNOPROGRESS = { CURLOPT_NOPROGRESS }; +CFURLSessionOption const CFURLSessionOptionNOBODY = { CURLOPT_NOBODY }; +CFURLSessionOption const CFURLSessionOptionFAILONERROR = { CURLOPT_FAILONERROR }; +CFURLSessionOption const CFURLSessionOptionUPLOAD = { CURLOPT_UPLOAD }; +CFURLSessionOption const CFURLSessionOptionPOST = { CURLOPT_POST }; +CFURLSessionOption const CFURLSessionOptionDIRLISTONLY = { CURLOPT_DIRLISTONLY }; +CFURLSessionOption const CFURLSessionOptionAPPEND = { CURLOPT_APPEND }; +CFURLSessionOption const CFURLSessionOptionNETRC = { CURLOPT_NETRC }; +CFURLSessionOption const CFURLSessionOptionFOLLOWLOCATION = { CURLOPT_FOLLOWLOCATION }; +CFURLSessionOption const CFURLSessionOptionTRANSFERTEXT = { CURLOPT_TRANSFERTEXT }; +CFURLSessionOption const CFURLSessionOptionPUT = { CURLOPT_PUT }; +CFURLSessionOption const CFURLSessionOptionPROGRESSFUNCTION = { CURLOPT_PROGRESSFUNCTION }; +CFURLSessionOption const CFURLSessionOptionPROGRESSDATA = { CURLOPT_PROGRESSDATA }; +CFURLSessionOption const CFURLSessionOptionAUTOREFERER = { CURLOPT_AUTOREFERER }; +CFURLSessionOption const CFURLSessionOptionPROXYPORT = { CURLOPT_PROXYPORT }; +CFURLSessionOption const CFURLSessionOptionPOSTFIELDSIZE = { CURLOPT_POSTFIELDSIZE }; +CFURLSessionOption const CFURLSessionOptionHTTPPROXYTUNNEL = { CURLOPT_HTTPPROXYTUNNEL }; +CFURLSessionOption const CFURLSessionOptionINTERFACE = { CURLOPT_INTERFACE }; +CFURLSessionOption const CFURLSessionOptionKRBLEVEL = { CURLOPT_KRBLEVEL }; +CFURLSessionOption const CFURLSessionOptionSSL_VERIFYPEER = { CURLOPT_SSL_VERIFYPEER }; +CFURLSessionOption const CFURLSessionOptionCAINFO = { CURLOPT_CAINFO }; +CFURLSessionOption const CFURLSessionOptionMAXREDIRS = { CURLOPT_MAXREDIRS }; +CFURLSessionOption const CFURLSessionOptionFILETIME = { CURLOPT_FILETIME }; +CFURLSessionOption const CFURLSessionOptionTELNETOPTIONS = { CURLOPT_TELNETOPTIONS }; +CFURLSessionOption const CFURLSessionOptionMAXCONNECTS = { CURLOPT_MAXCONNECTS }; +//CFURLSessionOption const CFURLSessionOptionOBSOLETE72 = { CURLOPT_OBSOLETE72 }; +CFURLSessionOption const CFURLSessionOptionFRESH_CONNECT = { CURLOPT_FRESH_CONNECT }; +CFURLSessionOption const CFURLSessionOptionFORBID_REUSE = { CURLOPT_FORBID_REUSE }; +CFURLSessionOption const CFURLSessionOptionRANDOM_FILE = { CURLOPT_RANDOM_FILE }; +CFURLSessionOption const CFURLSessionOptionEGDSOCKET = { CURLOPT_EGDSOCKET }; +CFURLSessionOption const CFURLSessionOptionCONNECTTIMEOUT = { CURLOPT_CONNECTTIMEOUT }; +CFURLSessionOption const CFURLSessionOptionHEADERFUNCTION = { CURLOPT_HEADERFUNCTION }; +CFURLSessionOption const CFURLSessionOptionHTTPGET = { CURLOPT_HTTPGET }; +CFURLSessionOption const CFURLSessionOptionSSL_VERIFYHOST = { CURLOPT_SSL_VERIFYHOST }; +CFURLSessionOption const CFURLSessionOptionCOOKIEJAR = { CURLOPT_COOKIEJAR }; +CFURLSessionOption const CFURLSessionOptionSSL_CIPHER_LIST = { CURLOPT_SSL_CIPHER_LIST }; +CFURLSessionOption const CFURLSessionOptionHTTP_VERSION = { CURLOPT_HTTP_VERSION }; +CFURLSessionOption const CFURLSessionOptionFTP_USE_EPSV = { CURLOPT_FTP_USE_EPSV }; +CFURLSessionOption const CFURLSessionOptionSSLCERTTYPE = { CURLOPT_SSLCERTTYPE }; +CFURLSessionOption const CFURLSessionOptionSSLKEY = { CURLOPT_SSLKEY }; +CFURLSessionOption const CFURLSessionOptionSSLKEYTYPE = { CURLOPT_SSLKEYTYPE }; +CFURLSessionOption const CFURLSessionOptionSSLENGINE = { CURLOPT_SSLENGINE }; +CFURLSessionOption const CFURLSessionOptionSSLENGINE_DEFAULT = { CURLOPT_SSLENGINE_DEFAULT }; +CFURLSessionOption const CFURLSessionOptionDNS_USE_GLOBAL_CACHE = { CURLOPT_DNS_USE_GLOBAL_CACHE }; +CFURLSessionOption const CFURLSessionOptionDNS_CACHE_TIMEOUT = { CURLOPT_DNS_CACHE_TIMEOUT }; +CFURLSessionOption const CFURLSessionOptionPREQUOTE = { CURLOPT_PREQUOTE }; +CFURLSessionOption const CFURLSessionOptionDEBUGFUNCTION = { CURLOPT_DEBUGFUNCTION }; +CFURLSessionOption const CFURLSessionOptionDEBUGDATA = { CURLOPT_DEBUGDATA }; +CFURLSessionOption const CFURLSessionOptionCOOKIESESSION = { CURLOPT_COOKIESESSION }; +CFURLSessionOption const CFURLSessionOptionCAPATH = { CURLOPT_CAPATH }; +CFURLSessionOption const CFURLSessionOptionBUFFERSIZE = { CURLOPT_BUFFERSIZE }; +CFURLSessionOption const CFURLSessionOptionNOSIGNAL = { CURLOPT_NOSIGNAL }; +CFURLSessionOption const CFURLSessionOptionSHARE = { CURLOPT_SHARE }; +CFURLSessionOption const CFURLSessionOptionPROXYTYPE = { CURLOPT_PROXYTYPE }; +CFURLSessionOption const CFURLSessionOptionACCEPT_ENCODING = { CURLOPT_ACCEPT_ENCODING }; +CFURLSessionOption const CFURLSessionOptionPRIVATE = { CURLOPT_PRIVATE }; +CFURLSessionOption const CFURLSessionOptionHTTP200ALIASES = { CURLOPT_HTTP200ALIASES }; +CFURLSessionOption const CFURLSessionOptionUNRESTRICTED_AUTH = { CURLOPT_UNRESTRICTED_AUTH }; +CFURLSessionOption const CFURLSessionOptionFTP_USE_EPRT = { CURLOPT_FTP_USE_EPRT }; +CFURLSessionOption const CFURLSessionOptionHTTPAUTH = { CURLOPT_HTTPAUTH }; +CFURLSessionOption const CFURLSessionOptionSSL_CTX_FUNCTION = { CURLOPT_SSL_CTX_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionSSL_CTX_DATA = { CURLOPT_SSL_CTX_DATA }; +CFURLSessionOption const CFURLSessionOptionFTP_CREATE_MISSING_DIRS = { CURLOPT_FTP_CREATE_MISSING_DIRS }; +CFURLSessionOption const CFURLSessionOptionPROXYAUTH = { CURLOPT_PROXYAUTH }; +CFURLSessionOption const CFURLSessionOptionFTP_RESPONSE_TIMEOUT = { CURLOPT_FTP_RESPONSE_TIMEOUT }; +CFURLSessionOption const CFURLSessionOptionIPRESOLVE = { CURLOPT_IPRESOLVE }; +CFURLSessionOption const CFURLSessionOptionMAXFILESIZE = { CURLOPT_MAXFILESIZE }; +CFURLSessionOption const CFURLSessionOptionINFILESIZE_LARGE = { CURLOPT_INFILESIZE_LARGE }; +CFURLSessionOption const CFURLSessionOptionRESUME_FROM_LARGE = { CURLOPT_RESUME_FROM_LARGE }; +CFURLSessionOption const CFURLSessionOptionMAXFILESIZE_LARGE = { CURLOPT_MAXFILESIZE_LARGE }; +CFURLSessionOption const CFURLSessionOptionNETRC_FILE = { CURLOPT_NETRC_FILE }; +CFURLSessionOption const CFURLSessionOptionUSE_SSL = { CURLOPT_USE_SSL }; +CFURLSessionOption const CFURLSessionOptionPOSTFIELDSIZE_LARGE = { CURLOPT_POSTFIELDSIZE_LARGE }; +CFURLSessionOption const CFURLSessionOptionTCP_NODELAY = { CURLOPT_TCP_NODELAY }; +CFURLSessionOption const CFURLSessionOptionFTPSSLAUTH = { CURLOPT_FTPSSLAUTH }; +CFURLSessionOption const CFURLSessionOptionIOCTLFUNCTION = { CURLOPT_IOCTLFUNCTION }; +CFURLSessionOption const CFURLSessionOptionIOCTLDATA = { CURLOPT_IOCTLDATA }; +CFURLSessionOption const CFURLSessionOptionFTP_ACCOUNT = { CURLOPT_FTP_ACCOUNT }; +CFURLSessionOption const CFURLSessionOptionCOOKIELIST = { CURLOPT_COOKIELIST }; +CFURLSessionOption const CFURLSessionOptionIGNORE_CONTENT_LENGTH = { CURLOPT_IGNORE_CONTENT_LENGTH }; +CFURLSessionOption const CFURLSessionOptionFTP_SKIP_PASV_IP = { CURLOPT_FTP_SKIP_PASV_IP }; +CFURLSessionOption const CFURLSessionOptionFTP_FILEMETHOD = { CURLOPT_FTP_FILEMETHOD }; +CFURLSessionOption const CFURLSessionOptionLOCALPORT = { CURLOPT_LOCALPORT }; +CFURLSessionOption const CFURLSessionOptionLOCALPORTRANGE = { CURLOPT_LOCALPORTRANGE }; +CFURLSessionOption const CFURLSessionOptionCONNECT_ONLY = { CURLOPT_CONNECT_ONLY }; +CFURLSessionOption const CFURLSessionOptionCONV_FROM_NETWORK_FUNCTION = { CURLOPT_CONV_FROM_NETWORK_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionCONV_TO_NETWORK_FUNCTION = { CURLOPT_CONV_TO_NETWORK_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionCONV_FROM_UTF8_FUNCTION = { CURLOPT_CONV_FROM_UTF8_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionMAX_SEND_SPEED_LARGE = { CURLOPT_MAX_SEND_SPEED_LARGE }; +CFURLSessionOption const CFURLSessionOptionMAX_RECV_SPEED_LARGE = { CURLOPT_MAX_RECV_SPEED_LARGE }; +CFURLSessionOption const CFURLSessionOptionFTP_ALTERNATIVE_TO_USER = { CURLOPT_FTP_ALTERNATIVE_TO_USER }; +CFURLSessionOption const CFURLSessionOptionSOCKOPTFUNCTION = { CURLOPT_SOCKOPTFUNCTION }; +CFURLSessionOption const CFURLSessionOptionSOCKOPTDATA = { CURLOPT_SOCKOPTDATA }; +CFURLSessionOption const CFURLSessionOptionSSL_SESSIONID_CACHE = { CURLOPT_SSL_SESSIONID_CACHE }; +CFURLSessionOption const CFURLSessionOptionSSH_AUTH_TYPES = { CURLOPT_SSH_AUTH_TYPES }; +CFURLSessionOption const CFURLSessionOptionSSH_PUBLIC_KEYFILE = { CURLOPT_SSH_PUBLIC_KEYFILE }; +CFURLSessionOption const CFURLSessionOptionSSH_PRIVATE_KEYFILE = { CURLOPT_SSH_PRIVATE_KEYFILE }; +CFURLSessionOption const CFURLSessionOptionFTP_SSL_CCC = { CURLOPT_FTP_SSL_CCC }; +CFURLSessionOption const CFURLSessionOptionTIMEOUT_MS = { CURLOPT_TIMEOUT_MS }; +CFURLSessionOption const CFURLSessionOptionCONNECTTIMEOUT_MS = { CURLOPT_CONNECTTIMEOUT_MS }; +CFURLSessionOption const CFURLSessionOptionHTTP_TRANSFER_DECODING = { CURLOPT_HTTP_TRANSFER_DECODING }; +CFURLSessionOption const CFURLSessionOptionHTTP_CONTENT_DECODING = { CURLOPT_HTTP_CONTENT_DECODING }; +CFURLSessionOption const CFURLSessionOptionNEW_FILE_PERMS = { CURLOPT_NEW_FILE_PERMS }; +CFURLSessionOption const CFURLSessionOptionNEW_DIRECTORY_PERMS = { CURLOPT_NEW_DIRECTORY_PERMS }; +CFURLSessionOption const CFURLSessionOptionPOSTREDIR = { CURLOPT_POSTREDIR }; +CFURLSessionOption const CFURLSessionOptionSSH_HOST_PUBLIC_KEY_MD5 = { CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 }; +CFURLSessionOption const CFURLSessionOptionOPENSOCKETFUNCTION = { CURLOPT_OPENSOCKETFUNCTION }; +CFURLSessionOption const CFURLSessionOptionOPENSOCKETDATA = { CURLOPT_OPENSOCKETDATA }; +CFURLSessionOption const CFURLSessionOptionCOPYPOSTFIELDS = { CURLOPT_COPYPOSTFIELDS }; +CFURLSessionOption const CFURLSessionOptionPROXY_TRANSFER_MODE = { CURLOPT_PROXY_TRANSFER_MODE }; +CFURLSessionOption const CFURLSessionOptionSEEKFUNCTION = { CURLOPT_SEEKFUNCTION }; +CFURLSessionOption const CFURLSessionOptionSEEKDATA = { CURLOPT_SEEKDATA }; +CFURLSessionOption const CFURLSessionOptionCRLFILE = { CURLOPT_CRLFILE }; +CFURLSessionOption const CFURLSessionOptionISSUERCERT = { CURLOPT_ISSUERCERT }; +CFURLSessionOption const CFURLSessionOptionADDRESS_SCOPE = { CURLOPT_ADDRESS_SCOPE }; +CFURLSessionOption const CFURLSessionOptionCERTINFO = { CURLOPT_CERTINFO }; +CFURLSessionOption const CFURLSessionOptionUSERNAME = { CURLOPT_USERNAME }; +CFURLSessionOption const CFURLSessionOptionPASSWORD = { CURLOPT_PASSWORD }; +CFURLSessionOption const CFURLSessionOptionPROXYUSERNAME = { CURLOPT_PROXYUSERNAME }; +CFURLSessionOption const CFURLSessionOptionPROXYPASSWORD = { CURLOPT_PROXYPASSWORD }; +CFURLSessionOption const CFURLSessionOptionNOPROXY = { CURLOPT_NOPROXY }; +CFURLSessionOption const CFURLSessionOptionTFTP_BLKSIZE = { CURLOPT_TFTP_BLKSIZE }; +CFURLSessionOption const CFURLSessionOptionSOCKS5_GSSAPI_SERVICE = { CURLOPT_SOCKS5_GSSAPI_SERVICE }; +CFURLSessionOption const CFURLSessionOptionSOCKS5_GSSAPI_NEC = { CURLOPT_SOCKS5_GSSAPI_NEC }; +CFURLSessionOption const CFURLSessionOptionPROTOCOLS = { CURLOPT_PROTOCOLS }; +CFURLSessionOption const CFURLSessionOptionREDIR_PROTOCOLS = { CURLOPT_REDIR_PROTOCOLS }; +CFURLSessionOption const CFURLSessionOptionSSH_KNOWNHOSTS = { CURLOPT_SSH_KNOWNHOSTS }; +CFURLSessionOption const CFURLSessionOptionSSH_KEYFUNCTION = { CURLOPT_SSH_KEYFUNCTION }; +CFURLSessionOption const CFURLSessionOptionSSH_KEYDATA = { CURLOPT_SSH_KEYDATA }; +CFURLSessionOption const CFURLSessionOptionMAIL_FROM = { CURLOPT_MAIL_FROM }; +CFURLSessionOption const CFURLSessionOptionMAIL_RCPT = { CURLOPT_MAIL_RCPT }; +CFURLSessionOption const CFURLSessionOptionFTP_USE_PRET = { CURLOPT_FTP_USE_PRET }; +CFURLSessionOption const CFURLSessionOptionRTSP_REQUEST = { CURLOPT_RTSP_REQUEST }; +CFURLSessionOption const CFURLSessionOptionRTSP_SESSION_ID = { CURLOPT_RTSP_SESSION_ID }; +CFURLSessionOption const CFURLSessionOptionRTSP_STREAM_URI = { CURLOPT_RTSP_STREAM_URI }; +CFURLSessionOption const CFURLSessionOptionRTSP_TRANSPORT = { CURLOPT_RTSP_TRANSPORT }; +CFURLSessionOption const CFURLSessionOptionRTSP_CLIENT_CSEQ = { CURLOPT_RTSP_CLIENT_CSEQ }; +CFURLSessionOption const CFURLSessionOptionRTSP_SERVER_CSEQ = { CURLOPT_RTSP_SERVER_CSEQ }; +CFURLSessionOption const CFURLSessionOptionINTERLEAVEDATA = { CURLOPT_INTERLEAVEDATA }; +CFURLSessionOption const CFURLSessionOptionINTERLEAVEFUNCTION = { CURLOPT_INTERLEAVEFUNCTION }; +CFURLSessionOption const CFURLSessionOptionWILDCARDMATCH = { CURLOPT_WILDCARDMATCH }; +CFURLSessionOption const CFURLSessionOptionCHUNK_BGN_FUNCTION = { CURLOPT_CHUNK_BGN_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionCHUNK_END_FUNCTION = { CURLOPT_CHUNK_END_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionFNMATCH_FUNCTION = { CURLOPT_FNMATCH_FUNCTION }; +CFURLSessionOption const CFURLSessionOptionCHUNK_DATA = { CURLOPT_CHUNK_DATA }; +CFURLSessionOption const CFURLSessionOptionFNMATCH_DATA = { CURLOPT_FNMATCH_DATA }; +CFURLSessionOption const CFURLSessionOptionRESOLVE = { CURLOPT_RESOLVE }; +CFURLSessionOption const CFURLSessionOptionTLSAUTH_USERNAME = { CURLOPT_TLSAUTH_USERNAME }; +CFURLSessionOption const CFURLSessionOptionTLSAUTH_PASSWORD = { CURLOPT_TLSAUTH_PASSWORD }; +CFURLSessionOption const CFURLSessionOptionTLSAUTH_TYPE = { CURLOPT_TLSAUTH_TYPE }; +CFURLSessionOption const CFURLSessionOptionTRANSFER_ENCODING = { CURLOPT_TRANSFER_ENCODING }; +CFURLSessionOption const CFURLSessionOptionCLOSESOCKETFUNCTION = { CURLOPT_CLOSESOCKETFUNCTION }; +CFURLSessionOption const CFURLSessionOptionCLOSESOCKETDATA = { CURLOPT_CLOSESOCKETDATA }; +CFURLSessionOption const CFURLSessionOptionGSSAPI_DELEGATION = { CURLOPT_GSSAPI_DELEGATION }; +CFURLSessionOption const CFURLSessionOptionDNS_SERVERS = { CURLOPT_DNS_SERVERS }; +CFURLSessionOption const CFURLSessionOptionACCEPTTIMEOUT_MS = { CURLOPT_ACCEPTTIMEOUT_MS }; +CFURLSessionOption const CFURLSessionOptionTCP_KEEPALIVE = { CURLOPT_TCP_KEEPALIVE }; +CFURLSessionOption const CFURLSessionOptionTCP_KEEPIDLE = { CURLOPT_TCP_KEEPIDLE }; +CFURLSessionOption const CFURLSessionOptionTCP_KEEPINTVL = { CURLOPT_TCP_KEEPINTVL }; +CFURLSessionOption const CFURLSessionOptionSSL_OPTIONS = { CURLOPT_SSL_OPTIONS }; +CFURLSessionOption const CFURLSessionOptionMAIL_AUTH = { CURLOPT_MAIL_AUTH }; +CFURLSessionOption const CFURLSessionOptionSASL_IR = { CURLOPT_SASL_IR }; +CFURLSessionOption const CFURLSessionOptionXFERINFOFUNCTION = { CURLOPT_XFERINFOFUNCTION }; +CFURLSessionOption const CFURLSessionOptionXFERINFODATA = { CURLOPT_XFERINFODATA }; +CFURLSessionOption const CFURLSessionOptionXOAUTH2_BEARER = { CURLOPT_XOAUTH2_BEARER }; +CFURLSessionOption const CFURLSessionOptionDNS_INTERFACE = { CURLOPT_DNS_INTERFACE }; +CFURLSessionOption const CFURLSessionOptionDNS_LOCAL_IP4 = { CURLOPT_DNS_LOCAL_IP4 }; +CFURLSessionOption const CFURLSessionOptionDNS_LOCAL_IP6 = { CURLOPT_DNS_LOCAL_IP6 }; +CFURLSessionOption const CFURLSessionOptionLOGIN_OPTIONS = { CURLOPT_LOGIN_OPTIONS }; + +//Options unavailable on Ubuntu 14.04 +/*CFURLSessionOption const CFURLSessionOptionSSL_ENABLE_NPN = { CURLOPT_SSL_ENABLE_NPN }; +CFURLSessionOption const CFURLSessionOptionSSL_ENABLE_ALPN = { CURLOPT_SSL_ENABLE_ALPN }; +CFURLSessionOption const CFURLSessionOptionEXPECT_100_TIMEOUT_MS = { CURLOPT_EXPECT_100_TIMEOUT_MS }; +CFURLSessionOption const CFURLSessionOptionPROXYHEADER = { CURLOPT_PROXYHEADER }; +CFURLSessionOption const CFURLSessionOptionHEADEROPT = { CURLOPT_HEADEROPT }; +CFURLSessionOption const CFURLSessionOptionPINNEDPUBLICKEY = { CURLOPT_PINNEDPUBLICKEY }; +CFURLSessionOption const CFURLSessionOptionUNIX_SOCKET_PATH = { CURLOPT_UNIX_SOCKET_PATH }; +CFURLSessionOption const CFURLSessionOptionSSL_VERIFYSTATUS = { CURLOPT_SSL_VERIFYSTATUS }; +CFURLSessionOption const CFURLSessionOptionSSL_FALSESTART = { CURLOPT_SSL_FALSESTART }; +CFURLSessionOption const CFURLSessionOptionPATH_AS_IS = { CURLOPT_PATH_AS_IS }; +CFURLSessionOption const CFURLSessionOptionPROXY_SERVICE_NAME = { CURLOPT_PROXY_SERVICE_NAME }; +CFURLSessionOption const CFURLSessionOptionSERVICE_NAME = { CURLOPT_SERVICE_NAME }; +CFURLSessionOption const CFURLSessionOptionPIPEWAIT = { CURLOPT_PIPEWAIT };*/ + + +CFURLSessionInfo const CFURLSessionInfoTEXT = { CURLINFO_TEXT }; +CFURLSessionInfo const CFURLSessionInfoHEADER_IN = { CURLINFO_HEADER_IN }; +CFURLSessionInfo const CFURLSessionInfoHEADER_OUT = { CURLINFO_HEADER_OUT }; +CFURLSessionInfo const CFURLSessionInfoDATA_IN = { CURLINFO_DATA_IN }; +CFURLSessionInfo const CFURLSessionInfoDATA_OUT = { CURLINFO_DATA_OUT }; +CFURLSessionInfo const CFURLSessionInfoSSL_DATA_IN = { CURLINFO_SSL_DATA_IN }; +CFURLSessionInfo const CFURLSessionInfoSSL_DATA_OUT = { CURLINFO_SSL_DATA_OUT }; +CFURLSessionInfo const CFURLSessionInfoEND = { CURLINFO_END }; +CFURLSessionInfo const CFURLSessionInfoNONE = { CURLINFO_NONE }; +CFURLSessionInfo const CFURLSessionInfoEFFECTIVE_URL = { CURLINFO_EFFECTIVE_URL }; +CFURLSessionInfo const CFURLSessionInfoRESPONSE_CODE = { CURLINFO_RESPONSE_CODE }; +CFURLSessionInfo const CFURLSessionInfoTOTAL_TIME = { CURLINFO_TOTAL_TIME }; +CFURLSessionInfo const CFURLSessionInfoNAMELOOKUP_TIME = { CURLINFO_NAMELOOKUP_TIME }; +CFURLSessionInfo const CFURLSessionInfoCONNECT_TIME = { CURLINFO_CONNECT_TIME }; +CFURLSessionInfo const CFURLSessionInfoPRETRANSFER_TIME = { CURLINFO_PRETRANSFER_TIME }; +CFURLSessionInfo const CFURLSessionInfoSIZE_UPLOAD = { CURLINFO_SIZE_UPLOAD }; +CFURLSessionInfo const CFURLSessionInfoSIZE_DOWNLOAD = { CURLINFO_SIZE_DOWNLOAD }; +CFURLSessionInfo const CFURLSessionInfoSPEED_DOWNLOAD = { CURLINFO_SPEED_DOWNLOAD }; +CFURLSessionInfo const CFURLSessionInfoSPEED_UPLOAD = { CURLINFO_SPEED_UPLOAD }; +CFURLSessionInfo const CFURLSessionInfoHEADER_SIZE = { CURLINFO_HEADER_SIZE }; +CFURLSessionInfo const CFURLSessionInfoREQUEST_SIZE = { CURLINFO_REQUEST_SIZE }; +CFURLSessionInfo const CFURLSessionInfoSSL_VERIFYRESULT = { CURLINFO_SSL_VERIFYRESULT }; +CFURLSessionInfo const CFURLSessionInfoFILETIME = { CURLINFO_FILETIME }; +CFURLSessionInfo const CFURLSessionInfoCONTENT_LENGTH_DOWNLOAD = { CURLINFO_CONTENT_LENGTH_DOWNLOAD }; +CFURLSessionInfo const CFURLSessionInfoCONTENT_LENGTH_UPLOAD = { CURLINFO_CONTENT_LENGTH_UPLOAD }; +CFURLSessionInfo const CFURLSessionInfoSTARTTRANSFER_TIME = { CURLINFO_STARTTRANSFER_TIME }; +CFURLSessionInfo const CFURLSessionInfoCONTENT_TYPE = { CURLINFO_CONTENT_TYPE }; +CFURLSessionInfo const CFURLSessionInfoREDIRECT_TIME = { CURLINFO_REDIRECT_TIME }; +CFURLSessionInfo const CFURLSessionInfoREDIRECT_COUNT = { CURLINFO_REDIRECT_COUNT }; +CFURLSessionInfo const CFURLSessionInfoPRIVATE = { CURLINFO_PRIVATE }; +CFURLSessionInfo const CFURLSessionInfoHTTP_CONNECTCODE = { CURLINFO_HTTP_CONNECTCODE }; +CFURLSessionInfo const CFURLSessionInfoHTTPAUTH_AVAIL = { CURLINFO_HTTPAUTH_AVAIL }; +CFURLSessionInfo const CFURLSessionInfoPROXYAUTH_AVAIL = { CURLINFO_PROXYAUTH_AVAIL }; +CFURLSessionInfo const CFURLSessionInfoOS_ERRNO = { CURLINFO_OS_ERRNO }; +CFURLSessionInfo const CFURLSessionInfoNUM_CONNECTS = { CURLINFO_NUM_CONNECTS }; +CFURLSessionInfo const CFURLSessionInfoSSL_ENGINES = { CURLINFO_SSL_ENGINES }; +CFURLSessionInfo const CFURLSessionInfoCOOKIELIST = { CURLINFO_COOKIELIST }; +CFURLSessionInfo const CFURLSessionInfoLASTSOCKET = { CURLINFO_LASTSOCKET }; +CFURLSessionInfo const CFURLSessionInfoFTP_ENTRY_PATH = { CURLINFO_FTP_ENTRY_PATH }; +CFURLSessionInfo const CFURLSessionInfoREDIRECT_URL = { CURLINFO_REDIRECT_URL }; +CFURLSessionInfo const CFURLSessionInfoPRIMARY_IP = { CURLINFO_PRIMARY_IP }; +CFURLSessionInfo const CFURLSessionInfoAPPCONNECT_TIME = { CURLINFO_APPCONNECT_TIME }; +CFURLSessionInfo const CFURLSessionInfoCERTINFO = { CURLINFO_CERTINFO }; +CFURLSessionInfo const CFURLSessionInfoCONDITION_UNMET = { CURLINFO_CONDITION_UNMET }; +CFURLSessionInfo const CFURLSessionInfoRTSP_SESSION_ID = { CURLINFO_RTSP_SESSION_ID }; +CFURLSessionInfo const CFURLSessionInfoRTSP_CLIENT_CSEQ = { CURLINFO_RTSP_CLIENT_CSEQ }; +CFURLSessionInfo const CFURLSessionInfoRTSP_SERVER_CSEQ = { CURLINFO_RTSP_SERVER_CSEQ }; +CFURLSessionInfo const CFURLSessionInfoRTSP_CSEQ_RECV = { CURLINFO_RTSP_CSEQ_RECV }; +CFURLSessionInfo const CFURLSessionInfoPRIMARY_PORT = { CURLINFO_PRIMARY_PORT }; +CFURLSessionInfo const CFURLSessionInfoLOCAL_IP = { CURLINFO_LOCAL_IP }; +CFURLSessionInfo const CFURLSessionInfoLOCAL_PORT = { CURLINFO_LOCAL_PORT }; +CFURLSessionInfo const CFURLSessionInfoTLS_SESSION = { CURLINFO_TLS_SESSION }; +CFURLSessionInfo const CFURLSessionInfoLASTONE = { CURLINFO_LASTONE }; + + +CFURLSessionMultiOption const CFURLSessionMultiOptionSOCKETFUNCTION = { CURLMOPT_SOCKETFUNCTION }; +CFURLSessionMultiOption const CFURLSessionMultiOptionSOCKETDATA = { CURLMOPT_SOCKETDATA }; +CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING = { CURLMOPT_PIPELINING }; +CFURLSessionMultiOption const CFURLSessionMultiOptionTIMERFUNCTION = { CURLMOPT_TIMERFUNCTION }; +CFURLSessionMultiOption const CFURLSessionMultiOptionTIMERDATA = { CURLMOPT_TIMERDATA }; +CFURLSessionMultiOption const CFURLSessionMultiOptionMAXCONNECTS = { CURLMOPT_MAXCONNECTS }; +CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_HOST_CONNECTIONS = { CURLMOPT_MAX_HOST_CONNECTIONS }; +CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_PIPELINE_LENGTH = { CURLMOPT_MAX_PIPELINE_LENGTH }; +CFURLSessionMultiOption const CFURLSessionMultiOptionCONTENT_LENGTH_PENALTY_SIZE = { CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE }; +CFURLSessionMultiOption const CFURLSessionMultiOptionCHUNK_LENGTH_PENALTY_SIZE = { CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE }; +CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING_SITE_BL = { CURLMOPT_PIPELINING_SITE_BL }; +CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING_SERVER_BL = { CURLMOPT_PIPELINING_SERVER_BL }; +CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_TOTAL_CONNECTIONS = { CURLMOPT_MAX_TOTAL_CONNECTIONS }; + + +CFURLSessionMultiCode const CFURLSessionMultiCodeCALL_MULTI_PERFORM = { CURLM_CALL_MULTI_PERFORM }; +CFURLSessionMultiCode const CFURLSessionMultiCodeOK = { CURLM_OK }; +CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_HANDLE = { CURLM_BAD_HANDLE }; +CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_EASY_HANDLE = { CURLM_BAD_EASY_HANDLE }; +CFURLSessionMultiCode const CFURLSessionMultiCodeOUT_OF_MEMORY = { CURLM_OUT_OF_MEMORY }; +CFURLSessionMultiCode const CFURLSessionMultiCodeINTERNAL_ERROR = { CURLM_INTERNAL_ERROR }; +CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_SOCKET = { CURLM_BAD_SOCKET }; +CFURLSessionMultiCode const CFURLSessionMultiCodeUNKNOWN_OPTION = { CURLM_UNKNOWN_OPTION }; +CFURLSessionMultiCode const CFURLSessionMultiCodeADDED_ALREADY = { CURLM_ADDED_ALREADY }; +CFURLSessionMultiCode const CFURLSessionMultiCodeLAST = { CURLM_LAST }; + + +CFURLSessionPoll const CFURLSessionPollNone = { CURL_POLL_NONE }; +CFURLSessionPoll const CFURLSessionPollIn = { CURL_POLL_IN }; +CFURLSessionPoll const CFURLSessionPollOut = { CURL_POLL_OUT }; +CFURLSessionPoll const CFURLSessionPollInOut = { CURL_POLL_INOUT }; +CFURLSessionPoll const CFURLSessionPollRemove = { CURL_POLL_REMOVE }; + +char *CFURLSessionCurlVersionString(void) { + return curl_version(); +} +CFURLSessionCurlVersion CFURLSessionCurlVersionInfo(void) { + curl_version_info_data *info = curl_version_info(CURLVERSION_NOW); + CFURLSessionCurlVersion v = { + info->version_num >> 16 & 0xff, + info->version_num >> 8 & 0xff, + info->version_num & 0xff, + }; + return v; +} + + +int const CFURLSessionWriteFuncPause = CURL_WRITEFUNC_PAUSE; +int const CFURLSessionReadFuncPause = CURL_READFUNC_PAUSE; +int const CFURLSessionReadFuncAbort = CURL_READFUNC_ABORT; + + +int const CFURLSessionSocketTimeout = CURL_SOCKET_TIMEOUT; + +int const CFURLSessionSeekOk = CURL_SEEKFUNC_OK; +int const CFURLSessionSeekCantSeek = CURL_SEEKFUNC_CANTSEEK; +int const CFURLSessionSeekFail = CURL_SEEKFUNC_FAIL; + +CFURLSessionSList *_Nullable CFURLSessionSListAppend(CFURLSessionSList *_Nullable list, const char * _Nullable string) { + return (CFURLSessionSList *) curl_slist_append((struct curl_slist *) list, string); +} +void CFURLSessionSListFreeAll(CFURLSessionSList *_Nullable list) { + curl_slist_free_all((struct curl_slist *) list); +} diff --git a/CoreFoundation/URL.subproj/CFURLSessionInterface.h b/CoreFoundation/URL.subproj/CFURLSessionInterface.h new file mode 100644 index 0000000000..6655ea8b51 --- /dev/null +++ b/CoreFoundation/URL.subproj/CFURLSessionInterface.h @@ -0,0 +1,605 @@ +//===-- CoreFoundation/URL/CFURLSessionInterface.h - Very brief description -----------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains wrappes / helpers to import libcurl into Swift. +/// It is used to implement the NSURLSession API. +/// +/// In most cases each `curl_…` API is mapped 1-to-1 to a corresponding +/// `CFURLSession_…` API. +/// +/// This approach lets us keep most of the logic inside Swift code as opposed +/// to more C code. +/// +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// +//===----------------------------------------------------------------------===// + +#if !defined(__COREFOUNDATION_URLSESSIONINTERFACE__) +#define __COREFOUNDATION_URLSESSIONINTERFACE__ 1 + +#include + +CF_IMPLICIT_BRIDGING_ENABLED +CF_EXTERN_C_BEGIN + + +/// CURL +typedef void * CFURLSessionEasyHandle; + +/// CURLM +typedef void * CFURLSessionMultiHandle; + +// This must match libcurl's curl_socket_t +typedef int CFURLSession_socket_t; + + + +typedef struct CFURLSessionEasyCode { + int value; +} CFURLSessionEasyCode; + +/// CURLcode +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOK; // CURLE_OK +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeUNSUPPORTED_PROTOCOL; // CURLE_UNSUPPORTED_PROTOCOL +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFAILED_INIT; // CURLE_FAILED_INIT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeURL_MALFORMAT; // CURLE_URL_MALFORMAT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeNOT_BUILT_IN; // CURLE_NOT_BUILT_IN +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_RESOLVE_PROXY; // CURLE_COULDNT_RESOLVE_PROXY +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_RESOLVE_HOST; // CURLE_COULDNT_RESOLVE_HOST +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCOULDNT_CONNECT; // CURLE_COULDNT_CONNECT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_SERVER_REPLY; // CURLE_FTP_WEIRD_SERVER_REPLY +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_ACCESS_DENIED; // CURLE_REMOTE_ACCESS_DENIED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_ACCEPT_FAILED; // CURLE_FTP_ACCEPT_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_PASS_REPLY; // CURLE_FTP_WEIRD_PASS_REPLY +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_ACCEPT_TIMEOUT; // CURLE_FTP_ACCEPT_TIMEOUT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_PASV_REPLY; // CURLE_FTP_WEIRD_PASV_REPLY +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_WEIRD_227_FORMAT; // CURLE_FTP_WEIRD_227_FORMAT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_CANT_GET_HOST; // CURLE_FTP_CANT_GET_HOST +//CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP2; // CURLE_HTTP2 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_SET_TYPE; // CURLE_FTP_COULDNT_SET_TYPE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodePARTIAL_FILE; // CURLE_PARTIAL_FILE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_RETR_FILE; // CURLE_FTP_COULDNT_RETR_FILE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE20; // CURLE_OBSOLETE20 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeQUOTE_ERROR; // CURLE_QUOTE_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP_RETURNED_ERROR; // CURLE_HTTP_RETURNED_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeWRITE_ERROR; // CURLE_WRITE_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE24; // CURLE_OBSOLETE24 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeUPLOAD_FAILED; // CURLE_UPLOAD_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeREAD_ERROR; // CURLE_READ_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOUT_OF_MEMORY; // CURLE_OUT_OF_MEMORY +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOPERATION_TIMEDOUT; // CURLE_OPERATION_TIMEDOUT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE29; // CURLE_OBSOLETE29 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_PORT_FAILED; // CURLE_FTP_PORT_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_COULDNT_USE_REST; // CURLE_FTP_COULDNT_USE_REST +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE32; // CURLE_OBSOLETE32 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeRANGE_ERROR; // CURLE_RANGE_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeHTTP_POST_ERROR; // CURLE_HTTP_POST_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CONNECT_ERROR; // CURLE_SSL_CONNECT_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_DOWNLOAD_RESUME; // CURLE_BAD_DOWNLOAD_RESUME +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFILE_COULDNT_READ_FILE; // CURLE_FILE_COULDNT_READ_FILE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_CANNOT_BIND; // CURLE_LDAP_CANNOT_BIND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_SEARCH_FAILED; // CURLE_LDAP_SEARCH_FAILED +//CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE40; // CURLE_OBSOLETE40 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFUNCTION_NOT_FOUND; // CURLE_FUNCTION_NOT_FOUND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeABORTED_BY_CALLBACK; // CURLE_ABORTED_BY_CALLBACK +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_FUNCTION_ARGUMENT; // CURLE_BAD_FUNCTION_ARGUMENT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE44; // CURLE_OBSOLETE44 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeINTERFACE_FAILED; // CURLE_INTERFACE_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE46; // CURLE_OBSOLETE46 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTOO_MANY_REDIRECTS; // CURLE_TOO_MANY_REDIRECTS +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeUNKNOWN_OPTION; // CURLE_UNKNOWN_OPTION +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTELNET_OPTION_SYNTAX; // CURLE_TELNET_OPTION_SYNTAX +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE50; // CURLE_OBSOLETE50 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodePEER_FAILED_VERIFICATION; // CURLE_PEER_FAILED_VERIFICATION +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeGOT_NOTHING; // CURLE_GOT_NOTHING +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_NOTFOUND; // CURLE_SSL_ENGINE_NOTFOUND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_SETFAILED; // CURLE_SSL_ENGINE_SETFAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSEND_ERROR; // CURLE_SEND_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeRECV_ERROR; // CURLE_RECV_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeOBSOLETE57; // CURLE_OBSOLETE57 +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CERTPROBLEM; // CURLE_SSL_CERTPROBLEM +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CIPHER; // CURLE_SSL_CIPHER +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CACERT; // CURLE_SSL_CACERT +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeBAD_CONTENT_ENCODING; // CURLE_BAD_CONTENT_ENCODING +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeLDAP_INVALID_URL; // CURLE_LDAP_INVALID_URL +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFILESIZE_EXCEEDED; // CURLE_FILESIZE_EXCEEDED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeUSE_SSL_FAILED; // CURLE_USE_SSL_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSEND_FAIL_REWIND; // CURLE_SEND_FAIL_REWIND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ENGINE_INITFAILED; // CURLE_SSL_ENGINE_INITFAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeLOGIN_DENIED; // CURLE_LOGIN_DENIED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_NOTFOUND; // CURLE_TFTP_NOTFOUND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_PERM; // CURLE_TFTP_PERM +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_DISK_FULL; // CURLE_REMOTE_DISK_FULL +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_ILLEGAL; // CURLE_TFTP_ILLEGAL +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_UNKNOWNID; // CURLE_TFTP_UNKNOWNID +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_FILE_EXISTS; // CURLE_REMOTE_FILE_EXISTS +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeTFTP_NOSUCHUSER; // CURLE_TFTP_NOSUCHUSER +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCONV_FAILED; // CURLE_CONV_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCONV_REQD; // CURLE_CONV_REQD +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CACERT_BADFILE; // CURLE_SSL_CACERT_BADFILE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeREMOTE_FILE_NOT_FOUND; // CURLE_REMOTE_FILE_NOT_FOUND +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSH; // CURLE_SSH +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_SHUTDOWN_FAILED; // CURLE_SSL_SHUTDOWN_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeAGAIN; // CURLE_AGAIN +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_CRL_BADFILE; // CURLE_SSL_CRL_BADFILE +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_ISSUER_ERROR; // CURLE_SSL_ISSUER_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_PRET_FAILED; // CURLE_FTP_PRET_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeRTSP_CSEQ_ERROR; // CURLE_RTSP_CSEQ_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeRTSP_SESSION_ERROR; // CURLE_RTSP_SESSION_ERROR +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeFTP_BAD_FILE_LIST; // CURLE_FTP_BAD_FILE_LIST +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeCHUNK_FAILED; // CURLE_CHUNK_FAILED +CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeNO_CONNECTION_AVAILABLE; // CURLE_NO_CONNECTION_AVAILABLE +//CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_PINNEDPUBKEYNOTMATCH; // CURLE_SSL_PINNEDPUBKEYNOTMATCH +//CF_EXPORT CFURLSessionEasyCode const CFURLSessionEasyCodeSSL_INVALIDCERTSTATUS; // CURLE_SSL_INVALIDCERTSTATUS + + + +/// CURLOPTTYPE +typedef enum { + CFURLSessionOptTypeLONG = 0, // CURLOPTTYPE_LONG + CFURLSessionOptTypeOBJECTPOINT = 10000, // CURLOPTTYPE_OBJECTPOINT + CFURLSessionOptTypeFUNCTIONPOINT = 20000, // CURLOPTTYPE_FUNCTIONPOINT + CFURLSessionOptTypeOFF_T = 30000, // CURLOPTTYPE_OFF_T +} CFURLSessionOptType; + + + +typedef struct CFURLSessionOption { + int value; +} CFURLSessionOption; + +/// CURLoption +CF_EXPORT CFURLSessionOption const CFURLSessionOptionWRITEDATA; // CURLOPT_WRITEDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionURL; // CURLOPT_URL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPORT; // CURLOPT_PORT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXY; // CURLOPT_PROXY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUSERPWD; // CURLOPT_USERPWD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYUSERPWD; // CURLOPT_PROXYUSERPWD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRANGE; // CURLOPT_RANGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionREADDATA; // CURLOPT_READDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionERRORBUFFER; // CURLOPT_ERRORBUFFER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionWRITEFUNCTION; // CURLOPT_WRITEFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionREADFUNCTION; // CURLOPT_READFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTIMEOUT; // CURLOPT_TIMEOUT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionINFILESIZE; // CURLOPT_INFILESIZE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOSTFIELDS; // CURLOPT_POSTFIELDS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionREFERER; // CURLOPT_REFERER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTPPORT; // CURLOPT_FTPPORT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUSERAGENT; // CURLOPT_USERAGENT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionLOW_SPEED_LIMIT; // CURLOPT_LOW_SPEED_LIMIT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionLOW_SPEED_TIME; // CURLOPT_LOW_SPEED_TIME +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRESUME_FROM; // CURLOPT_RESUME_FROM +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIE; // CURLOPT_COOKIE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPHEADER; // CURLOPT_HTTPHEADER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPPOST; // CURLOPT_HTTPPOST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLCERT; // CURLOPT_SSLCERT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionKEYPASSWD; // CURLOPT_KEYPASSWD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCRLF; // CURLOPT_CRLF +CF_EXPORT CFURLSessionOption const CFURLSessionOptionQUOTE; // CURLOPT_QUOTE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHEADERDATA; // CURLOPT_HEADERDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIEFILE; // CURLOPT_COOKIEFILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLVERSION; // CURLOPT_SSLVERSION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTIMECONDITION; // CURLOPT_TIMECONDITION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTIMEVALUE; // CURLOPT_TIMEVALUE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCUSTOMREQUEST; // CURLOPT_CUSTOMREQUEST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSTDERR; // CURLOPT_STDERR +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOSTQUOTE; // CURLOPT_POSTQUOTE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionOBSOLETE40; // CURLOPT_OBSOLETE40 +CF_EXPORT CFURLSessionOption const CFURLSessionOptionVERBOSE; // CURLOPT_VERBOSE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHEADER; // CURLOPT_HEADER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNOPROGRESS; // CURLOPT_NOPROGRESS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNOBODY; // CURLOPT_NOBODY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFAILONERROR; // CURLOPT_FAILONERROR +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUPLOAD; // CURLOPT_UPLOAD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOST; // CURLOPT_POST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDIRLISTONLY; // CURLOPT_DIRLISTONLY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionAPPEND; // CURLOPT_APPEND +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNETRC; // CURLOPT_NETRC +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFOLLOWLOCATION; // CURLOPT_FOLLOWLOCATION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTRANSFERTEXT; // CURLOPT_TRANSFERTEXT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPUT; // CURLOPT_PUT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROGRESSFUNCTION; // CURLOPT_PROGRESSFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROGRESSDATA; // CURLOPT_PROGRESSDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionAUTOREFERER; // CURLOPT_AUTOREFERER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYPORT; // CURLOPT_PROXYPORT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOSTFIELDSIZE; // CURLOPT_POSTFIELDSIZE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPPROXYTUNNEL; // CURLOPT_HTTPPROXYTUNNEL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionINTERFACE; // CURLOPT_INTERFACE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionKRBLEVEL; // CURLOPT_KRBLEVEL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_VERIFYPEER; // CURLOPT_SSL_VERIFYPEER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCAINFO; // CURLOPT_CAINFO +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAXREDIRS; // CURLOPT_MAXREDIRS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFILETIME; // CURLOPT_FILETIME +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTELNETOPTIONS; // CURLOPT_TELNETOPTIONS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAXCONNECTS; // CURLOPT_MAXCONNECTS +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionOBSOLETE72; // CURLOPT_OBSOLETE72 +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFRESH_CONNECT; // CURLOPT_FRESH_CONNECT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFORBID_REUSE; // CURLOPT_FORBID_REUSE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRANDOM_FILE; // CURLOPT_RANDOM_FILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionEGDSOCKET; // CURLOPT_EGDSOCKET +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONNECTTIMEOUT; // CURLOPT_CONNECTTIMEOUT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHEADERFUNCTION; // CURLOPT_HEADERFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPGET; // CURLOPT_HTTPGET +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_VERIFYHOST; // CURLOPT_SSL_VERIFYHOST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIEJAR; // CURLOPT_COOKIEJAR +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_CIPHER_LIST; // CURLOPT_SSL_CIPHER_LIST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTP_VERSION; // CURLOPT_HTTP_VERSION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_USE_EPSV; // CURLOPT_FTP_USE_EPSV +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLCERTTYPE; // CURLOPT_SSLCERTTYPE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLKEY; // CURLOPT_SSLKEY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLKEYTYPE; // CURLOPT_SSLKEYTYPE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLENGINE; // CURLOPT_SSLENGINE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSLENGINE_DEFAULT; // CURLOPT_SSLENGINE_DEFAULT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_USE_GLOBAL_CACHE; // CURLOPT_DNS_USE_GLOBAL_CACHE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_CACHE_TIMEOUT; // CURLOPT_DNS_CACHE_TIMEOUT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPREQUOTE; // CURLOPT_PREQUOTE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDEBUGFUNCTION; // CURLOPT_DEBUGFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDEBUGDATA; // CURLOPT_DEBUGDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIESESSION; // CURLOPT_COOKIESESSION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCAPATH; // CURLOPT_CAPATH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionBUFFERSIZE; // CURLOPT_BUFFERSIZE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNOSIGNAL; // CURLOPT_NOSIGNAL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSHARE; // CURLOPT_SHARE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYTYPE; // CURLOPT_PROXYTYPE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionACCEPT_ENCODING; // CURLOPT_ACCEPT_ENCODING +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPRIVATE; // CURLOPT_PRIVATE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTP200ALIASES; // CURLOPT_HTTP200ALIASES +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUNRESTRICTED_AUTH; // CURLOPT_UNRESTRICTED_AUTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_USE_EPRT; // CURLOPT_FTP_USE_EPRT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTPAUTH; // CURLOPT_HTTPAUTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_CTX_FUNCTION; // CURLOPT_SSL_CTX_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_CTX_DATA; // CURLOPT_SSL_CTX_DATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_CREATE_MISSING_DIRS; // CURLOPT_FTP_CREATE_MISSING_DIRS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYAUTH; // CURLOPT_PROXYAUTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_RESPONSE_TIMEOUT; // CURLOPT_FTP_RESPONSE_TIMEOUT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionIPRESOLVE; // CURLOPT_IPRESOLVE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAXFILESIZE; // CURLOPT_MAXFILESIZE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionINFILESIZE_LARGE; // CURLOPT_INFILESIZE_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRESUME_FROM_LARGE; // CURLOPT_RESUME_FROM_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAXFILESIZE_LARGE; // CURLOPT_MAXFILESIZE_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNETRC_FILE; // CURLOPT_NETRC_FILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUSE_SSL; // CURLOPT_USE_SSL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOSTFIELDSIZE_LARGE; // CURLOPT_POSTFIELDSIZE_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTCP_NODELAY; // CURLOPT_TCP_NODELAY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTPSSLAUTH; // CURLOPT_FTPSSLAUTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionIOCTLFUNCTION; // CURLOPT_IOCTLFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionIOCTLDATA; // CURLOPT_IOCTLDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_ACCOUNT; // CURLOPT_FTP_ACCOUNT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOOKIELIST; // CURLOPT_COOKIELIST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionIGNORE_CONTENT_LENGTH; // CURLOPT_IGNORE_CONTENT_LENGTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_SKIP_PASV_IP; // CURLOPT_FTP_SKIP_PASV_IP +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_FILEMETHOD; // CURLOPT_FTP_FILEMETHOD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionLOCALPORT; // CURLOPT_LOCALPORT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionLOCALPORTRANGE; // CURLOPT_LOCALPORTRANGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONNECT_ONLY; // CURLOPT_CONNECT_ONLY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONV_FROM_NETWORK_FUNCTION; // CURLOPT_CONV_FROM_NETWORK_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONV_TO_NETWORK_FUNCTION; // CURLOPT_CONV_TO_NETWORK_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONV_FROM_UTF8_FUNCTION; // CURLOPT_CONV_FROM_UTF8_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAX_SEND_SPEED_LARGE; // CURLOPT_MAX_SEND_SPEED_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAX_RECV_SPEED_LARGE; // CURLOPT_MAX_RECV_SPEED_LARGE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_ALTERNATIVE_TO_USER; // CURLOPT_FTP_ALTERNATIVE_TO_USER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSOCKOPTFUNCTION; // CURLOPT_SOCKOPTFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSOCKOPTDATA; // CURLOPT_SOCKOPTDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_SESSIONID_CACHE; // CURLOPT_SSL_SESSIONID_CACHE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_AUTH_TYPES; // CURLOPT_SSH_AUTH_TYPES +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_PUBLIC_KEYFILE; // CURLOPT_SSH_PUBLIC_KEYFILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_PRIVATE_KEYFILE; // CURLOPT_SSH_PRIVATE_KEYFILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_SSL_CCC; // CURLOPT_FTP_SSL_CCC +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTIMEOUT_MS; // CURLOPT_TIMEOUT_MS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCONNECTTIMEOUT_MS; // CURLOPT_CONNECTTIMEOUT_MS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTP_TRANSFER_DECODING; // CURLOPT_HTTP_TRANSFER_DECODING +CF_EXPORT CFURLSessionOption const CFURLSessionOptionHTTP_CONTENT_DECODING; // CURLOPT_HTTP_CONTENT_DECODING +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNEW_FILE_PERMS; // CURLOPT_NEW_FILE_PERMS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNEW_DIRECTORY_PERMS; // CURLOPT_NEW_DIRECTORY_PERMS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPOSTREDIR; // CURLOPT_POSTREDIR +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_HOST_PUBLIC_KEY_MD5; // CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 +CF_EXPORT CFURLSessionOption const CFURLSessionOptionOPENSOCKETFUNCTION; // CURLOPT_OPENSOCKETFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionOPENSOCKETDATA; // CURLOPT_OPENSOCKETDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCOPYPOSTFIELDS; // CURLOPT_COPYPOSTFIELDS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXY_TRANSFER_MODE; // CURLOPT_PROXY_TRANSFER_MODE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSEEKFUNCTION; // CURLOPT_SEEKFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSEEKDATA; // CURLOPT_SEEKDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCRLFILE; // CURLOPT_CRLFILE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionISSUERCERT; // CURLOPT_ISSUERCERT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionADDRESS_SCOPE; // CURLOPT_ADDRESS_SCOPE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCERTINFO; // CURLOPT_CERTINFO +CF_EXPORT CFURLSessionOption const CFURLSessionOptionUSERNAME; // CURLOPT_USERNAME +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPASSWORD; // CURLOPT_PASSWORD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYUSERNAME; // CURLOPT_PROXYUSERNAME +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYPASSWORD; // CURLOPT_PROXYPASSWORD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionNOPROXY; // CURLOPT_NOPROXY +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTFTP_BLKSIZE; // CURLOPT_TFTP_BLKSIZE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSOCKS5_GSSAPI_SERVICE; // CURLOPT_SOCKS5_GSSAPI_SERVICE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSOCKS5_GSSAPI_NEC; // CURLOPT_SOCKS5_GSSAPI_NEC +CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROTOCOLS; // CURLOPT_PROTOCOLS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionREDIR_PROTOCOLS; // CURLOPT_REDIR_PROTOCOLS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_KNOWNHOSTS; // CURLOPT_SSH_KNOWNHOSTS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_KEYFUNCTION; // CURLOPT_SSH_KEYFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSH_KEYDATA; // CURLOPT_SSH_KEYDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAIL_FROM; // CURLOPT_MAIL_FROM +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAIL_RCPT; // CURLOPT_MAIL_RCPT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFTP_USE_PRET; // CURLOPT_FTP_USE_PRET +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_REQUEST; // CURLOPT_RTSP_REQUEST +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_SESSION_ID; // CURLOPT_RTSP_SESSION_ID +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_STREAM_URI; // CURLOPT_RTSP_STREAM_URI +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_TRANSPORT; // CURLOPT_RTSP_TRANSPORT +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_CLIENT_CSEQ; // CURLOPT_RTSP_CLIENT_CSEQ +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRTSP_SERVER_CSEQ; // CURLOPT_RTSP_SERVER_CSEQ +CF_EXPORT CFURLSessionOption const CFURLSessionOptionINTERLEAVEDATA; // CURLOPT_INTERLEAVEDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionINTERLEAVEFUNCTION; // CURLOPT_INTERLEAVEFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionWILDCARDMATCH; // CURLOPT_WILDCARDMATCH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCHUNK_BGN_FUNCTION; // CURLOPT_CHUNK_BGN_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCHUNK_END_FUNCTION; // CURLOPT_CHUNK_END_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFNMATCH_FUNCTION; // CURLOPT_FNMATCH_FUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCHUNK_DATA; // CURLOPT_CHUNK_DATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionFNMATCH_DATA; // CURLOPT_FNMATCH_DATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionRESOLVE; // CURLOPT_RESOLVE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTLSAUTH_USERNAME; // CURLOPT_TLSAUTH_USERNAME +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTLSAUTH_PASSWORD; // CURLOPT_TLSAUTH_PASSWORD +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTLSAUTH_TYPE; // CURLOPT_TLSAUTH_TYPE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTRANSFER_ENCODING; // CURLOPT_TRANSFER_ENCODING +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCLOSESOCKETFUNCTION; // CURLOPT_CLOSESOCKETFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionCLOSESOCKETDATA; // CURLOPT_CLOSESOCKETDATA +CF_EXPORT CFURLSessionOption const CFURLSessionOptionGSSAPI_DELEGATION; // CURLOPT_GSSAPI_DELEGATION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_SERVERS; // CURLOPT_DNS_SERVERS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionACCEPTTIMEOUT_MS; // CURLOPT_ACCEPTTIMEOUT_MS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTCP_KEEPALIVE; // CURLOPT_TCP_KEEPALIVE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTCP_KEEPIDLE; // CURLOPT_TCP_KEEPIDLE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionTCP_KEEPINTVL; // CURLOPT_TCP_KEEPINTVL +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_OPTIONS; // CURLOPT_SSL_OPTIONS +CF_EXPORT CFURLSessionOption const CFURLSessionOptionMAIL_AUTH; // CURLOPT_MAIL_AUTH +CF_EXPORT CFURLSessionOption const CFURLSessionOptionSASL_IR; // CURLOPT_SASL_IR +CF_EXPORT CFURLSessionOption const CFURLSessionOptionXFERINFOFUNCTION; // CURLOPT_XFERINFOFUNCTION +CF_EXPORT CFURLSessionOption const CFURLSessionOptionXFERINFODATA; +CF_EXPORT CFURLSessionOption const CFURLSessionOptionXOAUTH2_BEARER; // CURLOPT_XOAUTH2_BEARER +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_INTERFACE; // CURLOPT_DNS_INTERFACE +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_LOCAL_IP4; // CURLOPT_DNS_LOCAL_IP4 +CF_EXPORT CFURLSessionOption const CFURLSessionOptionDNS_LOCAL_IP6; // CURLOPT_DNS_LOCAL_IP6 +CF_EXPORT CFURLSessionOption const CFURLSessionOptionLOGIN_OPTIONS; // CURLOPT_LOGIN_OPTIONS +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_ENABLE_NPN; // CURLOPT_SSL_ENABLE_NPN +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_ENABLE_ALPN; // CURLOPT_SSL_ENABLE_ALPN +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionEXPECT_100_TIMEOUT_MS; // CURLOPT_EXPECT_100_TIMEOUT_MS +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXYHEADER; // CURLOPT_PROXYHEADER +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionHEADEROPT; // CURLOPT_HEADEROPT +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionPINNEDPUBLICKEY; // CURLOPT_PINNEDPUBLICKEY +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionUNIX_SOCKET_PATH; // CURLOPT_UNIX_SOCKET_PATH +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_VERIFYSTATUS; // CURLOPT_SSL_VERIFYSTATUS +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSSL_FALSESTART; // CURLOPT_SSL_FALSESTART +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionPATH_AS_IS; // CURLOPT_PATH_AS_IS +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionPROXY_SERVICE_NAME; // CURLOPT_PROXY_SERVICE_NAME +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionSERVICE_NAME; // CURLOPT_SERVICE_NAME +//CF_EXPORT CFURLSessionOption const CFURLSessionOptionPIPEWAIT; // CURLOPT_PIPEWAIT + + +/// This is a mash-up of these two types: +/// curl_infotype & CURLoption +typedef struct CFURLSessionInfo { + int value; +} CFURLSessionInfo; + +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoTEXT; // CURLINFO_TEXT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoHEADER_IN; // CURLINFO_HEADER_IN +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoHEADER_OUT; // CURLINFO_HEADER_OUT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoDATA_IN; // CURLINFO_DATA_IN +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoDATA_OUT; // CURLINFO_DATA_OUT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSSL_DATA_IN; // CURLINFO_SSL_DATA_IN +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSSL_DATA_OUT; // CURLINFO_SSL_DATA_OUT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoEND; // CURLINFO_END +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoNONE; // CURLINFO_NONE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoEFFECTIVE_URL; // CURLINFO_EFFECTIVE_URL +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoRESPONSE_CODE; // CURLINFO_RESPONSE_CODE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoTOTAL_TIME; // CURLINFO_TOTAL_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoNAMELOOKUP_TIME; // CURLINFO_NAMELOOKUP_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCONNECT_TIME; // CURLINFO_CONNECT_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoPRETRANSFER_TIME; // CURLINFO_PRETRANSFER_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSIZE_UPLOAD; // CURLINFO_SIZE_UPLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSIZE_DOWNLOAD; // CURLINFO_SIZE_DOWNLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSPEED_DOWNLOAD; // CURLINFO_SPEED_DOWNLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSPEED_UPLOAD; // CURLINFO_SPEED_UPLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoHEADER_SIZE; // CURLINFO_HEADER_SIZE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoREQUEST_SIZE; // CURLINFO_REQUEST_SIZE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSSL_VERIFYRESULT; // CURLINFO_SSL_VERIFYRESULT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoFILETIME; // CURLINFO_FILETIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCONTENT_LENGTH_DOWNLOAD; // CURLINFO_CONTENT_LENGTH_DOWNLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCONTENT_LENGTH_UPLOAD; // CURLINFO_CONTENT_LENGTH_UPLOAD +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSTARTTRANSFER_TIME; // CURLINFO_STARTTRANSFER_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCONTENT_TYPE; // CURLINFO_CONTENT_TYPE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoREDIRECT_TIME; // CURLINFO_REDIRECT_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoREDIRECT_COUNT; // CURLINFO_REDIRECT_COUNT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoPRIVATE; // CURLINFO_PRIVATE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoHTTP_CONNECTCODE; // CURLINFO_HTTP_CONNECTCODE +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoHTTPAUTH_AVAIL; // CURLINFO_HTTPAUTH_AVAIL +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoPROXYAUTH_AVAIL; // CURLINFO_PROXYAUTH_AVAIL +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoOS_ERRNO; // CURLINFO_OS_ERRNO +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoNUM_CONNECTS; // CURLINFO_NUM_CONNECTS +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoSSL_ENGINES; // CURLINFO_SSL_ENGINES +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCOOKIELIST; // CURLINFO_COOKIELIST +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoLASTSOCKET; // CURLINFO_LASTSOCKET +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoFTP_ENTRY_PATH; // CURLINFO_FTP_ENTRY_PATH +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoREDIRECT_URL; // CURLINFO_REDIRECT_URL +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoPRIMARY_IP; // CURLINFO_PRIMARY_IP +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoAPPCONNECT_TIME; // CURLINFO_APPCONNECT_TIME +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCERTINFO; // CURLINFO_CERTINFO +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoCONDITION_UNMET; // CURLINFO_CONDITION_UNMET +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoRTSP_SESSION_ID; // CURLINFO_RTSP_SESSION_ID +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoRTSP_CLIENT_CSEQ; // CURLINFO_RTSP_CLIENT_CSEQ +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoRTSP_SERVER_CSEQ; // CURLINFO_RTSP_SERVER_CSEQ +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoRTSP_CSEQ_RECV; // CURLINFO_RTSP_CSEQ_RECV +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoPRIMARY_PORT; // CURLINFO_PRIMARY_PORT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoLOCAL_IP; // CURLINFO_LOCAL_IP +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoLOCAL_PORT; // CURLINFO_LOCAL_PORT +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoTLS_SESSION; // CURLINFO_TLS_SESSION +CF_EXPORT CFURLSessionInfo const CFURLSessionInfoLASTONE; // CURLINFO_LASTONE + +typedef struct CFURLSessionMultiOption { + int value; +} CFURLSessionMultiOption; + + +/// CURLMoption +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionSOCKETFUNCTION; // CURLMOPT_SOCKETFUNCTION +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionSOCKETDATA; // CURLMOPT_SOCKETDATA +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING; // CURLMOPT_PIPELINING +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionTIMERFUNCTION; // CURLMOPT_TIMERFUNCTION +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionTIMERDATA; // CURLMOPT_TIMERDATA +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionMAXCONNECTS; // CURLMOPT_MAXCONNECTS +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_HOST_CONNECTIONS; // CURLMOPT_MAX_HOST_CONNECTIONS +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_PIPELINE_LENGTH; // CURLMOPT_MAX_PIPELINE_LENGTH +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionCONTENT_LENGTH_PENALTY_SIZE; // CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionCHUNK_LENGTH_PENALTY_SIZE; // CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING_SITE_BL; // CURLMOPT_PIPELINING_SITE_BL +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionPIPELINING_SERVER_BL; // CURLMOPT_PIPELINING_SERVER_BL +CF_EXPORT CFURLSessionMultiOption const CFURLSessionMultiOptionMAX_TOTAL_CONNECTIONS; // CURLMOPT_MAX_TOTAL_CONNECTIONS + + + +typedef struct CFURLSessionMultiCode { + int value; +} CFURLSessionMultiCode; + + +/// CURLMcode +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeCALL_MULTI_PERFORM; // CURLM_CALL_MULTI_PERFORM +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeOK; // CURLM_OK +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_HANDLE; // CURLM_BAD_HANDLE +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_EASY_HANDLE; // CURLM_BAD_EASY_HANDLE +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeOUT_OF_MEMORY; // CURLM_OUT_OF_MEMORY +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeINTERNAL_ERROR; // CURLM_INTERNAL_ERROR +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeBAD_SOCKET; // CURLM_BAD_SOCKET +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeUNKNOWN_OPTION; // CURLM_UNKNOWN_OPTION +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeADDED_ALREADY; // CURLM_ADDED_ALREADY +CF_EXPORT CFURLSessionMultiCode const CFURLSessionMultiCodeLAST; // CURLM_LAST + + +typedef struct CFURLSessionPoll { + int value; +} CFURLSessionPoll; +CF_EXPORT CFURLSessionPoll const CFURLSessionPollNone; // CURL_POLL_NONE +CF_EXPORT CFURLSessionPoll const CFURLSessionPollIn; // CURL_POLL_IN +CF_EXPORT CFURLSessionPoll const CFURLSessionPollOut; // CURL_POLL_OUT +CF_EXPORT CFURLSessionPoll const CFURLSessionPollInOut; // CURL_POLL_INOUT +CF_EXPORT CFURLSessionPoll const CFURLSessionPollRemove; // CURL_POLL_REMOVE + + +typedef long CFURLSessionProtocol; + + +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolHTTP; // CURLPROTO_HTTP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolHTTPS; // CURLPROTO_HTTPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolFTP; // CURLPROTO_FTP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolFTPS; // CURLPROTO_FTPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSCP; // CURLPROTO_SCP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSFTP; // CURLPROTO_SFTP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolTELNET; // CURLPROTO_TELNET +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolLDAP; // CURLPROTO_LDAP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolLDAPS; // CURLPROTO_LDAPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolDICT; // CURLPROTO_DICT +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolFILE; // CURLPROTO_FILE +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolTFTP; // CURLPROTO_TFTP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolIMAP; // CURLPROTO_IMAP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolIMAPS; // CURLPROTO_IMAPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolPOP3; // CURLPROTO_POP3 +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolPOP3S; // CURLPROTO_POP3S +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSMTP; // CURLPROTO_SMTP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSMTPS; // CURLPROTO_SMTPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTSP; // CURLPROTO_RTSP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMP; // CURLPROTO_RTMP +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMPT; // CURLPROTO_RTMPT +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMPE; // CURLPROTO_RTMPE +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMPTE; // CURLPROTO_RTMPTE +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMPS; // CURLPROTO_RTMPS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolRTMPTS; // CURLPROTO_RTMPTS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolGOPHER; // CURLPROTO_GOPHER +//CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSMB; // CURLPROTO_SMB +//CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolSMBS; // CURLPROTO_SMBS +CF_EXPORT CFURLSessionProtocol const CFURLSessionProtocolALL; // CURLPROTO_ALL + + +CF_EXPORT size_t const CFURLSessionMaxWriteSize; // CURL_MAX_WRITE_SIZE + +CF_EXPORT char * _Nonnull CFURLSessionCurlVersionString(void); +typedef struct CFURLSessionCurlVersion { + int major; + int minor; + int patch; +} CFURLSessionCurlVersion; +CF_EXPORT CFURLSessionCurlVersion CFURLSessionCurlVersionInfo(void); + + +CF_EXPORT int const CFURLSessionWriteFuncPause; +CF_EXPORT int const CFURLSessionReadFuncPause; +CF_EXPORT int const CFURLSessionReadFuncAbort; + +CF_EXPORT int const CFURLSessionSocketTimeout; + +CF_EXPORT int const CFURLSessionSeekOk; +CF_EXPORT int const CFURLSessionSeekCantSeek; +CF_EXPORT int const CFURLSessionSeekFail; + +CF_EXPORT CFURLSessionEasyHandle _Nonnull CFURLSessionEasyHandleInit(); +CF_EXPORT void CFURLSessionEasyHandleDeinit(CFURLSessionEasyHandle _Nonnull handle); +CF_EXPORT CFURLSessionEasyCode CFURLSessionEasyHandleSetPauseState(CFURLSessionEasyHandle _Nonnull handle, int send, int receive); + +CF_EXPORT CFURLSessionMultiHandle _Nonnull CFURLSessionMultiHandleInit(); +CF_EXPORT CFURLSessionMultiCode CFURLSessionMultiHandleDeinit(CFURLSessionMultiHandle _Nonnull handle); +CF_EXPORT CFURLSessionMultiCode CFURLSessionMultiHandleAddHandle(CFURLSessionMultiHandle _Nonnull handle, CFURLSessionEasyHandle _Nonnull curl); +CF_EXPORT CFURLSessionMultiCode CFURLSessionMultiHandleRemoveHandle(CFURLSessionMultiHandle _Nonnull handle, CFURLSessionEasyHandle _Nonnull curl); +CF_EXPORT CFURLSessionMultiCode CFURLSessionMultiHandleAssign(CFURLSessionMultiHandle _Nonnull handle, CFURLSession_socket_t socket, void * _Nullable sockp); +CF_EXPORT CFURLSessionMultiCode CFURLSessionMultiHandleAction(CFURLSessionMultiHandle _Nonnull handle, CFURLSession_socket_t socket, int bitmask, int * _Nonnull running_handles); +typedef struct CFURLSessionMultiHandleInfo { + CFURLSessionEasyHandle _Nullable easyHandle; + CFURLSessionEasyCode resultCode; +} CFURLSessionMultiHandleInfo; +CF_EXPORT CFURLSessionMultiHandleInfo CFURLSessionMultiHandleInfoRead(CFURLSessionMultiHandle _Nonnull handle, int * _Nonnull msgs_in_queue); + +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_fptr(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, void *_Nullable a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_ptr(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, void *_Nullable a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_int(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, long a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_int64(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int64_t a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_wc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, size_t(*_Nonnull a)(char *_Nonnull, size_t, size_t, void *_Nullable)); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_fwc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, size_t(*_Nonnull a)(char *_Nonnull, size_t, size_t, void *_Nullable)); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_dc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, int(*_Nonnull a)(CFURLSessionEasyHandle _Nonnull handle, int type, char *_Nonnull data, size_t size, void *_Nullable userptr)); +typedef enum { + CFURLSessionSocketTypeIPCXN, // socket created for a specific IP connection + CFURLSessionSocketTypeAccept, // socket created by accept() call +} CFURLSessionSocketType; +typedef int (CFURLSessionSocketOptionCallback)(void *_Nullable clientp, int fd, CFURLSessionSocketType purpose); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_sc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionSocketOptionCallback * _Nullable a); +typedef int (CFURLSessionSeekCallback)(void *_Nullable userp, int64_t offset, int origin); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_seek(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionSeekCallback * _Nullable a); +typedef int (CFURLSessionTransferInfoCallback)(void *_Nullable userp, int64_t dltotal, int64_t dlnow, int64_t ultotal, int64_t ulnow); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_setopt_tc(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionOption option, CFURLSessionTransferInfoCallback * _Nullable a); + +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_getinfo_long(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, long *_Nonnull a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_getinfo_double(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, double *_Nonnull a); +CF_EXPORT CFURLSessionEasyCode CFURLSession_easy_getinfo_charp(CFURLSessionEasyHandle _Nonnull curl, CFURLSessionInfo info, char *_Nullable*_Nonnull a); + +CF_EXPORT CFURLSessionMultiCode CFURLSession_multi_setopt_ptr(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, void *_Nullable a); +CF_EXPORT CFURLSessionMultiCode CFURLSession_multi_setopt_l(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, long a); +CF_EXPORT CFURLSessionMultiCode CFURLSession_multi_setopt_sf(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, int (*_Nonnull a)(CFURLSessionEasyHandle _Nonnull, CFURLSession_socket_t, int, void *_Nullable, void *_Nullable)); +CF_EXPORT CFURLSessionMultiCode CFURLSession_multi_setopt_tf(CFURLSessionMultiHandle _Nonnull multi_handle, CFURLSessionMultiOption option, int (*_Nonnull a)(CFURLSessionMultiHandle _Nonnull, long, void *_Nullable)); + +CF_EXPORT CFURLSessionEasyCode CFURLSessionInit(void); + + +typedef struct CFURLSessionSList CFURLSessionSList; +CF_EXPORT CFURLSessionSList *_Nullable CFURLSessionSListAppend(CFURLSessionSList *_Nullable list, const char * _Nullable string); +CF_EXPORT void CFURLSessionSListFreeAll(CFURLSessionSList *_Nullable list); + + + +CF_EXTERN_C_END +CF_IMPLICIT_BRIDGING_DISABLED + +#endif /* __COREFOUNDATION_URLSESSIONINTERFACE__ */ diff --git a/Foundation/NSData.swift b/Foundation/NSData.swift index 34b5e2f33c..6b926c6e50 100644 --- a/Foundation/NSData.swift +++ b/Foundation/NSData.swift @@ -164,11 +164,11 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding { if url.isFileURL { try self.init(contentsOfFile: url.path, options: readOptionsMask) } else { - let session = URLSession(configuration: URLSessionConfiguration.defaultSessionConfiguration()) + let session = URLSession(configuration: URLSessionConfiguration.default) let cond = NSCondition() var resError: NSError? var resData: Data? - let task = session.dataTaskWithURL(url, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in + let task = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in resData = data resError = error cond.broadcast() diff --git a/Foundation/NSURLSession.swift b/Foundation/NSURLSession.swift deleted file mode 100644 index 7ec09f1a4b..0000000000 --- a/Foundation/NSURLSession.swift +++ /dev/null @@ -1,805 +0,0 @@ -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// - -/* - - NSURLSession is a replacement API for NSURLConnection. It provides - options that affect the policy of, and various aspects of the - mechanism by which NSURLRequest objects are retrieved from the - network. - - An NSURLSession may be bound to a delegate object. The delegate is - invoked for certain events during the lifetime of a session, such as - server authentication or determining whether a resource to be loaded - should be converted into a download. - - NSURLSession instances are threadsafe. - - The default NSURLSession uses a system provided delegate and is - appropriate to use in place of existing code that uses - +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] - - An NSURLSession creates NSURLSessionTask objects which represent the - action of a resource being loaded. These are analogous to - NSURLConnection objects but provide for more control and a unified - delegate model. - - NSURLSessionTask objects are always created in a suspended state and - must be sent the -resume message before they will execute. - - Subclasses of NSURLSessionTask are used to syntactically - differentiate between data and file downloads. - - An NSURLSessionDataTask receives the resource as a series of calls to - the URLSession:dataTask:didReceiveData: delegate method. This is type of - task most commonly associated with retrieving objects for immediate parsing - by the consumer. - - An NSURLSessionUploadTask differs from an NSURLSessionDataTask - in how its instance is constructed. Upload tasks are explicitly created - by referencing a file or data object to upload, or by utilizing the - -URLSession:task:needNewBodyStream: delegate message to supply an upload - body. - - An NSURLSessionDownloadTask will directly write the response data to - a temporary file. When completed, the delegate is sent - URLSession:downloadTask:didFinishDownloadingToURL: and given an opportunity - to move this file to a permanent location in its sandboxed container, or to - otherwise read the file. If canceled, an NSURLSessionDownloadTask can - produce a data blob that can be used to resume a download at a later - time. - - Beginning with iOS 9 and Mac OS X 10.11, NSURLSessionStream is - available as a task type. This allows for direct TCP/IP connection - to a given host and port with optional secure handshaking and - navigation of proxies. Data tasks may also be upgraded to a - NSURLSessionStream task via the HTTP Upgrade: header and appropriate - use of the pipelining option of NSURLSessionConfiguration. See RFC - 2817 and RFC 6455 for information about the Upgrade: header, and - comments below on turning data tasks into stream tasks. - */ - -/* DataTask objects receive the payload through zero or more delegate messages */ -/* UploadTask objects receive periodic progress updates but do not return a body */ -/* DownloadTask objects represent an active download to disk. They can provide resume data when canceled. */ -/* StreamTask objects may be used to create NSInput and NSOutputStreams, or used directly in reading and writing. */ - -/* - - NSURLSession is not available for i386 targets before Mac OS X 10.10. - - */ - -public let NSURLSessionTransferSizeUnknown: Int64 = -1 - -open class URLSession: NSObject { - - /* - * The shared session uses the currently set global NSURLCache, - * NSHTTPCookieStorage and NSURLCredentialStorage objects. - */ - open class func sharedSession() -> URLSession { NSUnimplemented() } - - /* - * Customization of NSURLSession occurs during creation of a new session. - * If you only need to use the convenience routines with custom - * configuration options it is not necessary to specify a delegate. - * If you do specify a delegate, the delegate will be retained until after - * the delegate has been sent the URLSession:didBecomeInvalidWithError: message. - */ - public /*not inherited*/ init(configuration: URLSessionConfiguration) { NSUnimplemented() } - public /*not inherited*/ init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate?, delegateQueue queue: OperationQueue?) { NSUnimplemented() } - - open var delegateQueue: OperationQueue { NSUnimplemented() } - open var delegate: URLSessionDelegate? { NSUnimplemented() } - /*@NSCopying*/ open var configuration: URLSessionConfiguration { NSUnimplemented() } - - /* - * The sessionDescription property is available for the developer to - * provide a descriptive label for the session. - */ - open var sessionDescription: String? - - /* -finishTasksAndInvalidate returns immediately and existing tasks will be allowed - * to run to completion. New tasks may not be created. The session - * will continue to make delegate callbacks until URLSession:didBecomeInvalidWithError: - * has been issued. - * - * -finishTasksAndInvalidate and -invalidateAndCancel do not - * have any effect on the shared session singleton. - * - * When invalidating a background session, it is not safe to create another background - * session with the same identifier until URLSession:didBecomeInvalidWithError: has - * been issued. - */ - open func finishTasksAndInvalidate() { NSUnimplemented() } - - /* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues - * -cancel to all outstanding tasks for this session. Note task - * cancellation is subject to the state of the task, and some tasks may - * have already have completed at the time they are sent -cancel. - */ - open func invalidateAndCancel() { NSUnimplemented() } - - public func resetWithCompletionHandler(_ completionHandler: () -> Void) { NSUnimplemented() }/* empty all cookies, cache and credential stores, removes disk files, issues -flushWithCompletionHandler:. Invokes completionHandler() on the delegate queue if not nil. */ - public func flushWithCompletionHandler(_ completionHandler: () -> Void) { NSUnimplemented() }/* flush storage to disk and clear transient network caches. Invokes completionHandler() on the delegate queue if not nil. */ - - public func getTasksWithCompletionHandler(_ completionHandler: ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with outstanding data, upload and download tasks. */ - - public func getAllTasksWithCompletionHandler(_ completionHandler: ([URLSessionTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with all outstanding tasks. */ - - /* - * NSURLSessionTask objects are always created in a suspended state and - * must be sent the -resume message before they will execute. - */ - - /* Creates a data task with the given request. The request may have a body stream. */ - open func dataTaskWithRequest(_ request: URLRequest) -> URLSessionDataTask { NSUnimplemented() } - - /* Creates a data task to retrieve the contents of the given URL. */ - open func dataTaskWithURL(_ url: URL) -> URLSessionDataTask { NSUnimplemented() } - - /* Creates an upload task with the given request. The body of the request will be created from the file referenced by fileURL */ - open func uploadTaskWithRequest(_ request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTask { NSUnimplemented() } - - /* Creates an upload task with the given request. The body of the request is provided from the bodyData. */ - open func uploadTaskWithRequest(_ request: URLRequest, fromData bodyData: Data) -> URLSessionUploadTask { NSUnimplemented() } - - /* Creates an upload task with the given request. The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */ - open func uploadTaskWithStreamedRequest(_ request: URLRequest) -> URLSessionUploadTask { NSUnimplemented() } - - /* Creates a download task with the given request. */ - open func downloadTaskWithRequest(_ request: URLRequest) -> URLSessionDownloadTask { NSUnimplemented() } - - /* Creates a download task to download the contents of the given URL. */ - open func downloadTaskWithURL(_ url: URL) -> URLSessionDownloadTask { NSUnimplemented() } - - /* Creates a download task with the resume data. If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */ - open func downloadTaskWithResumeData(_ resumeData: Data) -> URLSessionDownloadTask { NSUnimplemented() } - - /* Creates a bidirectional stream task to a given host and port. - */ - open func streamTaskWithHostName(_ hostname: String, port: Int) -> URLSessionStreamTask { NSUnimplemented() } -} - -/* - * NSURLSession convenience routines deliver results to - * a completion handler block. These convenience routines - * are not available to NSURLSessions that are configured - * as background sessions. - * - * Task objects are always created in a suspended state and - * must be sent the -resume message before they will execute. - */ -extension URLSession { - /* - * data task convenience methods. These methods create tasks that - * bypass the normal delegate calls for response and data delivery, - * and provide a simple cancelable asynchronous interface to receiving - * data. Errors will be returned in the NSURLErrorDomain, - * see . The delegate, if any, will still be - * called for authentication challenges. - */ - public func dataTaskWithRequest(_ request: URLRequest, completionHandler: (Data?, URLResponse?, NSError?) -> Void) -> URLSessionDataTask { NSUnimplemented() } - public func dataTaskWithURL(_ url: URL, completionHandler: (Data?, URLResponse?, NSError?) -> Void) -> URLSessionDataTask { NSUnimplemented() } - - /* - * upload convenience method. - */ - public func uploadTaskWithRequest(_ request: URLRequest, fromFile fileURL: NSURL, completionHandler: (Data?, URLResponse?, NSError?) -> Void) -> URLSessionUploadTask { NSUnimplemented() } - public func uploadTaskWithRequest(_ request: URLRequest, fromData bodyData: Data?, completionHandler: (Data?, URLResponse?, NSError?) -> Void) -> URLSessionUploadTask { NSUnimplemented() } - - /* - * download task convenience methods. When a download successfully - * completes, the NSURL will point to a file that must be read or - * copied during the invocation of the completion routine. The file - * will be removed automatically. - */ - public func downloadTaskWithRequest(_ request: URLRequest, completionHandler: (NSURL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { NSUnimplemented() } - public func downloadTaskWithURL(_ url: NSURL, completionHandler: (NSURL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { NSUnimplemented() } - public func downloadTaskWithResumeData(_ resumeData: Data, completionHandler: (NSURL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { NSUnimplemented() } -} - -extension URLSessionTask { - public enum State: Int { - - case running /* The task is currently being serviced by the session */ - case suspended - case canceling /* The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message. */ - case completed /* The task has completed and the session will receive no more delegate notifications */ - } -} - -/* - * NSURLSessionTask - a cancelable object that refers to the lifetime - * of processing a given request. - */ - -open class URLSessionTask: NSObject, NSCopying { - - public override init() { - NSUnimplemented() - } - - open override func copy() -> Any { - return copy(with: nil) - } - - open func copy(with zone: NSZone? = nil) -> Any { - NSUnimplemented() - } - - open var taskIdentifier: Int { NSUnimplemented() } /* an identifier for this task, assigned by and unique to the owning session */ - /*@NSCopying*/ open var originalRequest: URLRequest? { NSUnimplemented() } /* may be nil if this is a stream task */ - /*@NSCopying*/ open var currentRequest: URLRequest? { NSUnimplemented() } /* may differ from originalRequest due to http server redirection */ - /*@NSCopying*/ open var response: URLResponse? { NSUnimplemented() } /* may be nil if no response has been received */ - - /* Byte count properties may be zero if no body is expected, - * or NSURLSessionTransferSizeUnknown if it is not possible - * to know how many bytes will be transferred. - */ - - /* number of body bytes already received */ - open var countOfBytesReceived: Int64 { NSUnimplemented() } - - /* number of body bytes already sent */ - open var countOfBytesSent: Int64 { NSUnimplemented() } - - /* number of body bytes we expect to send, derived from the Content-Length of the HTTP request */ - open var countOfBytesExpectedToSend: Int64 { NSUnimplemented() } - - /* number of byte bytes we expect to receive, usually derived from the Content-Length header of an HTTP response. */ - open var countOfBytesExpectedToReceive: Int64 { NSUnimplemented() } - - /* - * The taskDescription property is available for the developer to - * provide a descriptive label for the task. - */ - open var taskDescription: String? - - /* -cancel returns immediately, but marks a task as being canceled. - * The task will signal -URLSession:task:didCompleteWithError: with an - * error value of { NSURLErrorDomain, NSURLErrorCancelled }. In some - * cases, the task may signal other work before it acknowledges the - * cancelation. -cancel may be sent to a task that has been suspended. - */ - open func cancel() { NSUnimplemented() } - - /* - * The current state of the task within the session. - */ - open var state: State { NSUnimplemented() } - - /* - * The error, if any, delivered via -URLSession:task:didCompleteWithError: - * This property will be nil in the event that no error occured. - */ - /*@NSCopying*/ open var error: NSError? { NSUnimplemented() } - - /* - * Suspending a task will prevent the NSURLSession from continuing to - * load data. There may still be delegate calls made on behalf of - * this task (for instance, to report data received while suspending) - * but no further transmissions will be made on behalf of the task - * until -resume is sent. The timeout timer associated with the task - * will be disabled while a task is suspended. -suspend and -resume are - * nestable. - */ - open func suspend() { NSUnimplemented() } - open func resume() { NSUnimplemented() } - - /* - * Sets a scaling factor for the priority of the task. The scaling factor is a - * value between 0.0 and 1.0 (inclusive), where 0.0 is considered the lowest - * priority and 1.0 is considered the highest. - * - * The priority is a hint and not a hard requirement of task performance. The - * priority of a task may be changed using this API at any time, but not all - * protocols support this; in these cases, the last priority that took effect - * will be used. - * - * If no priority is specified, the task will operate with the default priority - * as defined by the constant NSURLSessionTaskPriorityDefault. Two additional - * priority levels are provided: NSURLSessionTaskPriorityLow and - * NSURLSessionTaskPriorityHigh, but use is not restricted to these. - */ - open var priority: Float -} - -public let NSURLSessionTaskPriorityDefault: Float = 0.0 // NSUnimplemented -public let NSURLSessionTaskPriorityLow: Float = 0.0 // NSUnimplemented -public let NSURLSessionTaskPriorityHigh: Float = 0.0 // NSUnimplemented - -/* - * An NSURLSessionDataTask does not provide any additional - * functionality over an NSURLSessionTask and its presence is merely - * to provide lexical differentiation from download and upload tasks. - */ -open class URLSessionDataTask: URLSessionTask { -} - -/* - * An NSURLSessionUploadTask does not currently provide any additional - * functionality over an NSURLSessionDataTask. All delegate messages - * that may be sent referencing an NSURLSessionDataTask equally apply - * to NSURLSessionUploadTasks. - */ -open class URLSessionUploadTask: URLSessionDataTask { -} - -/* - * NSURLSessionDownloadTask is a task that represents a download to - * local storage. - */ -open class URLSessionDownloadTask: URLSessionTask { - - /* Cancel the download (and calls the superclass -cancel). If - * conditions will allow for resuming the download in the future, the - * callback will be called with an opaque data blob, which may be used - * with -downloadTaskWithResumeData: to attempt to resume the download. - * If resume data cannot be created, the completion handler will be - * called with nil resumeData. - */ - public func cancelByProducingResumeData(_ completionHandler: (Data?) -> Void) { NSUnimplemented() } -} - -/* - * An NSURLSessionStreamTask provides an interface to perform reads - * and writes to a TCP/IP stream created via NSURLSession. This task - * may be explicitly created from an NSURLSession, or created as a - * result of the appropriate disposition response to a - * -URLSession:dataTask:didReceiveResponse: delegate message. - * - * NSURLSessionStreamTask can be used to perform asynchronous reads - * and writes. Reads and writes are enquened and executed serially, - * with the completion handler being invoked on the sessions delegate - * queuee. If an error occurs, or the task is canceled, all - * outstanding read and write calls will have their completion - * handlers invoked with an appropriate error. - * - * It is also possible to create NSInputStream and NSOutputStream - * instances from an NSURLSessionTask by sending - * -captureStreams to the task. All outstanding read and writess are - * completed before the streams are created. Once the streams are - * delivered to the session delegate, the task is considered complete - * and will receive no more messsages. These streams are - * disassociated from the underlying session. - */ - -open class URLSessionStreamTask: URLSessionTask { - - /* Read minBytes, or at most maxBytes bytes and invoke the completion - * handler on the sessions delegate queue with the data or an error. - * If an error occurs, any outstanding reads will also fail, and new - * read requests will error out immediately. - */ - public func readDataOfMinLength(_ minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: (Data?, Bool, NSError?) -> Void) { NSUnimplemented() } - - /* Write the data completely to the underlying socket. If all the - * bytes have not been written by the timeout, a timeout error will - * occur. Note that invocation of the completion handler does not - * guarantee that the remote side has received all the bytes, only - * that they have been written to the kernel. */ - public func writeData(_ data: Data, timeout: TimeInterval, completionHandler: (NSError?) -> Void) { NSUnimplemented() } - - /* -captureStreams completes any already enqueued reads - * and writes, and then invokes the - * URLSession:streamTask:didBecomeInputStream:outputStream: delegate - * message. When that message is received, the task object is - * considered completed and will not receive any more delegate - * messages. */ - open func captureStreams() { NSUnimplemented() } - - /* Enqueue a request to close the write end of the underlying socket. - * All outstanding IO will complete before the write side of the - * socket is closed. The server, however, may continue to write bytes - * back to the client, so best practice is to continue reading from - * the server until you receive EOF. - */ - open func closeWrite() { NSUnimplemented() } - - /* Enqueue a request to close the read side of the underlying socket. - * All outstanding IO will complete before the read side is closed. - * You may continue writing to the server. - */ - open func closeRead() { NSUnimplemented() } - - /* - * Begin encrypted handshake. The hanshake begins after all pending - * IO has completed. TLS authentication callbacks are sent to the - * session's -URLSession:task:didReceiveChallenge:completionHandler: - */ - open func startSecureConnection() { NSUnimplemented() } - - /* - * Cleanly close a secure connection after all pending secure IO has - * completed. - */ - open func stopSecureConnection() { NSUnimplemented() } -} - -/* - * Configuration options for an NSURLSession. When a session is - * created, a copy of the configuration object is made - you cannot - * modify the configuration of a session after it has been created. - * - * The shared session uses the global singleton credential, cache - * and cookie storage objects. - * - * An ephemeral session has no persistent disk storage for cookies, - * cache or credentials. - * - * A background session can be used to perform networking operations - * on behalf of a suspended application, within certain constraints. - */ - -open class URLSessionConfiguration: NSObject, NSCopying { - - public override init() { - NSUnimplemented() - } - - open override func copy() -> Any { - return copy(with: nil) - } - - open func copy(with zone: NSZone? = nil) -> Any { - NSUnimplemented() - } - - open class func defaultSessionConfiguration() -> URLSessionConfiguration { NSUnimplemented() } - open class func ephemeralSessionConfiguration() -> URLSessionConfiguration { NSUnimplemented() } - open class func backgroundSessionConfigurationWithIdentifier(_ identifier: String) -> URLSessionConfiguration { NSUnimplemented() } - - /* identifier for the background session configuration */ - open var identifier: String? { NSUnimplemented() } - - /* default cache policy for requests */ - open var requestCachePolicy: NSURLRequest.CachePolicy - - /* default timeout for requests. This will cause a timeout if no data is transmitted for the given timeout value, and is reset whenever data is transmitted. */ - open var timeoutIntervalForRequest: TimeInterval - - /* default timeout for requests. This will cause a timeout if a resource is not able to be retrieved within a given timeout. */ - open var timeoutIntervalForResource: TimeInterval - - /* type of service for requests. */ - open var networkServiceType: URLRequest.NetworkServiceType - - /* allow request to route over cellular. */ - open var allowsCellularAccess: Bool - - /* allows background tasks to be scheduled at the discretion of the system for optimal performance. */ - open var discretionary: Bool - - /* The identifier of the shared data container into which files in background sessions should be downloaded. - * App extensions wishing to use background sessions *must* set this property to a valid container identifier, or - * all transfers in that session will fail with NSURLErrorBackgroundSessionRequiresSharedContainer. - */ - open var sharedContainerIdentifier: String? - - /* - * Allows the app to be resumed or launched in the background when tasks in background sessions complete - * or when auth is required. This only applies to configurations created with +backgroundSessionConfigurationWithIdentifier: - * and the default value is YES. - */ - - /* The proxy dictionary, as described by */ - open var connectionProxyDictionary: [NSObject : AnyObject]? - - // TODO: We don't have the SSLProtocol type from Security - /* - /* The minimum allowable versions of the TLS protocol, from */ - open var TLSMinimumSupportedProtocol: SSLProtocol - - /* The maximum allowable versions of the TLS protocol, from */ - open var TLSMaximumSupportedProtocol: SSLProtocol - */ - - /* Allow the use of HTTP pipelining */ - open var HTTPShouldUsePipelining: Bool - - /* Allow the session to set cookies on requests */ - open var HTTPShouldSetCookies: Bool - - /* Policy for accepting cookies. This overrides the policy otherwise specified by the cookie storage. */ - open var httpCookieAcceptPolicy: HTTPCookie.AcceptPolicy - - /* Specifies additional headers which will be set on outgoing requests. - Note that these headers are added to the request only if not already present. */ - open var HTTPAdditionalHeaders: [NSObject : AnyObject]? - - /* The maximum number of simultanous persistent connections per host */ - open var HTTPMaximumConnectionsPerHost: Int - - /* The cookie storage object to use, or nil to indicate that no cookies should be handled */ - open var httpCookieStorage: HTTPCookieStorage? - - /* The credential storage object, or nil to indicate that no credential storage is to be used */ - open var urlCredentialStorage: URLCredentialStorage? - - /* The URL resource cache, or nil to indicate that no caching is to be performed */ - open var urlCache: URLCache? - - /* Enable extended background idle mode for any tcp sockets created. Enabling this mode asks the system to keep the socket open - * and delay reclaiming it when the process moves to the background (see https://developer.apple.com/library/ios/technotes/tn2277/_index.html) - */ - open var shouldUseExtendedBackgroundIdleMode: Bool - - /* An optional array of Class objects which subclass NSURLProtocol. - The Class will be sent +canInitWithRequest: when determining if - an instance of the class can be used for a given URL scheme. - You should not use +[NSURLProtocol registerClass:], as that - method will register your class with the default session rather - than with an instance of NSURLSession. - Custom NSURLProtocol subclasses are not available to background - sessions. - */ - open var protocolClasses: [AnyClass]? -} - -/* - * Disposition options for various delegate messages - */ -extension URLSession { - public enum AuthChallengeDisposition: Int { - - case useCredential /* Use the specified credential, which may be nil */ - case performDefaultHandling /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ - case cancelAuthenticationChallenge /* The entire request will be canceled; the credential parameter is ignored. */ - case rejectProtectionSpace /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */ - } - - public enum ResponseDisposition: Int { - - case cancel /* Cancel the load, this is the same as -[task cancel] */ - case allow /* Allow the load to continue */ - case becomeDownload /* Turn this request into a download */ - case becomeStream /* Turn this task into a stream task */ - } -} - -/* - * NSURLSessionDelegate specifies the methods that a session delegate - * may respond to. There are both session specific messages (for - * example, connection based auth) as well as task based messages. - */ - -/* - * Messages related to the URL session as a whole - */ -public protocol URLSessionDelegate : NSObjectProtocol { - - /* The last message a session receives. A session will only become - * invalid because of a systemic error or when it has been - * explicitly invalidated, in which case the error parameter will be nil. - */ - func urlSession(_ session: URLSession, didBecomeInvalidWithError error: NSError?) - - /* If implemented, when a connection level authentication challenge - * has occurred, this delegate will be given the opportunity to - * provide authentication credentials to the underlying - * connection. Some types of authentication will apply to more than - * one request on a given connection to a server (SSL Server Trust - * challenges). If this delegate message is not implemented, the - * behavior will be to use the default handling, which may involve user - * interaction. - */ - func urlSession(_ session: URLSession, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -} - -extension URLSessionDelegate { - func urlSession(_ session: URLSession, didBecomeInvalidWithError error: NSError?) { } - func urlSession(_ session: URLSession, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } -} - -/* If an application has received an - * -application:handleEventsForBackgroundURLSession:completionHandler: - * message, the session delegate will receive this message to indicate - * that all messages previously enqueued for this session have been - * delivered. At this time it is safe to invoke the previously stored - * completion handler, or to begin any internal updates that will - * result in invoking the completion handler. - */ - -/* - * Messages related to the operation of a specific task. - */ -public protocol URLSessionTaskDelegate : URLSessionDelegate { - - /* An HTTP request is attempting to perform a redirection to a different - * URL. You must invoke the completion routine to allow the - * redirection, allow the redirection with a modified request, or - * pass nil to the completionHandler to cause the body of the redirection - * response to be delivered as the payload of this request. The default - * is to follow redirections. - * - * For tasks in background sessions, redirections will always be followed and this method will not be called. - */ - func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: URLRequest, completionHandler: (URLRequest?) -> Void) - - /* The task has received a request specific authentication challenge. - * If this delegate is not implemented, the session specific authentication challenge - * will *NOT* be called and the behavior will be the same as using the default handling - * disposition. - */ - func urlSession(_ session: URLSession, task: URLSessionTask, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) - - /* Sent if a task requires a new, unopened body stream. This may be - * necessary when authentication has failed for any request that - * involves a body stream. - */ - func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: (InputStream?) -> Void) - - /* Sent periodically to notify the delegate of upload progress. This - * information is also available as properties of the task. - */ - func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) - - /* Sent as the last message related to a specific task. Error may be - * nil, which implies that no error occurred and this task is complete. - */ - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) -} - -extension URLSessionTaskDelegate { - func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: URLRequest, completionHandler: (URLRequest?) -> Void) { } - - func urlSession(_ session: URLSession, task: URLSessionTask, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } - - func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: (InputStream?) -> Void) { } - - func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) { } -} - -/* - * Messages related to the operation of a task that delivers data - * directly to the delegate. - */ -public protocol URLSessionDataDelegate : URLSessionTaskDelegate { - - /* The task has received a response and no further messages will be - * received until the completion block is called. The disposition - * allows you to cancel a request or to turn a data task into a - * download task. This delegate message is optional - if you do not - * implement it, you can get the response as a property of the task. - * - * This method will not be called for background upload tasks (which cannot be converted to download tasks). - */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceiveResponse response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) - - /* Notification that a data task has become a download task. No - * future messages will be sent to the data task. - */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecomeDownloadTask downloadTask: URLSessionDownloadTask) - - /* - * Notification that a data task has become a bidirectional stream - * task. No future messages will be sent to the data task. The newly - * created streamTask will carry the original request and response as - * properties. - * - * For requests that were pipelined, the stream object will only allow - * reading, and the object will immediately issue a - * -URLSession:writeClosedForStream:. Pipelining can be disabled for - * all requests in a session, or by the NSURLRequest - * HTTPShouldUsePipelining property. - * - * The underlying connection is no longer considered part of the HTTP - * connection cache and won't count against the total number of - * connections per host. - */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecomeStreamTask streamTask: URLSessionStreamTask) - - /* Sent when data is available for the delegate to consume. It is - * assumed that the delegate will retain and not copy the data. As - * the data may be discontiguous, you should use - * [NSData enumerateByteRangesUsingBlock:] to access it. - */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceiveData data: Data) - - /* Invoke the completion routine with a valid NSCachedURLResponse to - * allow the resulting data to be cached, or pass nil to prevent - * caching. Note that there is no guarantee that caching will be - * attempted for a given resource, and you should not rely on this - * message to receive the resource data. - */ - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: (CachedURLResponse?) -> Void) -} - -extension URLSessionDataDelegate { - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceiveResponse response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) { } - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecomeDownloadTask downloadTask: URLSessionDownloadTask) { } - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecomeStreamTask streamTask: URLSessionStreamTask) { } - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: (CachedURLResponse?) -> Void) { } -} - -/* - * Messages related to the operation of a task that writes data to a - * file and notifies the delegate upon completion. - */ -public protocol URLSessionDownloadDelegate : URLSessionTaskDelegate { - - /* Sent when a download task that has completed a download. The delegate should - * copy or move the file at the given location to a new location as it will be - * removed when the delegate message returns. URLSession:task:didCompleteWithError: will - * still be called. - */ - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingToURL location: URL) - - /* Sent periodically to notify the delegate of download progress. */ - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) - - /* Sent when a download has been resumed. If a download failed with an - * error, the -userInfo dictionary of the error will contain an - * NSURLSessionDownloadTaskResumeData key, whose value is the resume - * data. - */ - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) -} - -extension URLSessionDownloadDelegate { - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { } - -} - -public protocol URLSessionStreamDelegate : URLSessionTaskDelegate { - - /* Indiciates that the read side of a connection has been closed. Any - * outstanding reads complete, but future reads will immediately fail. - * This may be sent even when no reads are in progress. However, when - * this delegate message is received, there may still be bytes - * available. You only know that no more bytes are available when you - * are able to read until EOF. */ - func urlSession(_ session: URLSession, readClosedForStreamTask streamTask: URLSessionStreamTask) - - /* Indiciates that the write side of a connection has been closed. - * Any outstanding writes complete, but future writes will immediately - * fail. - */ - func urlSession(_ session: URLSession, writeClosedForStreamTask streamTask: URLSessionStreamTask) - - /* A notification that the system has determined that a better route - * to the host has been detected (eg, a wi-fi interface becoming - * available.) This is a hint to the delegate that it may be - * desirable to create a new task for subsequent work. Note that - * there is no guarantee that the future task will be able to connect - * to the host, so callers should should be prepared for failure of - * reads and writes over any new interface. */ - func urlSession(_ session: URLSession, betterRouteDiscoveredForStreamTask streamTask: URLSessionStreamTask) - - /* The given task has been completed, and unopened NSInputStream and - * NSOutputStream objects are created from the underlying network - * connection. This will only be invoked after all enqueued IO has - * completed (including any necessary handshakes.) The streamTask - * will not receive any further delegate messages. - */ - func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecomeInputStream inputStream: InputStream, outputStream: NSOutputStream) -} - -extension URLSessionStreamDelegate { - func urlSession(_ session: URLSession, readClosedForStreamTask streamTask: URLSessionStreamTask) { } - - func urlSession(_ session: URLSession, writeClosedForStreamTask streamTask: URLSessionStreamTask) { } - - func urlSession(_ session: URLSession, betterRouteDiscoveredForStreamTask streamTask: URLSessionStreamTask) { } - - func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecomeInputStream inputStream: InputStream, outputStream: NSOutputStream) { } -} - -/* Key in the userInfo dictionary of an NSError received during a failed download. */ -public let NSURLSessionDownloadTaskResumeData: String = "" // NSUnimplemented diff --git a/Foundation/NSURLSession/Configuration.swift b/Foundation/NSURLSession/Configuration.swift new file mode 100644 index 0000000000..8587860d4b --- /dev/null +++ b/Foundation/NSURLSession/Configuration.swift @@ -0,0 +1,137 @@ +// Foundation/NSURLSession/Configuration.swift - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + + +internal extension URLSession { + /// This is an immutable / `struct` version of `URLSessionConfiguration`. + struct _Configuration { + /// identifier for the background session configuration + let identifier: String? + + /// default cache policy for requests + let requestCachePolicy: NSURLRequest.CachePolicy + + /// default timeout for requests. This will cause a timeout if no data is transmitted for the given timeout value, and is reset whenever data is transmitted. + let timeoutIntervalForRequest: TimeInterval + + /// default timeout for requests. This will cause a timeout if a resource is not able to be retrieved within a given timeout. + let timeoutIntervalForResource: TimeInterval + + /// type of service for requests. + let networkServiceType: NSURLRequest.NetworkServiceType + + /// allow request to route over cellular. + let allowsCellularAccess: Bool + + /// allows background tasks to be scheduled at the discretion of the system for optimal performance. + let discretionary: Bool + + /// The proxy dictionary, as described by + let connectionProxyDictionary: [AnyHashable : Any]? + + /// Allow the use of HTTP pipelining + let httpShouldUsePipelining: Bool + + /// Allow the session to set cookies on requests + let httpShouldSetCookies: Bool + + /// Policy for accepting cookies. This overrides the policy otherwise specified by the cookie storage. + let httpCookieAcceptPolicy: HTTPCookie.AcceptPolicy + + /// Specifies additional headers which will be set on outgoing requests. + /// Note that these headers are added to the request only if not already present. + + let httpAdditionalHeaders: [String : String]? + /// The maximum number of simultanous persistent connections per host + let httpMaximumConnectionsPerHost: Int + + /// The cookie storage object to use, or nil to indicate that no cookies should be handled + let httpCookieStorage: HTTPCookieStorage? + + /// The credential storage object, or nil to indicate that no credential storage is to be used + let urlCredentialStorage: URLCredentialStorage? + + /// The URL resource cache, or nil to indicate that no caching is to be performed + let urlCache: URLCache? + + /// Enable extended background idle mode for any tcp sockets created. + let shouldUseExtendedBackgroundIdleMode: Bool + + let protocolClasses: [AnyClass]? + } +} +internal extension URLSession._Configuration { + init(URLSessionConfiguration config: URLSessionConfiguration) { + identifier = config.identifier + requestCachePolicy = config.requestCachePolicy + timeoutIntervalForRequest = config.timeoutIntervalForRequest + timeoutIntervalForResource = config.timeoutIntervalForResource + networkServiceType = config.networkServiceType + allowsCellularAccess = config.allowsCellularAccess + discretionary = config.discretionary + connectionProxyDictionary = config.connectionProxyDictionary + httpShouldUsePipelining = config.httpShouldUsePipelining + httpShouldSetCookies = config.httpShouldSetCookies + httpCookieAcceptPolicy = config.httpCookieAcceptPolicy + httpAdditionalHeaders = config.httpAdditionalHeaders.map { convertToStringString(dictionary: $0) } + httpMaximumConnectionsPerHost = config.httpMaximumConnectionsPerHost + httpCookieStorage = config.httpCookieStorage + urlCredentialStorage = config.urlCredentialStorage + urlCache = config.urlCache + shouldUseExtendedBackgroundIdleMode = config.shouldUseExtendedBackgroundIdleMode + protocolClasses = config.protocolClasses + } +} + +// Configure NSURLRequests +internal extension URLSession._Configuration { + func configure(request: NSMutableURLRequest) { + httpAdditionalHeaders?.forEach { + guard request.value(forHTTPHeaderField: $0.0) == nil else { return } + request.setValue($0.1, forHTTPHeaderField: $0.0) + } + } + func setCookies(on request: NSMutableURLRequest) { + if httpShouldSetCookies { + //TODO: Ask the cookie storage what cookie to set. + } + } +} +// Cache Management +private extension URLSession._Configuration { + func cachedResponse(forRequest request: NSURLRequest) -> CachedURLResponse? { + //TODO: Check the policy & consult the cache. + // There's more detail on how this should work here: + // + switch requestCachePolicy { + default: return nil + } + } +} + +private func convertToStringString(dictionary: [AnyHashable:Any]) -> [String: String] { + //TODO: There's some confusion about [NSObject:AnyObject] vs. [String:String] for headers. + // C.f. + var r: [String: String] = [:] + dictionary.forEach { + let k = String(describing: $0.key as! NSString) + let v = String(describing: $0.value as! NSString) + r[k] = v + } + return r +} diff --git a/Foundation/NSURLSession/EasyHandle.swift b/Foundation/NSURLSession/EasyHandle.swift new file mode 100644 index 0000000000..7fb269e8c8 --- /dev/null +++ b/Foundation/NSURLSession/EasyHandle.swift @@ -0,0 +1,615 @@ +// Foundation/NSURLSession/EasyHandle.swift - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// libcurl *easy handle* wrapper. +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + +import CoreFoundation +import Dispatch + + + +extension URLSessionTask { + /// Minimal wrapper around the [curl easy interface](https://curl.haxx.se/libcurl/c/) + /// + /// An *easy handle* manages the state of a transfer inside libcurl. + /// + /// As such the easy handle's responsibility is implementing the HTTP + /// protocol while the *multi handle* is in charge of managing sockets and + /// reading from / writing to these sockets. + /// + /// An easy handle is added to a multi handle in order to associate it with + /// an actual socket. The multi handle will then feed bytes into the easy + /// handle and read bytes from the easy handle. But this process is opaque + /// to use. It is further worth noting, that with HTTP/1.1 persistent + /// connections and with HTTP/2 there's a 1-to-many relationship between + /// TCP streams and HTTP transfers / easy handles. A single TCP stream and + /// its socket may be shared by multiple easy handles. + /// + /// A single HTTP request-response exchange (refered to here as a + /// *transfer*) corresponds directly to an easy handle. Hence anything that + /// needs to be configured for a specific transfer (e.g. the URL) will be + /// configured on an easy handle. + /// + /// A single `URLSessionTask` may do multiple, sonecutive transfers, and + /// as a result it will have to reconfigure it's easy handle between + /// transfers. An easy handle can be re-used once its transfer has + /// completed. + /// + /// - Note: All code assumes that it is being called on a single thread / + /// `Dispatch` only -- it is intentionally **not** thread safe. + internal final class _EasyHandle { + let rawHandle = CFURLSessionEasyHandleInit() + unowned let delegate: _EasyHandleDelegate + fileprivate var headerList: _CurlStringList? + fileprivate var pauseState: _PauseState = [] + internal var fileLength: Int64 = 0 + init(delegate: _EasyHandleDelegate) { + self.delegate = delegate + setupCallbacks() + } + deinit { + CFURLSessionEasyHandleDeinit(rawHandle) + } + } +} +extension URLSessionTask._EasyHandle: Equatable {} + internal func ==(lhs: URLSessionTask._EasyHandle, rhs: URLSessionTask._EasyHandle) -> Bool { + return lhs.rawHandle == rhs.rawHandle +} + +extension URLSessionTask._EasyHandle { + enum _Action { + case abort + case proceed + case pause + } + enum _WriteBufferResult { + case abort + case pause + /// Write the given number of bytes into the buffer + case bytes(Int) + } +} + +internal extension URLSessionTask._EasyHandle { + func completedTransfer(withErrorCode errorCode: Int?) { + delegate.transferCompleted(withErrorCode: errorCode) + } +} +internal protocol _EasyHandleDelegate: class { + /// Handle data read from the network. + /// - returns: the action to be taken: abort, proceed, or pause. + func didReceive(data: Data) -> URLSessionTask._EasyHandle._Action + /// Handle header data read from the network. + /// - returns: the action to be taken: abort, proceed, or pause. + func didReceive(headerData data: Data) -> URLSessionTask._EasyHandle._Action + /// Fill a buffer with data to be sent. + /// + /// - parameter data: The buffer to fill + /// - returns: the number of bytes written to the `data` buffer, or `nil` to stop the current transfer immediately. + func fill(writeBuffer buffer: UnsafeMutableBufferPointer) -> URLSessionTask._EasyHandle._WriteBufferResult + /// The transfer for this handle completed. + /// - parameter errorCode: An NSURLError code, or `nil` if no error occured. + func transferCompleted(withErrorCode errorCode: Int?) + /// Seek the input stream to the given position + func seekInputStream(to position: UInt64) throws + /// Gets called during the transfer to update progress. + func updateProgressMeter(with propgress: URLSessionTask._EasyHandle._Progress) +} +extension URLSessionTask._EasyHandle { + func set(verboseModeOn flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionVERBOSE, flag ? 1 : 0).asError() + } + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CFURLSessionOptionDEBUGFUNCTION.html + func set(debugOutputOn flag: Bool, task: URLSessionTask) { + if flag { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionDEBUGDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(task).toOpaque())).asError() + try! CFURLSession_easy_setopt_dc(rawHandle, CFURLSessionOptionDEBUGFUNCTION, printLibcurlDebug(handle:type:data:size:userInfo:)).asError() + } else { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionDEBUGDATA, nil).asError() + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionDEBUGFUNCTION, nil).asError() + } + } + func set(passHeadersToDataStream flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHEADER, flag ? 1 : 0).asError() + } + /// Follow any Location: header that the server sends as part of a HTTP header in a 3xx response + func set(followLocation flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionFOLLOWLOCATION, flag ? 1 : 0).asError() + } + /// Switch off the progress meter. It will also prevent the CFURLSessionOptionPROGRESSFUNCTION from getting called. + func set(progressMeterOff flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionNOPROGRESS, flag ? 1 : 0).asError() + } + /// Skip all signal handling + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOSIGNAL.html + func set(skipAllSignalHandling flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionNOSIGNAL, flag ? 1 : 0).asError() + } + /// Set error buffer for error messages + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ERRORBUFFER.html + func set(errorBuffer buffer: UnsafeMutableBufferPointer?) { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionERRORBUFFER, buffer?.baseAddress ?? nil).asError() + } + /// Request failure on HTTP response >= 400 + func set(failOnHTTPErrorCode flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionFAILONERROR, flag ? 1 : 0).asError() + } + /// URL to use in the request + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_URL.html + func set(url: URL) { + url.absoluteString.withCString { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionURL, UnsafeMutablePointer(mutating: $0)).asError() + } + } + /// Set allowed protocols + /// + /// - Note: This has security implications. Not limiting this, someone could + /// redirect a HTTP request into one of the many other protocols that libcurl + /// supports. + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_PROTOCOLS.html + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS.html + func setAllowedProtocolsToHTTPAndHTTPS() { + let protocols = (CFURLSessionProtocolHTTP | CFURLSessionProtocolHTTPS) + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionPROTOCOLS, protocols).asError() + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionREDIR_PROTOCOLS, protocols).asError() + //TODO: Added in libcurl 7.45.0 + // "https".withCString { + // try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionDEFAULT_PROTOCOL, UnsafeMutablePointer($0)).asError() + //} + } + + //TODO: Proxy setting, namely CFURLSessionOptionPROXY, CFURLSessionOptionPROXYPORT, + // CFURLSessionOptionPROXYTYPE, CFURLSessionOptionNOPROXY, CFURLSessionOptionHTTPPROXYTUNNEL, CFURLSessionOptionPROXYHEADER, + // CFURLSessionOptionHEADEROPT, etc. + + /// set preferred receive buffer size + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_BUFFERSIZE.html + func set(preferredReceiveBufferSize size: Int) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionBUFFERSIZE, min(size, Int(CFURLSessionMaxWriteSize))).asError() + } + /// Set custom HTTP headers + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html + func set(customHeaders headers: [String]) { + let list = _CurlStringList(headers) + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionHTTPHEADER, list.asUnsafeMutablePointer).asError() + // We need to retain the list for as long as the rawHandle is in use. + headerList = list + } + /// Wait for pipelining/multiplexing + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_PIPEWAIT.html + //func set(waitForPipeliningAndMultiplexing flag: Bool) { + // try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionPIPEWAIT, flag ? 1 : 0).asError() + //} + + //TODO: The public API does not allow us to use CFURLSessionOptionSTREAM_DEPENDS / CFURLSessionOptionSTREAM_DEPENDS_E + // Might be good to add support for it, though. + + /// set numerical stream weight + /// - Parameter weight: values are clamped to lie between 0 and 1 + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_STREAM_WEIGHT.html + /// - SeeAlso: http://httpwg.org/specs/rfc7540.html#StreamPriority + //func set(streamWeight weight: Float) { + // // Scale and clamp such that the range 0->1 ends up 1->256 + // let w = 1 + max(0, min(255, Int(round(weight * 255)))) + // try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionPIPEWAIT, w).asError() + //} + /// Enable automatic decompression of HTTP downloads + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_CONTENT_DECODING.html + func set(automaticBodyDecompression flag: Bool) { + if flag { + "".withCString { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionACCEPT_ENCODING, UnsafeMutableRawPointer(mutating: $0)).asError() + } + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHTTP_CONTENT_DECODING, 1).asError() + } else { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionACCEPT_ENCODING, nil).asError() + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionHTTP_CONTENT_DECODING, 0).asError() + } + } + /// Set request method + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html + func set(requestMethod method: String) { + method.withCString { + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionCUSTOMREQUEST, UnsafeMutableRawPointer(mutating: $0)).asError() + } + } + + /// Download request without body + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOBODY.html + func set(noBody flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionNOBODY, flag ? 1 : 0).asError() + } + /// Enable data upload + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_UPLOAD.html + func set(upload flag: Bool) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionUPLOAD, flag ? 1 : 0).asError() + } + /// Set size of the request body to send + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_INFILESIZE_LARGE.html + func set(requestBodyLength length: Int64) { + try! CFURLSession_easy_setopt_int64(rawHandle, CFURLSessionOptionINFILESIZE_LARGE, length).asError() + } + + func set(timeout value: Int) { + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionTIMEOUT, value).asError() + } +} + +fileprivate func printLibcurlDebug(handle: CFURLSessionEasyHandle, type: CInt, data: UnsafeMutablePointer, size: Int, userInfo: UnsafeMutableRawPointer?) -> CInt { + // C.f. + let info = CFURLSessionInfo(value: type) + let text = data.withMemoryRebound(to: UInt8.self, capacity: size, { + let buffer = UnsafeBufferPointer(start: $0, count: size) + return String(utf8Buffer: buffer) + }) ?? ""; + + guard let userInfo = userInfo else { return 0 } + let task = Unmanaged.fromOpaque(userInfo).takeUnretainedValue() + printLibcurlDebug(type: info, data: text, task: task) + return 0 +} + +fileprivate func printLibcurlDebug(type: CFURLSessionInfo, data: String, task: URLSessionTask) { + // libcurl sends is data with trailing CRLF which inserts lots of newlines into our output. + print("[\(task.taskIdentifier)] \(type.debugHeader) \(data.mapControlToPictures)") +} + +fileprivate extension String { + /// Replace control characters U+0000 - U+0019 to Control Pictures U+2400 - U+2419 + var mapControlToPictures: String { + let d = self.unicodeScalars.map { (u: UnicodeScalar) -> UnicodeScalar in + switch u.value { + case 0..<0x20: return UnicodeScalar(u.value + 0x2400)! + default: return u + } + } + return String(String.UnicodeScalarView(d)) + } +} + +extension URLSessionTask._EasyHandle { + /// Send and/or receive pause state for an `EasyHandle` + struct _PauseState : OptionSet { + let rawValue: Int8 + init(rawValue: Int8) { self.rawValue = rawValue } + static let receivePaused = _PauseState(rawValue: 1 << 0) + static let sendPaused = _PauseState(rawValue: 1 << 1) + } +} +extension URLSessionTask._EasyHandle._PauseState { + func setState(on handle: URLSessionTask._EasyHandle) { + try! CFURLSessionEasyHandleSetPauseState(handle.rawHandle, contains(.sendPaused) ? 1 : 0, contains(.receivePaused) ? 1 : 0).asError() + } +} +extension URLSessionTask._EasyHandle._PauseState : TextOutputStreamable { + func write(to target: inout Target) { + switch (self.contains(.receivePaused), self.contains(.sendPaused)) { + case (false, false): target.write("unpaused") + case (true, false): target.write("receive paused") + case (false, true): target.write("send paused") + case (true, true): target.write("send & receive paused") + } + } +} +extension URLSessionTask._EasyHandle { + /// Pause receiving data. + /// + /// - SeeAlso: https://curl.haxx.se/libcurl/c/curl_easy_pause.html + func pauseReceive() { + URLSession.printDebug("[EasyHandle] pause receive (\(pauseState))") + guard !pauseState.contains(.receivePaused) else { return } + pauseState.insert(.receivePaused) + pauseState.setState(on: self) + } + /// Pause receiving data. + /// + /// - Note: Chances are high that delegate callbacks (with pending data) + /// will be called before this method returns. + /// - SeeAlso: https://curl.haxx.se/libcurl/c/curl_easy_pause.html + func unpauseReceive() { + URLSession.printDebug("[EasyHandle] unpause receive (\(pauseState))") + guard pauseState.contains(.receivePaused) else { return } + pauseState.remove(.receivePaused) + pauseState.setState(on: self) + } + /// Pause sending data. + /// + /// - SeeAlso: https://curl.haxx.se/libcurl/c/curl_easy_pause.html + func pauseSend() { + URLSession.printDebug("[EasyHandle] pause send (\(pauseState))") + guard !pauseState.contains(.sendPaused) else { return } + pauseState.insert(.sendPaused) + pauseState.setState(on: self) + } + /// Pause sending data. + /// + /// - Note: Chances are high that delegate callbacks (with pending data) + /// will be called before this method returns. + /// - SeeAlso: https://curl.haxx.se/libcurl/c/curl_easy_pause.html + func unpauseSend() { + URLSession.printDebug("[EasyHandle] unpause send (\(pauseState))") + guard pauseState.contains(.sendPaused) else { return } + pauseState.remove(.sendPaused) + pauseState.setState(on: self) + } +} + +internal extension URLSessionTask._EasyHandle { + /// errno number from last connect failure + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html + var connectFailureErrno: Int { + var errno = Int() + try! CFURLSession_easy_getinfo_long(rawHandle, CFURLSessionInfoOS_ERRNO, &errno).asError() + return errno + } +} + + +extension CFURLSessionInfo : Equatable {} + public func ==(lhs: CFURLSessionInfo, rhs: CFURLSessionInfo) -> Bool { + return lhs.value == rhs.value +} +extension CFURLSessionInfo { + public var debugHeader: String { + switch self { + case CFURLSessionInfoTEXT: return " " + case CFURLSessionInfoHEADER_OUT: return "=> Send header "; + case CFURLSessionInfoDATA_OUT: return "=> Send data "; + case CFURLSessionInfoSSL_DATA_OUT: return "=> Send SSL data "; + case CFURLSessionInfoHEADER_IN: return "<= Recv header "; + case CFURLSessionInfoDATA_IN: return "<= Recv data "; + case CFURLSessionInfoSSL_DATA_IN: return "<= Recv SSL data "; + default: return " " + } + } +} +extension URLSessionTask._EasyHandle { + /// the URL a redirect would go to + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLINFO_REDIRECT_URL.html + var redirectURL: URL? { + var p: UnsafeMutablePointer? = nil + try! CFURLSession_easy_getinfo_charp(rawHandle, CFURLSessionInfoREDIRECT_URL, &p).asError() + guard let cstring = p else { return nil } + guard let s = String(cString: cstring, encoding: String.Encoding.utf8) else { return nil } + return URL(string: s) + } +} + +fileprivate extension URLSessionTask._EasyHandle { + static func from(callbackUserData userdata: UnsafeMutableRawPointer?) -> URLSessionTask._EasyHandle? { + guard let userdata = userdata else { return nil } + return Unmanaged.fromOpaque(userdata).takeUnretainedValue() + } +} + +fileprivate extension URLSessionTask._EasyHandle { + /// Forward the libcurl callbacks into Swift methods + func setupCallbacks() { + // write + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionWRITEDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + + try! CFURLSession_easy_setopt_wc(rawHandle, CFURLSessionOptionWRITEFUNCTION) { (data: UnsafeMutablePointer, size: Int, nmemb: Int, userdata: UnsafeMutableRawPointer?) -> Int in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return 0 } + return handle.didReceive(data: data, size: size, nmemb: nmemb) + }.asError() + + // read + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionREADDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_easy_setopt_wc(rawHandle, CFURLSessionOptionREADFUNCTION) { (data: UnsafeMutablePointer, size: Int, nmemb: Int, userdata: UnsafeMutableRawPointer?) -> Int in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return 0 } + return handle.fill(writeBuffer: data, size: size, nmemb: nmemb) + }.asError() + + // header + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionHEADERDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_easy_setopt_wc(rawHandle, CFURLSessionOptionHEADERFUNCTION) { (data: UnsafeMutablePointer, size: Int, nmemb: Int, userdata: UnsafeMutableRawPointer?) -> Int in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return 0 } + var length = Double() + try! CFURLSession_easy_getinfo_double(handle.rawHandle, CFURLSessionInfoCONTENT_LENGTH_DOWNLOAD, &length).asError() + return handle.didReceive(headerData: data, size: size, nmemb: nmemb, fileLength: length) + }.asError() + + // socket options + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionSOCKOPTDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_easy_setopt_sc(rawHandle, CFURLSessionOptionSOCKOPTFUNCTION) { (userdata: UnsafeMutableRawPointer?, fd: CInt, type: CFURLSessionSocketType) -> CInt in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return 0 } + guard type == CFURLSessionSocketTypeIPCXN else { return 0 } + do { + try handle.setSocketOptions(for: fd) + return 0 + } catch { + return 1 + } + }.asError() + // seeking in input stream + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionSEEKDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_easy_setopt_seek(rawHandle, CFURLSessionOptionSEEKFUNCTION, { (userdata, offset, origin) -> Int32 in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return CFURLSessionSeekFail } + return handle.seekInputStream(offset: offset, origin: origin) + }).asError() + + // progress + + try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionNOPROGRESS, 0).asError() + + try! CFURLSession_easy_setopt_ptr(rawHandle, CFURLSessionOptionPROGRESSDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + + try! CFURLSession_easy_setopt_tc(rawHandle, CFURLSessionOptionXFERINFOFUNCTION, { (userdata: UnsafeMutableRawPointer?, dltotal :Int64, dlnow: Int64, ultotal: Int64, ulnow: Int64) -> Int32 in + guard let handle = URLSessionTask._EasyHandle.from(callbackUserData: userdata) else { return -1 } + handle.updateProgressMeter(with: _Progress(totalBytesSent: ulnow, totalBytesExpectedToSend: ultotal, totalBytesReceived: dlnow, totalBytesExpectedToReceive: dltotal)) + return 0 + }).asError() + + } + /// This callback function gets called by libcurl when it receives body + /// data. + /// + /// - SeeAlso: + func didReceive(data: UnsafeMutablePointer, size: Int, nmemb: Int) -> Int { + URLSession.printDebug("[EasyHandle] -> write callback \(size * nmemb)") + let d: Int = { + let buffer = Data(bytes: data, count: size*nmemb) + switch delegate.didReceive(data: buffer) { + case .proceed: return size * nmemb + case .abort: return 0 + case .pause: + URLSession.printDebug("[EasyHandle] pausing receive from callback (\(pauseState))") + pauseState.insert(.receivePaused) + return Int(CFURLSessionWriteFuncPause) + } + }() + URLSession.printDebug("[EasyHandle] <- write callback \(d)") + return d + } + /// This callback function gets called by libcurl when it receives header + /// data. + /// + /// - SeeAlso: + func didReceive(headerData data: UnsafeMutablePointer, size: Int, nmemb: Int, fileLength: Double) -> Int { + URLSession.printDebug("[EasyHandle] -> header callback \(size * nmemb)") + self.fileLength = Int64(fileLength) + let d: Int = { + let buffer = Data(bytes: data, count: size*nmemb) + switch delegate.didReceive(headerData: buffer) { + case .proceed: return size * nmemb + case .abort: return 0 + case .pause: + URLSession.printDebug("[EasyHandle] pausing receive from callback (\(pauseState))") + pauseState.insert(.receivePaused) + return Int(CFURLSessionWriteFuncPause) + } + }() + URLSession.printDebug("[EasyHandle] <- header callback \(d)") + return d + } + /// This callback function gets called by libcurl when it wants to send data + /// it to the network. + /// + /// - SeeAlso: + func fill(writeBuffer data: UnsafeMutablePointer, size: Int, nmemb: Int) -> Int { + URLSession.printDebug("[EasyHandle] -> read callback \(size * nmemb)") + let d: Int = { + let buffer = UnsafeMutableBufferPointer(start: data, count: size * nmemb) + switch delegate.fill(writeBuffer: buffer) { + case .pause: + URLSession.printDebug("[EasyHandle] pausing send from callback (\(pauseState))") + pauseState.insert(.sendPaused) + return Int(CFURLSessionReadFuncPause) + case .abort: + return Int(CFURLSessionReadFuncAbort) + case .bytes(let length): + return length + } + }() + URLSession.printDebug("[EasyHandle] <- read callback \(d)") + return d + } + + func setSocketOptions(for fd: CInt) throws { + URLSession.printDebug("[EasyHandle] -- socket options callback \(fd)") + //TODO: At this point we should call setsockopt(2) to set the QoS on + // the socket based on the QoS of the request. + // + // On Linux this can be done with IP_TOS. But there's both IntServ and + // DiffServ. + // + // Not sure what Darwin uses. + // + // C.f.: + // + // + } + func updateProgressMeter(with propgress: _Progress) { + delegate.updateProgressMeter(with: propgress) + } + + func seekInputStream(offset: Int64, origin: CInt) -> CInt { + URLSession.printDebug("[EasyHandle] -> seek callback \(offset) \(origin)") + let d: Int32 = { + /// libcurl should only use SEEK_SET + guard origin == SEEK_SET else { fatalError("Unexpected 'origin' in seek.") } + do { + try delegate.seekInputStream(to: UInt64(offset)) + return CFURLSessionSeekOk + } catch { + return CFURLSessionSeekCantSeek + } + }() + URLSession.printDebug("[EasyHandle] <- seek callback \(d)") + return d + } +} + +extension URLSessionTask._EasyHandle { + /// The progress of a transfer. + /// + /// The number of bytes that we expect to download and upload, and the + /// number of bytes downloaded and uploaded so far. + /// + /// Unknown values will be set to zero. E.g. if the number of bytes + /// expected to be downloaded is unknown, `totalBytesExpectedToReceive` + /// will be zero. + struct _Progress { + let totalBytesSent: Int64 + let totalBytesExpectedToSend: Int64 + let totalBytesReceived: Int64 + let totalBytesExpectedToReceive: Int64 + } +} + +extension URLSessionTask._EasyHandle { + /// A simple wrapper / helper for libcurl’s `slist`. + /// + /// It's libcurl's way to represent an array of strings. + internal class _CurlStringList { + fileprivate var rawList: OpaquePointer? = nil + init() {} + init(_ strings: [String]) { + strings.forEach { append($0) } + } + deinit { + CFURLSessionSListFreeAll(rawList) + } + } +} +extension URLSessionTask._EasyHandle._CurlStringList { + func append(_ string: String) { + string.withCString { + rawList = CFURLSessionSListAppend(rawList, $0) + } + } + var asUnsafeMutablePointer: UnsafeMutableRawPointer? { + return rawList.map{ UnsafeMutableRawPointer($0) } + } +} + +extension CFURLSessionEasyCode : Equatable {} +public func ==(lhs: CFURLSessionEasyCode, rhs: CFURLSessionEasyCode) -> Bool { + return lhs.value == rhs.value +} +extension CFURLSessionEasyCode : Error { + public var _domain: String { return "libcurl.Easy" } + public var _code: Int { return Int(self.value) } +} +internal extension CFURLSessionEasyCode { + func asError() throws { + if self == CFURLSessionEasyCodeOK { return } + throw self + } +} diff --git a/Foundation/NSURLSession/HTTPBodySource.swift b/Foundation/NSURLSession/HTTPBodySource.swift new file mode 100644 index 0000000000..ce03d81295 --- /dev/null +++ b/Foundation/NSURLSession/HTTPBodySource.swift @@ -0,0 +1,244 @@ +// Foundation/NSURLSession/HTTPBodySource.swift - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + +import CoreFoundation +import Dispatch + + +/// Turn `NSData` into `dispatch_data_t` +internal func createDispatchData(_ data: Data) -> DispatchData { + //TODO: Avoid copying data + let count = data.count + return data.withUnsafeBytes { (ptr: UnsafePointer) -> DispatchData in + return DispatchData(bytes: UnsafeBufferPointer(start: ptr, count: count)) + } +} + +/// Copy data from `dispatch_data_t` into memory pointed to by an `UnsafeMutableBufferPointer`. +internal func copyDispatchData(_ data: DispatchData, infoBuffer buffer: UnsafeMutableBufferPointer) { + precondition(data.count <= (buffer.count * MemoryLayout.size)) + _ = data.copyBytes(to: buffer) +} + +/// Split `dispatch_data_t` into `(head, tail)` pair. +internal func splitData(dispatchData data: DispatchData, atPosition position: Int) -> (DispatchData,DispatchData) { + /*let length = dispatch_data_get_size(data) + let head = dispatch_data_create_subrange(data, 0, position) + let tail = dispatch_data_create_subrange(data, position, length - position) + return (head, tail)*/ + return (data.subdata(in: 0.. _HTTPBodySourceDataChunk +} +internal enum _HTTPBodySourceDataChunk { + case data(DispatchData) + /// The source is depleted. + case done + /// Retry later to get more data. + case retryLater + case error +} + +/// A HTTP body data source backed by `dispatch_data_t`. +internal final class _HTTPBodyDataSource { + var data: DispatchData! + init(data: DispatchData) { + self.data = data + } +} + +extension _HTTPBodyDataSource : _HTTPBodySource { + enum _Error : Error { + case unableToRewindData + } + + func getNextChunk(withLength length: Int) -> _HTTPBodySourceDataChunk { + let remaining = data.count + if remaining == 0 { + return .done + } else if remaining <= length { + let r: DispatchData! = data + data = nil + return .data(r) + } else { + let (chunk, remainder) = splitData(dispatchData: data, atPosition: length) + data = remainder + return .data(chunk) + } + } +} + + +/// A HTTP body data source backed by a file. +/// +/// This allows non-blocking streaming of file data to the remote server. +/// +/// The source reads data using a `dispatch_io_t` channel, and hence reading +/// file data is non-blocking. It has a local buffer that it fills as calls +/// to `getNextChunk(withLength:)` drain it. +/// +/// - Note: Calls to `getNextChunk(withLength:)` and callbacks from libdispatch +/// should all happen on the same (serial) queue, and hence this code doesn't +/// have to be thread safe. +internal final class _HTTPBodyFileSource { + fileprivate let fileURL: URL + fileprivate let channel: DispatchIO + fileprivate let workQueue: DispatchQueue + fileprivate let dataAvailableHandler: () -> () + fileprivate var hasActiveReadHandler = false + fileprivate var availableChunk: _Chunk = .empty + /// Create a new data source backed by a file. + /// + /// - Parameter fileURL: the file to read from + /// - Parameter workQueue: the queue that it's safe to call + /// `getNextChunk(withLength:)` on, and that the `dataAvailableHandler` + /// will be called on. + /// - Parameter dataAvailableHandler: Will be called when data becomes + /// available. Reading data is done in a non-blocking way, such that + /// no data may be available even if there's more data in the file. + /// if `getNextChunk(withLength:)` returns `.retryLater`, this handler + /// will be called once data becomes available. + init(fileURL: URL, workQueue: DispatchQueue, dataAvailableHandler: @escaping () -> ()) { + guard fileURL.isFileURL else { fatalError("The body data URL must be a file URL.") } + self.fileURL = fileURL + self.workQueue = workQueue + self.dataAvailableHandler = dataAvailableHandler + var fileSystemRepresentation: UnsafePointer! = nil + fileURL.withUnsafeFileSystemRepresentation { + fileSystemRepresentation = $0 + } + self.channel = DispatchIO(type: .stream, path: fileSystemRepresentation, oflag: O_RDONLY, mode: 0, queue: workQueue, cleanupHandler: {_ in }) + self.channel.setLimit(highWater: CFURLSessionMaxWriteSize) + } + + fileprivate enum _Chunk { + /// Nothing has been read, yet + case empty + /// An error has occured while reading + case errorDetected(Int) + /// Data has been read + case data(DispatchData) + /// All data has been read from the file (EOF). + case done(DispatchData?) + } +} + +fileprivate extension _HTTPBodyFileSource { + fileprivate var desiredBufferLength: Int { return 3 * CFURLSessionMaxWriteSize } + /// Enqueue a dispatch I/O read to fill the buffer. + /// + /// - Note: This is a no-op if the buffer is full, or if a read operation + /// is already enqueued. + fileprivate func readNextChunk() { + // libcurl likes to use a buffer of size CFURLSessionMaxWriteSize, we'll + // try to keep 3 x of that around in the `chunk` buffer. + guard availableByteCount < desiredBufferLength else { return } + guard !hasActiveReadHandler else { return } // We're already reading + hasActiveReadHandler = true + + let lengthToRead = desiredBufferLength - availableByteCount + channel.read(offset: 0, length: lengthToRead, queue: workQueue) { (done: Bool, data: DispatchData?, errno: Int32) in + let wasEmpty = self.availableByteCount == 0 + self.hasActiveReadHandler = !done + + switch (done, data, errno) { + case (true, _, errno) where errno != 0: + self.availableChunk = .errorDetected(Int(errno)) + case (true, .some(let d), 0) where d.count == 0: + self.append(data: d, endOfFile: true) + case (true, .some(let d), 0): + self.append(data: d, endOfFile: false) + case (false, .some(let d), 0): + self.append(data: d, endOfFile: false) + default: + fatalError("Invalid arguments to read(3) callback.") + } + + if wasEmpty && (0 < self.availableByteCount) { + self.dataAvailableHandler() + } + } + } + + fileprivate func append(data: DispatchData, endOfFile: Bool) { + switch availableChunk { + case .empty: + availableChunk = endOfFile ? .done(data) : .data(data) + case .errorDetected: + break + case .data(var oldData): + oldData.append(data) + availableChunk = endOfFile ? .done(oldData) : .data(oldData) + case .done: + fatalError("Trying to append data, but end-of-file was already detected.") + } + } + + fileprivate var availableByteCount: Int { + switch availableChunk { + case .empty: return 0 + case .errorDetected: return 0 + case .data(let d): return d.count + case .done(.some(let d)): return d.count + case .done(.none): return 0 + } + } +} + +extension _HTTPBodyFileSource : _HTTPBodySource { + func getNextChunk(withLength length: Int) -> _HTTPBodySourceDataChunk { + switch availableChunk { + case .empty: + readNextChunk() + return .retryLater + case .errorDetected: + return .error + case .data(let data): + let l = min(length, data.count) + let (head, tail) = splitData(dispatchData: data, atPosition: l) + + availableChunk = (tail.count == 0) ? .empty : .data(tail) + readNextChunk() + + if head.count == 0 { + return .retryLater + } else { + return .data(head) + } + case .done(.some(let data)): + let l = min(length, data.count) + let (head, tail) = splitData(dispatchData: data, atPosition: l) + availableChunk = (tail.count == 0) ? .done(nil) : .done(tail) + if (head.count == 0) { + return .done + } else { + return .data(head) + } + case .done(.none): + return .done + } + } +} diff --git a/Foundation/NSURLSession/HTTPMessage.swift b/Foundation/NSURLSession/HTTPMessage.swift new file mode 100644 index 0000000000..c1b9cd57b1 --- /dev/null +++ b/Foundation/NSURLSession/HTTPMessage.swift @@ -0,0 +1,359 @@ +// Foundation/NSURLSession/HTTPMessage.swift - HTTP Message parsing +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// Helpers for parsing HTTP responses. +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + +import CoreFoundation + + +extension URLSessionTask { + /// An HTTP header being parsed. + /// + /// It can either be complete (i.e. the final CR LF CR LF has been + /// received), or partial. + internal enum _ParsedResponseHeader { + case partial(_ResponseHeaderLines) + case complete(_ResponseHeaderLines) + init() { + self = .partial(_ResponseHeaderLines()) + } + } + /// A type safe wrapper around multiple lines of headers. + /// + /// This can be converted into an `NSHTTPURLResponse`. + internal struct _ResponseHeaderLines { + let lines: [String] + init() { + self.lines = [] + } + init(headerLines: [String]) { + self.lines = headerLines + } + } +} + +extension URLSessionTask._ParsedResponseHeader { + /// Parse a header line passed by libcurl. + /// + /// These contain the ending and the final line contains nothing but + /// that ending. + /// - Returns: Returning nil indicates failure. Otherwise returns a new + /// `ParsedResponseHeader` with the given line added. + func byAppending(headerLine data: Data) -> URLSessionTask._ParsedResponseHeader? { + // The buffer must end in CRLF + guard + 2 <= data.count && + data[data.endIndex - 2] == _HTTPCharacters.CR && + data[data.endIndex - 1] == _HTTPCharacters.LF + else { return nil } + let lineBuffer = data.subdata(in: Range(data.startIndex.. URLSessionTask._ParsedResponseHeader { + if line.isEmpty { + switch self { + case .partial(let header): return .complete(header) + case .complete: return .partial(URLSessionTask._ResponseHeaderLines()) + } + } else { + let header = partialResponseHeader + return .partial(header.byAppending(headerLine: line)) + } + } + private var partialResponseHeader: URLSessionTask._ResponseHeaderLines { + switch self { + case .partial(let header): return header + case .complete: return URLSessionTask._ResponseHeaderLines() + } + } +} +private extension URLSessionTask._ResponseHeaderLines { + /// Returns a copy of the lines with the new line appended to it. + func byAppending(headerLine line: String) -> URLSessionTask._ResponseHeaderLines { + var l = self.lines + l.append(line) + return URLSessionTask._ResponseHeaderLines(headerLines: l) + } +} +internal extension URLSessionTask._ResponseHeaderLines { + /// Create an `NSHTTPRULResponse` from the lines. + /// + /// This will parse the header lines. + /// - Returns: `nil` if an error occured while parsing the header lines. + func createHTTPURLResponse(for URL: URL) -> NSHTTPURLResponse? { + guard let message = createHTTPMessage() else { return nil } + return NSHTTPURLResponse(message: message, URL: URL) + } + /// Parse the lines into a `URLSessionTask.HTTPMessage`. + func createHTTPMessage() -> URLSessionTask._HTTPMessage? { + guard let (head, tail) = lines.decompose else { return nil } + guard let startline = URLSessionTask._HTTPMessage._StartLine(line: head) else { return nil } + guard let headers = createHeaders(from: tail) else { return nil } + return URLSessionTask._HTTPMessage(startLine: startline, headers: headers) + } +} + +extension NSHTTPURLResponse { + fileprivate convenience init?(message: URLSessionTask._HTTPMessage, URL: URL) { + /// This needs to be a request, i.e. it needs to have a status line. + guard case .statusLine(let statusLine) = message.startLine else { return nil } + let fields = message.headersAsDictionary + self.init(url: URL, statusCode: statusLine.status, httpVersion: statusLine.version.rawValue, headerFields: fields) + } +} + + +extension URLSessionTask { + /// HTTP Message + /// + /// A message consist of a *start-line* optionally followed by one or multiple + /// message-header lines, and optionally a message body. + /// + /// This represents everything except for the message body. + /// + /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-4 + struct _HTTPMessage { + let startLine: URLSessionTask._HTTPMessage._StartLine + let headers: [URLSessionTask._HTTPMessage._Header] + } +} + +extension URLSessionTask._HTTPMessage { + var headersAsDictionary: [String: String] { + var result: [String: String] = [:] + headers.forEach { + result[$0.name] = $0.value + } + return result + } +} +extension URLSessionTask._HTTPMessage { + /// A single HTTP message header field + /// + /// Most HTTP messages have multiple header fields. + struct _Header { + let name: String + let value: String + } + /// The first line of a HTTP message + /// + /// This can either be the *request line* (RFC 2616 Section 5.1) or the + /// *status line* (RFC 2616 Section 6.1) + enum _StartLine { + /// RFC 2616 Section 5.1 *Request Line* + /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-5.1 + case requestLine(method: String, uri: URL, version: URLSessionTask._HTTPMessage._Version) + /// RFC 2616 Section 6.1 *Status Line* + /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-6.1 + case statusLine(version: URLSessionTask._HTTPMessage._Version, status: Int, reason: String) + } + /// A HTTP version, e.g. "HTTP/1.1" + struct _Version: RawRepresentable { + let rawValue: String + } +} +extension URLSessionTask._HTTPMessage._Version { + init?(versionString: String) { + rawValue = versionString + } +} + + +// Characters that we need for HTTP parsing: + +struct _HTTPCharacters { + /// *Carriage Return* symbol + static let CR: UInt8 = 0x0d + /// *Line Feed* symbol + static let LF: UInt8 = 0x0a + /// *Space* symbol + static let Space = UnicodeScalar(0x20) + static let HorizontalTab = UnicodeScalar(0x09) + static let Colon = UnicodeScalar(0x3a) + /// *Separators* according to RFC 2616 + static let Separators = NSCharacterSet(charactersIn: "()<>@,;:\\\"/[]?={} \t") +} + +private extension URLSessionTask._HTTPMessage._StartLine { + init?(line: String) { + guard let r = line.splitRequestLine() else { return nil } + if let version = URLSessionTask._HTTPMessage._Version(versionString: r.0) { + // Status line: + guard let status = Int(r.1), 100 <= status && status <= 999 else { return nil } + self = .statusLine(version: version, status: status, reason: r.2) + } else if let version = URLSessionTask._HTTPMessage._Version(versionString: r.2), + let URI = URL(string: r.1) { + // The request method must be a token (i.e. without seperators): + let seperatorIdx = r.0.unicodeScalars.index(where: { !$0.isValidMessageToken } ) + guard seperatorIdx == nil else { return nil } + self = .requestLine(method: r.0, uri: URI, version: version) + } else { + return nil + } + } +} +private extension String { + /// Split a request line into its 3 parts: *Method*, *Request-URI*, and *HTTP-Version*. + /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-5.1 + func splitRequestLine() -> (String, String, String)? { + let scalars = self.unicodeScalars + guard let firstSpace = scalars.rangeOfSpace else { return nil } + let remainingRange = firstSpace.upperBound..) -> [URLSessionTask._HTTPMessage._Header]? { + + var headerLines = Array(lines) + var headers: [URLSessionTask._HTTPMessage._Header] = [] + while !headerLines.isEmpty { + guard let (header, remaining) = URLSessionTask._HTTPMessage._Header.createOne(from: headerLines) else { return nil } + headers.append(header) + headerLines = remaining + } + return headers +} +private extension URLSessionTask._HTTPMessage._Header { + /// Parse a single HTTP message header field + /// + /// Each header field consists + /// of a name followed by a colon (":") and the field value. Field names + /// are case-insensitive. The field value MAY be preceded by any amount + /// of LWS, though a single SP is preferred. Header fields can be + /// extended over multiple lines by preceding each extra line with at + /// least one SP or HT. Applications ought to follow "common form", where + /// one is known or indicated, when generating HTTP constructs, since + /// there might exist some implementations that fail to accept anything + /// beyond the common forms. + /// + /// Consumes lines from the given array of lines to produce a single HTTP + /// message header and returns the resulting header plus the remainder. + /// + /// If an error occurs, it returns `nil`. + /// + /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-4.2 + static func createOne(from lines: [String]) -> (URLSessionTask._HTTPMessage._Header, [String])? { + // HTTP/1.1 header field values can be folded onto multiple lines if the + // continuation line begins with a space or horizontal tab. All linear + // white space, including folding, has the same semantics as SP. A + // recipient MAY replace any linear white space with a single SP before + // interpreting the field value or forwarding the message downstream. + guard let (head, tail) = lines.decompose else { return nil } + let headView = head.unicodeScalars + guard let nameRange = headView.rangeOfTokenPrefix else { return nil } + guard headView.index(after: nameRange.upperBound) <= headView.endIndex && headView[nameRange.upperBound] == _HTTPCharacters.Colon else { return nil } + let name = String(headView[nameRange]) + var value: String? + let line = headView[headView.index(after: nameRange.upperBound)..? { + var end = startIndex + while self[end].isValidMessageToken { + end = self.index(after: end) + } + guard end != startIndex else { return nil } + return startIndex..? { + guard !isEmpty else { return startIndex.. + easyHandles.forEach { + try! CFURLSessionMultiHandleRemoveHandle(rawHandle, $0.rawHandle).asError() + } + try! CFURLSessionMultiHandleDeinit(rawHandle).asError() + } + } +} + +extension URLSession._MultiHandle { + func configure(with configuration: URLSession._Configuration) { + try! CFURLSession_multi_setopt_l(rawHandle, CFURLSessionMultiOptionMAX_HOST_CONNECTIONS, configuration.httpMaximumConnectionsPerHost).asError() + try! CFURLSession_multi_setopt_l(rawHandle, CFURLSessionMultiOptionPIPELINING, configuration.httpShouldUsePipelining ? 3 : 2).asError() + //TODO: We may want to set + // CFURLSessionMultiOptionMAXCONNECTS + // CFURLSessionMultiOptionMAX_TOTAL_CONNECTIONS + } +} + +fileprivate extension URLSession._MultiHandle { + static func from(callbackUserData userdata: UnsafeMutableRawPointer?) -> URLSession._MultiHandle? { + guard let userdata = userdata else { return nil } + return Unmanaged.fromOpaque(userdata).takeUnretainedValue() + } +} + +fileprivate extension URLSession._MultiHandle { + /// Forward the libcurl callbacks into Swift methods + func setupCallbacks() { + // Socket + try! CFURLSession_multi_setopt_ptr(rawHandle, CFURLSessionMultiOptionSOCKETDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_multi_setopt_sf(rawHandle, CFURLSessionMultiOptionSOCKETFUNCTION) { (easyHandle: CFURLSessionEasyHandle, socket: CFURLSession_socket_t, what: Int32, userdata: UnsafeMutableRawPointer?, socketptr: UnsafeMutableRawPointer?) -> Int32 in + guard let handle = URLSession._MultiHandle.from(callbackUserData: userdata) else { fatalError() } + return handle.register(socket: socket, for: easyHandle, what: what, socketSourcePtr: socketptr) + }.asError() + // Timeout: + try! CFURLSession_multi_setopt_ptr(rawHandle, CFURLSessionMultiOptionTIMERDATA, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())).asError() + try! CFURLSession_multi_setopt_tf(rawHandle, CFURLSessionMultiOptionTIMERFUNCTION) { (_, timeout: Int, userdata: UnsafeMutableRawPointer?) -> Int32 in + guard let handle = URLSession._MultiHandle.from(callbackUserData: userdata) else { fatalError() } + handle.updateTimeoutTimer(to: timeout) + return 0 + }.asError() + } + /// and + /// + func register(socket: CFURLSession_socket_t, for easyHandle: CFURLSessionEasyHandle, what: Int32, socketSourcePtr: UnsafeMutableRawPointer?) -> Int32 { + // We get this callback whenever we need to register or unregister a + // given socket with libdispatch. + // The `action` / `what` defines if we should register or unregister + // that we're interested in read and/or write readiness. We will do so + // through libdispatch (DispatchSource) and store the source(s) inside + // a `SocketSources` which we in turn store inside libcurl's multi handle + // by means of curl_multi_assign() -- we retain the object fist. + let action = _SocketRegisterAction(rawValue: CFURLSessionPoll(value: what)) + var socketSources = _SocketSources.from(socketSourcePtr: socketSourcePtr) + if socketSources == nil && action.needsSource { + let s = _SocketSources() + let p = Unmanaged.passRetained(s).toOpaque() + CFURLSessionMultiHandleAssign(rawHandle, socket, UnsafeMutableRawPointer(p)) + socketSources = s + } else if socketSources != nil && action == .unregister { + // We need to release the stored pointer: + if let opaque = socketSourcePtr { + Unmanaged<_SocketSources>.fromOpaque(opaque).release() + } + socketSources = nil + } + if let ss = socketSources { + let handler = DispatchWorkItem { [weak self] in + self?.performAction(for: socket) + } + ss.createSources(with: action, fileDescriptor: Int(socket), queue: queue, handler: handler) + } + return 0 + } + + /// What read / write ready event to register / unregister. + /// + /// This re-maps `CFURLSessionPoll` / `CURL_POLL`. + enum _SocketRegisterAction { + case none + case registerRead + case registerWrite + case registerReadAndWrite + case unregister + } +} + +internal extension URLSession._MultiHandle { + /// Add an easy handle -- start its transfer. + func add(_ handle: URLSessionTask._EasyHandle) { + // If this is the first handle being added, we need to `kick` the + // underlying multi handle by calling `timeoutTimerFired` as + // described in + // . + // That will initiate the registration for timeout timer and socket + // readiness. + let needsTimeout = self.easyHandles.isEmpty + self.easyHandles.append(handle) + try! CFURLSessionMultiHandleAddHandle(self.rawHandle, handle.rawHandle).asError() + if needsTimeout { + self.timeoutTimerFired() + } + } + /// Remove an easy handle -- stop its transfer. + func remove(_ handle: URLSessionTask._EasyHandle) { + guard let idx = self.easyHandles.index(of: handle) else { + fatalError("Handle not in list.") + } + self.easyHandles.remove(at: idx) + try! CFURLSessionMultiHandleRemoveHandle(self.rawHandle, handle.rawHandle).asError() + } +} + +fileprivate extension URLSession._MultiHandle { + /// This gets called when we should ask curl to perform action on a socket. + func performAction(for socket: CFURLSession_socket_t) { + try! readAndWriteAvailableData(on: socket) + } + /// This gets called when our timeout timer fires. + /// + /// libcurl relies on us calling curl_multi_socket_action() every now and then. + func timeoutTimerFired() { + try! readAndWriteAvailableData(on: CFURLSessionSocketTimeout) + } + /// reads/writes available data given an action + func readAndWriteAvailableData(on socket: CFURLSession_socket_t) throws { + var runningHandlesCount = Int32(0) + try CFURLSessionMultiHandleAction(rawHandle, socket, 0, &runningHandlesCount).asError() + //TODO: Do we remove the timeout timer here if / when runningHandles == 0 ? + readMessages() + } + + /// Check the status of all individual transfers. + /// + /// libcurl refers to this as “read multi stack informationals”. + /// Check for transfers that completed. + func readMessages() { + // We pop the messages one by one in a loop: + repeat { + // count will contain the messages left in the queue + var count = Int32(0) + let info = CFURLSessionMultiHandleInfoRead(rawHandle, &count) + guard let handle = info.easyHandle else { break } + let code = info.resultCode + completedTransfer(forEasyHandle: handle, easyCode: code) + } while true + } + /// Transfer completed. + func completedTransfer(forEasyHandle handle: CFURLSessionEasyHandle, easyCode: CFURLSessionEasyCode) { + // Look up the matching wrapper: + guard let idx = easyHandles.index(where: { $0.rawHandle == handle }) else { + fatalError("Tansfer completed for easy handle, but it is not in the list of added handles.") + } + let easyHandle = easyHandles[idx] + // Find the NSURLError code + let errorCode = easyHandle.urlErrorCode(for: easyCode) + completedTransfer(forEasyHandle: easyHandle, errorCode: errorCode) + } + /// Transfer completed. + func completedTransfer(forEasyHandle handle: URLSessionTask._EasyHandle, errorCode: Int?) { + handle.completedTransfer(withErrorCode: errorCode) + } +} + +fileprivate extension URLSessionTask._EasyHandle { + /// An error code within the `NSURLErrorDomain` based on the error of the + /// easy handle. + /// - Note: The error value is set only on failure. You can't use it to + /// determine *if* something failed or not, only *why* it failed. + func urlErrorCode(for easyCode: CFURLSessionEasyCode) -> Int? { + switch (easyCode, CInt(connectFailureErrno)) { + case (CFURLSessionEasyCodeOK, _): + return nil + case (_, ECONNREFUSED): + return NSURLErrorCannotConnectToHost + case (CFURLSessionEasyCodeUNSUPPORTED_PROTOCOL, _): + return NSURLErrorUnsupportedURL + case (CFURLSessionEasyCodeURL_MALFORMAT, _): + return NSURLErrorBadURL + case (CFURLSessionEasyCodeCOULDNT_RESOLVE_HOST, _): + // Oddly, this appears to happen for malformed URLs, too. + return NSURLErrorCannotFindHost + case (CFURLSessionEasyCodeRECV_ERROR, ECONNRESET): + return NSURLErrorNetworkConnectionLost + case (CFURLSessionEasyCodeSEND_ERROR, ECONNRESET): + return NSURLErrorNetworkConnectionLost + case (CFURLSessionEasyCodeGOT_NOTHING, _): + return NSURLErrorBadServerResponse + case (CFURLSessionEasyCodeABORTED_BY_CALLBACK, _): + return NSURLErrorUnknown // Or NSURLErrorCancelled if we're in such a state + case (CFURLSessionEasyCodeCOULDNT_CONNECT, ETIMEDOUT): + return NSURLErrorTimedOut + case (CFURLSessionEasyCodeOPERATION_TIMEDOUT, _): + return NSURLErrorTimedOut + default: + //TODO: Need to map to one of the NSURLError... constants + NSUnimplemented() + } + } +} + +fileprivate extension URLSession._MultiHandle._SocketRegisterAction { + init(rawValue: CFURLSessionPoll) { + switch rawValue { + case CFURLSessionPollNone: + self = .none + case CFURLSessionPollIn: + self = .registerRead + case CFURLSessionPollOut: + self = .registerWrite + case CFURLSessionPollInOut: + self = .registerReadAndWrite + case CFURLSessionPollRemove: + self = .unregister + default: + fatalError("Invalid CFURLSessionPoll value.") + } + } +} +extension CFURLSessionPoll : Equatable {} +public func ==(lhs: CFURLSessionPoll, rhs: CFURLSessionPoll) -> Bool { + return lhs.value == rhs.value +} +fileprivate extension URLSession._MultiHandle._SocketRegisterAction { + /// Should a libdispatch source be registered for **read** readiness? + var needsReadSource: Bool { + switch self { + case .none: return false + case .registerRead: return true + case .registerWrite: return false + case .registerReadAndWrite: return true + case .unregister: return false + } + } + /// Should a libdispatch source be registered for **write** readiness? + var needsWriteSource: Bool { + switch self { + case .none: return false + case .registerRead: return false + case .registerWrite: return true + case .registerReadAndWrite: return true + case .unregister: return false + } + } + /// Should either a **read** or a **write** readiness libdispatch source be + /// registered? + var needsSource: Bool { + return needsReadSource || needsWriteSource + } +} + +/// A helper class that wraps a libdispatch timer. +/// +/// Used to implement the timeout of `URLSession.MultiHandle`. +fileprivate class _TimeoutSource { + let rawSource: DispatchSource + let milliseconds: Int + init(queue: DispatchQueue, milliseconds: Int, handler: DispatchWorkItem) { + self.milliseconds = milliseconds + self.rawSource = DispatchSource.makeTimerSource(queue: queue) as! DispatchSource + + let delay = UInt64(max(1, milliseconds - 1)) + //let leeway: UInt64 = (milliseconds == 1) ? NSEC_PER_USEC : NSEC_PER_MSEC + let start = DispatchTime.now() + DispatchTimeInterval.milliseconds(Int(delay)) + + rawSource.scheduleRepeating(deadline: start, interval: .milliseconds(Int(delay)), leeway: (milliseconds == 1) ? .microseconds(Int(1)) : .milliseconds(Int(1))) + rawSource.setEventHandler(handler: handler) + rawSource.resume() + } + deinit { + rawSource.cancel() + } +} + +fileprivate extension URLSession._MultiHandle { + + /// + func updateTimeoutTimer(to value: Int) { + updateTimeoutTimer(to: _Timeout(timeout: value)) + } + + func updateTimeoutTimer(to timeout: _Timeout) { + // Set up a timeout timer based on the given value: + switch timeout { + case .none: + timeoutSource = nil + case .immediate: + timeoutSource = nil + timeoutTimerFired() + case .milliseconds(let milliseconds): + if (timeoutSource == nil) || timeoutSource!.milliseconds != milliseconds { + //TODO: Could simply change the existing timer by calling + // dispatch_source_set_timer() again. + let block = DispatchWorkItem { [weak self] in + self?.timeoutTimerFired() + } + timeoutSource = _TimeoutSource(queue: queue, milliseconds: milliseconds, handler: block) + } + } + } + enum _Timeout { + case milliseconds(Int) + case none + case immediate + } +} + +fileprivate extension URLSession._MultiHandle._Timeout { + init(timeout: Int) { + switch timeout { + case -1: + self = .none + case 0: + self = .immediate + default: + self = .milliseconds(timeout) + } + } +} + + +/// Read and write libdispatch sources for a specific socket. +/// +/// A simple helper that combines two sources -- both being optional. +/// +/// This info is stored into the socket using `curl_multi_assign()`. +/// +/// - SeeAlso: URLSession.MultiHandle.SocketRegisterAction +fileprivate class _SocketSources { + var readSource: DispatchSource? + var writeSource: DispatchSource? + + func createReadSource(fileDescriptor fd: Int, queue: DispatchQueue, handler: DispatchWorkItem) { + guard readSource == nil else { return } + let s = DispatchSource.makeReadSource(fileDescriptor: Int32(fd), queue: queue) + s.setEventHandler(handler: handler) + readSource = s as? DispatchSource + s.resume() + } + + func createWriteSource(fileDescriptor fd: Int, queue: DispatchQueue, handler: DispatchWorkItem) { + guard writeSource == nil else { return } + let s = DispatchSource.makeWriteSource(fileDescriptor: Int32(fd), queue: queue) + s.setEventHandler(handler: handler) + writeSource = s as? DispatchSource + s.resume() + } + + func tearDown() { + if let s = readSource { + s.cancel() + } + readSource = nil + if let s = writeSource { + s.cancel() + } + writeSource = nil + } +} +extension _SocketSources { + /// Create a read and/or write source as specified by the action. + func createSources(with action: URLSession._MultiHandle._SocketRegisterAction, fileDescriptor fd: Int, queue: DispatchQueue, handler: DispatchWorkItem) { + if action.needsReadSource { + createReadSource(fileDescriptor: fd, queue: queue, handler: handler) + } + if action.needsWriteSource { + createWriteSource(fileDescriptor: fd, queue: queue, handler: handler) + } + } +} +extension _SocketSources { + /// Unwraps the `SocketSources` + /// + /// A `SocketSources` is stored into the multi handle's socket using + /// `curl_multi_assign()`. This helper unwraps it from the returned + /// `UnsafeMutablePointer`. + static func from(socketSourcePtr ptr: UnsafeMutableRawPointer?) -> _SocketSources? { + guard let ptr = ptr else { return nil } + return Unmanaged<_SocketSources>.fromOpaque(ptr).takeUnretainedValue() + } +} + + +extension CFURLSessionMultiCode : Equatable {} +public func ==(lhs: CFURLSessionMultiCode, rhs: CFURLSessionMultiCode) -> Bool { + return lhs.value == rhs.value +} +extension CFURLSessionMultiCode : Error { + public var _domain: String { return "libcurl.Multi" } + public var _code: Int { return Int(self.value) } +} +internal extension CFURLSessionMultiCode { + func asError() throws { + if self == CFURLSessionMultiCodeOK { return } + throw self + } +} diff --git a/Foundation/NSURLSession/NSURLSession.swift b/Foundation/NSURLSession/NSURLSession.swift new file mode 100644 index 0000000000..d9028a3623 --- /dev/null +++ b/Foundation/NSURLSession/NSURLSession.swift @@ -0,0 +1,519 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// + +/* + + URLSession is a replacement API for URLConnection. It provides + options that affect the policy of, and various aspects of the + mechanism by which NSURLRequest objects are retrieved from the + network. + + An URLSession may be bound to a delegate object. The delegate is + invoked for certain events during the lifetime of a session, such as + server authentication or determining whether a resource to be loaded + should be converted into a download. + + URLSession instances are threadsafe. + + The default URLSession uses a system provided delegate and is + appropriate to use in place of existing code that uses + +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] + + An URLSession creates URLSessionTask objects which represent the + action of a resource being loaded. These are analogous to + NSURLConnection objects but provide for more control and a unified + delegate model. + + URLSessionTask objects are always created in a suspended state and + must be sent the -resume message before they will execute. + + Subclasses of URLSessionTask are used to syntactically + differentiate between data and file downloads. + + An URLSessionDataTask receives the resource as a series of calls to + the URLSession:dataTask:didReceiveData: delegate method. This is type of + task most commonly associated with retrieving objects for immediate parsing + by the consumer. + + An URLSessionUploadTask differs from an URLSessionDataTask + in how its instance is constructed. Upload tasks are explicitly created + by referencing a file or data object to upload, or by utilizing the + -URLSession:task:needNewBodyStream: delegate message to supply an upload + body. + + An URLSessionDownloadTask will directly write the response data to + a temporary file. When completed, the delegate is sent + URLSession:downloadTask:didFinishDownloadingToURL: and given an opportunity + to move this file to a permanent location in its sandboxed container, or to + otherwise read the file. If canceled, an URLSessionDownloadTask can + produce a data blob that can be used to resume a download at a later + time. + + Beginning with iOS 9 and Mac OS X 10.11, URLSessionStream is + available as a task type. This allows for direct TCP/IP connection + to a given host and port with optional secure handshaking and + navigation of proxies. Data tasks may also be upgraded to a + URLSessionStream task via the HTTP Upgrade: header and appropriate + use of the pipelining option of URLSessionConfiguration. See RFC + 2817 and RFC 6455 for information about the Upgrade: header, and + comments below on turning data tasks into stream tasks. + */ + +/* DataTask objects receive the payload through zero or more delegate messages */ +/* UploadTask objects receive periodic progress updates but do not return a body */ +/* DownloadTask objects represent an active download to disk. They can provide resume data when canceled. */ +/* StreamTask objects may be used to create NSInput and NSOutputStreams, or used directly in reading and writing. */ + +/* + + URLSession is not available for i386 targets before Mac OS X 10.10. + + */ + + +// ----------------------------------------------------------------------------- +/// # URLSession API implementation overview +/// +/// ## Design Overview +/// +/// This implementation uses libcurl for the HTTP layer implementation. At a +/// high level, the `URLSession` keeps a *multi handle*, and each +/// `URLSessionTask` has an *easy handle*. This way these two APIs somewhat +/// have a 1-to-1 mapping. +/// +/// The `URLSessionTask` class is in charge of configuring its *easy handle* +/// and adding it to the owning session’s *multi handle*. Adding / removing +/// the handle effectively resumes / suspends the transfer. +/// +/// The `URLSessionTask` class has subclasses, but this design puts all the +/// logic into the parent `URLSessionTask`. +/// +/// Both the `URLSession` and `URLSessionTask` extensively use helper +/// types to ease testability, separate responsibilities, and improve +/// readability. These types are nested inside the `URLSession` and +/// `URLSessionTask` to limit their scope. Some of these even have sub-types. +/// +/// The session class uses the `URLSession.TaskRegistry` to keep track of its +/// tasks. +/// +/// The task class uses an `InternalState` type together with `TransferState` to +/// keep track of its state and each transfer’s state -- note that a single task +/// may do multiple transfers, e.g. as the result of a redirect. +/// +/// ## Error Handling +/// +/// Most libcurl functions either return a `CURLcode` or `CURLMcode` which +/// are represented in Swift as `CFURLSessionEasyCode` and +/// `CFURLSessionMultiCode` respectively. We turn these functions into throwing +/// functions by appending `.asError()` onto their calls. This turns the error +/// code into `Void` but throws the error if it's not `.OK` / zero. +/// +/// This is combined with `try!` is almost all places, because such an error +/// indicates a programming error. Hence the pattern used in this code is +/// +/// ``` +/// try! someFunction().asError() +/// ``` +/// +/// where `someFunction()` is a function that returns a `CFURLSessionEasyCode`. +/// +/// ## Threading +/// +/// The URLSession has a libdispatch ‘work queue’, and all internal work is +/// done on that queue, such that the code doesn't have to deal with thread +/// safety beyond that. All work inside a `URLSessionTask` will run on this +/// work queue, and so will code manipulating the session's *multi handle*. +/// +/// Delegate callbacks are, however, done on the passed in +/// `delegateQueue`. And any calls into this API need to switch onto the ‘work +/// queue’ as needed. +/// +/// - SeeAlso: https://curl.haxx.se/libcurl/c/threadsafe.html +/// - SeeAlso: URLSession+libcurl.swift +/// +/// The (publicly accessible) attributes of an `URLSessionTask` are made thread +/// safe by using a concurrent libdispatch queue and only doing writes with a +/// barrier while allowing concurrent reads. A single queue is shared for all +/// tasks of a given session for this isolation. C.f. `taskAttributesIsolation`. +/// +/// ## HTTP and RFC 2616 +/// +/// Most of HTTP is defined in [RFC 2616](https://tools.ietf.org/html/rfc2616). +/// While libcurl handles many of these details, some are handled by this +/// URLSession implementation. +/// +/// ## To Do +/// +/// - TODO: Is is not clear if using API that takes a URLRequest will override +/// all settings of the URLSessionConfiguration or just those that have not +/// explicitly been set. +/// E.g. creating an URLRequest will cause it to have the default timeoutInterval +/// of 60 seconds, but should this be used in stead of the configuration's +/// timeoutIntervalForRequest even if the request's timeoutInterval has not +/// been set explicitly? +/// +/// - TODO: We could re-use EasyHandles once they're complete. That'd be a +/// performance optimization. Not sure how much that'd help. The URLSession +/// would have to keep a pool of unused handles. +/// +/// - TODO: Could make `workQueue` concurrent and use a multiple reader / single +/// writer approach if it turns out that there's contention. +// ----------------------------------------------------------------------------- + + + +import CoreFoundation +import Dispatch + + +fileprivate var sessionCounter = Int32(0) +fileprivate func nextSessionIdentifier() -> Int32 { + //TODO: find an alternative for this on Linux + //return OSAtomicIncrement32Barrier(&sessionCounter) + sessionCounter += 1 + return sessionCounter +} +public let URLSessionTransferSizeUnknown: Int64 = -1 + +open class URLSession : NSObject { + fileprivate let _configuration: _Configuration + fileprivate let multiHandle: _MultiHandle + fileprivate var nextTaskIdentifier = 1 + internal let workQueue: DispatchQueue + /// This queue is used to make public attributes on `URLSessionTask` instances thread safe. + /// - Note: It's a **concurrent** queue. + internal let taskAttributesIsolation: DispatchQueue + fileprivate let taskRegistry = URLSession._TaskRegistry() + fileprivate let identifier: Int32 + + /* + * The shared session uses the currently set global NSURLCache, + * NSHTTPCookieStorage and NSURLCredentialStorage objects. + */ + open class var shared: URLSession { NSUnimplemented() } + + /* + * Customization of URLSession occurs during creation of a new session. + * If you only need to use the convenience routines with custom + * configuration options it is not necessary to specify a delegate. + * If you do specify a delegate, the delegate will be retained until after + * the delegate has been sent the URLSession:didBecomeInvalidWithError: message. + */ + public /*not inherited*/ init(configuration: URLSessionConfiguration) { + initializeLibcurl() + identifier = nextSessionIdentifier() + self.workQueue = DispatchQueue(label: "URLSession<\(identifier)>") + self.taskAttributesIsolation = DispatchQueue(label: "URLSession<\(identifier)>.taskAttributes", attributes: DispatchQueue.Attributes.concurrent) + self.delegateQueue = OperationQueue() + self.delegate = nil + //TODO: Make sure this one can't be written to? + // Could create a subclass of URLSessionConfiguration that wraps the + // URLSession._Configuration and with fatalError() in all setters. + self.configuration = configuration.copy() as! URLSessionConfiguration + let c = URLSession._Configuration(URLSessionConfiguration: configuration) + self._configuration = c + self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue) + } + public /*not inherited*/ init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate?, delegateQueue queue: OperationQueue?) { + initializeLibcurl() + identifier = nextSessionIdentifier() + self.workQueue = DispatchQueue(label: "URLSession<\(identifier)>") + self.taskAttributesIsolation = DispatchQueue(label: "URLSession<\(identifier)>.taskAttributes", attributes: DispatchQueue.Attributes.concurrent) + self.delegateQueue = queue ?? OperationQueue() + self.delegate = delegate + //TODO: Make sure this one can't be written to? + // Could create a subclass of URLSessionConfiguration that wraps the + // URLSession._Configuration and with fatalError() in all setters. + self.configuration = configuration.copy() as! URLSessionConfiguration + let c = URLSession._Configuration(URLSessionConfiguration: configuration) + self._configuration = c + self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue) + } + + open let delegateQueue: OperationQueue + open let delegate: URLSessionDelegate? + open let configuration: URLSessionConfiguration + + /* + * The sessionDescription property is available for the developer to + * provide a descriptive label for the session. + */ + open var sessionDescription: String? + + /* -finishTasksAndInvalidate returns immediately and existing tasks will be allowed + * to run to completion. New tasks may not be created. The session + * will continue to make delegate callbacks until URLSession:didBecomeInvalidWithError: + * has been issued. + * + * -finishTasksAndInvalidate and -invalidateAndCancel do not + * have any effect on the shared session singleton. + * + * When invalidating a background session, it is not safe to create another background + * session with the same identifier until URLSession:didBecomeInvalidWithError: has + * been issued. + */ + open func finishTasksAndInvalidate() { NSUnimplemented() } + + /* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues + * -cancel to all outstanding tasks for this session. Note task + * cancellation is subject to the state of the task, and some tasks may + * have already have completed at the time they are sent -cancel. + */ + open func invalidateAndCancel() { NSUnimplemented() } + + open func reset(completionHandler: @escaping () -> Void) { NSUnimplemented() } /* empty all cookies, cache and credential stores, removes disk files, issues -flushWithCompletionHandler:. Invokes completionHandler() on the delegate queue if not nil. */ + + open func flush(completionHandler: @escaping () -> Void) { NSUnimplemented() }/* flush storage to disk and clear transient network caches. Invokes completionHandler() on the delegate queue if not nil. */ + + open func getTasksWithCompletionHandler(completionHandler: @escaping ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with outstanding data, upload and download tasks. */ + + open func getAllTasks(completionHandler: @escaping ([URLSessionTask]) -> Void) { NSUnimplemented() }/* invokes completionHandler with all outstanding tasks. */ + + /* + * URLSessionTask objects are always created in a suspended state and + * must be sent the -resume message before they will execute. + */ + + /* Creates a data task with the given request. The request may have a body stream. */ + open func dataTask(with request: NSURLRequest) -> URLSessionDataTask { + return dataTask(with: _Request(request), behaviour: .callDelegate) + } + + /* Creates a data task to retrieve the contents of the given URL. */ + open func dataTask(with url: URL) -> URLSessionDataTask { + return dataTask(with: _Request(url), behaviour: .callDelegate) + } + + /* Creates an upload task with the given request. The body of the request will be created from the file referenced by fileURL */ + open func uploadTask(with request: NSURLRequest, fromFile fileURL: URL) -> URLSessionUploadTask { + let r = URLSession._Request(request) + return uploadTask(with: r, body: .file(fileURL), behaviour: .callDelegate) + } + + /* Creates an upload task with the given request. The body of the request is provided from the bodyData. */ + open func uploadTask(with request: NSURLRequest, fromData bodyData: Data) -> URLSessionUploadTask { + let r = URLSession._Request(request) + return uploadTask(with: r, body: .data(createDispatchData(bodyData)), behaviour: .callDelegate) + } + + /* Creates an upload task with the given request. The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */ + open func uploadTask(withStreamedRequest request: NSURLRequest) -> URLSessionUploadTask { NSUnimplemented() } + + /* Creates a download task with the given request. */ + open func downloadTask(with request: NSURLRequest) -> URLSessionDownloadTask { + let r = URLSession._Request(request) + return downloadTask(with: r, behavior: .callDelegate) + } + + /* Creates a download task to download the contents of the given URL. */ + open func downloadTask(with url: URL) -> URLSessionDownloadTask { + return downloadTask(with: _Request(url), behavior: .callDelegate) + } + + /* Creates a download task with the resume data. If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */ + open func downloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask { NSUnimplemented() } + + /* Creates a bidirectional stream task to a given host and port. + */ + open func streamTask(withHostName hostname: String, port: Int) -> URLSessionStreamTask { NSUnimplemented() } +} + + +// Helpers +fileprivate extension URLSession { + enum _Request { + case request(NSURLRequest) + case url(URL) + } + func createConfiguredRequest(from request: URLSession._Request) -> NSURLRequest { + let r = request.createMutableURLRequest() + _configuration.configure(request: r) + return r + } +} +extension URLSession._Request { + init(_ url: URL) { + self = .url(url) + } + init(_ request: NSURLRequest) { + self = .request(request) + } +} +extension URLSession._Request { + func createMutableURLRequest() -> NSMutableURLRequest { + switch self { + case .url(let url): return NSMutableURLRequest(url: url) + case .request(let r): return r.mutableCopy() as! NSMutableURLRequest + } + } +} + +fileprivate extension URLSession { + func createNextTaskIdentifier() -> Int { + let i = nextTaskIdentifier + nextTaskIdentifier += 1 + return i + } +} + +fileprivate extension URLSession { + /// Create a data task. + /// + /// All public methods funnel into this one. + func dataTask(with request: _Request, behaviour: _TaskRegistry._Behaviour) -> URLSessionDataTask { + let r = createConfiguredRequest(from: request) + let i = createNextTaskIdentifier() + let task = URLSessionDataTask(session: self, request: r, taskIdentifier: i) + workQueue.async { + self.taskRegistry.add(task, behaviour: behaviour) + } + return task + } + + /// Create an upload task. + /// + /// All public methods funnel into this one. + func uploadTask(with request: _Request, body: URLSessionTask._Body, behaviour: _TaskRegistry._Behaviour) -> URLSessionUploadTask { + let r = createConfiguredRequest(from: request) + let i = createNextTaskIdentifier() + let task = URLSessionUploadTask(session: self, request: r, taskIdentifier: i, body: body) + workQueue.async { + self.taskRegistry.add(task, behaviour: behaviour) + } + return task + } + + /// Create a download task + func downloadTask(with request: _Request, behavior: _TaskRegistry._Behaviour) -> URLSessionDownloadTask { + let r = createConfiguredRequest(from: request) + let i = createNextTaskIdentifier() + let task = URLSessionDownloadTask(session: self, request: r, taskIdentifier: i) + workQueue.async { + self.taskRegistry.add(task, behaviour: behavior) + } + return task + } +} + + +/* + * URLSession convenience routines deliver results to + * a completion handler block. These convenience routines + * are not available to URLSessions that are configured + * as background sessions. + * + * Task objects are always created in a suspended state and + * must be sent the -resume message before they will execute. + */ +extension URLSession { + /* + * data task convenience methods. These methods create tasks that + * bypass the normal delegate calls for response and data delivery, + * and provide a simple cancelable asynchronous interface to receiving + * data. Errors will be returned in the NSURLErrorDomain, + * see . The delegate, if any, will still be + * called for authentication challenges. + */ + open func dataTask(with request: NSURLRequest, completionHandler: @escaping (Data?, URLResponse?, NSError?) -> Void) -> URLSessionDataTask { + return dataTask(with: _Request(request), behaviour: .dataCompletionHandler(completionHandler)) + } + + open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, NSError?) -> Void) -> URLSessionDataTask { + return dataTask(with: _Request(url), behaviour: .dataCompletionHandler(completionHandler)) + } + + /* + * upload convenience method. + */ + open func uploadTask(with request: NSURLRequest, fromFile fileURL: URL, completionHandler: @escaping (Data?, URLResponse?, NSError?) -> Void) -> URLSessionUploadTask { + let fileData = try! Data(contentsOf: fileURL) + return uploadTask(with: request, fromData: fileData, completionHandler: completionHandler) + } + + open func uploadTask(with request: NSURLRequest, fromData bodyData: Data?, completionHandler: @escaping (Data?, URLResponse?, NSError?) -> Void) -> URLSessionUploadTask { + return uploadTask(with: _Request(request), body: .data(createDispatchData(bodyData!)), behaviour: .dataCompletionHandler(completionHandler)) + } + + /* + * download task convenience methods. When a download successfully + * completes, the URL will point to a file that must be read or + * copied during the invocation of the completion routine. The file + * will be removed automatically. + */ + open func downloadTask(with request: NSURLRequest, completionHandler: @escaping (URL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { + return downloadTask(with: _Request(request), behavior: .downloadCompletionHandler(completionHandler)) + } + + open func downloadTask(with url: URL, completionHandler: @escaping (URL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { + return downloadTask(with: _Request(url), behavior: .downloadCompletionHandler(completionHandler)) + } + + open func downloadTask(withResumeData resumeData: Data, completionHandler: (NSURL?, URLResponse?, NSError?) -> Void) -> URLSessionDownloadTask { NSUnimplemented() } +} + +internal extension URLSession { + /// The kind of callback / delegate behaviour of a task. + /// + /// This is similar to the `URLSession.TaskRegistry.Behaviour`, but it + /// also encodes the kind of delegate that the session has. + enum _TaskBehaviour { + /// The session has no delegate, or just a plain `URLSessionDelegate`. + case noDelegate + /// The session has a delegate of type `URLSessionTaskDelegate` + case taskDelegate(URLSessionTaskDelegate) + /// Default action for all events, except for completion. + /// - SeeAlso: URLSession.TaskRegistry.Behaviour.dataCompletionHandler + case dataCompletionHandler(URLSession._TaskRegistry.DataTaskCompletion) + /// Default action for all events, except for completion. + /// - SeeAlso: URLSession.TaskRegistry.Behaviour.downloadCompletionHandler + case downloadCompletionHandler(URLSession._TaskRegistry.DownloadTaskCompletion) + } + + func behaviour(for task: URLSessionTask) -> _TaskBehaviour { + switch taskRegistry.behaviour(for: task) { + case .dataCompletionHandler(let c): return .dataCompletionHandler(c) + case .downloadCompletionHandler(let c): return .downloadCompletionHandler(c) + case .callDelegate: + switch delegate { + case .none: return .noDelegate + case .some(let d as URLSessionTaskDelegate): return .taskDelegate(d) + case .some: return .noDelegate + } + } + } +} + + +internal protocol URLSessionProtocol: class { + func add(handle: URLSessionTask._EasyHandle) + func remove(handle: URLSessionTask._EasyHandle) + func behaviour(for: URLSessionTask) -> URLSession._TaskBehaviour +} +extension URLSession: URLSessionProtocol { + func add(handle: URLSessionTask._EasyHandle) { + multiHandle.add(handle) + } + func remove(handle: URLSessionTask._EasyHandle) { + multiHandle.remove(handle) + } +} +/// This class is only used to allow `URLSessionTask.init()` to work. +/// +/// - SeeAlso: URLSessionTask.init() +final internal class _MissingURLSession: URLSessionProtocol { + func add(handle: URLSessionTask._EasyHandle) { + fatalError() + } + func remove(handle: URLSessionTask._EasyHandle) { + fatalError() + } + func behaviour(for: URLSessionTask) -> URLSession._TaskBehaviour { + fatalError() + } +} diff --git a/Foundation/NSURLSession/NSURLSessionConfiguration.swift b/Foundation/NSURLSession/NSURLSessionConfiguration.swift new file mode 100644 index 0000000000..72018bde78 --- /dev/null +++ b/Foundation/NSURLSession/NSURLSessionConfiguration.swift @@ -0,0 +1,211 @@ +// Foundation/NSURLSession/NSURLSessionConfiguration.swift - NSURLSession Configuration +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// URLSession API code. +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + + + +/// Configuration options for an URLSession. +/// +/// When a session is +/// created, a copy of the configuration object is made - you cannot +/// modify the configuration of a session after it has been created. +/// +/// The shared session uses the global singleton credential, cache +/// and cookie storage objects. +/// +/// An ephemeral session has no persistent disk storage for cookies, +/// cache or credentials. +/// +/// A background session can be used to perform networking operations +/// on behalf of a suspended application, within certain constraints. +open class URLSessionConfiguration : NSObject, NSCopying { + public override init() { + self.requestCachePolicy = NSURLRequest.CachePolicy.useProtocolCachePolicy + self.timeoutIntervalForRequest = 60 + self.timeoutIntervalForResource = 604800 + self.networkServiceType = .default + self.allowsCellularAccess = true + self.discretionary = false + self.httpShouldUsePipelining = false + self.httpShouldSetCookies = true + self.httpCookieAcceptPolicy = .onlyFromMainDocumentDomain + self.httpMaximumConnectionsPerHost = 6 + self.httpCookieStorage = nil + self.urlCredentialStorage = nil + self.urlCache = nil + self.shouldUseExtendedBackgroundIdleMode = false + super.init() + } + + private init(identifier: String?, + requestCachePolicy: NSURLRequest.CachePolicy, + timeoutIntervalForRequest: TimeInterval, + timeoutIntervalForResource: TimeInterval, + networkServiceType: NSURLRequest.NetworkServiceType, + allowsCellularAccess: Bool, + discretionary: Bool, + connectionProxyDictionary: [AnyHashable:Any]?, + httpShouldUsePipelining: Bool, + httpShouldSetCookies: Bool, + httpCookieAcceptPolicy: HTTPCookie.AcceptPolicy, + httpAdditionalHeaders: [AnyHashable:Any]?, + httpMaximumConnectionsPerHost: Int, + httpCookieStorage: HTTPCookieStorage?, + urlCredentialStorage: URLCredentialStorage?, + urlCache: URLCache?, + shouldUseExtendedBackgroundIdleMode: Bool, + protocolClasses: [AnyClass]?) + { + self.identifier = identifier + self.requestCachePolicy = requestCachePolicy + self.timeoutIntervalForRequest = timeoutIntervalForRequest + self.timeoutIntervalForResource = timeoutIntervalForResource + self.networkServiceType = networkServiceType + self.allowsCellularAccess = allowsCellularAccess + self.discretionary = discretionary + self.connectionProxyDictionary = connectionProxyDictionary + self.httpShouldUsePipelining = httpShouldUsePipelining + self.httpShouldSetCookies = httpShouldSetCookies + self.httpCookieAcceptPolicy = httpCookieAcceptPolicy + self.httpAdditionalHeaders = httpAdditionalHeaders + self.httpMaximumConnectionsPerHost = httpMaximumConnectionsPerHost + self.httpCookieStorage = httpCookieStorage + self.urlCredentialStorage = urlCredentialStorage + self.urlCache = urlCache + self.shouldUseExtendedBackgroundIdleMode = shouldUseExtendedBackgroundIdleMode + self.protocolClasses = protocolClasses + } + + open override func copy() -> Any { + return copy(with: nil) + } + + open func copy(with zone: NSZone?) -> Any { + return URLSessionConfiguration( + identifier: identifier, + requestCachePolicy: requestCachePolicy, + timeoutIntervalForRequest: timeoutIntervalForRequest, + timeoutIntervalForResource: timeoutIntervalForResource, + networkServiceType: networkServiceType, + allowsCellularAccess: allowsCellularAccess, + discretionary: discretionary, + connectionProxyDictionary: connectionProxyDictionary, + httpShouldUsePipelining: httpShouldUsePipelining, + httpShouldSetCookies: httpShouldSetCookies, + httpCookieAcceptPolicy: httpCookieAcceptPolicy, + httpAdditionalHeaders: httpAdditionalHeaders, + httpMaximumConnectionsPerHost: httpMaximumConnectionsPerHost, + httpCookieStorage: httpCookieStorage, + urlCredentialStorage: urlCredentialStorage, + urlCache: urlCache, + shouldUseExtendedBackgroundIdleMode: shouldUseExtendedBackgroundIdleMode, + protocolClasses: protocolClasses) + } + + open class var `default`: URLSessionConfiguration { + return URLSessionConfiguration() + } + open class var ephemeral: URLSessionConfiguration { NSUnimplemented() } + + open class func background(withIdentifier identifier: String) -> URLSessionConfiguration { NSUnimplemented() } + + /* identifier for the background session configuration */ + open var identifier: String? + + /* default cache policy for requests */ + open var requestCachePolicy: NSURLRequest.CachePolicy + + /* default timeout for requests. This will cause a timeout if no data is transmitted for the given timeout value, and is reset whenever data is transmitted. */ + open var timeoutIntervalForRequest: TimeInterval + + /* default timeout for requests. This will cause a timeout if a resource is not able to be retrieved within a given timeout. */ + open var timeoutIntervalForResource: TimeInterval + + /* type of service for requests. */ + open var networkServiceType: NSURLRequest.NetworkServiceType + + /* allow request to route over cellular. */ + open var allowsCellularAccess: Bool + + /* allows background tasks to be scheduled at the discretion of the system for optimal performance. */ + open var discretionary: Bool + + /* The identifier of the shared data container into which files in background sessions should be downloaded. + * App extensions wishing to use background sessions *must* set this property to a valid container identifier, or + * all transfers in that session will fail with NSURLErrorBackgroundSessionRequiresSharedContainer. + */ + open var sharedContainerIdentifier: String? { return nil } + + /* + * Allows the app to be resumed or launched in the background when tasks in background sessions complete + * or when auth is required. This only applies to configurations created with +backgroundSessionConfigurationWithIdentifier: + * and the default value is YES. + */ + + /* The proxy dictionary, as described by */ + open var connectionProxyDictionary: [AnyHashable : Any]? = nil + + // TODO: We don't have the SSLProtocol type from Security + /* + /* The minimum allowable versions of the TLS protocol, from */ + open var TLSMinimumSupportedProtocol: SSLProtocol + + /* The maximum allowable versions of the TLS protocol, from */ + open var TLSMaximumSupportedProtocol: SSLProtocol + */ + + /* Allow the use of HTTP pipelining */ + open var httpShouldUsePipelining: Bool + + /* Allow the session to set cookies on requests */ + open var httpShouldSetCookies: Bool + + /* Policy for accepting cookies. This overrides the policy otherwise specified by the cookie storage. */ + open var httpCookieAcceptPolicy: HTTPCookie.AcceptPolicy + + /* Specifies additional headers which will be set on outgoing requests. + Note that these headers are added to the request only if not already present. */ + open var httpAdditionalHeaders: [AnyHashable : Any]? = nil + + /* The maximum number of simultanous persistent connections per host */ + open var httpMaximumConnectionsPerHost: Int + + /* The cookie storage object to use, or nil to indicate that no cookies should be handled */ + open var httpCookieStorage: HTTPCookieStorage? + + /* The credential storage object, or nil to indicate that no credential storage is to be used */ + open var urlCredentialStorage: URLCredentialStorage? + + /* The URL resource cache, or nil to indicate that no caching is to be performed */ + open var urlCache: URLCache? + + /* Enable extended background idle mode for any tcp sockets created. Enabling this mode asks the system to keep the socket open + * and delay reclaiming it when the process moves to the background (see https://developer.apple.com/library/ios/technotes/tn2277/_index.html) + */ + open var shouldUseExtendedBackgroundIdleMode: Bool + + /* An optional array of Class objects which subclass NSURLProtocol. + The Class will be sent +canInitWithRequest: when determining if + an instance of the class can be used for a given URL scheme. + You should not use +[NSURLProtocol registerClass:], as that + method will register your class with the default session rather + than with an instance of URLSession. + Custom NSURLProtocol subclasses are not available to background + sessions. + */ + open var protocolClasses: [AnyClass]? + +} diff --git a/Foundation/NSURLSession/NSURLSessionDelegate.swift b/Foundation/NSURLSession/NSURLSessionDelegate.swift new file mode 100644 index 0000000000..985e5cd96d --- /dev/null +++ b/Foundation/NSURLSession/NSURLSessionDelegate.swift @@ -0,0 +1,284 @@ +// Foundation/NSURLSession/NSURLSessionDelegate.swift - NSURLSession API +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// URLSession API code. +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + + + +extension URLSession { + /* + * Disposition options for various delegate messages + */ + public enum AuthChallengeDisposition : Int { + + case useCredential /* Use the specified credential, which may be nil */ + case performDefaultHandling /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ + case cancelAuthenticationChallenge /* The entire request will be canceled; the credential parameter is ignored. */ + case rejectProtectionSpace /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */ + } + + public enum ResponseDisposition : Int { + + case cancel /* Cancel the load, this is the same as -[task cancel] */ + case allow /* Allow the load to continue */ + case becomeDownload /* Turn this request into a download */ + case becomeStream /* Turn this task into a stream task */ + } +} + +/* + * URLSessionDelegate specifies the methods that a session delegate + * may respond to. There are both session specific messages (for + * example, connection based auth) as well as task based messages. + */ + +/* + * Messages related to the URL session as a whole + */ +public protocol URLSessionDelegate : NSObjectProtocol { + + /* The last message a session receives. A session will only become + * invalid because of a systemic error or when it has been + * explicitly invalidated, in which case the error parameter will be nil. + */ + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: NSError?) + + /* If implemented, when a connection level authentication challenge + * has occurred, this delegate will be given the opportunity to + * provide authentication credentials to the underlying + * connection. Some types of authentication will apply to more than + * one request on a given connection to a server (SSL Server Trust + * challenges). If this delegate message is not implemented, the + * behavior will be to use the default handling, which may involve user + * interaction. + */ + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) +} + +extension URLSessionDelegate { + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: NSError?) { } + public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } +} + +/* If an application has received an + * -application:handleEventsForBackgroundURLSession:completionHandler: + * message, the session delegate will receive this message to indicate + * that all messages previously enqueued for this session have been + * delivered. At this time it is safe to invoke the previously stored + * completion handler, or to begin any internal updates that will + * result in invoking the completion handler. + */ + +/* + * Messages related to the operation of a specific task. + */ +public protocol URLSessionTaskDelegate : URLSessionDelegate { + + /* An HTTP request is attempting to perform a redirection to a different + * URL. You must invoke the completion routine to allow the + * redirection, allow the redirection with a modified request, or + * pass nil to the completionHandler to cause the body of the redirection + * response to be delivered as the payload of this request. The default + * is to follow redirections. + * + * For tasks in background sessions, redirections will always be followed and this method will not be called. + */ + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: @escaping (NSURLRequest?) -> Void) + + /* The task has received a request specific authentication challenge. + * If this delegate is not implemented, the session specific authentication challenge + * will *NOT* be called and the behavior will be the same as using the default handling + * disposition. + */ + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + + /* Sent if a task requires a new, unopened body stream. This may be + * necessary when authentication has failed for any request that + * involves a body stream. + */ + func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) + + /* Sent periodically to notify the delegate of upload progress. This + * information is also available as properties of the task. + */ + func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) + + /* Sent as the last message related to a specific task. Error may be + * nil, which implies that no error occurred and this task is complete. + */ + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) +} + +extension URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: @escaping (NSURLRequest?) -> Void) { + completionHandler(request) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + completionHandler(.performDefaultHandling, nil) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + completionHandler(nil) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) { } +} + +/* + * Messages related to the operation of a task that delivers data + * directly to the delegate. + */ +public protocol URLSessionDataDelegate : URLSessionTaskDelegate { + + /// The task has received a response and no further messages will be + /// received until the completion block is called. The disposition + /// allows you to cancel a request or to turn a data task into a + /// download task. This delegate message is - if you do not + /// implement it, you can get the response as a property of the task. + /// + /// - Note: This method will not be called for background upload tasks + /// (which cannot be converted to download tasks). + /// - Bug: This will currently not wait for the completion handler to run, + /// and it will ignore anything passed to the completion handler. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) + + /* Notification that a data task has become a download task. No + * future messages will be sent to the data task. + */ + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) + + /* + * Notification that a data task has become a bidirectional stream + * task. No future messages will be sent to the data task. The newly + * created streamTask will carry the original request and response as + * properties. + * + * For requests that were pipelined, the stream object will only allow + * reading, and the object will immediately issue a + * -URLSession:writeClosedForStream:. Pipelining can be disabled for + * all requests in a session, or by the NSURLRequest + * HTTPShouldUsePipelining property. + * + * The underlying connection is no longer considered part of the HTTP + * connection cache and won't count against the total number of + * connections per host. + */ + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome streamTask: URLSessionStreamTask) + + /* Sent when data is available for the delegate to consume. It is + * assumed that the delegate will retain and not copy the data. As + * the data may be discontiguous, you should use + * [Data enumerateByteRangesUsingBlock:] to access it. + */ + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) + + /* Invoke the completion routine with a valid CachedURLResponse to + * allow the resulting data to be cached, or pass nil to prevent + * caching. Note that there is no guarantee that caching will be + * attempted for a given resource, and you should not rely on this + * message to receive the resource data. + */ + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) +} + +extension URLSessionDataDelegate { + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) { } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome streamTask: URLSessionStreamTask) { } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { } +} + +/* + * Messages related to the operation of a task that writes data to a + * file and notifies the delegate upon completion. + */ +public protocol URLSessionDownloadDelegate : URLSessionTaskDelegate { + + /* Sent when a download task that has completed a download. The delegate should + * copy or move the file at the given location to a new location as it will be + * removed when the delegate message returns. URLSession:task:didCompleteWithError: will + * still be called. + */ + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) + + /* Sent periodically to notify the delegate of download progress. */ + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) + + /* Sent when a download has been resumed. If a download failed with an + * error, the -userInfo dictionary of the error will contain an + * URLSessionDownloadTaskResumeData key, whose value is the resume + * data. + */ + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) +} + +extension URLSessionDownloadDelegate { + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { } +} + +public protocol URLSessionStreamDelegate : URLSessionTaskDelegate { + + /* Indiciates that the read side of a connection has been closed. Any + * outstanding reads complete, but future reads will immediately fail. + * This may be sent even when no reads are in progress. However, when + * this delegate message is received, there may still be bytes + * available. You only know that no more bytes are available when you + * are able to read until EOF. */ + func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask) + + /* Indiciates that the write side of a connection has been closed. + * Any outstanding writes complete, but future writes will immediately + * fail. + */ + func urlSession(_ session: URLSession, writeClosedFor streamTask: URLSessionStreamTask) + + /* A notification that the system has determined that a better route + * to the host has been detected (eg, a wi-fi interface becoming + * available.) This is a hint to the delegate that it may be + * desirable to create a new task for subsequent work. Note that + * there is no guarantee that the future task will be able to connect + * to the host, so callers should should be prepared for failure of + * reads and writes over any new interface. */ + func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: URLSessionStreamTask) + + /* The given task has been completed, and unopened NSInputStream and + * NSOutputStream objects are created from the underlying network + * connection. This will only be invoked after all enqueued IO has + * completed (including any necessary handshakes.) The streamTask + * will not receive any further delegate messages. + */ + func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecome inputStream: InputStream, outputStream: NSOutputStream) +} + +extension URLSessionStreamDelegate { + public func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask) { } + + public func urlSession(_ session: URLSession, writeClosedFor streamTask: URLSessionStreamTask) { } + + public func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: URLSessionStreamTask) { } + + public func urlSession(_ session: URLSession, streamTask: URLSessionStreamTask, didBecome inputStream: InputStream, outputStream: NSOutputStream) { } +} diff --git a/Foundation/NSURLSession/NSURLSessionTask.swift b/Foundation/NSURLSession/NSURLSessionTask.swift new file mode 100644 index 0000000000..27be9f909b --- /dev/null +++ b/Foundation/NSURLSession/NSURLSessionTask.swift @@ -0,0 +1,1222 @@ +// Foundation/NSURLSession/NSURLSessionTask.swift - NSURLSession API +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// URLSession API code. +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + + + + +import CoreFoundation +import Dispatch + + +/// A cancelable object that refers to the lifetime +/// of processing a given request. +open class URLSessionTask : NSObject, NSCopying { + /// How many times the task has been suspended, 0 indicating a running task. + fileprivate var suspendCount = 1 + fileprivate var easyHandle: _EasyHandle! + fileprivate var totalDownloaded = 0 + fileprivate unowned let session: URLSessionProtocol + fileprivate let body: _Body + fileprivate let tempFileURL: URL + + /// The internal state that the task is in. + /// + /// Setting this value will also add / remove the easy handle. + /// It is independt of the `state: URLSessionTask.State`. The + /// `internalState` tracks the state of transfers / waiting for callbacks. + /// The `state` tracks the overall state of the task (running vs. + /// completed). + /// - SeeAlso: URLSessionTask._InternalState + fileprivate var internalState = _InternalState.initial { + // We manage adding / removing the easy handle and pausing / unpausing + // here at a centralized place to make sure the internal state always + // matches up with the state of the easy handle being added and paused. + willSet { + if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused { + fatalError("Need to solve pausing receive.") + } + if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle { + session.remove(handle: easyHandle) + } + } + didSet { + if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle { + session.add(handle: easyHandle) + } + if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused { + fatalError("Need to solve pausing receive.") + } + if case .taskCompleted = internalState { + updateTaskState() + } + } + } + /// All operations must run on this queue. + fileprivate let workQueue: DispatchQueue + /// This queue is used to make public attributes thread safe. It's a + /// **concurrent** queue and must be used with a barries when writing. This + /// allows multiple concurrent readers or a single writer. + fileprivate let taskAttributesIsolation: DispatchQueue + + public override init() { + // Darwin Foundation oddly allows calling this initializer, even though + // such a task is quite broken -- it doesn't have a session. And calling + // e.g. `taskIdentifier` will crash. + // + // We set up the bare minimum for init to work, but don't care too much + // about things crashing later. + session = _MissingURLSession() + taskIdentifier = 0 + originalRequest = nil + body = .none + workQueue = DispatchQueue(label: "URLSessionTask.notused.0") + taskAttributesIsolation = DispatchQueue(label: "URLSessionTask.notused.1") + let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp" + _ = FileManager.default.createFile(atPath: fileName, contents: nil) + self.tempFileURL = URL(fileURLWithPath: fileName) + super.init() + } + /// Create a data task, i.e. with no body + internal convenience init(session: URLSession, request: NSURLRequest, taskIdentifier: Int) { + self.init(session: session, request: request, taskIdentifier: taskIdentifier, body: .none) + } + internal init(session: URLSession, request: NSURLRequest, taskIdentifier: Int, body: _Body) { + self.session = session + self.workQueue = session.workQueue + self.taskAttributesIsolation = session.taskAttributesIsolation + self.taskIdentifier = taskIdentifier + self.originalRequest = (request.copy() as! NSURLRequest) + self.body = body + let fileName = NSTemporaryDirectory() + NSUUID().uuidString + ".tmp" + _ = FileManager.default.createFile(atPath: fileName, contents: nil) + self.tempFileURL = URL(fileURLWithPath: fileName) + super.init() + self.easyHandle = _EasyHandle(delegate: self) + } + deinit { + //TODO: Can we ensure this somewhere else? This might run on the wrong + // thread / queue. + //if internalState.isEasyHandleAddedToMultiHandle { + // session.removeHandle(easyHandle) + //} + } + + open override func copy() -> Any { + return copy(with: nil) + } + + open func copy(with zone: NSZone?) -> Any { + NSUnimplemented() + } + + /// An identifier for this task, assigned by and unique to the owning session + open let taskIdentifier: Int + + /// May be nil if this is a stream task + /*@NSCopying*/ open let originalRequest: NSURLRequest? + + /// May differ from originalRequest due to http server redirection + /*@NSCopying*/ open fileprivate(set) var currentRequest: NSURLRequest? { + get { + var r: NSURLRequest? = nil + taskAttributesIsolation.sync { r = self._currentRequest } + return r + } + //TODO: dispatch_barrier_async + set { taskAttributesIsolation.async { self._currentRequest = newValue } } + } + fileprivate var _currentRequest: NSURLRequest? = nil + /*@NSCopying*/ open fileprivate(set) var response: URLResponse? { + get { + var r: URLResponse? = nil + taskAttributesIsolation.sync { r = self._response } + return r + } + set { taskAttributesIsolation.async { self._response = newValue } } + } + fileprivate var _response: URLResponse? = nil + + /* Byte count properties may be zero if no body is expected, + * or URLSessionTransferSizeUnknown if it is not possible + * to know how many bytes will be transferred. + */ + + /// Number of body bytes already received + open fileprivate(set) var countOfBytesReceived: Int64 { + get { + var r: Int64 = 0 + taskAttributesIsolation.sync { r = self._countOfBytesReceived } + return r + } + set { taskAttributesIsolation.async { self._countOfBytesReceived = newValue } } + } + fileprivate var _countOfBytesReceived: Int64 = 0 + + /// Number of body bytes already sent */ + open fileprivate(set) var countOfBytesSent: Int64 { + get { + var r: Int64 = 0 + taskAttributesIsolation.sync { r = self._countOfBytesSent } + return r + } + set { taskAttributesIsolation.async { self._countOfBytesSent = newValue } } + } + + fileprivate var _countOfBytesSent: Int64 = 0 + + /// Number of body bytes we expect to send, derived from the Content-Length of the HTTP request */ + open fileprivate(set) var countOfBytesExpectedToSend: Int64 = 0 + + /// Number of byte bytes we expect to receive, usually derived from the Content-Length header of an HTTP response. */ + open fileprivate(set) var countOfBytesExpectedToReceive: Int64 = 0 + + /// The taskDescription property is available for the developer to + /// provide a descriptive label for the task. + open var taskDescription: String? + + /* -cancel returns immediately, but marks a task as being canceled. + * The task will signal -URLSession:task:didCompleteWithError: with an + * error value of { NSURLErrorDomain, NSURLErrorCancelled }. In some + * cases, the task may signal other work before it acknowledges the + * cancelation. -cancel may be sent to a task that has been suspended. + */ + open func cancel() { NSUnimplemented() } + + /* + * The current state of the task within the session. + */ + open var state: URLSessionTask.State { + get { + var r: URLSessionTask.State = .suspended + taskAttributesIsolation.sync { r = self._state } + return r + } + set { taskAttributesIsolation.async { self._state = newValue } } + } + fileprivate var _state: URLSessionTask.State = .suspended + + /* + * The error, if any, delivered via -URLSession:task:didCompleteWithError: + * This property will be nil in the event that no error occured. + */ + /*@NSCopying*/ open var error: NSError? { NSUnimplemented() } + + /// Suspend the task. + /// + /// Suspending a task will prevent the URLSession from continuing to + /// load data. There may still be delegate calls made on behalf of + /// this task (for instance, to report data received while suspending) + /// but no further transmissions will be made on behalf of the task + /// until -resume is sent. The timeout timer associated with the task + /// will be disabled while a task is suspended. -suspend and -resume are + /// nestable. + open func suspend() { + // suspend / resume is implemented simply by adding / removing the task's + // easy handle fromt he session's multi-handle. + // + // This might result in slightly different behaviour than the Darwin Foundation + // implementation, but it'll be difficult to get complete parity anyhow. + // Too many things depend on timeout on the wire etc. + // + // TODO: It may be worth looking into starting over a task that gets + // resumed. The Darwin Foundation documentation states that that's what + // it does for anything but download tasks. + + // We perform the increment and call to `updateTaskState()` + // synchronous, to make sure the `state` is updated when this method + // returns, but the actual suspend will be done asynchronous to avoid + // dead-locks. + workQueue.sync { + self.suspendCount += 1 + guard self.suspendCount < Int.max else { fatalError("Task suspended too many times \(Int.max).") } + self.updateTaskState() + + if self.suspendCount == 1 { + self.workQueue.async { + self.performSuspend() + } + } + } + } + /// Resume the task. + /// + /// - SeeAlso: `suspend()` + open func resume() { + workQueue.sync { + self.suspendCount -= 1 + guard 0 <= self.suspendCount else { fatalError("Resuming a task that's not suspended. Calls to resume() / suspend() need to be matched.") } + self.updateTaskState() + if self.suspendCount == 0 { + self.workQueue.async { + self.performResume() + } + } + } + } + + /// The priority of the task. + /// + /// Sets a scaling factor for the priority of the task. The scaling factor is a + /// value between 0.0 and 1.0 (inclusive), where 0.0 is considered the lowest + /// priority and 1.0 is considered the highest. + /// + /// The priority is a hint and not a hard requirement of task performance. The + /// priority of a task may be changed using this API at any time, but not all + /// protocols support this; in these cases, the last priority that took effect + /// will be used. + /// + /// If no priority is specified, the task will operate with the default priority + /// as defined by the constant URLSessionTaskPriorityDefault. Two additional + /// priority levels are provided: URLSessionTaskPriorityLow and + /// URLSessionTaskPriorityHigh, but use is not restricted to these. + open var priority: Float { + get { + var r: Float = 0 + taskAttributesIsolation.sync { r = self._priority } + return r + } + set { + taskAttributesIsolation.async { self._priority = newValue } + } + } + fileprivate var _priority: Float = URLSessionTaskPriorityDefault +} + +extension URLSessionTask { + public enum State : Int { + /// The task is currently being serviced by the session + case running + case suspended + /// The task has been told to cancel. The session will receive a URLSession:task:didCompleteWithError: message. + case canceling + /// The task has completed and the session will receive no more delegate notifications + case completed + } +} + +fileprivate extension URLSessionTask { + /// The calls to `suspend` can be nested. This one is only called when the + /// task is not suspended and needs to go into suspended state. + func performSuspend() { + if case .transferInProgress(let transferState) = internalState { + internalState = .transferReady(transferState) + } + } + /// The calls to `resume` can be nested. This one is only called when the + /// task is suspended and needs to go out of suspended state. + func performResume() { + if case .initial = internalState { + guard let r = originalRequest else { fatalError("Task has no original request.") } + startNewTransfer(with: r) + } + if case .transferReady(let transferState) = internalState { + internalState = .transferInProgress(transferState) + } + } +} + +internal extension URLSessionTask { + /// The is independent of the public `state: URLSessionTask.State`. + enum _InternalState { + /// Task has been created, but nothing has been done, yet + case initial + /// The easy handle has been fully configured. But it is not added to + /// the multi handle. + case transferReady(_TransferState) + /// The easy handle is currently added to the multi handle + case transferInProgress(_TransferState) + /// The transfer completed. + /// + /// The easy handle has been removed from the multi handle. This does + /// not (necessarily mean the task completed. A task that gets + /// redirected will do multiple transfers. + case transferCompleted(response: NSHTTPURLResponse, bodyDataDrain: _TransferState._DataDrain) + /// The transfer failed. + /// + /// Same as `.transferCompleted`, but without response / body data + case transferFailed + /// Waiting for the completion handler of the HTTP redirect callback. + /// + /// When we tell the delegate that we're about to perform an HTTP + /// redirect, we need to wait for the delegate to let us know what + /// action to take. + case waitingForRedirectCompletionHandler(response: NSHTTPURLResponse, bodyDataDrain: _TransferState._DataDrain) + /// Waiting for the completion handler of the 'did receive response' callback. + /// + /// When we tell the delegate that we received a response (i.e. when + /// we received a complete header), we need to wait for the delegate to + /// let us know what action to take. In this state the easy handle is + /// paused in order to suspend delegate callbacks. + case waitingForResponseCompletionHandler(_TransferState) + /// The task is completed + /// + /// Contrast this with `.transferCompleted`. + case taskCompleted + } +} + +fileprivate extension URLSessionTask._InternalState { + var isEasyHandleAddedToMultiHandle: Bool { + switch self { + case .initial: return false + case .transferReady: return false + case .transferInProgress: return true + case .transferCompleted: return false + case .transferFailed: return false + case .waitingForRedirectCompletionHandler: return false + case .waitingForResponseCompletionHandler: return true + case .taskCompleted: return false + } + } + var isEasyHandlePaused: Bool { + switch self { + case .initial: return false + case .transferReady: return false + case .transferInProgress: return false + case .transferCompleted: return false + case .transferFailed: return false + case .waitingForRedirectCompletionHandler: return false + case .waitingForResponseCompletionHandler: return true + case .taskCompleted: return false + } + } +} + +internal extension URLSessionTask { + /// Updates the (public) state based on private / internal state. + /// + /// - Note: This must be called on the `workQueue`. + fileprivate func updateTaskState() { + func calculateState() -> URLSessionTask.State { + if case .taskCompleted = internalState { + return .completed + } + if suspendCount == 0 { + return .running + } else { + return .suspended + } + } + state = calculateState() + } +} + +internal extension URLSessionTask { + enum _Body { + case none + case data(DispatchData) + /// Body data is read from the given file URL + case file(URL) + case stream(InputStream) + } +} +fileprivate extension URLSessionTask._Body { + enum _Error : Error { + case fileForBodyDataNotFound + } + /// - Returns: The body length, or `nil` for no body (e.g. `GET` request). + func getBodyLength() throws -> UInt64? { + switch self { + case .none: + return 0 + case .data(let d): + return UInt64(d.count) + /// Body data is read from the given file URL + case .file(let fileURL): + guard let s = try FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as? NSNumber else { + throw _Error.fileForBodyDataNotFound + } + return s.uint64Value + case .stream: + return nil + } + } +} + +/// Easy handle related +fileprivate extension URLSessionTask { + /// Start a new transfer + func startNewTransfer(with request: NSURLRequest) { + currentRequest = request + guard let url = request.url else { fatalError("No URL in request.") } + internalState = .transferReady(createTransferState(url: url)) + configureEasyHandle(for: request) + if suspendCount < 1 { + performResume() + } + } + /// Creates a new transfer state with the given behaviour: + func createTransferState(url: URL) -> URLSessionTask._TransferState { + let drain = createTransferBodyDataDrain() + switch body { + case .none: + return URLSessionTask._TransferState(url: url, bodyDataDrain: drain) + case .data(let data): + let source = _HTTPBodyDataSource(data: data) + return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source) + case .file(let fileURL): + let source = _HTTPBodyFileSource(fileURL: fileURL, workQueue: workQueue, dataAvailableHandler: { [weak self] in + // Unpause the easy handle + self?.easyHandle.unpauseSend() + }) + return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source) + case .stream: + NSUnimplemented() + } + + } + /// The data drain. + /// + /// This depends on what the delegate / completion handler need. + fileprivate func createTransferBodyDataDrain() -> URLSessionTask._TransferState._DataDrain { + switch session.behaviour(for: self) { + case .noDelegate: + return .ignore + case .taskDelegate: + // Data will be forwarded to the delegate as we receive it, we don't + // need to do anything about it. + return .ignore + case .dataCompletionHandler: + // Data needs to be concatenated in-memory such that we can pass it + // to the completion handler upon completion. + return .inMemory(nil) + case .downloadCompletionHandler: + // Data needs to be written to a file (i.e. a download task). + let fileHandle = try! FileHandle(forWritingTo: tempFileURL) + return .toFile(tempFileURL, fileHandle) + } + } + /// Set options on the easy handle to match the given request. + /// + /// This performs a series of `curl_easy_setopt()` calls. + fileprivate func configureEasyHandle(for request: NSURLRequest) { + // At this point we will call the equivalent of curl_easy_setopt() + // to configure everything on the handle. Since we might be re-using + // a handle, we must be sure to set everything and not rely on defaul + // values. + + //TODO: We could add a strong reference from the easy handle back to + // its URLSessionTask by means of CURLOPT_PRIVATE -- that would ensure + // that the task is always around while the handle is running. + // We would have to break that retain cycle once the handle completes + // its transfer. + + // Behavior Options + easyHandle.set(verboseModeOn: enableLibcurlDebugOutput) + easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: self) + easyHandle.set(passHeadersToDataStream: false) + easyHandle.set(progressMeterOff: true) + easyHandle.set(skipAllSignalHandling: true) + + // Error Options: + easyHandle.set(errorBuffer: nil) + easyHandle.set(failOnHTTPErrorCode: false) + + // Network Options: + guard let url = request.url else { fatalError("No URL in request.") } + easyHandle.set(url: url) + easyHandle.setAllowedProtocolsToHTTPAndHTTPS() + easyHandle.set(preferredReceiveBufferSize: Int.max) + do { + switch (body, try body.getBodyLength()) { + case (.none, _): + set(requestBodyLength: .noBody) + case (_, .some(let length)): + set(requestBodyLength: .length(length)) + case (_, .none): + set(requestBodyLength: .unknown) + } + } catch let e { + // Fail the request here. + // TODO: We have multiple options: + // NSURLErrorNoPermissionsToReadFile + // NSURLErrorFileDoesNotExist + internalState = .transferFailed + failWith(errorCode: errorCode(fileSystemError: e), request: request) + return + } + + // HTTP Options: + easyHandle.set(followLocation: false) + easyHandle.set(customHeaders: curlHeaders(for: request)) + + //Options unavailable on Ubuntu 14.04 (libcurl 7.36) + //TODO: Introduce something like an #if + //easyHandle.set(waitForPipeliningAndMultiplexing: true) + //easyHandle.set(streamWeight: priority) + + //set the request timeout + //TODO: the timeout value needs to be reset on every data transfer + let s = session as! URLSession + easyHandle.set(timeout: Int(s.configuration.timeoutIntervalForRequest)) + + easyHandle.set(automaticBodyDecompression: true) + easyHandle.set(requestMethod: request.httpMethod ?? "GET") + if request.httpMethod == "HEAD" { + easyHandle.set(noBody: true) + } + } +} + +fileprivate extension URLSessionTask { + /// These are a list of headers that should be passed to libcurl. + /// + /// Headers will be returned as `Accept: text/html` strings for + /// setting fields, `Accept:` for disabling the libcurl default header, or + /// `Accept;` for a header with no content. This is the format that libcurl + /// expects. + /// + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html + func curlHeaders(for request: NSURLRequest) -> [String] { + var result: [String] = [] + var names = Set() + if let hh = currentRequest?.allHTTPHeaderFields { + hh.forEach { + let name = $0.0.lowercased() + guard !names.contains(name) else { return } + names.insert(name) + + if $0.1.isEmpty { + result.append($0.0 + ";") + } else { + result.append($0.0 + ": " + $0.1) + } + } + } + curlHeadersToSet.forEach { + let name = $0.0.lowercased() + guard !names.contains(name) else { return } + names.insert(name) + + if $0.1.isEmpty { + result.append($0.0 + ";") + } else { + result.append($0.0 + ": " + $0.1) + } + } + curlHeadersToRemove.forEach { + let name = $0.lowercased() + guard !names.contains(name) else { return } + names.insert(name) + result.append($0 + ":") + } + return result + } + /// Any header values that should be passed to libcurl + /// + /// These will only be set if not already part of the request. + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html + var curlHeadersToSet: [(String,String)] { + var result = [("Connection", "keep-alive"), + ("User-Agent", userAgentString), + ] + if let language = NSLocale.current.languageCode { + result.append(("Accept-Language", language)) + } + return result + } + /// Any header values that should be removed from the ones set by libcurl + /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html + var curlHeadersToRemove: [String] { + if case .none = body { + return [] + } else { + return ["Expect"] + } + } +} + +fileprivate var userAgentString: String = { + // Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)" + let info = ProcessInfo.processInfo + let name = info.processName + let curlVersion = CFURLSessionCurlVersionInfo() + //TODO: Should probably use sysctl(3) to get these: + // kern.ostype: Darwin + // kern.osrelease: 15.4.0 + //TODO: Use NSBundle to get the version number? + return "\(name) (unknown version) curl/\(curlVersion.major).\(curlVersion.minor).\(curlVersion.patch)" +}() + +fileprivate func errorCode(fileSystemError error: Error) -> Int { + func fromCocoaErrorCode(_ code: Int) -> Int { + switch code { + case NSCocoaError.FileReadNoSuchFileError.rawValue: + return NSURLErrorFileDoesNotExist + case NSCocoaError.FileReadNoPermissionError.rawValue: + return NSURLErrorNoPermissionsToReadFile + default: + return NSURLErrorUnknown + } + } + switch error { + case let e as NSError where e.domain == NSCocoaErrorDomain: + return fromCocoaErrorCode(e.code) + default: + return NSURLErrorUnknown + } +} + +fileprivate extension URLSessionTask { + /// Set request body length. + /// + /// An unknown length + func set(requestBodyLength length: URLSessionTask._RequestBodyLength) { + switch length { + case .noBody: + easyHandle.set(upload: false) + easyHandle.set(requestBodyLength: 0) + case .length(let length): + easyHandle.set(upload: true) + easyHandle.set(requestBodyLength: Int64(length)) + case .unknown: + easyHandle.set(upload: true) + easyHandle.set(requestBodyLength: -1) + } + } + enum _RequestBodyLength { + case noBody + /// + case length(UInt64) + /// Will result in a chunked upload + case unknown + } +} + +extension URLSessionTask: _EasyHandleDelegate { + func didReceive(data: Data) -> _EasyHandle._Action { + guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") } + guard ts.isHeaderComplete else { fatalError("Received body data, but the header is not complete, yet.") } + notifyDelegate(aboutReceivedData: data) + internalState = .transferInProgress(ts.byAppending(bodyData: data)) + return .proceed + } + + fileprivate func notifyDelegate(aboutReceivedData data: Data) { + if case .taskDelegate(let delegate) = session.behaviour(for: self), + let dataDelegate = delegate as? URLSessionDataDelegate, + let task = self as? URLSessionDataTask { + // Forward to the delegate: + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + dataDelegate.urlSession(s, dataTask: task, didReceive: data) + } + } else if case .taskDelegate(let delegate) = session.behaviour(for: self), + let downloadDelegate = delegate as? URLSessionDownloadDelegate, + let task = self as? URLSessionDownloadTask { + guard let s = session as? URLSession else { fatalError() } + let fileHandle = try! FileHandle(forWritingTo: tempFileURL) + _ = fileHandle.seekToEndOfFile() + fileHandle.write(data) + self.totalDownloaded += data.count + + s.delegateQueue.addOperation { + downloadDelegate.urlSession(s, downloadTask: task, didWriteData: Int64(data.count), totalBytesWritten: Int64(self.totalDownloaded), + totalBytesExpectedToWrite: Int64(self.easyHandle.fileLength)) + } + if Int(self.easyHandle.fileLength) == totalDownloaded { + fileHandle.closeFile() + s.delegateQueue.addOperation { + downloadDelegate.urlSession(s, downloadTask: task, didFinishDownloadingTo: self.tempFileURL) + } + } + + } + } + + func didReceive(headerData data: Data) -> _EasyHandle._Action { + guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") } + do { + let newTS = try ts.byAppending(headerLine: data) + internalState = .transferInProgress(newTS) + let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete + if didCompleteHeader { + // The header is now complete, but wasn't before. + didReceiveResponse() + } + return .proceed + } catch { + return .abort + } + } + + func fill(writeBuffer buffer: UnsafeMutableBufferPointer) -> URLSessionTask._EasyHandle._WriteBufferResult { + guard case .transferInProgress(let ts) = internalState else { fatalError("Requested to fill write buffer, but transfer isn't in progress.") } + guard let source = ts.requestBodySource else { fatalError("Requested to fill write buffer, but transfer state has no body source.") } + switch source.getNextChunk(withLength: buffer.count) { + case .data(let data): + copyDispatchData(data, infoBuffer: buffer) + let count = data.count + assert(count > 0) + return .bytes(count) + case .done: + return .bytes(0) + case .retryLater: + // At this point we'll try to pause the easy handle. The body source + // is responsible for un-pausing the handle once data becomes + // available. + return .pause + case .error: + return .abort + } + } + + func transferCompleted(withErrorCode errorCode: Int?) { + // At this point the transfer is complete and we can decide what to do. + // If everything went well, we will simply forward the resulting data + // to the delegate. But in case of redirects etc. we might send another + // request. + guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer completed, but it wasn't in progress.") } + guard let request = currentRequest else { fatalError("Transfer completed, but there's no currect request.") } + guard errorCode == nil else { + internalState = .transferFailed + failWith(errorCode: errorCode!, request: request) + return + } + + guard let response = ts.response else { fatalError("Transfer completed, but there's no response.") } + internalState = .transferCompleted(response: response, bodyDataDrain: ts.bodyDataDrain) + + let action = completionAction(forCompletedRequest: request, response: response) + switch action { + case .completeTask: + completeTask() + case .failWithError(let errorCode): + internalState = .transferFailed + failWith(errorCode: errorCode, request: request) + case .redirectWithRequest(let newRequest): + redirectFor(request: newRequest) + } + } + func seekInputStream(to position: UInt64) throws { + // We will reset the body sourse and seek forward. + NSUnimplemented() + } + func updateProgressMeter(with propgress: URLSessionTask._EasyHandle._Progress) { + //TODO: Update progress. Note that a single URLSessionTask might + // perform multiple transfers. The values in `progress` are only for + // the current transfer. + } +} + +/// State Transfers +extension URLSessionTask { + func completeTask() { + guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else { + fatalError("Trying to complete the task, but its transfer isn't complete.") + } + internalState = .taskCompleted + self.response = response + switch session.behaviour(for: self) { + case .taskDelegate(let delegate): + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + delegate.urlSession(s, task: self, didCompleteWithError: nil) + } + case .noDelegate: + break + case .dataCompletionHandler(let completion): + guard case .inMemory(let bodyData) = bodyDataDrain else { + fatalError("Task has data completion handler, but data drain is not in-memory.") + } + guard let s = session as? URLSession else { fatalError() } + + var data = Data(capacity: bodyData!.length) + data.append(Data(bytes: bodyData!.bytes, count: bodyData!.length)) + + s.delegateQueue.addOperation { + completion(data, response, nil) + } + case .downloadCompletionHandler(let completion): + guard case .toFile(let url, let fileHandle?) = bodyDataDrain else { + fatalError("Task has data completion handler, but data drain is not a file handle.") + } + + guard let s = session as? URLSession else { fatalError() } + //The contents are already written, just close the file handle and call the handler + fileHandle.closeFile() + + s.delegateQueue.addOperation { + completion(url, response, nil) + } + + } + } + func completeTask(withError error: NSError) { + guard case .transferFailed = internalState else { + fatalError("Trying to complete the task, but its transfer isn't complete / failed.") + } + internalState = .taskCompleted + switch session.behaviour(for: self) { + case .taskDelegate(let delegate): + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + delegate.urlSession(s, task: self, didCompleteWithError: error) + } + case .noDelegate: + break + case .dataCompletionHandler(let completion): + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + completion(nil, nil, error) + } + case .downloadCompletionHandler(let completion): + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + completion(nil, nil, error) + } + } + } + func failWith(errorCode: Int, request: NSURLRequest) { + //TODO: Error handling + let userInfo: [String : Any]? = request.url.map { + [ + NSURLErrorFailingURLErrorKey: $0, + NSURLErrorFailingURLStringErrorKey: $0.absoluteString, + ] + } + let error = NSError(domain: NSURLErrorDomain, code: errorCode, userInfo: userInfo) + completeTask(withError: error) + } + func redirectFor(request: NSURLRequest) { + //TODO: Should keep track of the number of redirects that this + // request has gone through and err out once it's too large, i.e. + // call into `failWith(errorCode: )` with NSURLErrorHTTPTooManyRedirects + guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else { + fatalError("Trying to redirect, but the transfer is not complete.") + } + + switch session.behaviour(for: self) { + case .taskDelegate(let delegate): + // At this point we need to change the internal state to note + // that we're waiting for the delegate to call the completion + // handler. Then we'll call the delegate callback + // (willPerformHTTPRedirection). The task will then switch out of + // its internal state once the delegate calls the completion + // handler. + + //TODO: Should the `public response: URLResponse` property be updated + // before we call delegate API + // `func urlSession(session: session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void)` + // ? + + internalState = .waitingForRedirectCompletionHandler(response: response, bodyDataDrain: bodyDataDrain) + // We need this ugly cast in order to be able to support `URLSessionTask.init()` + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + delegate.urlSession(s, task: self, willPerformHTTPRedirection: response, newRequest: request) { [weak self] (request: NSURLRequest?) in + guard let task = self else { return } + task.workQueue.async { + task.didCompleteRedirectCallback(request) + } + } + } + case .noDelegate, .dataCompletionHandler, .downloadCompletionHandler: + // Follow the redirect. + startNewTransfer(with: request) + } + } + fileprivate func didCompleteRedirectCallback(_ request: NSURLRequest?) { + guard case .waitingForRedirectCompletionHandler(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else { + fatalError("Received callback for HTTP redirection, but we're not waiting for it. Was it called multiple times?") + } + // If the request is `nil`, we're supposed to treat the current response + // as the final response, i.e. not do any redirection. + // Otherwise, we'll start a new transfer with the passed in request. + if let r = request { + startNewTransfer(with: r) + } else { + internalState = .transferCompleted(response: response, bodyDataDrain: bodyDataDrain) + completeTask() + } + } +} + + +/// Response processing +fileprivate extension URLSessionTask { + /// Whenever we receive a response (i.e. a complete header) from libcurl, + /// this method gets called. + func didReceiveResponse() { + guard let dt = self as? URLSessionDataTask else { return } + guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") } + guard let response = ts.response else { fatalError("Header complete, but not URL response.") } + switch session.behaviour(for: self) { + case .noDelegate: + break + case .taskDelegate(let delegate as URLSessionDataDelegate): + //TODO: There's a problem with libcurl / with how we're using it. + // We're currently unable to pause the transfer / the easy handle: + // https://curl.haxx.se/mail/lib-2016-03/0222.html + // + // For now, we'll notify the delegate, but won't pause the transfer, + // and we'll disregard the completion handler: + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in + print("warning: Ignoring dispotion from completion handler.") + }) + } + case .taskDelegate: + break + case .dataCompletionHandler: + break + case .downloadCompletionHandler: + break + } + } + /// Give the delegate a chance to tell us how to proceed once we have a + /// response / complete header. + /// + /// This will pause the transfer. + func askDelegateHowToProceedAfterCompleteResponse(_ response: NSHTTPURLResponse, delegate: URLSessionDataDelegate) { + // Ask the delegate how to proceed. + + // This will pause the easy handle. We need to wait for the + // delegate before processing any more data. + guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") } + internalState = .waitingForResponseCompletionHandler(ts) + + let dt = self as! URLSessionDataTask + + // We need this ugly cast in order to be able to support `URLSessionTask.init()` + guard let s = session as? URLSession else { fatalError() } + s.delegateQueue.addOperation { + delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { [weak self] disposition in + guard let task = self else { return } + task.workQueue.async { + task.didCompleteResponseCallback(disposition: disposition) + } + }) + } + } + /// This gets called (indirectly) when the data task delegates lets us know + /// how we should proceed after receiving a response (i.e. complete header). + func didCompleteResponseCallback(disposition: URLSession.ResponseDisposition) { + guard case .waitingForResponseCompletionHandler(let ts) = internalState else { fatalError("Received response disposition, but we're not waiting for it.") } + switch disposition { + case .cancel: + //TODO: Fail the task with NSURLErrorCancelled + NSUnimplemented() + case .allow: + // Continue the transfer. This will unpause the easy handle. + internalState = .transferInProgress(ts) + case .becomeDownload: + /* Turn this request into a download */ + NSUnimplemented() + case .becomeStream: + /* Turn this task into a stream task */ + NSUnimplemented() + } + } + + /// Action to be taken after a transfer completes + enum _CompletionAction { + case completeTask + case failWithError(Int) + case redirectWithRequest(NSURLRequest) + } + + /// What action to take + func completionAction(forCompletedRequest request: NSURLRequest, response: NSHTTPURLResponse) -> _CompletionAction { + // Redirect: + if let request = redirectRequest(for: response, fromRequest: request) { + return .redirectWithRequest(request) + } + return .completeTask + } + /// If the response is a redirect, return the new request + /// + /// RFC 7231 section 6.4 defines redirection behavior for HTTP/1.1 + /// + /// - SeeAlso: + func redirectRequest(for response: NSHTTPURLResponse, fromRequest: NSURLRequest) -> NSURLRequest? { + //TODO: Do we ever want to redirect for HEAD requests? + func methodAndURL() -> (String, URL)? { + guard + let location = response.value(forHeaderField: .location), + let targetURL = URL(string: location) + else { + // Can't redirect when there's no location to redirect to. + return nil + } + + // Check for a redirect: + switch response.statusCode { + //TODO: Should we do this for 300 "Multiple Choices", too? + case 301, 302, 303: + // Change into "GET": + return ("GET", targetURL) + case 307: + // Re-use existing method: + return (fromRequest.httpMethod ?? "GET", targetURL) + default: + return nil + } + } + guard let (method, targetURL) = methodAndURL() else { return nil } + let request = fromRequest.mutableCopy() as! NSMutableURLRequest + request.httpMethod = method + request.url = targetURL + return request + } +} + + +fileprivate extension NSHTTPURLResponse { + /// Type safe HTTP header field name(s) + enum _Field: String { + /// `Location` + /// - SeeAlso: RFC 2616 section 14.30 + case location = "Location" + } + func value(forHeaderField field: _Field) -> String? { + return field.rawValue + } +} + +public let URLSessionTaskPriorityDefault: Float = 0.5 +public let URLSessionTaskPriorityLow: Float = 0.25 +public let URLSessionTaskPriorityHigh: Float = 0.75 + +/* + * An URLSessionDataTask does not provide any additional + * functionality over an URLSessionTask and its presence is merely + * to provide lexical differentiation from download and upload tasks. + */ +open class URLSessionDataTask : URLSessionTask { +} + +/* + * An URLSessionUploadTask does not currently provide any additional + * functionality over an URLSessionDataTask. All delegate messages + * that may be sent referencing an URLSessionDataTask equally apply + * to URLSessionUploadTasks. + */ +open class URLSessionUploadTask : URLSessionDataTask { +} + +/* + * URLSessionDownloadTask is a task that represents a download to + * local storage. + */ +open class URLSessionDownloadTask : URLSessionTask { + + internal var fileLength = -1.0 + + /* Cancel the download (and calls the superclass -cancel). If + * conditions will allow for resuming the download in the future, the + * callback will be called with an opaque data blob, which may be used + * with -downloadTaskWithResumeData: to attempt to resume the download. + * If resume data cannot be created, the completion handler will be + * called with nil resumeData. + */ + open func cancel(byProducingResumeData completionHandler: (NSData?) -> Void) { NSUnimplemented() } +} + +/* + * An URLSessionStreamTask provides an interface to perform reads + * and writes to a TCP/IP stream created via URLSession. This task + * may be explicitly created from an URLSession, or created as a + * result of the appropriate disposition response to a + * -URLSession:dataTask:didReceiveResponse: delegate message. + * + * URLSessionStreamTask can be used to perform asynchronous reads + * and writes. Reads and writes are enquened and executed serially, + * with the completion handler being invoked on the sessions delegate + * queuee. If an error occurs, or the task is canceled, all + * outstanding read and write calls will have their completion + * handlers invoked with an appropriate error. + * + * It is also possible to create NSInputStream and NSOutputStream + * instances from an URLSessionTask by sending + * -captureStreams to the task. All outstanding read and writess are + * completed before the streams are created. Once the streams are + * delivered to the session delegate, the task is considered complete + * and will receive no more messsages. These streams are + * disassociated from the underlying session. + */ + +open class URLSessionStreamTask : URLSessionTask { + + /* Read minBytes, or at most maxBytes bytes and invoke the completion + * handler on the sessions delegate queue with the data or an error. + * If an error occurs, any outstanding reads will also fail, and new + * read requests will error out immediately. + */ + open func readData(ofMinLength minBytes: Int, maxLength maxBytes: Int, timeout: TimeInterval, completionHandler: (NSData?, Bool, NSError?) -> Void) { NSUnimplemented() } + + /* Write the data completely to the underlying socket. If all the + * bytes have not been written by the timeout, a timeout error will + * occur. Note that invocation of the completion handler does not + * guarantee that the remote side has received all the bytes, only + * that they have been written to the kernel. */ + open func write(data: NSData, timeout: TimeInterval, completionHandler: (NSError?) -> Void) { NSUnimplemented() } + + /* -captureStreams completes any already enqueued reads + * and writes, and then invokes the + * URLSession:streamTask:didBecomeInputStream:outputStream: delegate + * message. When that message is received, the task object is + * considered completed and will not receive any more delegate + * messages. */ + open func captureStreams() { NSUnimplemented() } + + /* Enqueue a request to close the write end of the underlying socket. + * All outstanding IO will complete before the write side of the + * socket is closed. The server, however, may continue to write bytes + * back to the client, so best practice is to continue reading from + * the server until you receive EOF. + */ + open func closeWrite() { NSUnimplemented() } + + /* Enqueue a request to close the read side of the underlying socket. + * All outstanding IO will complete before the read side is closed. + * You may continue writing to the server. + */ + open func closeRead() { NSUnimplemented() } + + /* + * Begin encrypted handshake. The hanshake begins after all pending + * IO has completed. TLS authentication callbacks are sent to the + * session's -URLSession:task:didReceiveChallenge:completionHandler: + */ + open func startSecureConnection() { NSUnimplemented() } + + /* + * Cleanly close a secure connection after all pending secure IO has + * completed. + */ + open func stopSecureConnection() { NSUnimplemented() } +} + +/* Key in the userInfo dictionary of an NSError received during a failed download. */ +public let URLSessionDownloadTaskResumeData: String = "" // NSUnimplemented + + +extension URLSession { + static func printDebug(_ text: @autoclosure () -> String) { + guard enableDebugOutput else { return } + debugPrint(text()) + } +} + +fileprivate let enableLibcurlDebugOutput: Bool = { + return (ProcessInfo.processInfo.environment["URLSessionDebugLibcurl"] != nil) +}() +fileprivate let enableDebugOutput: Bool = { + return (ProcessInfo.processInfo.environment["URLSessionDebug"] != nil) +}() diff --git a/Foundation/NSURLSession/TaskRegistry.swift b/Foundation/NSURLSession/TaskRegistry.swift new file mode 100644 index 0000000000..2731d783f1 --- /dev/null +++ b/Foundation/NSURLSession/TaskRegistry.swift @@ -0,0 +1,95 @@ +// Foundation/NSURLSession/TaskRegistry.swift - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + +import CoreFoundation + + +extension URLSession { + /// This helper class keeps track of all tasks, and their behaviours. + /// + /// Each `URLSession` has a `TaskRegistry` for its running tasks. The + /// *behaviour* defines what action is to be taken e.g. upon completion. + /// The behaviour stores the completion handler for tasks that are + /// completion handler based. + /// + /// - Note: This must **only** be accessed on the owning session's work queue. + class _TaskRegistry { + /// Completion handler for `URLSessionDataTask`, and `URLSessionUploadTask`. + typealias DataTaskCompletion = (Data?, URLResponse?, NSError?) -> Void + /// Completion handler for `URLSessionDownloadTask`. + typealias DownloadTaskCompletion = (URL?, URLResponse?, NSError?) -> Void + /// What to do upon events (such as completion) of a specific task. + enum _Behaviour { + /// Call the `URLSession`s delegate + case callDelegate + /// Default action for all events, except for completion. + case dataCompletionHandler(DataTaskCompletion) + /// Default action for all events, except for completion. + case downloadCompletionHandler(DownloadTaskCompletion) + } + + fileprivate var tasks: [Int: URLSessionTask] = [:] + fileprivate var behaviours: [Int: _Behaviour] = [:] + } +} + +extension URLSession._TaskRegistry { + /// Add a task + /// + /// - Note: This must **only** be accessed on the owning session's work queue. + func add(_ task: URLSessionTask, behaviour: _Behaviour) { + let identifier = task.taskIdentifier + guard identifier != 0 else { fatalError("Invalid task identifier") } + guard tasks.index(forKey: identifier) == nil else { + if tasks[identifier] === task { + fatalError("Trying to re-insert a task that's already in the registry.") + } else { + fatalError("Trying to insert a task, but a different task with the same identifier is already in the registry.") + } + } + tasks[identifier] = task + behaviours[identifier] = behaviour + } + /// Remove a task + /// + /// - Note: This must **only** be accessed on the owning session's work queue. + func remove(_ task: URLSessionTask) { + let identifier = task.taskIdentifier + guard identifier != 0 else { fatalError("Invalid task identifier") } + guard let tasksIdx = tasks.index(forKey: identifier) else { + fatalError("Trying to remove task, but it's not in the registry.") + } + tasks.remove(at: tasksIdx) + guard let behaviourIdx = behaviours.index(forKey: identifier) else { + fatalError("Trying to remove task's behaviour, but it's not in the registry.") + } + behaviours.remove(at: behaviourIdx) + } +} +extension URLSession._TaskRegistry { + /// The behaviour that's registered for the given task. + /// + /// - Note: It is a programming error to pass a task that isn't registered. + /// - Note: This must **only** be accessed on the owning session's work queue. + func behaviour(for task: URLSessionTask) -> _Behaviour { + guard let b = behaviours[task.taskIdentifier] else { + fatalError("Trying to access a behaviour for a task that in not in the registry.") + } + return b + } +} diff --git a/Foundation/NSURLSession/TransferState.swift b/Foundation/NSURLSession/TransferState.swift new file mode 100644 index 0000000000..23b6052d49 --- /dev/null +++ b/Foundation/NSURLSession/TransferState.swift @@ -0,0 +1,137 @@ +// Foundation/NSURLSession/TransferState.swift - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// The state of a single transfer. +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + +import CoreFoundation + + + +extension URLSessionTask { + /// State related to an ongoing transfer. + /// + /// This contains headers received so far, body data received so far, etc. + /// + /// There's a strict 1-to-1 relationship between an `EasyHandle` and a + /// `TransferState`. + /// + /// - TODO: Might move the `EasyHandle` into this `struct` ? + /// - SeeAlso: `URLSessionTask.EasyHandle` + internal struct _TransferState { + /// The URL that's being requested + let url: URL + /// Raw headers received. + let parsedResponseHeader: _ParsedResponseHeader + /// Once the headers is complete, this will contain the response + let response: NSHTTPURLResponse? + /// The body data to be sent in the request + let requestBodySource: _HTTPBodySource? + /// Body data received + let bodyDataDrain: _DataDrain + /// Describes what to do with received body data for this transfer: + enum _DataDrain { + /// Concatenate in-memory + case inMemory(NSMutableData?) + /// Write to file + case toFile(URL, FileHandle?) + /// Do nothing. Might be forwarded to delegate + case ignore + } + } +} + + + +extension URLSessionTask._TransferState { + /// Transfer state that can receive body data, but will not send body data. + init(url: URL, bodyDataDrain: _DataDrain) { + self.url = url + self.parsedResponseHeader = URLSessionTask._ParsedResponseHeader() + self.response = nil + self.requestBodySource = nil + self.bodyDataDrain = bodyDataDrain + } + /// Transfer state that sends body data and can receive body data. + init(url: URL, bodyDataDrain: _DataDrain, bodySource: _HTTPBodySource) { + self.url = url + self.parsedResponseHeader = URLSessionTask._ParsedResponseHeader() + self.response = nil + self.requestBodySource = bodySource + self.bodyDataDrain = bodyDataDrain + } +} + +extension URLSessionTask._TransferState { + enum _Error: Error { + case parseSingleLineError + case parseCompleteHeaderError + } + /// Appends a header line + /// + /// Will set the complete response once the header is complete, i.e. the + /// return value's `isHeaderComplete` will then by `true`. + /// + /// - Throws: When a parsing error occurs + func byAppending(headerLine data: Data) throws -> URLSessionTask._TransferState { + guard let h = parsedResponseHeader.byAppending(headerLine: data) else { + throw _Error.parseSingleLineError + } + if case .complete(let lines) = h { + // Header is complete + let response = lines.createHTTPURLResponse(for: url) + guard response != nil else { + throw _Error.parseCompleteHeaderError + } + return URLSessionTask._TransferState(url: url, parsedResponseHeader: URLSessionTask._ParsedResponseHeader(), response: response, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain) + } else { + return URLSessionTask._TransferState(url: url, parsedResponseHeader: h, response: nil, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain) + } + } + var isHeaderComplete: Bool { + return response != nil + } + /// Append body data + /// + /// - Important: This will mutate the existing `NSMutableData` that the + /// struct may already have in place -- copying the data is too + /// expensive. This behaviour + func byAppending(bodyData buffer: Data) -> URLSessionTask._TransferState { + switch bodyDataDrain { + case .inMemory(let bodyData): + let data: NSMutableData = bodyData ?? NSMutableData() + data.append(buffer) + let drain = _DataDrain.inMemory(data) + return URLSessionTask._TransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: requestBodySource, bodyDataDrain: drain) + case .toFile(_, let fileHandle): + //TODO: Create / open the file for writing + // Append to the file + _ = fileHandle!.seekToEndOfFile() + fileHandle!.write(buffer) + return self + case .ignore: + return self + } + } + /// Sets the given body source on the transfer state. + /// + /// This can be used to either set the initial body source, or to reset it + /// e.g. when restarting a transfer. + func bySetting(bodySource newSource: _HTTPBodySource) -> URLSessionTask._TransferState { + return URLSessionTask._TransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: newSource, bodyDataDrain: bodyDataDrain) + } +} + diff --git a/Foundation/NSURLSession/libcurlHelpers.swift b/Foundation/NSURLSession/libcurlHelpers.swift new file mode 100644 index 0000000000..514aef15e3 --- /dev/null +++ b/Foundation/NSURLSession/libcurlHelpers.swift @@ -0,0 +1,50 @@ +// Foundation/NSURLSession/libcurlHelpers - NSURLSession & libcurl +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +// ----------------------------------------------------------------------------- +/// +/// These are libcurl helpers for the URLSession API code. +/// - SeeAlso: https://curl.haxx.se/libcurl/c/ +/// - SeeAlso: NSURLSession.swift +/// +// ----------------------------------------------------------------------------- + + +import CoreFoundation + + +//TODO: Move things in this file? + + +internal func initializeLibcurl() { + try! CFURLSessionInit().asError() +} + + +internal extension String { + /// Create a string by a buffer of UTF 8 code points that is not zero + /// terminated. + init?(utf8Buffer: UnsafeBufferPointer) { + var bufferIterator = utf8Buffer.makeIterator() + var codec = UTF8() + var result: String = "" + iter: repeat { + switch codec.decode(&bufferIterator) { + case .scalarValue(let scalar): + result.append(String(describing: scalar)) + case .error: + return nil + case .emptyInput: + break iter + } + } while true + self.init(stringLiteral: result) + } +} diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift new file mode 100644 index 0000000000..2c7c9652a8 --- /dev/null +++ b/TestFoundation/TestNSURLSession.swift @@ -0,0 +1,269 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// + + +#if DEPLOYMENT_RUNTIME_OBJC || os(Linux) +import Foundation +import XCTest +#else +import SwiftFoundation +import SwiftXCTest +#endif + +class TestURLSession : XCTestCase { + + static var allTests: [(String, (TestURLSession) -> () throws -> Void)] { + return [ + ("test_dataTaskWithURL", test_dataTaskWithURL), + ("test_dataTaskWithURLRequest", test_dataTaskWithURLRequest), + ("test_dataTaskWithURLCompletionHandler", test_dataTaskWithURLCompletionHandler), + ("test_dataTaskWithURLRequestCompletionHandler", test_dataTaskWithURLRequestCompletionHandler), + ("test_downloadTaskWithURL", test_downloadTaskWithURL), + ("test_downloadTaskWithURLRequest", test_downloadTaskWithURLRequest), + ("test_downloadTaskWithRequestAndHandler", test_downloadTaskWithRequestAndHandler), + ("test_downloadTaskWithURLAndHandler", test_downloadTaskWithURLAndHandler), + + ] + } + + func test_dataTaskWithURL() { + let urlString = "https://restcountries.eu/rest/v1/name/Nepal?fullText=true" + let url = URL(string: urlString)! + let d = DataTask(with: expectation(description: "data task")) + d.run(with: url) + waitForExpectations(timeout: 12) + if !d.error { + XCTAssertEqual(d.capital, "Kathmandu", "test_dataTaskWithURLRequest returned an unexpected result") + } + } + + func test_dataTaskWithURLCompletionHandler() { + let urlString = "https://restcountries.eu/rest/v1/name/USA?fullText=true" + let url = URL(string: urlString)! + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler") + var expectedResult = "unknown" + let task = session.dataTask(with: url) { data, response, error in + if let e = error { + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + expect.fulfill() + return + } + + let httpResponse = response as! NSHTTPURLResponse? + XCTAssertEqual(200, httpResponse!.statusCode, "HTTP response code is not 200") + do { + let json = try JSONSerialization.jsonObject(with: data!, options: []) + let arr = json as? Array + let first = arr![0] + let result = first as? [String : Any] + expectedResult = result!["capital"] as! String + } catch { } + XCTAssertEqual("Washington D.C.", expectedResult, "Did not receive expected value") + expect.fulfill() + } + task.resume() + waitForExpectations(timeout: 12) + } + + func test_dataTaskWithURLRequest() { + let urlString = "https://restcountries.eu/rest/v1/name/Peru?fullText=true" + let urlRequest = NSURLRequest(url: URL(string: urlString)!) + let d = DataTask(with: expectation(description: "data task")) + d.run(with: urlRequest) + waitForExpectations(timeout: 12) + if !d.error { + XCTAssertEqual(d.capital, "Lima", "test_dataTaskWithURLRequest returned an unexpected result") + } + } + + func test_dataTaskWithURLRequestCompletionHandler() { + let urlString = "https://restcountries.eu/rest/v1/name/Italy?fullText=true" + let urlRequest = NSURLRequest(url: URL(string: urlString)!) + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler") + var expectedResult = "unknown" + let task = session.dataTask(with: urlRequest) { data, response, error in + if let e = error { + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + expect.fulfill() + return + } + let httpResponse = response as! NSHTTPURLResponse? + XCTAssertEqual(200, httpResponse!.statusCode, "HTTP response code is not 200") + do { + let json = try JSONSerialization.jsonObject(with: data!, options: []) + let arr = json as? Array + let first = arr![0] + let result = first as? [String : Any] + expectedResult = result!["capital"] as! String + } catch { } + XCTAssertEqual("Rome", expectedResult, "Did not receive expected value") + expect.fulfill() + } + task.resume() + waitForExpectations(timeout: 12) + } + + func test_downloadTaskWithURL() { + let urlString = "https://swift.org/LICENSE.txt" + let url = URL(string: urlString)! + let d = DownloadTask(with: expectation(description: "download task with delegate")) + d.run(with: url) + waitForExpectations(timeout: 12) + } + + func test_downloadTaskWithURLRequest() { + let urlString = "https://swift.org/LICENSE.txt" + let urlRequest = NSURLRequest(url: URL(string: urlString)!) + let d = DownloadTask(with: expectation(description: "download task with delegate")) + d.run(with: urlRequest) + waitForExpectations(timeout: 12) + } + + func test_downloadTaskWithRequestAndHandler() { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "download task with handler") + let req = NSMutableURLRequest(url: URL(string: "https://swift.org/LICENSE.txt")!) + let task = session.downloadTask(with: req) { (_, _, error) -> Void in + if let e = error { + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + } + expect.fulfill() + } + task.resume() + waitForExpectations(timeout: 12) + } + + func test_downloadTaskWithURLAndHandler() { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "download task with handler") + let req = NSMutableURLRequest(url: URL(string: "https://swift.org/LICENSE.txt")!) + let task = session.downloadTask(with: req) { (_, _, error) -> Void in + if let e = error { + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + } + expect.fulfill() + } + task.resume() + waitForExpectations(timeout: 12) + } +} + +class DataTask: NSObject { + let dataTaskExpectation: XCTestExpectation! + var capital = "unknown" + var session: URLSession! = nil + var task: URLSessionDataTask! = nil + public var error = false + + init(with expectation: XCTestExpectation) { + dataTaskExpectation = expectation + } + + func run(with request: NSURLRequest) { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + session = URLSession(configuration: config, delegate: self, delegateQueue: nil) + task = session.dataTask(with: request) + task.resume() + } + + func run(with url: URL) { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + session = URLSession(configuration: config, delegate: self, delegateQueue: nil) + task = session.dataTask(with: url) + task.resume() + } +} + +extension DataTask : URLSessionDataDelegate { + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + do { + let json = try JSONSerialization.jsonObject(with: data, options: []) + let arr = json as? Array + let first = arr![0] + let result = first as? [String : Any] + capital = result!["capital"] as! String + } catch { } + + dataTaskExpectation.fulfill() + } +} + +extension DataTask : URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) { + guard let e = error else { return } + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + dataTaskExpectation.fulfill() + self.error = true + } +} + +class DownloadTask : NSObject { + var totalBytesWritten: Int64 = 0 + let dwdExpectation: XCTestExpectation! + var session: URLSession! = nil + var task: URLSessionDownloadTask! = nil + + init(with expectation: XCTestExpectation) { + dwdExpectation = expectation + } + + func run(with url: URL) { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + session = URLSession(configuration: config, delegate: self, delegateQueue: nil) + task = session.downloadTask(with: url) + task.resume() + } + + func run(with urlRequest: NSURLRequest) { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + session = URLSession(configuration: config, delegate: self, delegateQueue: nil) + task = session.downloadTask(with: urlRequest) + task.resume() + } +} + +extension DownloadTask : URLSessionDownloadDelegate { + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) -> Void { + self.totalBytesWritten = totalBytesWritten + } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + do { + let attr = try FileManager.default.attributesOfItem(atPath: location.path) + XCTAssertEqual((attr[.size]! as? NSNumber)!.int64Value, totalBytesWritten, "Size of downloaded file not equal to total bytes downloaded") + } catch { + XCTFail("Unable to calculate size of the downloaded file") + } + dwdExpectation.fulfill() + } +} + +extension DownloadTask : URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: NSError?) { + guard let e = error else { return } + XCTAssertEqual(e.code, NSURLErrorTimedOut, "Unexpected error code") + dwdExpectation.fulfill() + } +} diff --git a/TestFoundation/main.swift b/TestFoundation/main.swift index 7f70e8115d..7e60e92ca0 100644 --- a/TestFoundation/main.swift +++ b/TestFoundation/main.swift @@ -70,6 +70,7 @@ XCTMain([ testCase(TestNSURLRequest.allTests), testCase(TestNSURLResponse.allTests), testCase(TestNSHTTPURLResponse.allTests), + testCase(TestURLSession.allTests), testCase(TestNSNull.allTests), testCase(TestNSUUID.allTests), testCase(TestNSValue.allTests), diff --git a/build.py b/build.py index 0e466ab854..876ee85cdf 100644 --- a/build.py +++ b/build.py @@ -18,7 +18,7 @@ foundation.LDFLAGS = '${SWIFT_USE_LINKER} -Wl,@./CoreFoundation/linux.ld -lswiftGlibc `${PKG_CONFIG} icu-uc icu-i18n --libs` -Wl,-defsym,__CFConstantStringClassReference=_TMC10Foundation19_NSCFConstantString -Wl,-Bsymbolic ' Configuration.current.requires_pkg_config = True elif Configuration.current.target.sdk == OSType.FreeBSD: - foundation.CFLAGS = '-DDEPLOYMENT_TARGET_FREEBSD -I/usr/local/include -I/usr/local/include/libxml2 ' + foundation.CFLAGS = '-DDEPLOYMENT_TARGET_FREEBSD -I/usr/local/include -I/usr/local/include/libxml2 -I/usr/local/include/curl ' foundation.LDFLAGS = '' elif Configuration.current.target.sdk == OSType.MacOSX: foundation.CFLAGS = '-DDEPLOYMENT_TARGET_MACOSX ' @@ -57,22 +57,25 @@ '-Wno-int-conversion', '-Wno-unused-function', '-I${SYSROOT}/usr/include/libxml2', + '-I${SYSROOT}/usr/include/curl', '-I./', ]) swift_cflags = [ '-I${BUILD_DIR}/Foundation/usr/lib/swift', - '-I${SYSROOT}/usr/include/libxml2' + '-I${SYSROOT}/usr/include/libxml2', + '-I${SYSROOT}/usr/include/curl' ] if "XCTEST_BUILD_DIR" in Configuration.current.variables: swift_cflags += [ '-I${XCTEST_BUILD_DIR}', '-L${XCTEST_BUILD_DIR}', - '-I${SYSROOT}/usr/include/libxml2' + '-I${SYSROOT}/usr/include/libxml2', + '-I${SYSROOT}/usr/include/curl' ] -foundation.LDFLAGS += '-lpthread -ldl -lm -lswiftCore -lxml2 ' +foundation.LDFLAGS += '-lpthread -ldl -lm -lswiftCore -lxml2 -lcurl ' # Configure use of Dispatch in CoreFoundation and Foundation if libdispatch is being built if "LIBDISPATCH_SOURCE_DIR" in Configuration.current.variables: @@ -128,6 +131,7 @@ 'CoreFoundation/Collections.subproj/CFArray.h', 'CoreFoundation/RunLoop.subproj/CFRunLoop.h', 'CoreFoundation/URL.subproj/CFURLAccess.h', + 'CoreFoundation/URL.subproj/CFURLSessionInterface.h', 'CoreFoundation/Locale.subproj/CFDateFormatter.h', 'CoreFoundation/RunLoop.subproj/CFMachPort.h', 'CoreFoundation/PlugIn.subproj/CFPlugInCOM.h', @@ -140,6 +144,7 @@ 'CoreFoundation/NumberDate.subproj/CFNumber.h', 'CoreFoundation/Collections.subproj/CFData.h', 'CoreFoundation/String.subproj/CFAttributedString.h', + 'CoreFoundation/Base.subproj/CoreFoundation_Prefix.h' ], private = [ 'CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h', @@ -274,6 +279,7 @@ 'CoreFoundation/String.subproj/CFRegularExpression.c', 'CoreFoundation/String.subproj/CFAttributedString.c', 'CoreFoundation/String.subproj/CFRunArray.c', + 'CoreFoundation/URL.subproj/CFURLSessionInterface.c', ]) sources.add_dependency(headers) @@ -372,7 +378,18 @@ 'Foundation/NSURLProtocol.swift', 'Foundation/NSURLRequest.swift', 'Foundation/NSURLResponse.swift', - 'Foundation/NSURLSession.swift', + 'Foundation/NSURLSession/Configuration.swift', + 'Foundation/NSURLSession/EasyHandle.swift', + 'Foundation/NSURLSession/HTTPBodySource.swift', + 'Foundation/NSURLSession/HTTPMessage.swift', + 'Foundation/NSURLSession/MultiHandle.swift', + 'Foundation/NSURLSession/NSURLSession.swift', + 'Foundation/NSURLSession/NSURLSessionConfiguration.swift', + 'Foundation/NSURLSession/NSURLSessionDelegate.swift', + 'Foundation/NSURLSession/NSURLSessionTask.swift', + 'Foundation/NSURLSession/TaskRegistry.swift', + 'Foundation/NSURLSession/TransferState.swift', + 'Foundation/NSURLSession/libcurlHelpers.swift', 'Foundation/NSUserDefaults.swift', 'Foundation/NSUUID.swift', 'Foundation/NSValue.swift',