├── .gitignore ├── LICENSE ├── __init__.py ├── cdef.c ├── cffi_build.py ├── curl_constants.py ├── generate_curl_constants.sh ├── include ├── index.cpp └── index.h ├── pycurl_patch └── __init__.py ├── readme.md ├── setup.py └── test_curl.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .pytest_cache 3 | Release 4 | *.pyc 5 | *.pyd 6 | *.so 7 | _*.cpp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 multippt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from curl_constants import CurlOpt, CurlInfo 3 | try: 4 | from ._curl_cffi import ffi, lib 5 | except ImportError: 6 | ffi = lib = None 7 | 8 | 9 | class Curl(object): 10 | def __init__(self): 11 | self.instance = lib.bind_curl_easy_init() 12 | self.buffer_values = {} 13 | 14 | def __del__(self): 15 | self.close() 16 | 17 | def setopt(self, option, value): 18 | original_value = value 19 | input_option = { 20 | 0: "int*", 21 | 10000: "char*", 22 | 20000: "void*", 23 | 30000: "int*", # offset type 24 | } 25 | 26 | # Convert value 27 | value_type = input_option.get(int(option / 10000) * 10000) 28 | if value_type in ["int*"]: 29 | value = ffi.new(value_type, value) 30 | elif option in [CurlOpt.WRITEFUNCTION, CurlOpt.WRITEDATA]: 31 | print("write_data") 32 | value = lib.make_string() 33 | target_func = original_value 34 | if option == CurlOpt.WRITEDATA: 35 | target_func = original_value.write 36 | self.buffer_values[option] = (target_func, value) 37 | option = CurlOpt.WRITEDATA 38 | elif option in [CurlOpt.HEADERFUNCTION, CurlOpt.WRITEHEADER]: 39 | value = lib.make_string() 40 | target_func = original_value 41 | if option == CurlOpt.WRITEHEADER: 42 | target_func = original_value.write 43 | self.buffer_values[option] = (target_func, value) 44 | option = CurlOpt.WRITEHEADER 45 | elif value_type in ["char*"]: 46 | pass # Pass string as is 47 | else: 48 | raise NotImplementedError("Option unsupported: %s" % option) 49 | val = lib.bind_curl_easy_setopt(self.instance, option, value) 50 | assert val == 0 51 | return val 52 | 53 | def getinfo(self, option): 54 | ret_option = { 55 | 0x100000: "char*", 56 | 0x200000: "long*", 57 | 0x300000: "double*", 58 | } 59 | ret_cast_option = { 60 | 0x100000: str, 61 | 0x200000: int, 62 | 0x300000: float, 63 | } 64 | ret_val = ffi.new(ret_option[option & 0xF00000]) 65 | assert lib.bind_curl_easy_getinfo(self.instance, option, ret_val) == 0 66 | return ret_cast_option[option & 0xF00000](ret_val[0]) 67 | 68 | def perform(self): 69 | lib.bind_curl_easy_perform(self.instance) 70 | # Invoke the callbacks 71 | for option in self.buffer_values: 72 | obj = self.buffer_values[option][1] 73 | self.buffer_values[option][0](ffi.buffer(obj.content, obj.size)[:]) 74 | 75 | # Free memory allocated 76 | def close(self): 77 | if self.instance: 78 | lib.bind_curl_easy_cleanup(self.instance) 79 | self.instance = None 80 | 81 | opt_to_delete = [] 82 | for option in self.buffer_values: 83 | opt_to_delete.append(self.buffer_values[option][1]) 84 | self.buffer_values[option] = (self.buffer_values[option][0], ffi.gc(self.buffer_values[option][1], lib.free_string)) 85 | self.buffer_values = {} 86 | 87 | 88 | def patch_as_pycurl(): 89 | from . import pycurl_patch # Force package to be loaded so it appears in sys.modules 90 | sys.modules["curl"] = sys.modules["pycurl"] = sys.modules["python_curl_cffi.pycurl_patch"] 91 | -------------------------------------------------------------------------------- /cdef.c: -------------------------------------------------------------------------------- 1 | typedef struct binary_string { 2 | size_t size; 3 | char* content; 4 | ...; 5 | } binary_string_t; 6 | 7 | typedef struct curl_instance { 8 | void* curl; 9 | ...; 10 | } curl_instance_t; 11 | 12 | // Bindings 13 | binary_string_t* make_string(); 14 | void free_string(binary_string_t* obj); 15 | curl_instance_t* bind_curl_easy_init(); 16 | int bind_curl_easy_setopt(curl_instance_t* instance, int option, void* parameter); 17 | int bind_curl_easy_getinfo(curl_instance_t* instance, int option, void* retValue); 18 | int bind_curl_easy_perform(curl_instance_t* instance); 19 | void bind_curl_easy_cleanup(curl_instance_t* instance); 20 | -------------------------------------------------------------------------------- /cffi_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | from cffi import FFI 3 | 4 | 5 | libraries = ["curl"] 6 | if os.name == "nt": 7 | libraries = [ 8 | "libcurl_a", 9 | "wldap32", 10 | "crypt32", 11 | "Ws2_32", 12 | ] 13 | 14 | def build_ffi(module_name="_curl_cffi"): 15 | ffi_builder = FFI() 16 | ffi_builder.set_source(module_name, r""" // passed to the real C compiler, 17 | #include "index.h" 18 | """, libraries=libraries, library_dirs=[], source_extension='.cpp', include_dirs=[ 19 | os.path.join(os.path.dirname(__file__), "include"), 20 | ], sources=[ 21 | os.path.join(os.path.dirname(__file__), "include/index.cpp"), 22 | ]) 23 | 24 | with open("cdef.c", "r") as f: 25 | cdef_content = f.read() 26 | ffi_builder.cdef(cdef_content) 27 | return ffi_builder 28 | 29 | 30 | ffibuilder = build_ffi() 31 | 32 | 33 | def ffi_setup(): 34 | return build_ffi("python_curl_cffi._curl_cffi") 35 | 36 | if __name__ == "__main__": 37 | ffibuilder.compile(verbose=False) 38 | -------------------------------------------------------------------------------- /curl_constants.py: -------------------------------------------------------------------------------- 1 | # This file is automatically generated, do not modify directly 2 | class CurlOpt(object): 3 | WRITEDATA = 10000 + 1 4 | URL = 10000 + 2 5 | PORT = 0 + 3 6 | PROXY = 10000 + 4 7 | USERPWD = 10000 + 5 8 | PROXYUSERPWD = 10000 + 6 9 | RANGE = 10000 + 7 10 | READDATA = 10000 + 9 11 | ERRORBUFFER = 10000 + 10 12 | WRITEFUNCTION = 20000 + 11 13 | READFUNCTION = 20000 + 12 14 | TIMEOUT = 0 + 13 15 | INFILESIZE = 0 + 14 16 | POSTFIELDS = 10000 + 15 17 | REFERER = 10000 + 16 18 | FTPPORT = 10000 + 17 19 | USERAGENT = 10000 + 18 20 | LOW_SPEED_LIMIT = 0 + 19 21 | LOW_SPEED_TIME = 0 + 20 22 | RESUME_FROM = 0 + 21 23 | COOKIE = 10000 + 22 24 | HTTPHEADER = 10000 + 23 25 | HTTPPOST = 10000 + 24 26 | SSLCERT = 10000 + 25 27 | KEYPASSWD = 10000 + 26 28 | CRLF = 0 + 27 29 | QUOTE = 10000 + 28 30 | HEADERDATA = 10000 + 29 31 | COOKIEFILE = 10000 + 31 32 | SSLVERSION = 0 + 32 33 | TIMECONDITION = 0 + 33 34 | TIMEVALUE = 0 + 34 35 | CUSTOMREQUEST = 10000 + 36 36 | STDERR = 10000 + 37 37 | POSTQUOTE = 10000 + 39 38 | OBSOLETE40 = 10000 + 40 39 | VERBOSE = 0 + 41 40 | HEADER = 0 + 42 41 | NOPROGRESS = 0 + 43 42 | NOBODY = 0 + 44 43 | FAILONERROR = 0 + 45 44 | UPLOAD = 0 + 46 45 | POST = 0 + 47 46 | DIRLISTONLY = 0 + 48 47 | APPEND = 0 + 50 48 | NETRC = 0 + 51 49 | FOLLOWLOCATION = 0 + 52 50 | TRANSFERTEXT = 0 + 53 51 | PUT = 0 + 54 52 | PROGRESSFUNCTION = 20000 + 56 53 | PROGRESSDATA = 10000 + 57 54 | AUTOREFERER = 0 + 58 55 | PROXYPORT = 0 + 59 56 | POSTFIELDSIZE = 0 + 60 57 | HTTPPROXYTUNNEL = 0 + 61 58 | INTERFACE = 10000 + 62 59 | KRBLEVEL = 10000 + 63 60 | SSL_VERIFYPEER = 0 + 64 61 | CAINFO = 10000 + 65 62 | MAXREDIRS = 0 + 68 63 | FILETIME = 0 + 69 64 | TELNETOPTIONS = 10000 + 70 65 | MAXCONNECTS = 0 + 71 66 | OBSOLETE72 = 0 + 72 67 | FRESH_CONNECT = 0 + 74 68 | FORBID_REUSE = 0 + 75 69 | RANDOM_FILE = 10000 + 76 70 | EGDSOCKET = 10000 + 77 71 | CONNECTTIMEOUT = 0 + 78 72 | HEADERFUNCTION = 20000 + 79 73 | HTTPGET = 0 + 80 74 | SSL_VERIFYHOST = 0 + 81 75 | COOKIEJAR = 10000 + 82 76 | SSL_CIPHER_LIST = 10000 + 83 77 | HTTP_VERSION = 0 + 84 78 | FTP_USE_EPSV = 0 + 85 79 | SSLCERTTYPE = 10000 + 86 80 | SSLKEY = 10000 + 87 81 | SSLKEYTYPE = 10000 + 88 82 | SSLENGINE = 10000 + 89 83 | SSLENGINE_DEFAULT = 0 + 90 84 | DNS_USE_GLOBAL_CACHE = 0 + 91 85 | DNS_CACHE_TIMEOUT = 0 + 92 86 | PREQUOTE = 10000 + 93 87 | DEBUGFUNCTION = 20000 + 94 88 | DEBUGDATA = 10000 + 95 89 | COOKIESESSION = 0 + 96 90 | CAPATH = 10000 + 97 91 | BUFFERSIZE = 0 + 98 92 | NOSIGNAL = 0 + 99 93 | SHARE = 10000 + 100 94 | PROXYTYPE = 0 + 101 95 | ACCEPT_ENCODING = 10000 + 102 96 | PRIVATE = 10000 + 103 97 | HTTP200ALIASES = 10000 + 104 98 | UNRESTRICTED_AUTH = 0 + 105 99 | FTP_USE_EPRT = 0 + 106 100 | HTTPAUTH = 0 + 107 101 | SSL_CTX_FUNCTION = 20000 + 108 102 | SSL_CTX_DATA = 10000 + 109 103 | FTP_CREATE_MISSING_DIRS = 0 + 110 104 | PROXYAUTH = 0 + 111 105 | FTP_RESPONSE_TIMEOUT = 0 + 112 106 | IPRESOLVE = 0 + 113 107 | MAXFILESIZE = 0 + 114 108 | INFILESIZE_LARGE = 30000 + 115 109 | RESUME_FROM_LARGE = 30000 + 116 110 | MAXFILESIZE_LARGE = 30000 + 117 111 | NETRC_FILE = 10000 + 118 112 | USE_SSL = 0 + 119 113 | POSTFIELDSIZE_LARGE = 30000 + 120 114 | TCP_NODELAY = 0 + 121 115 | FTPSSLAUTH = 0 + 129 116 | IOCTLFUNCTION = 20000 + 130 117 | IOCTLDATA = 10000 + 131 118 | FTP_ACCOUNT = 10000 + 134 119 | COOKIELIST = 10000 + 135 120 | IGNORE_CONTENT_LENGTH = 0 + 136 121 | FTP_SKIP_PASV_IP = 0 + 137 122 | FTP_FILEMETHOD = 0 + 138 123 | LOCALPORT = 0 + 139 124 | LOCALPORTRANGE = 0 + 140 125 | CONNECT_ONLY = 0 + 141 126 | CONV_FROM_NETWORK_FUNCTION = 20000 + 142 127 | CONV_TO_NETWORK_FUNCTION = 20000 + 143 128 | CONV_FROM_UTF8_FUNCTION = 20000 + 144 129 | MAX_SEND_SPEED_LARGE = 30000 + 145 130 | MAX_RECV_SPEED_LARGE = 30000 + 146 131 | FTP_ALTERNATIVE_TO_USER = 10000 + 147 132 | SOCKOPTFUNCTION = 20000 + 148 133 | SOCKOPTDATA = 10000 + 149 134 | SSL_SESSIONID_CACHE = 0 + 150 135 | SSH_AUTH_TYPES = 0 + 151 136 | SSH_PUBLIC_KEYFILE = 10000 + 152 137 | SSH_PRIVATE_KEYFILE = 10000 + 153 138 | FTP_SSL_CCC = 0 + 154 139 | TIMEOUT_MS = 0 + 155 140 | CONNECTTIMEOUT_MS = 0 + 156 141 | HTTP_TRANSFER_DECODING = 0 + 157 142 | HTTP_CONTENT_DECODING = 0 + 158 143 | NEW_FILE_PERMS = 0 + 159 144 | NEW_DIRECTORY_PERMS = 0 + 160 145 | POSTREDIR = 0 + 161 146 | SSH_HOST_PUBLIC_KEY_MD5 = 10000 + 162 147 | OPENSOCKETFUNCTION = 20000 + 163 148 | OPENSOCKETDATA = 10000 + 164 149 | COPYPOSTFIELDS = 10000 + 165 150 | PROXY_TRANSFER_MODE = 0 + 166 151 | SEEKFUNCTION = 20000 + 167 152 | SEEKDATA = 10000 + 168 153 | CRLFILE = 10000 + 169 154 | ISSUERCERT = 10000 + 170 155 | ADDRESS_SCOPE = 0 + 171 156 | CERTINFO = 0 + 172 157 | USERNAME = 10000 + 173 158 | PASSWORD = 10000 + 174 159 | PROXYUSERNAME = 10000 + 175 160 | PROXYPASSWORD = 10000 + 176 161 | NOPROXY = 10000 + 177 162 | TFTP_BLKSIZE = 0 + 178 163 | SOCKS5_GSSAPI_SERVICE = 10000 + 179 164 | SOCKS5_GSSAPI_NEC = 0 + 180 165 | PROTOCOLS = 0 + 181 166 | REDIR_PROTOCOLS = 0 + 182 167 | SSH_KNOWNHOSTS = 10000 + 183 168 | SSH_KEYFUNCTION = 20000 + 184 169 | SSH_KEYDATA = 10000 + 185 170 | MAIL_FROM = 10000 + 186 171 | MAIL_RCPT = 10000 + 187 172 | FTP_USE_PRET = 0 + 188 173 | RTSP_REQUEST = 0 + 189 174 | RTSP_SESSION_ID = 10000 + 190 175 | RTSP_STREAM_URI = 10000 + 191 176 | RTSP_TRANSPORT = 10000 + 192 177 | RTSP_CLIENT_CSEQ = 0 + 193 178 | RTSP_SERVER_CSEQ = 0 + 194 179 | INTERLEAVEDATA = 10000 + 195 180 | INTERLEAVEFUNCTION = 20000 + 196 181 | WILDCARDMATCH = 0 + 197 182 | CHUNK_BGN_FUNCTION = 20000 + 198 183 | CHUNK_END_FUNCTION = 20000 + 199 184 | FNMATCH_FUNCTION = 20000 + 200 185 | CHUNK_DATA = 10000 + 201 186 | FNMATCH_DATA = 10000 + 202 187 | RESOLVE = 10000 + 203 188 | TLSAUTH_USERNAME = 10000 + 204 189 | TLSAUTH_PASSWORD = 10000 + 205 190 | TLSAUTH_TYPE = 10000 + 206 191 | TRANSFER_ENCODING = 0 + 207 192 | CLOSESOCKETFUNCTION = 20000 + 208 193 | CLOSESOCKETDATA = 10000 + 209 194 | GSSAPI_DELEGATION = 0 + 210 195 | DNS_SERVERS = 10000 + 211 196 | ACCEPTTIMEOUT_MS = 0 + 212 197 | TCP_KEEPALIVE = 0 + 213 198 | TCP_KEEPIDLE = 0 + 214 199 | TCP_KEEPINTVL = 0 + 215 200 | SSL_OPTIONS = 0 + 216 201 | MAIL_AUTH = 10000 + 217 202 | SASL_IR = 0 + 218 203 | XFERINFOFUNCTION = 20000 + 219 204 | XOAUTH2_BEARER = 10000 + 220 205 | DNS_INTERFACE = 10000 + 221 206 | DNS_LOCAL_IP4 = 10000 + 222 207 | DNS_LOCAL_IP6 = 10000 + 223 208 | LOGIN_OPTIONS = 10000 + 224 209 | SSL_ENABLE_NPN = 0 + 225 210 | SSL_ENABLE_ALPN = 0 + 226 211 | EXPECT_100_TIMEOUT_MS = 0 + 227 212 | PROXYHEADER = 10000 + 228 213 | HEADEROPT = 0 + 229 214 | PINNEDPUBLICKEY = 10000 + 230 215 | UNIX_SOCKET_PATH = 10000 + 231 216 | SSL_VERIFYSTATUS = 0 + 232 217 | SSL_FALSESTART = 0 + 233 218 | PATH_AS_IS = 0 + 234 219 | PROXY_SERVICE_NAME = 10000 + 235 220 | SERVICE_NAME = 10000 + 236 221 | PIPEWAIT = 0 + 237 222 | DEFAULT_PROTOCOL = 10000 + 238 223 | STREAM_WEIGHT = 0 + 239 224 | STREAM_DEPENDS = 10000 + 240 225 | STREAM_DEPENDS_E = 10000 + 241 226 | if locals().get("WRITEDATA"): 227 | FILE = locals().get("WRITEDATA") 228 | if locals().get("READDATA"): 229 | INFILE = locals().get("READDATA") 230 | if locals().get("HEADERDATA"): 231 | WRITEHEADER = locals().get("READDATA") 232 | 233 | 234 | class CurlInfo(object): 235 | TEXT = 0 236 | EFFECTIVE_URL = 0x100000 + 1 237 | RESPONSE_CODE = 0x200000 + 2 238 | TOTAL_TIME = 0x300000 + 3 239 | NAMELOOKUP_TIME = 0x300000 + 4 240 | CONNECT_TIME = 0x300000 + 5 241 | PRETRANSFER_TIME = 0x300000 + 6 242 | SIZE_UPLOAD = 0x300000 + 7 243 | SIZE_DOWNLOAD = 0x300000 + 8 244 | SPEED_DOWNLOAD = 0x300000 + 9 245 | SPEED_UPLOAD = 0x300000 + 10 246 | HEADER_SIZE = 0x200000 + 11 247 | REQUEST_SIZE = 0x200000 + 12 248 | SSL_VERIFYRESULT = 0x200000 + 13 249 | FILETIME = 0x200000 + 14 250 | CONTENT_LENGTH_DOWNLOAD = 0x300000 + 15 251 | CONTENT_LENGTH_UPLOAD = 0x300000 + 16 252 | STARTTRANSFER_TIME = 0x300000 + 17 253 | CONTENT_TYPE = 0x100000 + 18 254 | REDIRECT_TIME = 0x300000 + 19 255 | REDIRECT_COUNT = 0x200000 + 20 256 | PRIVATE = 0x100000 + 21 257 | HTTP_CONNECTCODE = 0x200000 + 22 258 | HTTPAUTH_AVAIL = 0x200000 + 23 259 | PROXYAUTH_AVAIL = 0x200000 + 24 260 | OS_ERRNO = 0x200000 + 25 261 | NUM_CONNECTS = 0x200000 + 26 262 | SSL_ENGINES = 0x400000 + 27 263 | COOKIELIST = 0x400000 + 28 264 | LASTSOCKET = 0x200000 + 29 265 | FTP_ENTRY_PATH = 0x100000 + 30 266 | REDIRECT_URL = 0x100000 + 31 267 | PRIMARY_IP = 0x100000 + 32 268 | APPCONNECT_TIME = 0x300000 + 33 269 | CERTINFO = 0x400000 + 34 270 | CONDITION_UNMET = 0x200000 + 35 271 | RTSP_SESSION_ID = 0x100000 + 36 272 | RTSP_CLIENT_CSEQ = 0x200000 + 37 273 | RTSP_SERVER_CSEQ = 0x200000 + 38 274 | RTSP_CSEQ_RECV = 0x200000 + 39 275 | PRIMARY_PORT = 0x200000 + 40 276 | LOCAL_IP = 0x100000 + 41 277 | LOCAL_PORT = 0x200000 + 42 278 | TLS_SESSION = 0x400000 + 43 279 | ACTIVESOCKET = 0x500000 + 44 280 | LASTONE = 44 281 | if locals().get("RESPONSE_CODE"): 282 | HTTP_CODE = locals().get("RESPONSE_CODE") 283 | -------------------------------------------------------------------------------- /generate_curl_constants.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "# This file is automatically generated, do not modify directly" > curl_constants.py 3 | echo "class CurlOpt(object):" >> curl_constants.py 4 | echo "#include "|gcc -E -|grep -i "CURLOPT_.\+ ="|sed "s/ CURLOPT_/\t/g"|sed "s/,//g" >> curl_constants.py 5 | # Handling opt renames 6 | cat << EOF >> curl_constants.py 7 | if locals().get("WRITEDATA"): 8 | FILE = locals().get("WRITEDATA") 9 | if locals().get("READDATA"): 10 | INFILE = locals().get("READDATA") 11 | if locals().get("HEADERDATA"): 12 | WRITEHEADER = locals().get("READDATA") 13 | EOF 14 | 15 | echo "" >> curl_constants.py 16 | echo "" >> curl_constants.py 17 | echo "class CurlInfo(object):" >> curl_constants.py 18 | echo "#include "|gcc -E -|grep -i "CURLINFO_.\+ ="|sed "s/ CURLINFO_/\t/g"|sed "s/,//g" >> curl_constants.py 19 | # Handling info renames 20 | cat << EOF >> curl_constants.py 21 | if locals().get("RESPONSE_CODE"): 22 | HTTP_CODE = locals().get("RESPONSE_CODE") 23 | EOF -------------------------------------------------------------------------------- /include/index.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "index.h" 3 | 4 | binary_string_t* make_string() { 5 | binary_string_t* mem = new binary_string_t(); 6 | mem->size = 0; 7 | mem->content = NULL; 8 | return mem; 9 | } 10 | 11 | void free_string(binary_string_t* obj) { 12 | delete(obj); 13 | } 14 | 15 | size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { 16 | size_t realsize = size * nmemb; 17 | binary_string_t* mem = (binary_string_t*)userp; 18 | if (mem->content == NULL) { 19 | mem->content = (char*)malloc(1); 20 | } 21 | 22 | mem->content = (char*)realloc(mem->content, mem->size + realsize + 1); 23 | if (mem->content == NULL) { 24 | return 0; // Out-of-memory 25 | } 26 | 27 | memcpy(&(mem->content[mem->size]), contents, realsize); 28 | mem->size += realsize; 29 | mem->content[mem->size] = 0; 30 | return realsize; 31 | } 32 | 33 | // Curl bindings 34 | curl_instance_t* bind_curl_easy_init() { 35 | curl_instance_t* instance = new curl_instance_t(); 36 | instance->curl = curl_easy_init(); 37 | return instance; 38 | } 39 | int bind_curl_easy_setopt(curl_instance_t* instance, int option, void* parameter) { 40 | CURLoption opt_value = (CURLoption) option; 41 | CURLcode res = CURLE_OK; 42 | if (opt_value == CURLOPT_WRITEDATA) { 43 | res = curl_easy_setopt(instance->curl, CURLOPT_WRITEFUNCTION, write_callback); 44 | } else if (opt_value == CURLOPT_WRITEHEADER) { 45 | res = curl_easy_setopt(instance->curl, CURLOPT_HEADERFUNCTION, write_callback); 46 | } 47 | if (res != CURLE_OK) { 48 | return (int)res; 49 | } 50 | return (int)curl_easy_setopt(instance->curl, (CURLoption)option, parameter); 51 | } 52 | int bind_curl_easy_perform(curl_instance_t* instance) { 53 | return (int)curl_easy_perform(instance->curl); 54 | } 55 | int bind_curl_easy_getinfo(curl_instance_t* instance, int option, void* retValue) { 56 | return (int)curl_easy_getinfo(instance->curl, (CURLINFO)option, retValue); 57 | } 58 | void bind_curl_easy_cleanup(curl_instance_t* instance) { 59 | curl_easy_cleanup(instance->curl); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /include/index.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define CURL_STATICLIB 5 | #include 6 | 7 | typedef struct binary_string { 8 | size_t size; 9 | char* content; 10 | ~binary_string() { 11 | if (content) { 12 | free(content); 13 | } 14 | } 15 | } binary_string_t; 16 | 17 | typedef struct curl_instance { 18 | void* curl; 19 | } curl_instance_t; 20 | 21 | // Bindings 22 | binary_string_t* make_string(); 23 | void free_string(binary_string_t* obj); 24 | curl_instance_t* bind_curl_easy_init(); 25 | int bind_curl_easy_setopt(curl_instance_t* instance, int option, void* parameter); 26 | int bind_curl_easy_getinfo(curl_instance_t* instance, int option, void* retValue); 27 | int bind_curl_easy_perform(curl_instance_t* instance); 28 | void bind_curl_easy_cleanup(curl_instance_t* instance); 29 | -------------------------------------------------------------------------------- /pycurl_patch/__init__.py: -------------------------------------------------------------------------------- 1 | from .. import Curl 2 | from .. import curl_constants 3 | 4 | for key in dir(curl_constants.CurlInfo): 5 | if key[0] != "_": 6 | globals()[key] = getattr(curl_constants.CurlInfo, key) 7 | 8 | for key in dir(curl_constants.CurlOpt): 9 | if key[0] != "_": 10 | globals()[key] = getattr(curl_constants.CurlOpt, key) 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Python Curl CFFI 2 | 3 | CFFI bindings for libcurl. 4 | 5 | # Example Usage 6 | 7 | ```python 8 | import python_curl_cffi 9 | from python_curl_cffi.curl_constants import CurlOpt 10 | from StringIO import StringIO 11 | buffer = StringIO() 12 | c = python_curl_cffi.Curl() 13 | c.setopt(CurlOpt.URL, 'http://www.onfry.com') 14 | c.setopt(CurlOpt.WRITEDATA, buffer) 15 | c.perform() 16 | c.close() 17 | body = buffer.getvalue() 18 | ``` 19 | 20 | # API 21 | 22 | Curl object: 23 | * setopt(CurlOpt, value): Sets curl options as in `curl_easy_setopt` 24 | * perform(): Performs curl request, as in `curl_easy_perform` 25 | * getinfo(CurlInfo): Gets information in response after curl perform, as in `curl_easy_getinfo` 26 | * close(): Closes and cleans up the curl object, as in `curl_easy_cleanup` 27 | 28 | Enum values to be used with `setopt` and `getinfo` can be accessed from `curl_constants` module. 29 | 30 | This library can act as a drop-in replacement for `pycurl` by calling the following monkey patch function 31 | 32 | ```python 33 | import python_curl_cffi 34 | python_curl_cffi.patch_as_pycurl() 35 | ``` 36 | 37 | # Installation 38 | 39 | **Unix** 40 | 41 | 1. Install libcurl (e.g. `apt-get install libcurl4-openssl-dev`) 42 | 2. pip install git+https://multippt@bitbucket.org/multippt/python_curl_cffi.git 43 | 44 | **Windows (Python 2 64-bit)** 45 | 46 | Replace `x64` with `x86` for 32-bit versions of Python. 47 | 48 | Compile libcurl: 49 | 1. Install Microsoft Visual C++ Compiler (MSVC 2008). https://www.microsoft.com/en-us/download/details.aspx?id=44266 50 | 2. `git clone https://github.com/curl/curl.git` 51 | 3. Open MSVC C++ Command Prompt (MSVC 2008). Subsequent commands to be executed in this command prompt. 52 | 4. Build libcurl. `cd \winbuild\ && nmake /f Makefile.vc mode=static VC=9 MACHINE=x64` 53 | 5. Set `LIB` environment variable (e.g. `set LIB=\builds\libcurl-vc9-x64-release-static-ipv6-sspi-winssl\lib`) 54 | 6. Set `INCLUDE` environment variable (e.g. `set INCLUDE=\builds\libcurl-vc9-x64-release-static-ipv6-sspi-winssl\include`) 55 | 7. pip install git+https://multippt@bitbucket.org/multippt/python_curl_cffi.git 56 | 57 | **Windows (Python 3 64-bit)** 58 | 59 | Replace `x64` with `x86` for 32-bit versions of Python. 60 | 61 | Compile libcurl: 62 | 1. Install Microsoft Visual C++ Compiler (MSVC 2017+). https://www.visualstudio.com 63 | 2. `git clone https://github.com/curl/curl.git` 64 | 3. Open MSVC C++ Command Prompt. Subsequent commands to be executed in this command prompt. 65 | 4. Build libcurl. `cd \winbuild\ && nmake /f Makefile.vc mode=static VC=14 MACHINE=x64` 66 | 5. Set `LIB` environment variable (e.g. `set LIB=\builds\libcurl-vc14-x64-release-static-ipv6-sspi-winssl\lib`) 67 | 6. Set `INCLUDE` environment variable (e.g. `set INCLUDE=\builds\libcurl-vc14-x64-release-static-ipv6-sspi-winssl\include`) 68 | 7. pip install git+https://multippt@bitbucket.org/multippt/python_curl_cffi.git 69 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from setuptools import setup 4 | 5 | 6 | def do_setup(): 7 | os.chdir(os.path.dirname(__file__)) 8 | 9 | if "_cffi_backend" in sys.builtin_module_names: 10 | # pypy has cffi bundled 11 | import _cffi_backend 12 | requires_cffi = "cffi==" + _cffi_backend.__version__ 13 | else: 14 | requires_cffi = "cffi>=1.0.0" 15 | 16 | if requires_cffi.startswith("cffi==0."): 17 | # Existing cffi version present 18 | from cffi_build import ffibuilder 19 | ext_config = ffibuilder.verifier.get_extension() 20 | ext_config.name = "python_curl_cffi._curl_cffi" 21 | extra_args = { 22 | "setup_requires": [requires_cffi], 23 | "ext_modules": [ext_config], 24 | } 25 | else: 26 | extra_args = { 27 | "setup_requires": [requires_cffi], 28 | "cffi_modules": ["cffi_build.py:ffi_setup"] 29 | } 30 | 31 | setup( 32 | name='python-curl-cffi', 33 | version='0.1.0', 34 | author='Nicholas Kwan', 35 | description="libcurl ffi bindings for Python", 36 | url="https://bitbucket.org/multippt/python_curl_cffi", 37 | package_dir={"python_curl_cffi": ""}, 38 | packages=['python_curl_cffi', 'python_curl_cffi.pycurl_patch'], 39 | **extra_args 40 | ) 41 | 42 | 43 | if __name__ == "__main__": 44 | do_setup() 45 | -------------------------------------------------------------------------------- /test_curl.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import python_curl_cffi 3 | from python_curl_cffi.curl_constants import CurlOpt 4 | from StringIO import StringIO 5 | 6 | 7 | class TestCurl(unittest.TestCase): 8 | def test_curl(self): 9 | b = StringIO() 10 | c = python_curl_cffi.Curl() 11 | c.setopt(CurlOpt.URL, 'https://www.onfry.com') 12 | c.setopt(CurlOpt.WRITEDATA, b) 13 | c.perform() 14 | c.close() 15 | 16 | body = b.getvalue() 17 | print(body) 18 | --------------------------------------------------------------------------------