├── .gitignore ├── LICENSE ├── README.md ├── curl.lua └── example.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.sublime-project 3 | *.sublime-workspace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Playermet 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CurLua 2 | ====== 3 | 4 | CURL bind and wrapper for Lua+FFI 5 | -------------------------------------------------------------------------------- /curl.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------- 2 | -- FFI 3 | ----------------------------------------------------------- 4 | local ffi = require 'ffi' 5 | 6 | ffi.cdef [[ 7 | typedef void* CURL; 8 | typedef int CODE; 9 | 10 | int curl_version (); 11 | void curl_free (void* p); 12 | 13 | CURL curl_easy_init (); 14 | CURL curl_easy_duphandle (CURL handle); 15 | void curl_easy_cleanup (CURL handle); 16 | void curl_easy_reset (CURL handle); 17 | 18 | CODE curl_easy_perform (CURL handle); 19 | CODE curl_easy_setopt (CURL handle, int option, ...); 20 | CODE curl_easy_getinfo (CURL handle, int info, ...); 21 | 22 | char* curl_easy_escape (CURL handle, char* url, int length); 23 | char* curl_easy_unescape (CURL handle, char* url, int length, 24 | int* outlength); 25 | 26 | CODE curl_easy_recv (CURL handle, void* buffer, 27 | size_t length, size_t* n); 28 | CODE curl_easy_send (CURL handle, const void* buffer, 29 | size_t length, size_t* n); 30 | 31 | const char* curl_easy_strerror (CODE errcode); 32 | ]] 33 | 34 | local lib = ffi.load 'libcurl.dll' 35 | 36 | 37 | ----------------------------------------------------------- 38 | -- CURL Option Data 39 | ----------------------------------------------------------- 40 | local co_long = { 00000 } 41 | local co_objectpoint = { 10000 } 42 | local co_functionpoint = { 20000 } 43 | local co_off_t = { 30000 } 44 | 45 | local curl_opt = { 46 | file = { 001, co_objectpoint }, 47 | url = { 002, co_objectpoint }, 48 | port = { 003, co_long }, 49 | proxy = { 004, co_objectpoint }, 50 | userpwd = { 005, co_objectpoint }, 51 | proxyuserpwd = { 006, co_objectpoint }, 52 | range = { 007, co_objectpoint }, 53 | infile = { 009, co_objectpoint }, 54 | errorbuffer = { 010, co_objectpoint }, 55 | writefunction = { 011, co_functionpoint }, 56 | readfunction = { 012, co_functionpoint }, 57 | timeout = { 013, co_long }, 58 | infilesize = { 014, co_long }, 59 | postfields = { 015, co_objectpoint }, 60 | referer = { 016, co_objectpoint }, 61 | ftpport = { 017, co_objectpoint }, 62 | useragent = { 018, co_objectpoint }, 63 | low_speed_limit = { 019, co_long }, 64 | low_speed_time = { 020, co_long }, 65 | resume_from = { 021, co_long }, 66 | cookie = { 022, co_objectpoint }, 67 | httpheader = { 023, co_objectpoint }, 68 | httppost = { 024, co_objectpoint }, 69 | sslcert = { 025, co_objectpoint }, 70 | keypasswd = { 026, co_objectpoint }, 71 | crlf = { 027, co_long }, 72 | quote = { 028, co_objectpoint }, 73 | writeheader = { 029, co_objectpoint }, 74 | cookiefile = { 031, co_objectpoint }, 75 | sslversion = { 032, co_long }, 76 | timecondition = { 033, co_long }, 77 | timevalue = { 034, co_long }, 78 | customrequest = { 036, co_objectpoint }, 79 | stderr = { 037, co_objectpoint }, 80 | postquote = { 039, co_objectpoint }, 81 | writeinfo = { 040, co_objectpoint }, 82 | verbose = { 041, co_long }, 83 | header = { 042, co_long }, 84 | noprogress = { 043, co_long }, 85 | nobody = { 044, co_long }, 86 | failonerror = { 045, co_long }, 87 | upload = { 046, co_long }, 88 | post = { 047, co_long }, 89 | dirlistonly = { 048, co_long }, 90 | append = { 050, co_long }, 91 | netrc = { 051, co_long }, 92 | followlocation = { 052, co_long }, 93 | transfertext = { 053, co_long }, 94 | put = { 054, co_long }, 95 | progressfunction = { 056, co_functionpoint }, 96 | progressdata = { 057, co_objectpoint }, 97 | autoreferer = { 058, co_long }, 98 | proxyport = { 059, co_long }, 99 | postfieldsize = { 060, co_long }, 100 | httpproxytunnel = { 061, co_long }, 101 | interface = { 062, co_objectpoint }, 102 | krblevel = { 063, co_objectpoint }, 103 | ssl_verifypeer = { 064, co_long }, 104 | cainfo = { 065, co_objectpoint }, 105 | maxredirs = { 068, co_long }, 106 | filetime = { 069, co_long }, 107 | telnetoptions = { 070, co_objectpoint }, 108 | maxconnects = { 071, co_long }, 109 | closepolicy = { 072, co_long }, 110 | fresh_connect = { 074, co_long }, 111 | forbid_reuse = { 075, co_long }, 112 | random_file = { 076, co_objectpoint }, 113 | egdsocket = { 077, co_objectpoint }, 114 | connecttimeout = { 078, co_long }, 115 | headerfunction = { 079, co_functionpoint }, 116 | httpget = { 080, co_long }, 117 | ssl_verifyhost = { 081, co_long }, 118 | cookiejar = { 082, co_objectpoint }, 119 | ssl_cipher_list = { 083, co_objectpoint }, 120 | http_version = { 084, co_long }, 121 | ftp_use_epsv = { 085, co_long }, 122 | sslcerttype = { 086, co_objectpoint }, 123 | sslkey = { 087, co_objectpoint }, 124 | sslkeytype = { 088, co_objectpoint }, 125 | sslengine = { 089, co_objectpoint }, 126 | sslengine_default = { 090, co_long }, 127 | dns_use_global_cache = { 091, co_long }, 128 | dns_cache_timeout = { 092, co_long }, 129 | prequote = { 093, co_objectpoint }, 130 | debugfunction = { 094, co_functionpoint }, 131 | debugdata = { 095, co_objectpoint }, 132 | cookiesession = { 096, co_long }, 133 | capath = { 097, co_objectpoint }, 134 | buffersize = { 098, co_long }, 135 | nosignal = { 099, co_long }, 136 | share = { 100, co_objectpoint }, 137 | proxytype = { 101, co_long }, 138 | accept_encoding = { 102, co_objectpoint }, 139 | private = { 103, co_objectpoint }, 140 | http200aliases = { 104, co_objectpoint }, 141 | unrestricted_auth = { 105, co_long }, 142 | ftp_use_eprt = { 106, co_long }, 143 | httpauth = { 107, co_long }, 144 | ssl_ctx_function = { 108, co_functionpoint }, 145 | ssl_ctx_data = { 109, co_objectpoint }, 146 | ftp_create_missing_dirs = { 110, co_long }, 147 | proxyauth = { 111, co_long }, 148 | ftp_response_timeout = { 112, co_long }, 149 | ipresolve = { 113, co_long }, 150 | maxfilesize = { 114, co_long }, 151 | infilesize_large = { 115, co_off_t }, 152 | resume_from_large = { 116, co_off_t }, 153 | maxfilesize_large = { 117, co_off_t }, 154 | netrc_file = { 118, co_objectpoint }, 155 | use_ssl = { 119, co_long }, 156 | postfieldsize_large = { 120, co_off_t }, 157 | tcp_nodelay = { 121, co_long }, 158 | ftpsslauth = { 129, co_long }, 159 | ioctlfunction = { 130, co_functionpoint }, 160 | ioctldata = { 131, co_objectpoint }, 161 | ftp_account = { 134, co_objectpoint }, 162 | cookielist = { 135, co_objectpoint }, 163 | ignore_content_length = { 136, co_long }, 164 | ftp_skip_pasv_ip = { 137, co_long }, 165 | ftp_filemethod = { 138, co_long }, 166 | localport = { 139, co_long }, 167 | localportrange = { 140, co_long }, 168 | connect_only = { 141, co_long }, 169 | conv_from_network_function = { 142, co_functionpoint }, 170 | conv_to_network_function = { 143, co_functionpoint }, 171 | conv_from_utf8_function = { 144, co_functionpoint }, 172 | max_send_speed_large = { 145, co_off_t }, 173 | max_recv_speed_large = { 146, co_off_t }, 174 | ftp_alternative_to_user = { 147, co_objectpoint }, 175 | sockoptfunction = { 148, co_functionpoint }, 176 | sockoptdata = { 149, co_objectpoint }, 177 | ssl_sessionid_cache = { 150, co_long }, 178 | ssh_auth_types = { 151, co_long }, 179 | ssh_public_keyfile = { 152, co_objectpoint }, 180 | ssh_private_keyfile = { 153, co_objectpoint }, 181 | ftp_ssl_ccc = { 154, co_long }, 182 | timeout_ms = { 155, co_long }, 183 | connecttimeout_ms = { 156, co_long }, 184 | http_transfer_decoding = { 157, co_long }, 185 | http_content_decoding = { 158, co_long }, 186 | new_file_perms = { 159, co_long }, 187 | new_directory_perms = { 160, co_long }, 188 | postredir = { 161, co_long }, 189 | ssh_host_public_key_md5 = { 162, co_objectpoint }, 190 | opensocketfunction = { 163, co_functionpoint }, 191 | opensocketdata = { 164, co_objectpoint }, 192 | copypostfields = { 165, co_objectpoint }, 193 | proxy_transfer_mode = { 166, co_long }, 194 | seekfunction = { 167, co_functionpoint }, 195 | seekdata = { 168, co_objectpoint }, 196 | crlfile = { 169, co_objectpoint }, 197 | issuercert = { 170, co_objectpoint }, 198 | address_scope = { 171, co_long }, 199 | certinfo = { 172, co_long }, 200 | username = { 173, co_objectpoint }, 201 | password = { 174, co_objectpoint }, 202 | proxyusername = { 175, co_objectpoint }, 203 | proxypassword = { 176, co_objectpoint }, 204 | noproxy = { 177, co_objectpoint }, 205 | tftp_blksize = { 178, co_long }, 206 | socks5_gssapi_service = { 179, co_objectpoint }, 207 | socks5_gssapi_nec = { 180, co_long }, 208 | protocols = { 181, co_long }, 209 | redir_protocols = { 182, co_long }, 210 | ssh_knownhosts = { 183, co_objectpoint }, 211 | ssh_keyfunction = { 184, co_functionpoint }, 212 | ssh_keydata = { 185, co_objectpoint }, 213 | mail_from = { 186, co_objectpoint }, 214 | mail_rcpt = { 187, co_objectpoint }, 215 | ftp_use_pret = { 188, co_long }, 216 | rtsp_request = { 189, co_long }, 217 | rtsp_session_id = { 190, co_objectpoint }, 218 | rtsp_stream_uri = { 191, co_objectpoint }, 219 | rtsp_transport = { 192, co_objectpoint }, 220 | rtsp_client_cseq = { 193, co_long }, 221 | rtsp_server_cseq = { 194, co_long }, 222 | interleavedata = { 195, co_objectpoint }, 223 | interleavefunction = { 196, co_functionpoint }, 224 | wildcardmatch = { 197, co_long }, 225 | chunk_bgn_function = { 198, co_functionpoint }, 226 | chunk_end_function = { 199, co_functionpoint }, 227 | fnmatch_function = { 200, co_functionpoint }, 228 | chunk_data = { 201, co_objectpoint }, 229 | fnmatch_data = { 202, co_objectpoint }, 230 | resolve = { 203, co_objectpoint }, 231 | tlsauth_username = { 204, co_objectpoint }, 232 | tlsauth_password = { 205, co_objectpoint }, 233 | tlsauth_type = { 206, co_objectpoint }, 234 | transfer_encoding = { 207, co_long }, 235 | closesocketfunction = { 208, co_functionpoint }, 236 | closesocketdata = { 209, co_objectpoint }, 237 | gssapi_delegation = { 210, co_long }, 238 | dns_servers = { 211, co_objectpoint }, 239 | accepttimeout_ms = { 212, co_long }, 240 | tcp_keepalive = { 213, co_long }, 241 | tcp_keepidle = { 214, co_long }, 242 | tcp_keepintvl = { 215, co_long }, 243 | ssl_options = { 216, co_long }, 244 | mail_auth = { 217, co_objectpoint }, 245 | sasl_ir = { 218, co_long }, 246 | xferinfofunction = { 219, co_functionpoint }, 247 | xoauth2_bearer = { 220, co_objectpoint }, 248 | dns_interface = { 221, co_objectpoint }, 249 | dns_local_ip4 = { 222, co_objectpoint }, 250 | dns_local_ip6 = { 223, co_objectpoint }, 251 | login_options = { 224, co_objectpoint }, 252 | ssl_enable_npn = { 225, co_long }, 253 | ssl_enable_alpn = { 226, co_long }, 254 | expect_100_timeout_ms = { 227, co_long } 255 | } 256 | 257 | -- Aliases 258 | curl_opt.xferinfodata = curl_opt.progressdata 259 | curl_opt.server_response_timeout = curl_opt.ftp_response_timeout 260 | curl_opt.writedata = curl_opt.file 261 | curl_opt.readdata = curl_opt.infile 262 | curl_opt.headerdata = curl_opt.writeheader 263 | curl_opt.rtspheader = curl_opt.httpheader 264 | 265 | 266 | ----------------------------------------------------------- 267 | -- CURL Info Data 268 | ----------------------------------------------------------- 269 | local ci_string = { 0x100000, 'char* [1]', ffi.string } 270 | local ci_long = { 0x200000, 'long [1]', tonumber } 271 | local ci_double = { 0x300000, 'double[1]', tonumber } 272 | local ci_slist = { 0x400000, 'slist [1]', tostring } 273 | 274 | local curl_info = { 275 | effective_url = { 01, ci_string }, 276 | response_code = { 02, ci_long }, 277 | total_time = { 03, ci_double }, 278 | namelookup_time = { 04, ci_double }, 279 | connect_time = { 05, ci_double }, 280 | pretransfer_time = { 06, ci_double }, 281 | size_upload = { 07, ci_double }, 282 | size_download = { 08, ci_double }, 283 | speed_download = { 09, ci_double }, 284 | speed_upload = { 10, ci_double }, 285 | header_size = { 11, ci_long }, 286 | request_size = { 12, ci_long }, 287 | ssl_verifyresult = { 13, ci_long }, 288 | filetime = { 14, ci_long }, 289 | content_length_download = { 15, ci_double }, 290 | content_length_upload = { 16, ci_double }, 291 | starttransfer_time = { 17, ci_double }, 292 | content_type = { 18, ci_string }, 293 | redirect_time = { 19, ci_double }, 294 | redirect_count = { 20, ci_long }, 295 | private = { 21, ci_string }, 296 | http_connectcode = { 22, ci_long }, 297 | httpauth_avail = { 23, ci_long }, 298 | proxyauth_avail = { 24, ci_long }, 299 | os_errno = { 25, ci_long }, 300 | num_connects = { 26, ci_long }, 301 | ssl_engines = { 27, ci_slist }, 302 | cookielist = { 28, ci_slist }, 303 | lastsocket = { 29, ci_long }, 304 | ftp_entry_path = { 30, ci_string }, 305 | redirect_url = { 31, ci_string }, 306 | primary_ip = { 32, ci_string }, 307 | appconnect_time = { 33, ci_double }, 308 | certinfo = { 34, ci_slist }, 309 | condition_unmet = { 35, ci_long }, 310 | rtsp_session_id = { 36, ci_string }, 311 | rtsp_client_cseq = { 37, ci_long }, 312 | rtsp_server_cseq = { 38, ci_long }, 313 | rtsp_cseq_recv = { 39, ci_long }, 314 | primary_port = { 40, ci_long }, 315 | local_ip = { 41, ci_string }, 316 | local_port = { 42, ci_long }, 317 | tls_session = { 43, ci_slist } 318 | } 319 | 320 | 321 | ----------------------------------------------------------- 322 | -- Function Wrapping 323 | ----------------------------------------------------------- 324 | local opt_utils = {} 325 | 326 | local function version() 327 | return lib.curl_version() 328 | end 329 | 330 | local function easy_init() 331 | local handle = lib.curl_easy_init() 332 | if handle ~=nil then 333 | ffi.gc(handle, lib.curl_easy_cleanup) 334 | return handle 335 | end 336 | return false 337 | end 338 | 339 | local function easy_duphandle(handle) 340 | local duplicate = lib.curl_easy_duphandle(handle) 341 | if handle ~=nil then 342 | ffi.gc(handle, lib.curl_easy_cleanup) 343 | return handle 344 | end 345 | return false 346 | end 347 | 348 | local function easy_reset(handle) 349 | lib.curl_easy_reset(handle) 350 | end 351 | 352 | local function easy_perform(handle) 353 | return lib.curl_easy_perform(handle); 354 | end 355 | 356 | local function easy_setopt(handle, key, val) 357 | local data = curl_opt[key] 358 | if data then 359 | local opt_code = data[2][1] + data[1] 360 | local opt_util = opt_utils[key] 361 | 362 | if opt_util then 363 | val = opt_util(handle, val) 364 | end 365 | 366 | return lib.curl_easy_setopt(handle, opt_code, val) 367 | end 368 | end 369 | 370 | local function easy_getinfo(handle, key) 371 | local data = curl_info[key] 372 | if data then 373 | local info_code = data[2][1] + data[1] 374 | local info_type = data[2][2] 375 | local cast_func = data[2][3] 376 | 377 | local result = ffi.new(info_type) 378 | lib.curl_easy_getinfo(handle, info_code, result) 379 | if result[0] ~= nil then 380 | return cast_func(result[0]) 381 | end 382 | end 383 | end 384 | 385 | local function easy_escape(handle, url) 386 | local i_cstr = ffi.new('char[?]', #url, url) 387 | local o_cstr = lib.curl_easy_escape(handle, i_cstr, #url) 388 | 389 | local str = ffi.string(o_cstr) 390 | lib.curl_free(o_cstr) 391 | return str 392 | end 393 | 394 | local function easy_unescape(handle, url) 395 | local out_n = ffi.new('int[1]') -- int* 396 | local i_cstr = ffi.new('char[?]', #url, url) 397 | local o_cstr = lib.curl_easy_unescape(handle, i_cstr, #url, out_n) 398 | 399 | local str = ffi.string(o_cstr) 400 | lib.curl_free(o_cstr) 401 | return str 402 | end 403 | 404 | local function easy_strerror(code) 405 | local cstr = lib.curl_easy_strerror(code) 406 | return ffi.string(cstr) 407 | end 408 | 409 | 410 | ----------------------------------------------------------- 411 | -- OOP Wrapper 412 | ----------------------------------------------------------- 413 | local wrapper_mt = {} 414 | 415 | wrapper_mt.__index = wrapper_mt 416 | 417 | local function wrap(handle) 418 | local wrapper = { 419 | handle = handle 420 | } 421 | return setmetatable(wrapper, wrapper_mt) 422 | end 423 | 424 | local function init() 425 | return wrap(easy_init()) 426 | end 427 | 428 | function wrapper_mt:reset() 429 | easy_reset(self.handle) 430 | end 431 | 432 | function wrapper_mt:clone() 433 | return wrap(easy_duphandle(self.handle)) 434 | end 435 | 436 | function wrapper_mt:options(options) 437 | local e = {} 438 | for key, val in pairs(options) do 439 | local err = easy_setopt(self.handle, key, val) 440 | end 441 | end 442 | 443 | function wrapper_mt:perform(options) 444 | self:options(options) 445 | return easy_perform(self.handle) 446 | end 447 | 448 | function wrapper_mt:escape(url) 449 | return easy_escape(self.handle, url) 450 | end 451 | 452 | function wrapper_mt:unescape(url) 453 | return easy_unescape(self.handle, url) 454 | end 455 | 456 | 457 | info_mt = {} 458 | 459 | function info_mt:__index(key) 460 | return easy_getinfo(self.handle, key) 461 | end 462 | 463 | function wrapper_mt:info() 464 | local info = { 465 | handle = self.handle 466 | } 467 | return setmetatable(info, info_mt) 468 | end 469 | 470 | 471 | ----------------------------------------------------------- 472 | -- Option Utils 473 | ----------------------------------------------------------- 474 | function opt_utils.postfields(handle, post_data) 475 | if type(post_data) == 'string' then 476 | return post_data 477 | end 478 | if type(post_data) == 'table' then 479 | local fields = {} 480 | for k,v in pairs(post_data) do 481 | fields[#fields + 1] = k .. '=' .. v 482 | end 483 | return table.concat(fields, '&') 484 | end 485 | return '' 486 | end 487 | 488 | function opt_utils.writefunction(handle, callback) 489 | local type = 'size_t (*)(char*, size_t, size_t, void*)' 490 | return ffi.cast(type, callback) 491 | end 492 | 493 | function opt_utils.readfunction(handle, callback) 494 | local type = 'size_t (*)(void*, size_t, size_t, void*)' 495 | return ffi.cast(type, callback) 496 | end 497 | 498 | function opt_utils.headerfunction(handle, callback) 499 | local type = 'size_t (*)(void*, size_t, size_t, void*)' 500 | return ffi.cast(type, callback) 501 | end 502 | 503 | function opt_utils.progressfunction(handle, callback) 504 | local type = 'size_t (*)(void*, double, double, double, double)' 505 | return ffi.cast(type, callback) 506 | end 507 | 508 | 509 | ----------------------------------------------------------- 510 | -- Interface 511 | ----------------------------------------------------------- 512 | return { 513 | version = version; 514 | init = init; 515 | } 516 | -------------------------------------------------------------------------------- /example.lua: -------------------------------------------------------------------------------- 1 | local libcurl = require 'curl' 2 | 3 | local curl = libcurl.init() 4 | curl:perform { 5 | url = 'http://example.com', 6 | postfields = { a = 1, b = 2 } 7 | } 8 | 9 | info = curl:info() 10 | print('Response code:', info.response_code) 11 | print('Content type:', info.content_type) 12 | --------------------------------------------------------------------------------