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',