module Ethon # :nodoc: module Curl # :nodoc: def Curl.windows? !(RbConfig::CONFIG['host_os'] !~ /mingw|mswin|bccwin/) end extend ::FFI::Library # :nodoc: VERSION_NOW = 3 # :nodoc: GLOBAL_SSL = 0x01 # :nodoc: GLOBAL_WIN32 = 0x02 # :nodoc: GLOBAL_ALL = (GLOBAL_SSL | GLOBAL_WIN32) # :nodoc: GLOBAL_DEFAULT = GLOBAL_ALL # :nodoc: EasyCode = enum :easy_code, [ :ok, :unsupported_protocol, :failed_init, :url_malformat, :not_built_in, :couldnt_resolve_proxy, :couldnt_resolve_host, :couldnt_connect, :ftp_weird_server_reply, :remote_access_denied, :ftp_accept_failed, :ftp_weird_pass_reply, :ftp_accept_timeout, :ftp_weird_pasv_reply, :ftp_weird_227_format, :ftp_cant_get_host, :obsolete16, :ftp_couldnt_set_type, :partial_file, :ftp_couldnt_retr_file, :obsolete20, :quote_error, :http_returned_error, :write_error, :obsolete24, :upload_failed, :read_error, :out_of_memory, :operation_timedout, :obsolete29, :ftp_port_failed, :ftp_couldnt_use_rest, :obsolete32, :range_error, :http_post_error, :ssl_connect_error, :bad_download_resume, :file_couldnt_read_file, :ldap_cannot_bind, :ldap_search_failed, :obsolete40, :function_not_found, :aborted_by_callback, :bad_function_argument, :obsolete44, :interface_failed, :obsolete46, :too_many_redirects , :unknown_option, :telnet_option_syntax , :obsolete50, :peer_failed_verification, :got_nothing, :ssl_engine_notfound, :ssl_engine_setfailed, :send_error, :recv_error, :obsolete57, :ssl_certproblem, :ssl_cipher, :ssl_cacert, :bad_content_encoding, :ldap_invalid_url, :filesize_exceeded, :use_ssl_failed, :send_fail_rewind, :ssl_engine_initfailed, :login_denied, :tftp_notfound, :tftp_perm, :remote_disk_full, :tftp_illegal, :tftp_unknownid, :remote_file_exists, :tftp_nosuchuser, :conv_failed, :conv_reqd, :ssl_cacert_badfile, :remote_file_not_found, :ssh, :ssl_shutdown_failed, :again, :ssl_crl_badfile, :ssl_issuer_error, :ftp_pret_failed, :rtsp_cseq_error, :rtsp_session_error, :ftp_bad_file_list, :chunk_failed, :last] # :nodoc: MultiCode = enum :multi_code, [ :call_multi_perform, -1, :ok, :bad_handle, :bad_easy_handle, :out_of_memory, :internal_error, :bad_socket, :unknown_option, :last] # :nodoc: OptionType = enum [ :long, 0, :object_point, 10000, :function_point, 20000, :off_t, 30000] # :nodoc: Option = enum :option, [ :file, OptionType[:object_point] + 1, :writedata, OptionType[:object_point] + 1, :url, OptionType[:object_point] + 2, :port, OptionType[:long] + 3, :proxy, OptionType[:object_point] + 4, :userpwd, OptionType[:object_point] + 5, :proxyuserpwd, OptionType[:object_point] + 6, :range, OptionType[:object_point] + 7, :infile, OptionType[:object_point] + 9, :readdata, OptionType[:object_point] + 9, :errorbuffer, OptionType[:object_point] + 10, :writefunction, OptionType[:function_point] + 11, :readfunction, OptionType[:function_point] + 12, :timeout, OptionType[:long] + 13, :infilesize, OptionType[:long] + 14, :postfields, OptionType[:object_point] + 15, :referer, OptionType[:object_point] + 16, :ftpport, OptionType[:object_point] + 17, :useragent, OptionType[:object_point] + 18, :low_speed_time, OptionType[:long] + 20, :resume_from, OptionType[:long] + 21, :cookie, OptionType[:object_point] + 22, :httpheader, OptionType[:object_point] + 23, :httppost, OptionType[:object_point] + 24, :sslcert, OptionType[:object_point] + 25, :sslcertpasswd, OptionType[:object_point] + 26, :sslkeypasswd, OptionType[:object_point] + 26, :crlf, OptionType[:long] + 27, :quote, OptionType[:object_point] + 28, :writeheader, OptionType[:object_point] + 29, :headerdata, OptionType[:object_point] + 29, :cookiefile, OptionType[:object_point] + 31, :sslversion, OptionType[:long] + 32, :timecondition, OptionType[:long] + 33, :timevalue, OptionType[:long] + 34, :customrequest, OptionType[:object_point] + 36, :stderr, OptionType[:object_point] + 37, :postquote, OptionType[:object_point] + 39, :writeinfo, OptionType[:object_point] + 40, :verbose, OptionType[:long] + 41, :header, OptionType[:long] + 42, :noprogress, OptionType[:long] + 43, :nobody, OptionType[:long] + 44, :failonerror, OptionType[:long] + 45, :upload, OptionType[:long] + 46, :post, OptionType[:long] + 47, :ftplistonly, OptionType[:long] + 48, :ftpappend, OptionType[:long] + 50, :netrc, OptionType[:long] + 51, :followlocation, OptionType[:long] + 52, :transfertext, OptionType[:long] + 53, :put, OptionType[:long] + 54, :progressfunction, OptionType[:function_point] + 56, :progressdata, OptionType[:object_point] + 57, :autoreferer, OptionType[:long] + 58, :proxyport, OptionType[:long] + 59, :postfieldsize, OptionType[:long] + 60, :httpproxytunnel, OptionType[:long] + 61, :interface, OptionType[:object_point] + 62, :ssl_verifypeer, OptionType[:long] + 64, :cainfo, OptionType[:object_point] + 65, :maxredirs, OptionType[:long] + 68, :filetime, OptionType[:long] + 69, :telnetoptions, OptionType[:object_point] + 70, :maxconnects, OptionType[:long] + 71, :closepolicy, OptionType[:long] + 72, :fresh_connect, OptionType[:long] + 74, :forbid_reuse, OptionType[:long] + 75, :random_file, OptionType[:object_point] + 76, :egdsocket, OptionType[:object_point] + 77, :connecttimeout, OptionType[:long] + 78, :headerfunction, OptionType[:function_point] + 79, :httpget, OptionType[:long] + 80, :ssl_verifyhost, OptionType[:long] + 81, :cookiejar, OptionType[:object_point] + 82, :ssl_cipher_list, OptionType[:object_point] + 83, :http_version, OptionType[:long] + 84, :ftp_use_epsv, OptionType[:long] + 85, :sslcerttype, OptionType[:object_point] + 86, :sslkey, OptionType[:object_point] + 87, :sslkeytype, OptionType[:object_point] + 88, :sslengine, OptionType[:object_point] + 89, :sslengine_default, OptionType[:long] + 90, :dns_use_global_cache, OptionType[:long] + 91, :dns_cache_timeout, OptionType[:long] + 92, :prequote, OptionType[:object_point] + 93, :debugfunction, OptionType[:function_point] + 94, :debugdata, OptionType[:object_point] + 95, :cookiesession, OptionType[:long] + 96, :capath, OptionType[:object_point] + 97, :buffersize, OptionType[:long] + 98, :nosignal, OptionType[:long] + 99, :share, OptionType[:object_point] + 100, :proxytype, OptionType[:long] + 101, :encoding, OptionType[:object_point] + 102, :private, OptionType[:object_point] + 103, :unrestricted_auth, OptionType[:long] + 105, :ftp_use_eprt, OptionType[:long] + 106, :httpauth, OptionType[:long] + 107, :ssl_ctx_function, OptionType[:function_point] + 108, :ssl_ctx_data, OptionType[:object_point] + 109, :ftp_create_missing_dirs, OptionType[:long] + 110, :proxyauth, OptionType[:long] + 111, :ipresolve, OptionType[:long] + 113, :maxfilesize, OptionType[:long] + 114, :infilesize_large, OptionType[:off_t] + 115, :resume_from_large, OptionType[:off_t] + 116, :maxfilesize_large, OptionType[:off_t] + 117, :netrc_file, OptionType[:object_point] + 118, :ftp_ssl, OptionType[:long] + 119, :postfieldsize_large, OptionType[:off_t] + 120, :tcp_nodelay, OptionType[:long] + 121, :ftpsslauth, OptionType[:long] + 129, :ioctlfunction, OptionType[:function_point] + 130, :ioctldata, OptionType[:object_point] + 131, :ftp_account, OptionType[:object_point] + 134, :cookielist, OptionType[:object_point] + 135, :ignore_content_length, OptionType[:long] + 136, :ftp_skip_pasv_ip, OptionType[:long] + 137, :ftp_filemethod, OptionType[:long] + 138, :localport, OptionType[:long] + 139, :localportrange, OptionType[:long] + 140, :connect_only, OptionType[:long] + 141, :conv_from_network_function, OptionType[:function_point] + 142, :conv_to_network_function, OptionType[:function_point] + 143, :max_send_speed_large, OptionType[:off_t] + 145, :max_recv_speed_large, OptionType[:off_t] + 146, :ftp_alternative_to_user, OptionType[:object_point] + 147, :sockoptfunction, OptionType[:function_point] + 148, :sockoptdata, OptionType[:object_point] + 149, :ssl_sessionid_cache, OptionType[:long] + 150, :ssh_auth_types, OptionType[:long] + 151, :ssh_public_keyfile, OptionType[:object_point] + 152, :ssh_private_keyfile, OptionType[:object_point] + 153, :ftp_ssl_ccc, OptionType[:long] + 154, :timeout_ms, OptionType[:long] + 155, :connecttimeout_ms, OptionType[:long] + 156, :http_transfer_decoding, OptionType[:long] + 157, :http_content_decoding, OptionType[:long] + 158, :copypostfields, OptionType[:object_point] + 165] # :nodoc: InfoType = enum [ :string, 0x100000, :long, 0x200000, :double, 0x300000, :slist, 0x400000] # :nodoc: Info = enum :info, [ :effective_url, InfoType[:string] + 1, :response_code, InfoType[:long] + 2, :total_time, InfoType[:double] + 3, :namelookup_time, InfoType[:double] + 4, :connect_time, InfoType[:double] + 5, :pretransfer_time, InfoType[:double] + 6, :size_upload, InfoType[:double] + 7, :size_download, InfoType[:double] + 8, :speed_download, InfoType[:double] + 9, :speed_upload, InfoType[:double] + 10, :header_size, InfoType[:long] + 11, :request_size, InfoType[:long] + 12, :ssl_verifyresult, InfoType[:long] + 13, :filetime, InfoType[:long] + 14, :content_length_download, InfoType[:double] + 15, :content_length_upload, InfoType[:double] + 16, :starttransfer_time, InfoType[:double] + 17, :content_type, InfoType[:string] + 18, :redirect_time, InfoType[:double] + 19, :redirect_count, InfoType[:long] + 20, :private, InfoType[:string] + 21, :http_connectcode, InfoType[:long] + 22, :httpauth_avail, InfoType[:long] + 23, :proxyauth_avail, InfoType[:long] + 24, :os_errno, InfoType[:long] + 25, :num_connects, InfoType[:long] + 26, :ssl_engines, InfoType[:slist] + 27, :cookielist, InfoType[:slist] + 28, :lastsocket, InfoType[:long] + 29, :ftp_entry_path, InfoType[:string] + 30, :redirect_url, InfoType[:string] + 31, :primary_ip, InfoType[:string] + 32, :appconnect_time, InfoType[:double] + 33, :certinfo, InfoType[:slist] + 34, :condition_unmet, InfoType[:long] + 35, :rtsp_session_id, InfoType[:string] + 36, :rtsp_client_cseq, InfoType[:long] + 37, :rtsp_server_cseq, InfoType[:long] + 38, :rtsp_cseq_recv, InfoType[:long] + 39, :primary_port, InfoType[:long] + 40, :local_ip, InfoType[:string] + 41, :local_port, InfoType[:long] + 42, :last, 42] # :nodoc: FormOption = enum :form_option, [ :none, :copyname, :ptrname, :namelength, :copycontents, :ptrcontents, :contentslength, :filecontent, :array, :obsolete, :file, :buffer, :bufferptr, :bufferlength, :contenttype, :contentheader, :filename, :end, :obsolete2, :stream, :last] # :nodoc: Auth = enum [ :basic, 0x01, :digest, 0x02, :gssnegotiate, 0x04, :ntlm, 0x08, :digest_ie, 0x10, :auto, 0x1f] # all options or'd together # :nodoc: Proxy = enum [ :http, 0, :http_1_0, 1, :socks4, 4, :socks5, 5, :socks4a, 6, :socks5_hostname, 7] # :nodoc: SSLVersion = enum [ :default, 0, :tlsv1, 1, :sslv2, 2, :sslv3, 3] # :nodoc: MsgCode = enum :msg_code, [:none, :done, :last] # :nodoc: class MsgData < ::FFI::Union layout :whatever, :pointer, :code, :easy_code end # :nodoc: class Msg < ::FFI::Struct layout :code, :msg_code, :easy_handle, :pointer, :data, MsgData end # :nodoc: class FDSet < ::FFI::Struct # XXX how does this work on non-windows? how can curl know the new size... FD_SETSIZE = 524288 # set a higher maximum number of fds. this has never applied to windows, so just use the default there if Curl.windows? layout :fd_count, :u_int, :fd_array, [:u_int, 64] # 2048 FDs def clear; self[:fd_count] = 0; end else layout :fds_bits, [:long, FD_SETSIZE / ::FFI::Type::LONG.size] # :nodoc: def clear; super; end end end # :nodoc: class Timeval < ::FFI::Struct layout :sec, :time_t, :usec, :suseconds_t end callback :callback, [:pointer, :size_t, :size_t, :pointer], :size_t ffi_lib_flags :now, :global ffi_lib ['libcurl', 'libcurl.so.4'] attach_function :global_init, :curl_global_init, [:long], :int attach_function :easy_init, :curl_easy_init, [], :pointer attach_function :easy_cleanup, :curl_easy_cleanup, [:pointer], :void attach_function :easy_getinfo, :curl_easy_getinfo, [:pointer, :info, :pointer], :easy_code attach_function :easy_setopt, :curl_easy_setopt, [:pointer, :option, :pointer], :easy_code attach_function :easy_setopt_string, :curl_easy_setopt, [:pointer, :option, :string], :easy_code attach_function :easy_setopt_long, :curl_easy_setopt, [:pointer, :option, :long], :easy_code attach_function :easy_setopt_callback, :curl_easy_setopt, [:pointer, :option, :callback], :easy_code attach_function :easy_perform, :curl_easy_perform, [:pointer], :easy_code attach_function :easy_strerror, :curl_easy_strerror, [:int], :string attach_function :easy_escape, :curl_easy_escape, [:pointer, :pointer, :int], :string attach_function :easy_reset, :curl_easy_reset, [:pointer], :void attach_function :formadd, :curl_formadd, [:pointer, :pointer, :varargs], :int attach_function :multi_init, :curl_multi_init, [], :pointer attach_function :multi_add_handle, :curl_multi_add_handle, [:pointer, :pointer], :multi_code attach_function :multi_remove_handle, :curl_multi_remove_handle, [:pointer, :pointer], :multi_code attach_function :multi_info_read, :curl_multi_info_read, [:pointer, :pointer], Msg.ptr attach_function :multi_perform, :curl_multi_perform, [:pointer, :pointer], :multi_code attach_function :multi_timeout, :curl_multi_timeout, [:pointer, :pointer], :multi_code attach_function :multi_fdset, :curl_multi_fdset, [:pointer, FDSet.ptr, FDSet.ptr, FDSet.ptr, :pointer], :multi_code attach_function :multi_strerror, :curl_multi_strerror, [:int], :string attach_function :version, :curl_version, [], :string attach_function :slist_append, :curl_slist_append, [:pointer, :string], :pointer attach_function :slist_free_all, :curl_slist_free_all, [:pointer], :void if windows? ffi_lib 'ws2_32' else ffi_lib ::FFI::Library::LIBC end @blocking = true attach_function :select, [:int, FDSet.ptr, FDSet.ptr, FDSet.ptr, Timeval.ptr], :int @@initialized = false @@init_mutex = Mutex.new class << self # This function sets up the program environment that libcurl needs. # Think of it as an extension of the library loader. # # This function must be called at least once within a program (a program is all the # code that shares a memory space) before the program calls any other function in libcurl. # The environment it sets up is constant for the life of the program and is the same for # every program, so multiple calls have the same effect as one call. # # The flags option is a bit pattern that tells libcurl exactly what features to init, # as described below. Set the desired bits by ORing the values together. In normal # operation, you must specify CURL_GLOBAL_ALL. Don't use any other value unless # you are familiar with it and mean to control internal operations of libcurl. # # This function is not thread safe. You must not call it when any other thread in # the program (i.e. a thread sharing the same memory) is running. This doesn't just # mean no other thread that is using libcurl. Because curl_global_init() calls # functions of other libraries that are similarly thread unsafe, it could conflict with # any other thread that uses these other libraries. def init @@init_mutex.synchronize { if not @@initialized raise RuntimeError.new('curl failed to initialise') if Curl.global_init(GLOBAL_ALL) != 0 @@initialized = true end } end # Sets appropriate option for easy, depending on value type. def set_option(option, value, handle) case value when String easy_setopt_string(handle, option, value.to_s) when Integer easy_setopt_long(handle, option, value) when Proc, FFI::Function easy_setopt_callback(handle, option, value) else easy_setopt(handle, option, value) if value end end # Return info as string. # # @example Return info. # Curl.get_info_string(:primary_ip, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ String ] The info. def get_info_string(option, handle) if easy_getinfo(handle, option, string_ptr) == :ok string_ptr.read_pointer.read_string else nil end end # Return info as integer. # # @example Return info. # Curl.get_info_long(:response_code, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ Integer ] The info. def get_info_long(option, handle) if easy_getinfo(handle, option, long_ptr) == :ok long_ptr.read_long else nil end end # Return info as float. # # @example Return info. # Curl.get_info_double(:response_code, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ Float ] The info. def get_info_double(option, handle) if easy_getinfo(handle, option, double_ptr) == :ok double_ptr.read_double else nil end end # Return a string pointer. # # @example Return a string pointer. # Curl.string_ptr # # @return [ ::FFI::Pointer ] The string pointer. def string_ptr @string_ptr ||= ::FFI::MemoryPointer.new(:pointer) end # Return a long pointer. # # @example Return a long pointer. # Curl.long_ptr # # @return [ ::FFI::Pointer ] The long pointer. def long_ptr @long_ptr ||= ::FFI::MemoryPointer.new(:long) end # Return a double pointer. # # @example Return a double pointer. # Curl.double_ptr # # @return [ ::FFI::Pointer ] The double pointer. def double_ptr @double_ptr ||= ::FFI::MemoryPointer.new(:double) end end end end