curb-1.3.5/0000755000004100000410000000000015203731642012506 5ustar www-datawww-datacurb-1.3.5/curb.gemspec0000644000004100000410000001200415203731642015003 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: curb 1.3.5 ruby libext # stub: ext/extconf.rb Gem::Specification.new do |s| s.name = "curb".freeze s.version = "1.3.5".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "changelog_uri" => "https://github.com/taf2/curb/blob/master/ChangeLog.md" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze, "ext".freeze] s.authors = ["Ross Bamford".freeze, "Todd A. Fisher".freeze] s.date = "2026-05-14" s.description = "Curb (probably CUrl-RuBy or something) provides Ruby-language bindings for the libcurl(3), a fully-featured client-side URL transfer library. cURL and libcurl live at http://curl.haxx.se/".freeze s.email = "todd.fisher@gmail.com".freeze s.extensions = ["ext/extconf.rb".freeze] s.extra_rdoc_files = ["LICENSE".freeze, "README.md".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "doc.rb".freeze, "ext/banned.h".freeze, "ext/curb.c".freeze, "ext/curb.h".freeze, "ext/curb_easy.c".freeze, "ext/curb_easy.h".freeze, "ext/curb_errors.c".freeze, "ext/curb_errors.h".freeze, "ext/curb_macros.h".freeze, "ext/curb_multi.c".freeze, "ext/curb_multi.h".freeze, "ext/curb_postfield.c".freeze, "ext/curb_postfield.h".freeze, "ext/curb_upload.c".freeze, "ext/curb_upload.h".freeze, "ext/extconf.rb".freeze, "lib/curb.rb".freeze, "lib/curl.rb".freeze, "lib/curl/easy.rb".freeze, "lib/curl/multi.rb".freeze, "tests/alltests.rb".freeze, "tests/bug_crash_on_debug.rb".freeze, "tests/bug_crash_on_progress.rb".freeze, "tests/bug_curb_easy_blocks_ruby_threads.rb".freeze, "tests/bug_curb_easy_post_with_string_no_content_length_header.rb".freeze, "tests/bug_follow_redirect_288.rb".freeze, "tests/bug_instance_post_differs_from_class_post.rb".freeze, "tests/bug_issue102.rb".freeze, "tests/bug_issue_noproxy.rb".freeze, "tests/bug_issue_post_redirect.rb".freeze, "tests/bug_issue_spnego.rb".freeze, "tests/bug_multi_segfault.rb".freeze, "tests/bug_postfields_crash.rb".freeze, "tests/bug_postfields_crash2.rb".freeze, "tests/bug_raise_on_callback.rb".freeze, "tests/bug_require_last_or_segfault.rb".freeze, "tests/bugtests.rb".freeze, "tests/helper.rb".freeze, "tests/leak_trace.rb".freeze, "tests/mem_check.rb".freeze, "tests/require_last_or_segfault_script.rb".freeze, "tests/signals.rb".freeze, "tests/tc_curl.rb".freeze, "tests/tc_curl_download.rb".freeze, "tests/tc_curl_easy.rb".freeze, "tests/tc_curl_easy_cookielist.rb".freeze, "tests/tc_curl_easy_request_target.rb".freeze, "tests/tc_curl_easy_resolve.rb".freeze, "tests/tc_curl_easy_setopt.rb".freeze, "tests/tc_curl_maxfilesize.rb".freeze, "tests/tc_curl_multi.rb".freeze, "tests/tc_curl_native_coverage.rb".freeze, "tests/tc_curl_postfield.rb".freeze, "tests/tc_curl_protocols.rb".freeze, "tests/tc_fiber_scheduler.rb".freeze, "tests/tc_ftp_options.rb".freeze, "tests/tc_gc_compact.rb".freeze, "tests/tc_test_server_methods.rb".freeze, "tests/timeout.rb".freeze, "tests/timeout_server.rb".freeze, "tests/unittests.rb".freeze] s.homepage = "https://github.com/taf2/curb".freeze s.licenses = ["Ruby".freeze] s.rdoc_options = ["--main".freeze, "README.md".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze) s.rubygems_version = "4.0.10".freeze s.summary = "Ruby libcurl bindings".freeze s.test_files = ["tests/alltests.rb".freeze, "tests/bug_crash_on_debug.rb".freeze, "tests/bug_crash_on_progress.rb".freeze, "tests/bug_curb_easy_blocks_ruby_threads.rb".freeze, "tests/bug_curb_easy_post_with_string_no_content_length_header.rb".freeze, "tests/bug_follow_redirect_288.rb".freeze, "tests/bug_instance_post_differs_from_class_post.rb".freeze, "tests/bug_issue102.rb".freeze, "tests/bug_issue_noproxy.rb".freeze, "tests/bug_issue_post_redirect.rb".freeze, "tests/bug_issue_spnego.rb".freeze, "tests/bug_multi_segfault.rb".freeze, "tests/bug_postfields_crash.rb".freeze, "tests/bug_postfields_crash2.rb".freeze, "tests/bug_raise_on_callback.rb".freeze, "tests/bug_require_last_or_segfault.rb".freeze, "tests/bugtests.rb".freeze, "tests/helper.rb".freeze, "tests/leak_trace.rb".freeze, "tests/mem_check.rb".freeze, "tests/require_last_or_segfault_script.rb".freeze, "tests/signals.rb".freeze, "tests/tc_curl.rb".freeze, "tests/tc_curl_download.rb".freeze, "tests/tc_curl_easy.rb".freeze, "tests/tc_curl_easy_cookielist.rb".freeze, "tests/tc_curl_easy_request_target.rb".freeze, "tests/tc_curl_easy_resolve.rb".freeze, "tests/tc_curl_easy_setopt.rb".freeze, "tests/tc_curl_maxfilesize.rb".freeze, "tests/tc_curl_multi.rb".freeze, "tests/tc_curl_native_coverage.rb".freeze, "tests/tc_curl_postfield.rb".freeze, "tests/tc_curl_protocols.rb".freeze, "tests/tc_fiber_scheduler.rb".freeze, "tests/tc_ftp_options.rb".freeze, "tests/tc_gc_compact.rb".freeze, "tests/tc_test_server_methods.rb".freeze, "tests/timeout.rb".freeze, "tests/timeout_server.rb".freeze, "tests/unittests.rb".freeze] end curb-1.3.5/lib/0000755000004100000410000000000015203731642013254 5ustar www-datawww-datacurb-1.3.5/lib/curb.rb0000644000004100000410000000005515203731642014534 0ustar www-datawww-data# frozen_string_literal: true require 'curl' curb-1.3.5/lib/curl/0000755000004100000410000000000015203731642014221 5ustar www-datawww-datacurb-1.3.5/lib/curl/multi.rb0000644000004100000410000002615115203731642015705 0ustar www-datawww-data# frozen_string_literal: true module Curl class Multi class DownloadError < RuntimeError attr_accessor :errors end class << self # call-seq: # Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy| # easy # end # # Blocking call to fetch multiple url's in parallel. def get(urls, easy_options={}, multi_options={}, &blk) url_confs = [] urls.each do|url| url_confs << {:url => url, :method => :get}.merge(easy_options) end self.http(url_confs, multi_options) {|c,code,method| blk.call(c) if blk } end # call-seq: # # Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}, # {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}, # {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}], # { :follow_location => true, :multipart_form_post => true }, # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy| # easy_handle_on_request_complete # end # # Blocking call to POST multiple form's in parallel. # # urls_with_config: is a hash of url's pointing to the postfields to send # easy_options: are a set of common options to set on all easy handles # multi_options: options to set on the Curl::Multi handle # def post(urls_with_config, easy_options={}, multi_options={}, &blk) url_confs = [] urls_with_config.each do|uconf| url_confs << uconf.merge(:method => :post).merge(easy_options) end self.http(url_confs, multi_options) {|c,code,method| blk.call(c) } end # call-seq: # # Curl::Multi.put([{:url => 'url1', :put_data => "some message"}, # {:url => 'url2', :put_data => IO.read('filepath')}, # {:url => 'url3', :put_data => "maybe another string or socket?"], # {:follow_location => true}, # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy| # easy_handle_on_request_complete # end # # Blocking call to POST multiple form's in parallel. # # urls_with_config: is a hash of url's pointing to the postfields to send # easy_options: are a set of common options to set on all easy handles # multi_options: options to set on the Curl::Multi handle # def put(urls_with_config, easy_options={}, multi_options={}, &blk) url_confs = [] urls_with_config.each do|uconf| url_confs << uconf.merge(:method => :put).merge(easy_options) end self.http(url_confs, multi_options) {|c,code,method| blk.call(c) } end # call-seq: # # Curl::Multi.http( [ # { :url => 'url1', :method => :post, # :post_fields => {'field1' => 'value1', 'field2' => 'value2'} }, # { :url => 'url2', :method => :get, # :follow_location => true, :max_redirects => 3 }, # { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') }, # { :url => 'url4', :method => :head } # ], {:pipeline => Curl::CURLPIPE_HTTP1}) # # Blocking call to issue multiple HTTP requests with varying verb's. # # urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used # multi_options: options for the multi handle # blk: a callback, that yeilds when a handle is completed # def http(urls_with_config, multi_options={}, &blk) m = Curl::Multi.new # maintain a sane number of easy handles multi_options[:max_connects] = max_connects = multi_options.key?(:max_connects) ? multi_options[:max_connects] : 10 free_handles = [] # keep a list of free easy handles # configure the multi handle multi_options.each { |k,v| m.send("#{k}=", v) } callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_missing,:on_body,:on_header] add_free_handle = proc do|conf, easy| c = conf.dup # avoid being destructive to input url = c.delete(:url) method = c.delete(:method) headers = c.delete(:headers) easy = Curl::Easy.new if easy.nil? easy.url = url # assign callbacks callbacks.each do |cb| cbproc = c.delete(cb) easy.send(cb,&cbproc) if cbproc end case method when :post fields = c.delete(:post_fields) # set the post post using the url fields easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&') when :put easy.put_data = c.delete(:put_data) when :head easy.head = true when :delete easy.delete = true when :get else # XXX: nil is treated like a GET end # headers is a special key headers.each {|k,v| easy.headers[k] = v } if headers # # use the remaining options as specific configuration to the easy handle # bad options should raise an undefined method error # c.each { |k,v| easy.send("#{k}=",v) } easy.on_complete {|curl| free_handles << curl blk.call(curl,curl.response_code,method) if blk } m.add(easy) end max_connects.times do conf = urls_with_config.pop add_free_handle.call(conf, nil) if conf break if urls_with_config.empty? end consume_free_handles = proc do # as we idle consume free handles if urls_with_config.size > 0 && free_handles.size > 0 easy = free_handles.pop conf = urls_with_config.pop add_free_handle.call(conf, easy) if conf end end begin if urls_with_config.empty? m.perform else until urls_with_config.empty? m.perform do consume_free_handles.call end consume_free_handles.call end free_handles = nil end ensure m.close end end # call-seq: # # Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|} # # will create 2 new files file1.txt and file2.txt # # 2 files will be opened, and remain open until the call completes # # when using the :post or :put method, urls should be a hash, including the individual post fields per post # def download(urls,easy_options={},multi_options={},download_paths=nil,&blk) errors = [] procs = [] files = [] urls_with_config = [] url_to_download_paths = {} urls.each_with_index do|urlcfg,i| if urlcfg.is_a?(Hash) url = url[:url] else url = urlcfg end if download_paths and download_paths[i] download_path = download_paths[i] else download_path = File.basename(url) end file = lambda do|dp| file = File.open(dp,"wb") procs << (lambda {|data| file.write data; data.size }) files << file file end.call(download_path) if urlcfg.is_a?(Hash) urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options)) else urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options) end url_to_download_paths[url] = {:path => download_path, :file => file} # store for later end if blk # when injecting the block, ensure file is closed before yielding Curl::Multi.http(urls_with_config, multi_options) do |c,code,method| info = url_to_download_paths[c.url] begin file = info[:file] files.reject!{|f| f == file } file.close rescue => e errors << e end blk.call(c,info[:path]) end else Curl::Multi.http(urls_with_config, multi_options) end ensure files.each {|f| begin f.close rescue => e errors << e end } if errors.any? de = Curl::Multi::DownloadError.new de.errors = errors raise de end end end def cancel! requests.each do |_,easy| remove(easy) end end def idle? requests.empty? end def requests @requests ||= {} end def __idle_easy_references @__curb_idle_easy_references ||= ObjectSpace::WeakMap.new end def __register_idle_easy_reference(easy) __idle_easy_references[easy] = true self end def __unregister_idle_easy_reference(easy) return self unless instance_variable_defined?(:@__curb_idle_easy_references) if @__curb_idle_easy_references.respond_to?(:delete) @__curb_idle_easy_references.delete(easy) else retained_references = ObjectSpace::WeakMap.new @__curb_idle_easy_references.each_key do |tracked_easy| next if tracked_easy.equal?(easy) retained_references[tracked_easy] = true end @__curb_idle_easy_references = retained_references end self end def __clear_idle_easy_references return unless instance_variable_defined?(:@__curb_idle_easy_references) @__curb_idle_easy_references.keys.each do |easy| easy.multi = nil if easy.multi.equal?(self) end @__curb_idle_easy_references = ObjectSpace::WeakMap.new end private :__idle_easy_references, :__register_idle_easy_reference, :__unregister_idle_easy_reference, :__clear_idle_easy_references def add(easy) return self if requests[easy.object_id] # Once a deferred callback exception is pending, Multi#perform is # draining existing transfers only and must not start replacement work. return self if instance_variable_defined?(:@__curb_deferred_exception) _add(easy) __unregister_idle_easy_reference(easy) requests[easy.object_id] = easy self end def remove(easy) return self if !requests[easy.object_id] requests.delete(easy.object_id) _remove(easy) self end def close __close(true) end def _autoclose __close(false) end private :_autoclose private def __close(permanent) requests.values.each {|easy| _remove(easy) } __clear_idle_easy_references if permanent @requests = {} _close _mark_closed if permanent self end end end curb-1.3.5/lib/curl/easy.rb0000644000004100000410000005335615203731642015523 0ustar www-datawww-data# frozen_string_literal: true module Curl class Easy class << self def deferred_multi_close_mutex @deferred_multi_close_mutex ||= Mutex.new end def deferred_multi_closes deferred_multi_close_mutex.synchronize do (@deferred_multi_closes ||= []).dup end end def release_deferred_multi_close(multi, easy) if easy && multi.requests[easy.object_id] begin multi.remove(easy) rescue StandardError # Deferred cleanup only applies to implicit single-easy multis, so # clear any stale Ruby bookkeeping and continue closing the handle. multi.instance_variable_set(:@requests, {}) end else multi.instance_variable_set(:@requests, {}) end multi.instance_variable_set(:@deferred_close, false) multi._close true rescue StandardError false end def defer_multi_close(multi, easy, owner: Thread.current) deferred_multi_close_mutex.synchronize do @deferred_multi_closes ||= [] return if @deferred_multi_closes.any? { |entry| entry[:multi].equal?(multi) } multi.instance_variable_set(:@deferred_close, true) @deferred_multi_closes << { multi: multi, easy: easy, owner: owner } end end def flush_deferred_multi_closes(all_threads: false) pending = deferred_multi_close_mutex.synchronize do @deferred_multi_closes ||= [] if all_threads @deferred_multi_closes.shift(@deferred_multi_closes.length) else owner = Thread.current remaining = [] current = [] @deferred_multi_closes.each do |entry| if entry[:owner].equal?(owner) current << entry else remaining << entry end end @deferred_multi_closes = remaining current end end pending.each do |entry| multi = entry[:multi] easy = entry[:easy] unless release_deferred_multi_close(multi, easy) defer_multi_close(multi, easy, owner: entry[:owner]) end end end end at_exit do flush_deferred_multi_closes(all_threads: true) end alias_method :_curb_native_close, :close alias_method :_curb_native_multi_set, :multi= def close previous_multi = self.multi result = _curb_native_close previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi result end def multi=(multi) previous_multi = self.multi return multi if previous_multi.equal?(multi) result = _curb_native_multi_set(multi) previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi multi.__send__(:__register_idle_easy_reference, self) if multi result end alias post http_post alias put http_put alias body body_str alias head header_str class Error < StandardError attr_accessor :message, :code def initialize(code, msg) self.message = msg self.code = code end end # # call-seq: # easy.status => String # def status # Matches the last HTTP Status - following the HTTP protocol specification 'Status-Line = HTTP-Version SP Status-Code SP (Opt:)Reason-Phrase CRLF' statuses = self.header_str.to_s.scan(/HTTP\/\d(\.\d)?\s(\d+\s.*)\r\n/).map {|match| match[1] } statuses.last.strip if statuses.length > 0 end # # call-seq: # easy.set :sym|Fixnum, value # # set options on the curl easy handle see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html # def set(opt,val) if opt.is_a?(Symbol) option = sym2curl(opt) else option = opt.to_i end begin setopt(option, val) rescue TypeError raise TypeError, "Curb doesn't support setting #{opt} [##{option}] option" end end # # call-seq: # easy.sym2curl :symbol => Fixnum # # translates ruby symbols to libcurl options # def sym2curl(opt) Curl.const_get("CURLOPT_#{opt.to_s.upcase}") end # # call-seq: # easy.perform => true # # Transfer the currently configured URL using the options set for this # Curl::Easy instance. If this is an HTTP URL, it will be transferred via # the configured HTTP Verb. # def perform self.class.flush_deferred_multi_closes if Curl.scheduler_active? && self.multi.nil? ret = Curl.perform_with_scheduler(self) else multi = self.multi created_multi = multi.nil? raised = false if created_multi multi = Curl::Multi.new self.multi = multi end begin multi.add(self) ret = multi.perform multi.remove(self) if self.multi == multi rescue Exception raised = true raise ensure if created_multi if raised unless self.class.release_deferred_multi_close(multi, self) self.class.defer_multi_close(multi, self) end self.multi = nil if self.multi == multi elsif Curl::Multi.autoclose multi.__send__(:_autoclose) self.multi = nil if self.multi == multi else self.multi = multi end elsif Curl::Multi.autoclose multi.__send__(:_autoclose) self.multi = nil if self.multi == multi else self.multi = multi end end end if (callback_error = _take_callback_error) raise callback_error end if self.last_result != 0 && self.on_failure.nil? err_class, err_summary = Curl::Easy.error(self.last_result) err_detail = self.last_error raise err_class.new([err_summary, err_detail].compact.join(": ")) end ret end # # call-seq: # # easy = Curl::Easy.new # easy.nosignal = true # def nosignal=(onoff) set :nosignal, !!onoff end # # call-seq: # easy = Curl::Easy.new("url") do|c| # c.delete = true # end # easy.perform # def delete=(onoff) set :customrequest, onoff ? 'DELETE' : nil onoff end # # call-seq: # # easy = Curl::Easy.new("url") # easy.version = Curl::HTTP_2_0 # easy.http_version = Curl::HTTP_1_1 # easy.http_version = Curl::HTTP_1_0 # easy.http_version = Curl::HTTP_NONE # def version=(http_version) self.http_version = http_version end def version http_version end # # call-seq: # easy.url = "http://some.url/" => "http://some.url/" # # Set the URL for subsequent calls to +perform+. It is acceptable # (and even recommended) to reuse Curl::Easy instances by reassigning # the URL between calls to +perform+. # def url=(u) set :url, u end # # call-seq: # easy.proxy_url = string => string # # Set the URL of the HTTP proxy to use for subsequent calls to +perform+. # The URL should specify the the host name or dotted IP address. To specify # port number in this string, append :[port] to the end of the host name. # The proxy string may be prefixed with [protocol]:// since any such prefix # will be ignored. The proxy's port number may optionally be specified with # the separate option proxy_port . # # When you tell the library to use an HTTP proxy, libcurl will transparently # convert operations to HTTP even if you specify an FTP URL etc. This may have # an impact on what other features of the library you can use, such as # FTP specifics that don't work unless you tunnel through the HTTP proxy. Such # tunneling is activated with proxy_tunnel = true. # # libcurl respects the environment variables *http_proxy*, *ftp_proxy*, # *all_proxy* etc, if any of those is set. The proxy_url option does however # override any possibly set environment variables. # # Starting with libcurl 7.14.1, the proxy host string given in environment # variables can be specified the exact same way as the proxy can be set with # proxy_url, including protocol prefix (http://) and embedded user + password. # def proxy_url=(url) set :proxy, url end # # call-seq: # easy.request_target = string => string # # Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET). # Useful for absolute-form request targets (e.g., when speaking to proxies) or # special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support. # def request_target=(value) if Curl.const_defined?(:CURLOPT_REQUEST_TARGET) set :request_target, value else raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl" end end def ssl_verify_host=(value) value = 1 if value.class == TrueClass value = 0 if value.class == FalseClass self.ssl_verify_host_integer=value end # # call-seq: # easy.ssl_verify_host? => boolean # # Deprecated: call easy.ssl_verify_host instead # can be one of [0,1,2] # # Determine whether this Curl instance will verify that the server cert # is for the server it is known as. # def ssl_verify_host? ssl_verify_host.nil? ? false : (ssl_verify_host > 0) end # # call-seq: # easy.interface = string => string # # Set the interface name to use as the outgoing network interface. # The name can be an interface name, an IP address or a host name. # def interface=(value) set :interface, value end # # call-seq: # easy.userpwd = string => string # # Set the username/password string to use for subsequent calls to +perform+. # The supplied string should have the form "username:password" # def userpwd=(value) set :userpwd, value end # # call-seq: # easy.proxypwd = string => string # # Set the username/password string to use for proxy connection during # subsequent calls to +perform+. The supplied string should have the # form "username:password" # def proxypwd=(value) set :proxyuserpwd, value end # # call-seq: # easy.cookies = "name1=content1; name2=content2;" => string # # Set the manual Cookie request header for this Curl::Easy instance. # The format of the string should be NAME=CONTENTS, where NAME is the cookie name and # CONTENTS is what the cookie should contain. Set multiple cookies in one string like this: # "name1=content1; name2=content2;". # # Notes: # - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT # alter the internal libcurl cookie engine (which stores cookies from Set-Cookie). # - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or # {#set} with :cookielist. # - To clear a previously set manual Cookie header, assign an empty string (''). # Assigning +nil+ has no effect in current curb versions. # def cookies=(value) set :cookie, value end # # call-seq: # easy.cookiefile = string => string # # Set a file that contains cookies to be sent in subsequent requests by this Curl::Easy instance. # # *Note* that you must set enable_cookies true to enable the cookie # engine, or this option will be ignored. # # Note: assigning +nil+ has no effect; pass a path string to use a cookie file. # def cookiefile=(value) set :cookiefile, value end # # call-seq: # easy.cookiejar = string => string # # Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response # will be written into this file. # # *Note* that you must set enable_cookies true to enable the cookie # engine, or this option will be ignored. # # Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file. # def cookiejar=(value) set :cookiejar, value end # # call-seq: # easy.cookielist = string => string # # Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST). # Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format, # or one of the special commands: "ALL" (clear), "SESS" (remove session cookies), # "FLUSH" (write to jar), "RELOAD" (reload from file). # # Examples: # easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;" # easy.cookielist = [ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"), # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"), # '' # ].join("\n") # easy.cookielist = 'ALL' # clear all cookies in the engine # def cookielist=(value) set :cookielist, value end # # call-seq: # easy = Curl::Easy.new("url") do|c| # c.head = true # end # easy.perform # def head=(onoff) set :nobody, onoff end # # call-seq: # easy.follow_location = boolean => boolean # # Configure whether this Curl instance will follow Location: headers # in HTTP responses. Redirects will only be followed to the extent # specified by +max_redirects+. # def follow_location=(onoff) set :followlocation, onoff end # # call-seq: # easy.http_head => true # # Request headers from the currently configured URL using the HEAD # method and current options set for this Curl::Easy instance. This # method always returns true, or raises an exception (defined under # Curl::Err) on error. # def http_head set :nobody, true ret = self.perform set :nobody, false ret end # # call-seq: # easy.http_get => true # # GET the currently configured URL using the current options set for # this Curl::Easy instance. This method always returns true, or raises # an exception (defined under Curl::Err) on error. # def http_get set :httpget, true http :GET end alias get http_get # # call-seq: # easy.http_delete # # DELETE the currently configured URL using the current options set for # this Curl::Easy instance. This method always returns true, or raises # an exception (defined under Curl::Err) on error. # def http_delete self.http :DELETE end alias delete http_delete class << self # # call-seq: # Curl::Easy.perform(url) { |easy| ... } => # # # Convenience method that creates a new Curl::Easy instance with # the specified URL and calls the general +perform+ method, before returning # the new instance. For HTTP URLs, this is equivalent to calling +http_get+. # # If a block is supplied, the new instance will be yielded just prior to # the +http_get+ call. # def perform(*args) c = Curl::Easy.new(*args) yield c if block_given? c.perform c end # # call-seq: # Curl::Easy.http_get(url) { |easy| ... } => # # # Convenience method that creates a new Curl::Easy instance with # the specified URL and calls +http_get+, before returning the new instance. # # If a block is supplied, the new instance will be yielded just prior to # the +http_get+ call. # def http_get(*args) c = Curl::Easy.new(*args) yield c if block_given? c.http_get c end # # call-seq: # Curl::Easy.http_head(url) { |easy| ... } => # # # Convenience method that creates a new Curl::Easy instance with # the specified URL and calls +http_head+, before returning the new instance. # # If a block is supplied, the new instance will be yielded just prior to # the +http_head+ call. # def http_head(*args) c = Curl::Easy.new(*args) yield c if block_given? c.http_head c end # # call-seq: # Curl::Easy.http_put(url, data) {|c| ... } # Curl::Easy.http_put(url, "some=urlencoded%20form%20data&and=so%20on") => true # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true # Curl::Easy.http_put(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true # # see easy.http_put # def http_put(*args) url = args.shift c = Curl::Easy.new(url) yield c if block_given? c.http_put(*args) c end # # call-seq: # Curl::Easy.http_patch(url, data) {|c| ... } # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data&and=so%20on") => true # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true # Curl::Easy.http_patch(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true # # see easy.http_patch # def http_patch(*args) url = args.shift c = Curl::Easy.new(url) yield c if block_given? c.http_patch(*args) c end # # call-seq: # Curl::Easy.http_post(url, "some=urlencoded%20form%20data&and=so%20on") => true # Curl::Easy.http_post(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true # Curl::Easy.http_post(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true # Curl::Easy.http_post(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true # # POST the specified formdata to the currently configured URL using # the current options set for this Curl::Easy instance. This method # always returns true, or raises an exception (defined under # Curl::Err) on error. # # If you wish to use multipart form encoding, you'll need to supply a block # in order to set multipart_form_post true. See #http_post for more # information. # def http_post(*args) url = args.shift c = Curl::Easy.new url yield c if block_given? c.http_post(*args) c end # # call-seq: # Curl::Easy.http_delete(url) { |easy| ... } => # # # Convenience method that creates a new Curl::Easy instance with # the specified URL and calls +http_delete+, before returning the new instance. # # If a block is supplied, the new instance will be yielded just prior to # the +http_delete+ call. # def http_delete(*args) c = Curl::Easy.new(*args) yield c if block_given? c.http_delete c end # call-seq: # Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... } # # Stream the specified url (via perform) and save the data directly to the # supplied filename (defaults to the last component of the URL path, which will # usually be the filename most simple urls). # # If a block is supplied, it will be passed the curl instance prior to the # perform call. # # *Note* that the semantics of the on_body handler are subtly changed when using # download, to account for the automatic routing of data to the specified file: The # data string is passed to the handler *before* it is written # to the file, allowing the handler to perform mutative operations where # necessary. As usual, the transfer will be aborted if the on_body handler # returns a size that differs from the data chunk size - in this case, the # offending chunk will *not* be written to the file, the file will be closed, # and a Curl::Err::AbortedByCallbackError will be raised. def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk) curl = Curl::Easy.new(url, &blk) output = if filename.is_a? IO filename.binmode if filename.respond_to?(:binmode) filename else File.open(filename, 'wb') end begin old_on_body = curl.on_body do |data| result = old_on_body ? old_on_body.call(data) : data.length output << data if result == data.length result end curl.perform ensure output.close rescue IOError end return curl end end # Allow the incoming cert string to be file:password # but be careful to not use a colon from a windows file path # as the split point. Mimic what curl's main does if respond_to?(:cert=) alias_method :native_cert=, :cert= def cert=(cert_file) pos = cert_file.rindex(':') if pos && pos > 1 self.native_cert= cert_file[0..pos-1] self.certpassword= cert_file[pos+1..-1] else self.native_cert= cert_file end self.cert end end end end curb-1.3.5/lib/curl.rb0000644000004100000410000001577215203731642014562 0ustar www-datawww-data# frozen_string_literal: true require 'curb_core' require 'curl/easy' require 'curl/multi' require 'uri' # expose shortcut methods module Curl def self.scheduler_active? Fiber.respond_to?(:scheduler) && !Fiber.scheduler.nil? end def self.deferred_exception_source_id(state) return unless state[:multi].instance_variable_defined?(:@__curb_deferred_exception_source_id) state[:multi].instance_variable_get(:@__curb_deferred_exception_source_id) end def self.scheduler_waiter_blocking_supported? scheduler = Fiber.scheduler scheduler && scheduler.respond_to?(:block) && scheduler.respond_to?(:unblock) end def self.wake_scheduler_waiter(waiter) fiber = waiter[:fiber] scheduler = waiter[:scheduler] return unless fiber&.alive? && scheduler&.respond_to?(:unblock) scheduler.unblock(waiter, fiber) end def self.complete_scheduler_waiter(waiter) return if waiter[:done] waiter[:done] = true wake_scheduler_waiter(waiter) end def self.fail_scheduler_waiter(waiter, error) return if waiter[:error] waiter[:error] = error wake_scheduler_waiter(waiter) end def self.release_scheduler_error(state, error) source_waiter = state[:waiters][deferred_exception_source_id(state)] if source_waiter fail_scheduler_waiter(source_waiter, error) else state[:error] = error state[:waiters].each_value { |waiter| wake_scheduler_waiter(waiter) } end end def self.block_scheduler_waiter(waiter) unless scheduler_waiter_blocking_supported? sleep 0 return end waiter[:fiber] = Fiber.current waiter[:scheduler] ||= Fiber.scheduler return if waiter[:done] || waiter[:error] waiter[:scheduler].block(waiter, nil) ensure waiter[:fiber] = nil if waiter[:fiber].equal?(Fiber.current) end def self.scheduler_yield scheduler = Fiber.scheduler if scheduler&.respond_to?(:kernel_sleep) scheduler.kernel_sleep(0) else sleep 0 end end def self.release_scheduler_waiters(state) source_id = deferred_exception_source_id(state) state[:waiters].each do |easy_id, waiter| next if source_id == easy_id complete_scheduler_waiter(waiter) if waiter[:completed] end end def self.perform_with_scheduler(easy) state = scheduler_state waiter = {completed: false, done: false, error: nil, fiber: nil, scheduler: Fiber.scheduler} state[:waiters][easy.object_id] = waiter previous_complete = easy.on_complete do |completed_easy| previous_complete.call(completed_easy) if previous_complete waiter[:completed] = true end state[:pending] << easy ensure_scheduler_driver(state) until waiter[:done] raise waiter[:error] if waiter[:error] raise state[:error] if state[:error] block_scheduler_waiter(waiter) end while state[:driver_running] && state[:pending].empty? && state[:waiters].length == 1 && state[:waiters].key?(easy.object_id) scheduler_yield end true ensure state[:waiters].delete(easy.object_id) if defined?(state) && state[:waiters] if defined?(previous_complete) if previous_complete easy.on_complete(&previous_complete) else easy.on_complete end end end def self.scheduler_state Thread.current.thread_variable_get(:curb_scheduler_state) || begin state = { multi: Curl::Multi.new, pending: [], driver_running: false, error: nil, waiters: {}, } Thread.current.thread_variable_set(:curb_scheduler_state, state) state end end def self.ensure_scheduler_driver(state) return if state[:driver_running] state[:driver_running] = true state[:error] = nil runner = proc do begin # Give sibling fibers a chance to enqueue work so the shared multi can # batch scheduler-driven Easy#perform calls together. pending_count = -1 until pending_count == state[:pending].size pending_count = state[:pending].size scheduler_yield end loop do drain_scheduler_pending(state) break if state[:multi].idle? begin state[:multi].perform do drain_scheduler_pending(state) release_scheduler_waiters(state) scheduler_yield end ensure # Release any siblings that completed just before a deferred # callback exception is re-raised. release_scheduler_waiters(state) end end rescue => e release_scheduler_waiters(state) release_scheduler_error(state, e) ensure state[:driver_running] = false ensure_scheduler_driver(state) if state[:error].nil? && !state[:pending].empty? end end if Fiber.respond_to?(:schedule) Fiber.schedule(&runner) else Fiber.new(blocking: false, &runner).resume end end def self.drain_scheduler_pending(state) pending = state[:pending] until pending.empty? easy = pending.first break if state[:multi].instance_variable_defined?(:@__curb_deferred_exception) state[:multi].add(easy) break unless state[:multi].requests.key?(easy.object_id) pending.shift end end def self.http(verb, url, post_body=nil, put_data=nil, &block) if Thread.current[:curb_curl_yielding] handle = Curl::Easy.new # we can't reuse this else handle = Thread.current[:curb_curl] ||= Curl::Easy.new handle.reset end handle.url = url handle.post_body = post_body if post_body handle.put_data = put_data if put_data if block_given? Thread.current[:curb_curl_yielding] = true yield handle Thread.current[:curb_curl_yielding] = false end handle.http(verb) handle end def self.get(url, params={}, &block) http :GET, urlalize(url, params), nil, nil, &block end def self.post(url, params={}, &block) http :POST, url, postalize(params), nil, &block end def self.put(url, params={}, &block) http :PUT, url, nil, postalize(params), &block end def self.delete(url, params={}, &block) http :DELETE, url, postalize(params), nil, &block end def self.patch(url, params={}, &block) http :PATCH, url, postalize(params), nil, &block end def self.head(url, params={}, &block) http :HEAD, urlalize(url, params), nil, nil, &block end def self.options(url, params={}, &block) http :OPTIONS, urlalize(url, params), nil, nil, &block end def self.urlalize(url, params={}) uri = URI(url) # early return if we didn't specify any extra params return uri.to_s if (params || {}).empty? params_query = URI.encode_www_form(params || {}) uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&') uri.to_s end def self.postalize(params={}) params.respond_to?(:map) ? URI.encode_www_form(params) : (params.respond_to?(:to_s) ? params.to_s : params) end def self.reset Thread.current[:curb_curl] = Curl::Easy.new end end curb-1.3.5/tests/0000755000004100000410000000000015203731642013650 5ustar www-datawww-datacurb-1.3.5/tests/unittests.rb0000644000004100000410000000017115203731642016236 0ustar www-datawww-data$: << $TESTDIR = File.expand_path(File.dirname(__FILE__)) Dir[File.join($TESTDIR, 'tc_*.rb')].each { |lib| require lib } curb-1.3.5/tests/bug_raise_on_callback.rb0000644000004100000410000000117715203731642020453 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugRaiseOnCallback < Test::Unit::TestCase include BugTestServerSetupTeardown def setup @port = unused_local_port super end def test_on_complte url = "http://127.0.0.1:#{@port}/test" c = Curl::Easy.new(url) did_raise = false begin c.on_complete do|x| assert_equal url, x.url raise "error complete" # this will get swallowed end c.perform rescue => e did_raise = true end assert did_raise, "we want to raise an exception if the ruby callbacks raise" end end #test_on_debug curb-1.3.5/tests/bug_issue_noproxy.rb0000644000004100000410000000310515203731642017757 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugIssueNoproxy < Test::Unit::TestCase def test_noproxy_option_support # Test that CURLOPT_NOPROXY constant is defined assert Curl.const_defined?(:CURLOPT_NOPROXY), "CURLOPT_NOPROXY constant should be defined" # Test basic noproxy setting using setopt c = Curl::Easy.new('https://google.com') # This should not raise an error assert_nothing_raised do c.setopt(Curl::CURLOPT_NOPROXY, "localhost,127.0.0.1") end # Test using the convenience method if it exists if c.respond_to?(:noproxy=) assert_nothing_raised do c.noproxy = "localhost,127.0.0.1" end # Test getter if it exists if c.respond_to?(:noproxy) assert_equal "localhost,127.0.0.1", c.noproxy end end end def test_noproxy_with_set_method c = Curl::Easy.new('https://google.com') # The issue specifically mentions using the set method # This currently raises TypeError as reported in the issue assert_nothing_raised(TypeError) do c.set(:noproxy, "localhost,127.0.0.1") end end def test_noproxy_empty_string c = Curl::Easy.new('https://google.com') # Test setting empty string to override environment variables assert_nothing_raised do c.setopt(Curl::CURLOPT_NOPROXY, "") end end def test_noproxy_nil_value c = Curl::Easy.new('https://google.com') # Test setting nil to reset assert_nothing_raised do c.setopt(Curl::CURLOPT_NOPROXY, nil) end end endcurb-1.3.5/tests/bug_issue102.rb0000644000004100000410000000056315203731642016411 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugIssue102 < Test::Unit::TestCase def test_interface test = "https://api.twitter.com/1/users/show.json?screen_name=TwitterAPI&include_entities=true" ip = "0.0.0.0" c = Curl::Easy.new do |curl| curl.url = test curl.interface = ip end c.perform end end curb-1.3.5/tests/alltests.rb0000644000004100000410000000021615203731642016027 0ustar www-datawww-data$: << $TESTDIR = File.expand_path(File.dirname(__FILE__)) require 'unittests' Dir[File.join($TESTDIR, 'bug_*.rb')].each { |lib| require lib } curb-1.3.5/tests/mem_check.rb0000644000004100000410000000345215203731642016114 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) #require 'rubygems' #require 'rmem' # # Run some tests to measure the memory usage of curb, these tests require fork and ps # class TestCurbMemory < Test::Unit::TestCase def setup omit('Memory fork/ps tests not supported on Windows') if WINDOWS || NO_FORK end def test_easy_memory easy_avg, easy_std = measure_object_memory(Curl::Easy) printf "Easy average: %.2f kilobytes +/- %.2f kilobytes\n", easy_avg.to_f, easy_std.to_f multi_avg, multi_std = measure_object_memory(Curl::Multi) printf "Multi average: %.2f kilobytes +/- %.2f kilobytes\n", multi_avg.to_f, multi_std.to_f # now that we have the average size of an easy handle lets see how much a multi request consumes with 10 requests end def c_avg(report) sum = 0 report.each {|r| sum += r.last } (sum.to_f / report.size) end def c_std(report,avg) var = 0 report.each {|r| var += (r.last-avg)*(r.last-avg) } Math.sqrt(var / (report.size-1)) end def measure_object_memory(klass) report = [] 200.times do res = mem_check do obj = klass.new end report << res end avg = c_avg(report) std = c_std(report,avg) [avg,std] end def mem_check # see: http://gist.github.com/264060 for inspiration of ps command line rd, wr = IO.pipe memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes fork do before = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes rd.close yield after = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes wr.write((after - before)) wr.flush wr.close end wr.close total = rd.read.to_i rd.close Process.wait # return the delta and the total [memory_usage, total] end end curb-1.3.5/tests/tc_curl_easy_cookielist.rb0000644000004100000410000002657015203731642021110 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) require 'json' class TestCurbCurlEasyCookielist < Test::Unit::TestCase def test_setopt_cookielist easy = Curl::Easy.new # DateTime handles time zone correctly expires = (Date.today + 2).to_datetime easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};") easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c2=v2; domain=localhost') easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c3=v3; expires=#{expires.httpdate};") easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c4=v4;') easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c5=v5; domain=127.0.0.1; expires=#{expires.httpdate};") easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c6=v6; domain=127.0.0.1;;') # Since 7.43.0 cookies that were imported in the Set-Cookie format without a domain name are not exported by this option. # So, before 7.43.0, c3 and c4 will be exported too; but that version is far too old for current curb version, so it's not handled here. if Curl::CURL_VERSION.to_f > 8 expected_cookielist = [ ".127.0.0.1\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5", ".127.0.0.1\tTRUE\t/\tFALSE\t0\tc6\tv6", ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1", ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2", ] else expected_cookielist = [ "127.0.0.1\tFALSE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5", "127.0.0.1\tFALSE\t/\tFALSE\t0\tc6\tv6", ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1", ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2", ] end assert_equal expected_cookielist, easy.cookielist easy.url = "#{TestServlet.url}/get_cookies" easy.perform assert_equal 'c6=v6; c5=v5; c4=v4; c3=v3', easy.body_str easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.perform assert_equal 'c2=v2; c1=v1', easy.body_str end # libcurl documentation says: "This option also enables the cookie engine", but it's not tracked on the curb level def test_setopt_cookielist_enables_cookie_engine easy = Curl::Easy.new expires = (Date.today + 2).to_datetime easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies" easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};") easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }]) easy.perform easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.post_body = nil easy.perform assert !easy.enable_cookies? assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1", ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist assert_equal 'c2=v2; c1=v1', easy.body_str end def test_setopt_cookielist_invalid_format easy = Curl::Easy.new easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.setopt(Curl::CURLOPT_COOKIELIST, 'Not a cookie') assert_nil easy.cookielist easy.perform assert_equal '', easy.body_str end def test_setopt_cookielist_netscape_format easy = Curl::Easy.new expires = (Date.today + 2).to_datetime easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" # Note domain changes for include subdomains [ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"), ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"), ['localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"), ['.localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"), ].each { |cookie| easy.setopt(Curl::CURLOPT_COOKIELIST, cookie) } expected_cookielist = [ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"), ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"), ['.localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"), ['localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"), ] assert_equal expected_cookielist, easy.cookielist easy.perform assert_equal 'permanent_http_only=45; session_http_only=42; permanent=44; session=43', easy.body_str end # Multiple cookies and comments are not supported def test_setopt_cookielist_netscape_format_mutliline easy = Curl::Easy.new easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.setopt( Curl::CURLOPT_COOKIELIST, [ '# Netscape HTTP Cookie File', ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"), '', ].join("\n"), ) assert_nil easy.cookielist easy.perform assert_equal '', easy.body_str easy.setopt( Curl::CURLOPT_COOKIELIST, [ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"), ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session2', '84'].join("\t"), '', ].join("\n"), ) # Only first cookie is set assert_equal [".localhost\tTRUE\t/\tFALSE\t0\tsession\t42"], easy.cookielist easy.perform assert_equal 'session=42', easy.body_str end # ALL erases all cookies held in memory # ALL was added in 7.14.1 def test_setopt_cookielist_command_all expires = (Date.today + 2).to_datetime with_permanent_and_session_cookies(expires) do |easy| easy.setopt(Curl::CURLOPT_COOKIELIST, 'ALL') assert_nil easy.cookielist easy.perform assert_equal '', easy.body_str end end # SESS erases all session cookies held in memory # SESS was added in 7.15.4 def test_setopt_cookielist_command_sess expires = (Date.today + 2).to_datetime with_permanent_and_session_cookies(expires) do |easy| easy.setopt(Curl::CURLOPT_COOKIELIST, 'SESS') assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42"], easy.cookielist easy.perform assert_equal 'permanent=42', easy.body_str end end # FLUSH writes all known cookies to the file specified by CURLOPT_COOKIEJAR # FLUSH was added in 7.17.1 def test_setopt_cookielist_command_flush expires = (Date.today + 2).to_datetime with_permanent_and_session_cookies(expires) do |easy| cookiejar = File.join(Dir.tmpdir, 'curl_test_cookiejar') assert !File.exist?(cookiejar) begin easy.cookiejar = cookiejar # trick to actually set CURLOPT_COOKIEJAR easy.enable_cookies = true easy.perform assert !File.exist?(cookiejar) easy.setopt(Curl::CURLOPT_COOKIELIST, 'FLUSH') expected_cookiejar = <<~COOKIEJAR # Netscape HTTP Cookie File # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. .localhost TRUE / FALSE 0 session 420 .localhost TRUE / FALSE #{expires.to_time.to_i} permanent 42 COOKIEJAR assert_equal expected_cookiejar, File.read(cookiejar) ensure # Otherwise it'll create this file again easy.close File.unlink(cookiejar) if File.exist?(cookiejar) end end end # RELOAD loads all cookies from the files specified by CURLOPT_COOKIEFILE # RELOAD was added in 7.39.0 def test_setopt_cookielist_command_reload expires = (Date.today + 2).to_datetime expires_file = (Date.today + 4).to_datetime with_permanent_and_session_cookies(expires) do |easy| cookiefile = File.join(Dir.tmpdir, 'curl_test_cookiefile') assert !File.exist?(cookiefile) begin cookielist = [ # Won't be updated, added instead ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84", ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84", # Won't be updated, added instead ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840", ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840", '', ] File.write(cookiefile, cookielist.join("\n")) easy.cookiefile = cookiefile # trick to actually set CURLOPT_COOKIEFILE easy.enable_cookies = true easy.perform easy.setopt(Curl::CURLOPT_COOKIELIST, 'RELOAD') expected_cookielist = [ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42", ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420", ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84", ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84", ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840", ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840", ] assert_equal expected_cookielist, easy.cookielist easy.perform # Be careful, duplicates are not removed assert_equal 'permanent_file=84; session_file=840; permanent=84; session=840; permanent=42; session=420', easy.body_str ensure File.unlink(cookiefile) if File.exist?(cookiefile) end end end def test_commands_do_not_enable_cookie_engine %w[ALL SESS FLUSH RELOAD].each do |command| easy = Curl::Easy.new expires = (Date.today + 2).to_datetime easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies" easy.setopt(Curl::CURLOPT_COOKIELIST, command) easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }]) easy.perform easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.post_body = nil easy.perform assert !easy.enable_cookies? assert_nil easy.cookielist assert_equal '', easy.body_str end end def test_strings_without_cookie_enable_cookie_engine [ '', '# Netscape HTTP Cookie File', 'no_a_cookie', ].each do |command| easy = Curl::Easy.new expires = (Date.today + 2).to_datetime easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies" easy.setopt(Curl::CURLOPT_COOKIELIST, command) easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }]) easy.perform easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.post_body = nil easy.perform assert !easy.enable_cookies? assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist assert_equal 'c2=v2', easy.body_str end end def with_permanent_and_session_cookies(expires) easy = Curl::Easy.new easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies" easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: permanent=42; domain=localhost; expires=#{expires.httpdate};") easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: session=420; domain=localhost;') assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42", ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420"], easy.cookielist easy.perform assert_equal 'permanent=42; session=420', easy.body_str yield easy end include TestServerMethods def setup server_setup end end curb-1.3.5/tests/tc_curl_download.rb0000644000004100000410000000467415203731642017532 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlDownload < Test::Unit::TestCase include TestServerMethods def setup server_setup end def test_download_url_to_file_via_string dl_url = "http://127.0.0.1:9129/ext/curb_easy.c" dl_path = File.join(Dir::tmpdir, "dl_url_test.file") Curl::Easy.download(dl_url, dl_path) assert File.exist?(dl_path) assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path) ensure File.unlink(dl_path) if File.exist?(dl_path) end def test_download_url_to_file_via_file_io dl_url = "http://127.0.0.1:9129/ext/curb_easy.c" dl_path = File.join(Dir::tmpdir, "dl_url_test.file") io = File.open(dl_path, 'wb') Curl::Easy.download(dl_url, io) assert io.closed? assert File.exist?(dl_path) assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path) ensure File.unlink(dl_path) if File.exist?(dl_path) end def test_download_url_to_file_via_io omit('fork not available on this platform') if NO_FORK || WINDOWS dl_url = "http://127.0.0.1:9129/ext/curb_easy.c" dl_path = File.join(Dir::tmpdir, "dl_url_test.file") reader, writer = IO.pipe # Write to local file child_pid = fork do begin writer.close File.open(dl_path, 'wb') { |file| file << reader.read } exit! 0 rescue StandardError exit! 1 ensure reader.close rescue IOError # if the stream has already been closed end end # Download remote source begin reader.close Curl::Easy.download(dl_url, writer) _pid, status = Process.wait2(child_pid) assert_predicate status, :success? ensure writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download end assert File.exist?(dl_path) assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path) ensure File.unlink(dl_path) if dl_path && File.exist?(dl_path) end def test_download_bad_url_gives_404 dl_url = "http://127.0.0.1:9129/this_file_does_not_exist.html" dl_path = File.join(Dir::tmpdir, "dl_url_test.file") curb = Curl::Easy.download(dl_url, dl_path) assert_equal Curl::Easy, curb.class assert_equal 404, curb.response_code ensure File.unlink(dl_path) if File.exist?(dl_path) end end curb-1.3.5/tests/bug_crash_on_debug.rb0000644000004100000410000000101215203731642017766 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugCrashOnDebug < Test::Unit::TestCase include BugTestServerSetupTeardown def test_on_debug c = Curl::Easy.new("http://127.0.0.1:#{@port}/test") did_raise = false did_call = false begin c.on_success do|x| did_call = true raise "error" # this will get swallowed end c.perform rescue => e did_raise = true end assert did_raise assert did_call end end #test_on_debug curb-1.3.5/tests/timeout_server.rb0000644000004100000410000000145215203731642017253 0ustar www-datawww-data# This Sinatra application must be run with mongrel # or possibly with unicorn for the serve action to work properly. # See http://efreedom.com/Question/1-3669674/Streaming-Data-Sinatra-Rack-Application require 'sinatra' get '/wait/:time' do |time| time = time.to_i sleep(time) "Slept #{time} at #{Time.now}" end # http://efreedom.com/Question/1-3027435/Way-Flush-Html-Wire-Sinatra class Streamer def initialize(time, chunks) @time = time @chunks = chunks end def each @chunks.each do |chunk| sleep(@time) yield chunk end end end get '/serve/:chunk_size/every/:time/for/:count' do |chunk_size, time, count| chunk_size, time, count = chunk_size.to_i, time.to_i, count.to_i chunk = 'x' * chunk_size chunks = [chunk] * count Streamer.new(time, chunks) end curb-1.3.5/tests/bug_issue_post_redirect.rb0000644000004100000410000000612315203731642021112 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) require 'json' class BugIssuePostRedirect < Test::Unit::TestCase include BugTestServerSetupTeardown def setup @port = 9998 super # Mount a POST endpoint that returns a redirect @server.mount_proc("/post_redirect") do |req, res| if req.request_method == "POST" res.status = 302 res['Location'] = "http://127.0.0.1:#{@port}/redirected" res.body = "Redirecting..." else res.status = 405 res.body = "Method not allowed" end end # Mount the redirect target @server.mount_proc("/redirected") do |req, res| res.status = 200 res['Content-Type'] = "text/plain" res.body = "You have been redirected" end end def test_post_with_max_redirects_zero_should_not_follow_redirect # Test case replicating the issue: POST with max_redirects=0 and follow_location=false # should NOT trigger on_redirect callback or follow the redirect redirect_called = false handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl| curl.max_redirects = 0 curl.follow_location = false curl.on_redirect do |easy| redirect_called = true end curl.headers['Content-Type'] = 'application/json' curl.post_body = {test: "data"}.to_json end handle.http(:POST) # The response should be the redirect response (302) assert_equal 302, handle.response_code assert_match(/Redirecting/, handle.body_str) # on_redirect should NOT be called when follow_location is false assert !redirect_called, "on_redirect callback should not be called when follow_location is false" end def test_post_with_follow_location_true_triggers_redirect # Test that on_redirect IS called when follow_location is true redirect_called = false handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl| curl.follow_location = true curl.on_redirect do |easy| redirect_called = true end curl.headers['Content-Type'] = 'application/json' curl.post_body = {test: "data"}.to_json end handle.http(:POST) # Should follow the redirect and get the final response assert_equal 200, handle.response_code assert_match(/You have been redirected/, handle.body_str) # on_redirect SHOULD be called when follow_location is true assert redirect_called, "on_redirect callback should be called when follow_location is true" end def test_curl_post_class_method_respects_redirect_settings # Test that Curl.post (class method) respects redirect settings # According to the issue, this works correctly response = Curl.post("http://127.0.0.1:#{@port}/post_redirect", {test: "data"}.to_json) do |curl| curl.max_redirects = 0 curl.follow_location = false curl.headers['Content-Type'] = 'application/json' end # Should get the redirect response, not follow it assert_equal 302, response.response_code assert_match(/Redirecting/, response.body_str) end endcurb-1.3.5/tests/tc_curl_maxfilesize.rb0000644000004100000410000000037615203731642020236 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlMaxFileSize < Test::Unit::TestCase def setup @easy = Curl::Easy.new end def test_maxfilesize @easy.set(Curl::CURLOPT_MAXFILESIZE, 5000000) end end curb-1.3.5/tests/tc_ftp_options.rb0000644000004100000410000000270315203731642017231 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbFtpOptions < Test::Unit::TestCase # Ensure FTP-related set(:option, ...) mappings are accepted and do not raise # a TypeError (they used to be unsupported in setopt dispatch). def test_can_set_ftp_listing_related_flags c = Curl::Easy.new('ftp://example.com/') assert_nothing_raised do c.set(:dirlistonly, true) if Curl.const_defined?(:CURLOPT_DIRLISTONLY) c.set(:ftp_use_epsv, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPSV) # These may not be present on all libcurl builds; guard by constant c.set(:ftp_use_eprt, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPRT) c.set(:ftp_skip_pasv_ip, 1) if Curl.const_defined?(:CURLOPT_FTP_SKIP_PASV_IP) end end # Setting ftp_commands remains supported for control-connection commands. def test_can_assign_ftp_commands c = Curl::Easy.new('ftp://example.com/') c.ftp_commands = ["PWD", "CWD /"] assert_kind_of(Array, c.ftp_commands) assert_equal ["PWD", "CWD /"], c.ftp_commands end def test_ftp_command_entries_can_be_objects_that_convert_to_string command = Object.new command.define_singleton_method(:to_s) { "PWD" } c = Curl::Easy.new($TEST_URL) c.ftp_commands = [command] m = Curl::Multi.new assert_nothing_raised { m.add(c) } ensure m.remove(c) if defined?(m) && m && m.requests[c.object_id] m.close if defined?(m) && m end end curb-1.3.5/tests/tc_curl_easy_setopt.rb0000644000004100000410000000105215203731642020245 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlEasySetOpt < Test::Unit::TestCase def setup @easy = Curl::Easy.new end def test_opt_verbose @easy.set :verbose, true assert @easy.verbose? end def test_opt_header @easy.set :header, true end def test_opt_noprogress @easy.set :noprogress, true end def test_opt_nosignal @easy.set :nosignal, true end def test_opt_url url = "http://google.com/" @easy.set :url, url assert_equal url, @easy.url end end curb-1.3.5/tests/bugtests.rb0000644000004100000410000000031015203731642016027 0ustar www-datawww-data$: << $TESTDIR = File.expand_path(File.dirname(__FILE__)) puts "start" begin Dir[File.join($TESTDIR, 'bug_*.rb')].each { |lib| require lib } rescue Object => e puts e.message ensure puts "done" end curb-1.3.5/tests/helper.rb0000644000004100000410000003441015203731642015456 0ustar www-datawww-data# DO NOT REMOVE THIS COMMENT - PART OF TESTMODEL. # Copyright (c)2006 Ross Bamford. See LICENSE. $CURB_TESTING = true require 'uri' require 'stringio' require 'digest/md5' require 'rbconfig' require File.join(RbConfig::CONFIG['rubylibdir'], 'timeout') $TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), '..')) $EXTDIR = File.join($TOPDIR, 'ext') $LIBDIR = File.join($TOPDIR, 'lib') $:.unshift($LIBDIR) $:.unshift($EXTDIR) # Setup SimpleCov for Ruby code coverage if COVERAGE env var is set if ENV['COVERAGE'] begin require 'simplecov' require 'simplecov-lcov' SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::LcovFormatter ]) SimpleCov.start do add_filter '/tests/' add_filter '/spec/' add_filter '/vendor/' add_filter '/.bundle/' add_group 'Library', 'lib' add_group 'Extensions', 'ext' # Track branch coverage if available enable_coverage :branch if respond_to?(:enable_coverage) end rescue LoadError puts "SimpleCov not available. Install it with: gem install simplecov simplecov-lcov" end end require 'curb' begin require 'test/unit' rescue LoadError gem 'test/unit' require 'test/unit' end require 'fileutils' require 'rbconfig' # Platform helpers WINDOWS = /mswin|msys|mingw|cygwin|bccwin|wince|emc|windows/i.match?(RbConfig::CONFIG['host_os']) NO_FORK = !Process.respond_to?(:fork) $TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}" require 'thread' require 'webrick' require 'socket' # set this to true to avoid testing with multiple threads # or to test with multiple threads set it to false # this is important since, some code paths will change depending # on the presence of multiple threads TEST_SINGLE_THREADED=false WEBRICK_TEST_LOG = WEBrick::Log.new(File.open(File::NULL, 'w'), WEBrick::BasicLog::ERROR) module CurbTestResourceCleanup def teardown super ensure begin if Curl::Easy.respond_to?(:flush_deferred_multi_closes) Curl::Easy.flush_deferred_multi_closes(all_threads: true) end rescue StandardError nil end begin ObjectSpace.each_object(Curl::Multi) do |multi| begin next if multi.instance_variable_defined?(:@deferred_close) && multi.instance_variable_get(:@deferred_close) multi.instance_variable_set(:@requests, {}) multi._close rescue StandardError nil end end rescue StandardError nil end end end Test::Unit::TestCase.prepend(CurbTestResourceCleanup) # # Simple test server to record number of times a request is sent/recieved of a specific # request type, e.g. GET,POST,PUT,DELETE # class TestServlet < WEBrick::HTTPServlet::AbstractServlet def self.port=(p) @port = p end def self.port @port ||= 9129 end def self.path '/methods' end def self.url "http://127.0.0.1:#{port}#{path}" end def respond_with(method,req,res) res.body = method.to_s $auth_header = req['Authorization'] res['Content-Type'] = "text/plain" end def do_GET(req,res) if req.path.match(/redirect$/) res.status = 302 res['Location'] = '/foo' elsif req.path.match(/not_here$/) res.status = 404 elsif req.path.match(/error$/) res.status = 500 elsif req.path.match(/get_cookies$/) res['Content-Type'] = "text/plain" res.body = req['Cookie'] return end respond_with("GET#{req.query_string}",req,res) end def do_HEAD(req,res) res['Location'] = "/nonexistent" respond_with("HEAD#{req.query_string}",req,res) end def do_POST(req,res) if req.path.match(/set_cookies$/) JSON.parse(req.body || '[]', symbolize_names: true).each do |hash| cookie = WEBrick::Cookie.new(hash.fetch(:name), hash.fetch(:value)) cookie.domain = hash[:domain] if hash.key?(:domain) cookie.expires = hash[:expires] if hash.key?(:expires) cookie.path = hash[:path] if hash.key?(:path) cookie.secure = hash[:secure] if hash.key?(:secure) cookie.max_age = hash[:max_age] if hash.key?(:max_age) res.cookies.push(cookie) end respond_with('OK', req, res) elsif req.query['filename'].nil? if req.body params = {} req.body.split('&').map{|s| k,v=s.split('='); params[k] = v } end if params and params['s'] == '500' res.status = 500 elsif params and params['c'] cookie = URI.decode_www_form_component(params['c']).split('=') res.cookies << WEBrick::Cookie.new(*cookie) else respond_with("POST\n#{req.body}",req,res) end else respond_with(req.query['filename'],req,res) end end def do_PUT(req,res) res['X-Requested-Content-Type'] = req.content_type respond_with("PUT\n#{req.body}",req,res) end def do_DELETE(req,res) respond_with("DELETE#{req.query_string}",req,res) end def do_PURGE(req,res) respond_with("PURGE#{req.query_string}",req,res) end def do_COPY(req,res) respond_with("COPY#{req.query_string}",req,res) end def do_PATCH(req,res) respond_with("PATCH\n#{req.body}",req,res) end def do_OPTIONS(req,res) respond_with("OPTIONS#{req.query_string}",req,res) end end module BugTestServerSetupTeardown def unused_local_port socket = TCPServer.new('127.0.0.1', 0) socket.addr[1] ensure socket.close if socket end def setup @port ||= 9992 @server = WEBrick::HTTPServer.new(:Port => @port, :Logger => WEBRICK_TEST_LOG, :AccessLog => []) @server.mount_proc("/test") do|req,res| if @response_proc @response_proc.call(res) else res.body = "hi" res['Content-Type'] = "text/html" end end @thread = Thread.new(@server) do|srv| srv.start end end def teardown while @server.status != :Shutdown @server.shutdown end @thread.join end end module TestServerMethods def server_responding?(port) socket = TCPSocket.new('127.0.0.1', port) socket.close true rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, IOError false end def server_startup_timeout ENV['RUBY_MEMCHECK_RUNNING'] ? 30 : 5 end def wait_for_server_ready(port, thread: nil) deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + server_startup_timeout loop do if thread && !thread.alive? return false end return true if server_responding?(port) raise "Failed to startup test server on port #{port}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline sleep 0.01 end end def server_shutdown_timeout [server_startup_timeout, 0.25].max end def wait_for_server_stopped(port, thread: nil) deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + server_shutdown_timeout loop do return true unless server_responding?(port) if thread && !thread.alive? return !server_responding?(port) end return false if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline sleep 0.01 end end def locked_file File.join(File.dirname(__FILE__),"server_lock-#{@__port}") end def read_server_lock_pid Integer(File.read(locked_file).strip, 10) rescue Errno::ENOENT, ArgumentError, TypeError nil end def write_server_lock(pid = Process.pid) File.open(locked_file, 'w') { |f| f << "#{pid}\n" } end def process_alive?(pid) return false unless pid && pid.positive? Process.kill(0, pid) true rescue Errno::ESRCH false rescue Errno::EPERM true end def server_lock_fresh? (Time.now - File.mtime(locked_file)) < server_startup_timeout rescue Errno::ENOENT false end def stale_server_lock?(port) return false unless File.exist?(locked_file) pid = read_server_lock_pid return false if pid && process_alive?(pid) && server_lock_fresh? return false if server_responding?(port) true end def clear_stale_server_lock(port) return unless stale_server_lock?(port) File.unlink(locked_file) rescue Errno::ENOENT nil end def stop_test_server server = instance_variable_defined?(:@server) ? @server : nil pid = instance_variable_defined?(:@__pid) ? @__pid : nil return unless server || pid if TEST_SINGLE_THREADED @__pid = nil if pid begin Process.kill('INT', pid) rescue Errno::ESRCH nil end begin Process.wait(pid) rescue Errno::ECHILD, Errno::ESRCH nil end end else thread = instance_variable_defined?(:@test_thread) ? @test_thread : nil port = instance_variable_defined?(:@__port) ? @__port : nil @server = nil @test_thread = nil server.shutdown if server wait_for_server_stopped(port, thread: thread) if port thread.join(server_shutdown_timeout) if thread if thread&.alive? thread.kill thread.join(server_shutdown_timeout) wait_for_server_stopped(port) if port end end File.unlink(locked_file) if File.exist?(locked_file) rescue Errno::ENOENT nil end def teardown super ensure stop_test_server end def server_setup(port=9129,servlet=TestServlet) @__port = port @server = nil unless instance_variable_defined?(:@server) @__pid = nil unless instance_variable_defined?(:@__pid) @test_thread = nil unless instance_variable_defined?(:@test_thread) clear_stale_server_lock(port) if @server.nil? && File.exist?(locked_file) begin wait_for_server_ready(port) return rescue RuntimeError clear_stale_server_lock(port) end end if @server.nil? and !File.exist?(locked_file) write_server_lock if TEST_SINGLE_THREADED rd, wr = IO.pipe @__pid = fork do rd.close rd = nil # start up a webrick server for testing delete server = WEBrick::HTTPServer.new(:Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__)), :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount(servlet.path, servlet) server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext')) trap("INT") { server.shutdown } GC.start server_thread = Thread.new { server.start } begin if wait_for_server_ready(port, thread: server_thread) wr.write('1') else wr.write('0') end rescue StandardError wr.write('0') ensure wr.flush wr.close end server_thread.join end wr.close ready = rd.read rd.close if ready != '1' STDERR.puts "Failed to startup test server!" exit(1) end else # start up a webrick server for testing delete server = WEBrick::HTTPServer.new(:Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__)), :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount(servlet.path, servlet) server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext')) @server = server # Keep a stable reference inside the thread so helper shutdown can clear # @server without racing the server startup path. @test_thread = Thread.new(server) { |srv| srv.start } if !wait_for_server_ready(port, thread: @test_thread) STDERR.puts "Failed to startup test server!" exit(1) end end exit_code = lambda do begin stop_test_server rescue Object => e puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}" end end trap("INT"){exit_code.call} at_exit{exit_code.call} end rescue Errno::EADDRINUSE end end # Backport for Ruby 1.8 module Backports module Ruby18 module URIFormEncoding TBLENCWWWCOMP_ = {} TBLDECWWWCOMP_ = {} def encode_www_form_component(str) if TBLENCWWWCOMP_.empty? 256.times do |i| TBLENCWWWCOMP_[i.chr] = '%%%02X' % i end TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze end str.to_s.gsub( /([^*\-.0-9A-Z_a-z])/ ) {|*| TBLENCWWWCOMP_[$1] } end def decode_www_form_component(str) if TBLDECWWWCOMP_.empty? 256.times do |i| h, l = i>>4, i&15 TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr end TBLDECWWWCOMP_['+'] = ' ' TBLDECWWWCOMP_.freeze end raise ArgumentError, "invalid %-encoding (#{str.dump})" unless /\A(?:%[[:xdigit:]]{2}|[^%]+)*\z/ =~ str str.gsub( /(\+|%[[:xdigit:]]{2})/ ) {|*| TBLDECWWWCOMP_[$1] } end def encode_www_form( enum ) enum.map do |k,v| if v.nil? encode_www_form_component(k) elsif v.respond_to?(:to_ary) v.to_ary.map do |w| str = encode_www_form_component(k) unless w.nil? str << '=' str << encode_www_form_component(w) end end.join('&') else str = encode_www_form_component(k) str << '=' str << encode_www_form_component(v) end end.join('&') end WFKV_ = '(?:%\h\h|[^%#=;&])' def decode_www_form(str, _) return [] if str.to_s == '' unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/ =~ str raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})" end ary = [] $&.scan(/([^=;&]+)=([^;&]*)/) do ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)] end ary end end end end unless URI.methods.include?(:encode_www_form) URI.extend(Backports::Ruby18::URIFormEncoding) end curb-1.3.5/tests/require_last_or_segfault_script.rb0000644000004100000410000000243315203731642022654 0ustar www-datawww-data# From Vlad Jebelev: # # - if I have a require statement after "require 'curb'" and there is a # POST with at least 1 field, the script will fail with a segmentation # fault, e.g. the following sequence fails every time for me (Ruby 1.8.5): # ----------------------------------------------------------------- # require 'curb' # require 'uri' # # url = 'https://www.google.com/accounts/ServiceLoginAuth' # # c = Curl::Easy.http_post( # 'https://www.google.com/accounts/ServiceLoginAuth', # [Curl:: PostField.content('ltmpl','m_blanco')] ) do |curl| # end # ------------------------------------------------------------------ # :..dev/util$ ruby seg.rb # seg.rb:6: [BUG] Segmentation fault # ruby 1.8.5 (2006-08-25) [i686-linux] # # Aborted # ------------------------------------------------------------------ # $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'ext'))) $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) require 'curb' require 'uri' url = 'https://www.google.com/accounts/ServiceLoginAuth' c = Curl::Easy.http_post('https://www.google.com/accounts/ServiceLoginAuth', Curl:: PostField.content('ltmpl','m_blanco')) #do # end puts "success" curb-1.3.5/tests/tc_curl.rb0000644000004100000410000000373615203731642015641 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurl < Test::Unit::TestCase def test_get curl = Curl.get(TestServlet.url, {:foo => "bar"}) assert_equal "GETfoo=bar", curl.body_str curl = Curl.options(TestServlet.url, {:foo => "bar"}) do|http| http.headers['Cookie'] = 'foo=1;bar=2' end assert_equal "OPTIONSfoo=bar", curl.body_str end def test_post curl = Curl.post(TestServlet.url, {:foo => "bar"}) assert_equal "POST\nfoo=bar", curl.body_str end def test_put curl = Curl.put(TestServlet.url, {:foo => "bar"}) assert_equal "PUT\nfoo=bar", curl.body_str end def test_patch curl = Curl.patch(TestServlet.url, {:foo => "bar"}) assert_equal "PATCH\nfoo=bar", curl.body_str end def test_options curl = Curl.options(TestServlet.url, {:foo => "bar"}) assert_equal "OPTIONSfoo=bar", curl.body_str end def test_urlalize_without_extra_params url_no_params = 'http://localhost/test' url_with_params = 'http://localhost/test?a=1' assert_equal(url_no_params, Curl.urlalize(url_no_params)) assert_equal(url_with_params, Curl.urlalize(url_with_params)) end def test_urlalize_with_nil_as_params url = 'http://localhost/test' assert_equal(url, Curl.urlalize(url, nil)) end def test_urlalize_with_extra_params url_no_params = 'http://localhost/test' url_with_params = 'http://localhost/test?a=1' extra_params = { :b => 2 } expected_url_no_params = 'http://localhost/test?b=2' expected_url_with_params = 'http://localhost/test?a=1&b=2' assert_equal(expected_url_no_params, Curl.urlalize(url_no_params, extra_params)) assert_equal(expected_url_with_params, Curl.urlalize(url_with_params, extra_params)) end def test_urlalize_does_not_strip_trailing_? url_empty_params = 'http://localhost/test?' assert_equal(url_empty_params, Curl.urlalize(url_empty_params)) end include TestServerMethods def setup server_setup end end curb-1.3.5/tests/bug_curb_easy_blocks_ruby_threads.rb0000644000004100000410000000200215203731642023110 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase include BugTestServerSetupTeardown def setup @port = unused_local_port @response_proc = lambda do|res| sleep 0.5 res.body = "hi" res['Content-Type'] = "text/html" end super end def test_bug threads = [] timer = Time.now 5.times do |i| t = Thread.new do c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test") c.header_str end threads << t end multi_responses = threads.collect do|t| t.value end multi_time = (Time.now - timer) puts "requested in #{multi_time}" timer = Time.now single_responses = [] 5.times do |i| c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test") single_responses << c.header_str end single_time = (Time.now - timer) puts "requested in #{single_time}" assert single_time > multi_time end end curb-1.3.5/tests/tc_fiber_scheduler.rb0000644000004100000410000003435715203731642020024 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) begin require 'async' rescue LoadError HAS_ASYNC = false else HAS_ASYNC = true end # This test verifies that Curl::Easy#perform cooperates with Ruby's Fiber scheduler # by running multiple requests concurrently in a single thread using the Async gem. class TestCurbFiberScheduler < Test::Unit::TestCase include BugTestServerSetupTeardown include TestServerMethods class RecordingScheduler attr_reader :io_wait_events, :io_select_calls def initialize @io_wait_events = [] @io_select_calls = 0 end def fiber(&block) Fiber.new(blocking: false, &block) end def io_wait(io, events, timeout = nil) @io_wait_events << events raise TypeError, "expected Integer events, got #{events.class}" unless events.is_a?(Integer) readers = (events & IO::READABLE) != 0 ? [io] : nil writers = (events & IO::WRITABLE) != 0 ? [io] : nil readable, writable = blocking_io { IO.select(readers, writers, nil, timeout) } ready = 0 ready |= IO::READABLE if readable && !readable.empty? ready |= IO::WRITABLE if writable && !writable.empty? ready.zero? ? false : ready end def io_select(readers, writers, excepts, timeout = nil) @io_select_calls += 1 blocking_io { IO.select(readers, writers, excepts, timeout) } end def kernel_sleep(duration = nil) sleep(duration || 0) end def block(_blocker, timeout = nil) sleep(timeout || 0) false end def unblock(*) end def close end def fiber_interrupt(*) end private def blocking_io(&block) if Fiber.respond_to?(:blocking) Fiber.blocking(&block) else block.call end end end ITERS = 4 MIN_S = 0.25 # Each request sleeps 0.25s; two concurrent requests should be ~0.25–0.5s. # Allow some jitter in CI environments. THRESHOLD = ((MIN_S*(ITERS/2.0)) + (MIN_S/2.0)) # add more jitter for slower CI environments SERIAL_TIME_WOULD_BE_ABOUT = MIN_S * ITERS def setup @port = unused_local_port @response_proc = lambda do |res| res['Content-Type'] = 'text/plain' sleep MIN_S res.body = '200' end super end def test_multi_is_scheduler_friendly if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" started = Time.now results = [] async_run do m = Curl::Multi.new ITERS.times.each do c = Curl::Easy.new(url) c.on_complete { results << c.code } m.add(c) end m.perform ensure m.close if m end duration = Time.now - started assert duration < THRESHOLD, "Requests did not run concurrently under fiber scheduler (#{duration}s) which exceeds the expected threshold of: #{THRESHOLD} serial time would be about: #{SERIAL_TIME_WOULD_BE_ABOUT}" assert_equal ITERS, results.size assert_equal ITERS.times.map {200}, results end def test_easy_perform_is_scheduler_friendly if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" started = Time.now results = [] async_run do |top| tasks = ITERS.times.map do top.async do #t = Time.now.to_i #puts "starting fiber [#{results.size}] -> #{t}" c = Curl.get(url) #puts "received result: #{results.size} -> #{Time.now.to_f - t.to_f}" results << c.code end end tasks.each(&:wait) end duration = Time.now - started assert duration < THRESHOLD, "Requests did not run concurrently under fiber scheduler (#{duration}s) which exceeds the expected threshold of: #{THRESHOLD} serial time would be about: #{SERIAL_TIME_WOULD_BE_ABOUT}" assert_equal ITERS, results.size assert_equal ITERS.times.map {200}, results end def test_multi_perform_yields_block_under_scheduler if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" yielded = 0 results = [] async_run do m = Curl::Multi.new ITERS.times do c = Curl::Easy.new(url) c.on_complete { results << c.code } m.add(c) end m.perform do yielded += 1 end ensure m.close if m end assert_operator yielded, :>=, 1, 'perform did not yield block while waiting under scheduler' assert_equal ITERS, results.size assert_equal ITERS.times.map {200}, results end def test_multi_single_request_scheduler_path if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" result = nil async_run do m = Curl::Multi.new c = Curl::Easy.new(url) c.on_complete { result = c.code } m.add(c) m.perform ensure m.close if m end assert_equal 200, result end def test_multi_single_request_socket_perform_passes_integer_events_to_io_wait omit('Skipping custom scheduler test on Windows') if WINDOWS omit('Fiber scheduler API unavailable on this Ruby') unless fiber_scheduler_supported? url = "http://127.0.0.1:#{@port}/test" scheduler = RecordingScheduler.new result = nil with_scheduler(scheduler) do m = Curl::Multi.new c = Curl::Easy.new(url) c.on_complete { result = c.code } m.add(c) unless m.respond_to?(:_socket_perform, true) omit('socket-action perform path is not available in this build') end m.send(:_socket_perform) ensure m.close if m end assert_equal 200, result assert_operator scheduler.io_wait_events.length + scheduler.io_select_calls, :>=, 1 unless scheduler.io_wait_events.empty? assert scheduler.io_wait_events.all? { |events| events.is_a?(Integer) } assert scheduler.io_wait_events.any? { |events| (events & (IO::READABLE | IO::WRITABLE)) != 0 } end end def test_multi_reuse_after_scheduler_perform unless HAS_ASYNC warn 'Skipping fiber scheduler test (Async gem not available)' return end url = "http://127.0.0.1:#{@port}/test" results = [] async_run do m = Curl::Multi.new # First round c1 = Curl::Easy.new(url) c1.on_complete { results << c1.code } m.add(c1) m.perform # Second round on same multi c2 = Curl::Easy.new(url) c2.on_complete { results << c2.code } m.add(c2) m.perform ensure m.close if m end assert_equal [200, 200], results end def test_easy_perform_reuses_scheduler_multi_when_autoclose_is_enabled if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" results = [] previous_autoclose = Curl::Multi.autoclose Curl::Multi.autoclose = true async_run do 2.times do easy = Curl::Easy.new(url) easy.perform results << easy.code end end assert_equal [200, 200], results ensure Curl::Multi.autoclose = previous_autoclose if defined?(previous_autoclose) cleanup_scheduler_state end def test_easy_perform_reraises_on_body_exception_under_scheduler if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" async_run do |top| task = top.async do c = Curl::Easy.new(url) c.on_body { raise "body blew up" } c.on_complete { sleep 0 } c.perform end error = assert_raise(RuntimeError) do task.wait end assert_equal "body blew up", error.message end end def test_easy_perform_reraises_on_header_exception_under_scheduler if skip_no_async return end url = "http://127.0.0.1:#{@port}/test" async_run do |top| task = top.async do c = Curl::Easy.new(url) c.on_header { raise "header blew up" } c.on_complete { sleep 0 } c.perform end error = assert_raise(RuntimeError) do task.wait end assert_equal "header blew up", error.message end end def test_easy_perform_does_not_reraise_sibling_on_complete_exception_under_scheduler if skip_no_async return end with_ephemeral_http_server do |port, hits| results = {} async_run do |top| successful = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast") easy.perform results[:successful] = easy.response_code rescue => e results[:successful] = e end failing = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow") easy.on_complete { raise "boom" } easy.perform results[:failing] = :returned rescue => e results[:failing] = e end successful.wait failing.wait end assert_equal 200, results[:successful], "successful scheduler peer should return normally" assert_kind_of Curl::Err::AbortedByCallbackError, results[:failing] assert_equal "boom", results[:failing].message assert_equal 1, hits[:fast] assert_equal 1, hits[:slow] end end def test_easy_perform_does_not_reraise_fast_on_complete_exception_into_slow_successful_peer_under_scheduler if skip_no_async return end with_ephemeral_http_server do |port, hits| results = {} async_run do |top| failing = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast") easy.on_complete { raise "boom" } easy.perform results[:failing] = :returned rescue => e results[:failing] = e end successful = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow") easy.perform results[:successful] = easy.response_code rescue => e results[:successful] = e end failing.wait successful.wait end assert_equal 200, results[:successful], "slow successful scheduler peer should not inherit a fast sibling callback error" assert_kind_of Curl::Err::AbortedByCallbackError, results[:failing] assert_equal "boom", results[:failing].message assert_equal 1, hits[:fast] assert_equal 1, hits[:slow] end end def test_easy_perform_keeps_scheduler_timers_running_while_draining_deferred_on_complete_exception if skip_no_async return end with_ephemeral_http_server do |port, hits| marks = {} started = Process.clock_gettime(Process::CLOCK_MONOTONIC) async_run do |top| timer = top.async do sleep 0.05 marks[:timer] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started end failing = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/fast") easy.on_complete { raise "boom" } easy.perform marks[:failing] = :returned rescue => e marks[:failing] = e end successful = top.async do easy = Curl::Easy.new("http://127.0.0.1:#{port}/slow") easy.perform marks[:slow_done] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started end timer.wait failing.wait successful.wait end assert_kind_of Curl::Err::AbortedByCallbackError, marks[:failing] assert_operator marks[:timer], :<, marks[:slow_done] - 0.02, "scheduler timer should fire before the slow sibling finishes draining" assert_equal 1, hits[:fast] assert_equal 1, hits[:slow] end end def test_drain_scheduler_pending_does_not_drop_work_rejected_during_deferred_abort state = Curl.scheduler_state easy = Curl::Easy.new("http://127.0.0.1:#{@port}/test") waiter = {completed: false, done: false, error: nil} state[:waiters][easy.object_id] = waiter state[:pending] << easy state[:multi].instance_variable_set(:@__curb_deferred_exception, RuntimeError.new("boom")) Curl.drain_scheduler_pending(state) assert(state[:pending].include?(easy) || waiter[:error], "scheduler enqueue rejected during deferred abort should remain pending or fail its waiter instead of disappearing") assert_equal 0, state[:multi].requests.length ensure cleanup_scheduler_state end private def skip_no_async if WINDOWS warn 'Skipping fiber scheduler tests on Windows' return true end unless fiber_scheduler_supported? warn 'Skipping fiber scheduler tests (Fiber scheduler API unavailable)' return true end unless HAS_ASYNC warn 'Skipping fiber scheduler test (Async gem not available)' return true end false end def async_run(&block) # Prefer newer Async.run to avoid deprecated scheduler.async path. if defined?(Async) && Async.respond_to?(:run) Async.run(&block) else Async(&block) end end def fiber_scheduler_supported? Fiber.respond_to?(:set_scheduler) && Fiber.respond_to?(:schedule) end def with_scheduler(scheduler) previous_scheduler = Fiber.scheduler if Fiber.respond_to?(:scheduler) Fiber.set_scheduler(scheduler) fiber = Fiber.schedule { yield } fiber.resume while fiber.alive? ensure Fiber.set_scheduler(previous_scheduler) if Fiber.respond_to?(:set_scheduler) end def cleanup_scheduler_state state = Thread.current.thread_variable_get(:curb_scheduler_state) state[:multi].close if state && state[:multi] Thread.current.thread_variable_set(:curb_scheduler_state, nil) end def with_ephemeral_http_server port_socket = TCPServer.new('127.0.0.1', 0) port = port_socket.addr[1] port_socket.close hits = Hash.new(0) server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount_proc('/fast') do |_req, res| hits[:fast] += 1 res['Content-Type'] = 'text/plain' res.body = 'fast' end server.mount_proc('/slow') do |_req, res| hits[:slow] += 1 res['Content-Type'] = 'text/plain' sleep 0.2 res.body = 'slow' end server_thread = Thread.new(server) { |srv| srv.start } wait_for_server_ready(port, thread: server_thread) yield port, hits ensure server.shutdown if defined?(server) && server server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread server_thread.kill if defined?(server_thread) && server_thread&.alive? end end curb-1.3.5/tests/bug_curb_easy_post_with_string_no_content_length_header.rb0000644000004100000410000000377215203731642027604 0ustar www-datawww-data=begin From jwhitmire Todd, I'm trying to use curb to post data to a REST url. We're using it to post support questions from our iphone app directly to tender. The post looks good to me, but curl is not adding the content-length header so I get a 411 length required response from the server. Here's my post block, do you see anything obvious? Do I need to manually add the Content-Length header? c = Curl::Easy.http_post(url) do |curl| curl.headers["User-Agent"] = "Curl/Ruby" if user curl.headers["X-Multipass"] = user.multipass else curl.headers["X-Tender-Auth"] = TOKEN end curl.headers["Accept"] = "application/vnd.tender-v1+json" curl.post_body = params.map{|f,k| "#{curl.escape(f)}=#{curl.escape(k)}"}.join('&') curl.verbose = true curl.follow_location = true curl.enable_cookies = true end Any insight you care to share would be helpful. Thanks. =end require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugCurbEasyPostWithStringNoContentLengthHeader < Test::Unit::TestCase include BugTestServerSetupTeardown def test_bug_workaround params = {:cat => "hat", :foo => "bar"} post_body = params.map{|f,k| "#{Curl::Easy.new.escape(f)}=#{Curl::Easy.new.escape(k)}"}.join('&') c = Curl::Easy.http_post("http://127.0.0.1:#{@port}/test",post_body) do |curl| curl.headers["User-Agent"] = "Curl/Ruby" curl.headers["X-Tender-Auth"] = "A Token" curl.headers["Accept"] = "application/vnd.tender-v1+json" curl.follow_location = true curl.enable_cookies = true end end def test_bug params = {:cat => "hat", :foo => "bar"} c = Curl::Easy.http_post("http://127.0.0.1:#{@port}/test") do |curl| curl.headers["User-Agent"] = "Curl/Ruby" curl.headers["X-Tender-Auth"] = "A Token" curl.headers["Accept"] = "application/vnd.tender-v1+json" curl.post_body = params.map{|f,k| "#{curl.escape(f)}=#{curl.escape(k)}"}.join('&') curl.follow_location = true curl.enable_cookies = true end end end curb-1.3.5/tests/tc_gc_compact.rb0000644000004100000410000001272715203731642016773 0ustar www-datawww-data# frozen_string_literal: true require File.expand_path('helper', __dir__) class TestGcCompact < Test::Unit::TestCase ITERATIONS = (ENV['CURB_GC_COMPACT_ITERATIONS'] || 50).to_i EASY_PER_MULTI = 3 GC_CRASH_REGRESSION_ITERATIONS = (ENV['CURB_GC_CRASH_REGRESSION_ITERATIONS'] || [ITERATIONS, 10].min).to_i def setup omit('GC.compact unavailable on this Ruby') unless defined?(GC.compact) end def test_multi_perform_with_gc_compact ITERATIONS.times do run_multi_perform_compact_iteration collect_after_iteration end end def test_gc_compact_during_multi_cleanup omit('GC cleanup isolation requires fork') if NO_FORK || WINDOWS pid = fork do begin ITERATIONS.times do run_multi_cleanup_compact_iteration collect_after_iteration end exit! 0 rescue StandardError => e warn("#{e.class}: #{e.message}") warn(e.backtrace.join("\n")) if e.backtrace exit! 1 end end _child_pid, status = Process.wait2(pid) assert_predicate status, :success? end def test_gc_compact_after_detach run_multi_detach_compact_iteration collect_after_iteration end def test_gc_compact_after_failed_implicit_multi_cleanup GC_CRASH_REGRESSION_ITERATIONS.times do run_failed_implicit_multi_cleanup_iteration(compact: true, cleanup_with_objectspace: false) collect_after_iteration end end def test_gc_cleanup_after_failed_implicit_multi_callback omit('GC cleanup isolation requires fork') if NO_FORK || WINDOWS assert_child_process_success do GC_CRASH_REGRESSION_ITERATIONS.times do run_failed_implicit_multi_cleanup_iteration(compact: true, cleanup_with_objectspace: true) end end end def test_gc_cleanup_after_invalid_multi_assignment_rejection omit('GC cleanup isolation requires fork') if NO_FORK || WINDOWS assert_child_process_success do GC_CRASH_REGRESSION_ITERATIONS.times do easy = Curl::Easy.new($TEST_URL) begin easy.multi = Object.new raise 'expected TypeError from Curl::Easy#multi=' rescue TypeError => error raise "unexpected error message: #{error.message}" unless /Curl::Multi/.match?(error.message) end easy = nil full_gc(compact: true) end end end def test_gc_compact_easy iteration = 0 responses = [] while iteration < ITERATIONS res = Curl.get($TEST_URL) do |easy| easy.timeout = 5 easy.on_complete { |_e| } easy.on_failure { |_e, _code| } end iteration += 1 responses << res.body compact end end private def run_multi_perform_compact_iteration multi = Curl::Multi.new handles = add_easy_handles(multi) compact multi.perform { compact } compact ensure handles&.each do |easy| begin easy.close rescue StandardError nil end end multi&.close end def run_multi_cleanup_compact_iteration multi = Curl::Multi.new add_easy_handles(multi) compact multi = nil compact end def run_multi_detach_compact_iteration multi = Curl::Multi.new handles = add_easy_handles(multi) compact multi.perform { compact } handles.each { |easy| multi.remove(easy); compact } compact ensure multi&.close end def run_failed_implicit_multi_cleanup_iteration(compact:, cleanup_with_objectspace:) easy = Curl::Easy.new($TEST_URL) easy.on_complete { raise 'complete blew up' } begin easy.perform raise 'expected callback abort' rescue Curl::Err::AbortedByCallbackError => error raise "unexpected callback message: #{error.message}" unless error.message == 'complete blew up' end raise 'expected implicit cleanup to clear easy.multi' unless easy.multi.nil? ensure easy = nil if cleanup_with_objectspace run_teardown_multi_cleanup flush_deferred_multi_closes_all else flush_deferred_multi_closes_all end full_gc(compact: compact) end def add_easy_handles(multi) Array.new(EASY_PER_MULTI) do Curl::Easy.new($TEST_URL) do |easy| easy.timeout = 5 easy.on_complete { |_e| } easy.on_failure { |_e, _code| } end.tap { |easy| multi.add(easy) } end end def compact GC.compact end def full_gc(compact: false) GC.start(full_mark: true, immediate_sweep: true) GC.compact if compact && GC.respond_to?(:compact) GC.start(full_mark: true, immediate_sweep: true) end def flush_deferred_multi_closes_all return unless Curl::Easy.respond_to?(:flush_deferred_multi_closes) Curl::Easy.flush_deferred_multi_closes(all_threads: true) end def run_teardown_multi_cleanup ObjectSpace.each_object(Curl::Multi) do |multi| begin next if multi.instance_variable_defined?(:@deferred_close) && multi.instance_variable_get(:@deferred_close) multi.instance_variable_set(:@requests, {}) multi._close rescue StandardError nil end end end def assert_child_process_success pid = fork do begin yield exit! 0 rescue Exception => e warn("#{e.class}: #{e.message}") warn(e.backtrace.join("\n")) if e.backtrace exit! 1 end end _child_pid, status = Process.wait2(pid) assert_predicate status, :success? end def collect_after_iteration ObjectSpace.garbage_collect ObjectSpace.garbage_collect end end curb-1.3.5/tests/bug_postfields_crash2.rb0000644000004100000410000000360515203731642020454 0ustar www-datawww-data# Not sure if this is an IRB bug, but thought you guys should know. # # ** My Ruby version: ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux] # ** Version of Rubygems: 1.3.5 # ** Version of the Curb gem: 0.6.6.0 # # # Transcript of IRB session: # ------------------------------------------------------------------------------------------------------------------ # irb(main):001:0> a = { # irb(main):002:1* :type => :pie, # irb(main):003:1* :series => { # irb(main):004:2* :names => [:a,:b], # irb(main):005:2* :values => [70,30], # irb(main):006:2* :colors => [:red,:green] # irb(main):007:2> }, # irb(main):008:1* :output_format => :png # irb(main):009:1> } # => {:type=>:pie, :output_format=>:png, :series=>{:names=>[:a, :b], :values=>[70, 30], :colors=>[:red, :green]}} # irb(main):010:0> post = [] # => [] # irb(main):011:0> require 'rubygems' # => true # irb(main):012:0> require 'curb' # => true # irb(main):013:0> include Curl # => Object # irb(main):014:0> a.each_pair do |k,v| # irb(main):015:1* post << PostField.content(k,v) # irb(main):016:1> end # => {:type=>:pie, :output_format=>:png, :series=>{:names=>[:a, :b], :values=>[70, 30], :colors=>[:red, :green]}} # irb(main):017:0> post # /usr/lib/ruby/1.8/irb.rb:302: [BUG] Segmentation fault # ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux] # # Aborted # ------------------------------------------------------------------------------------------------------------------ require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugPostFieldsCrash2 < Test::Unit::TestCase def test_crash a = { :type => :pie, :series => { :names => [:a,:b], :values => [70,30], :colors => [:red,:green] }, :output_format => :png } post = [] a.each_pair do |k,v| post << Curl::PostField.content(k,v) end post.inspect end end curb-1.3.5/tests/tc_curl_protocols.rb0000644000004100000410000000171415203731642017737 0ustar www-datawww-dataclass TestCurbCurlProtocols < Test::Unit::TestCase include TestServerMethods def setup @easy = Curl::Easy.new @easy.set :protocols, Curl::CURLPROTO_HTTP | Curl::CURLPROTO_HTTPS @easy.follow_location = true server_setup end def test_protocol_allowed @easy.set :url, "http://127.0.0.1:9129/this_file_does_not_exist.html" @easy.perform assert_equal 404, @easy.response_code end def test_protocol_denied @easy.set :url, "gopher://google.com/" assert_raises Curl::Err::UnsupportedProtocolError do @easy.perform end end def test_redir_protocol_allowed @easy.set :url, TestServlet.url + "/redirect" @easy.set :redir_protocols, Curl::CURLPROTO_HTTP @easy.perform end def test_redir_protocol_denied @easy.set :url, TestServlet.url + "/redirect" @easy.set :redir_protocols, Curl::CURLPROTO_HTTPS assert_raises Curl::Err::UnsupportedProtocolError do @easy.perform end end end curb-1.3.5/tests/bug_crash_on_progress.rb0000644000004100000410000000214215203731642020551 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugCrashOnDebug < Test::Unit::TestCase include BugTestServerSetupTeardown def test_on_progress_raise c = Curl::Easy.new("http://127.0.0.1:#{@port}/test") c.on_progress do|x| raise "error" end c.perform assert false, "should not reach this point" rescue => e assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s c.close end def test_on_progress_abort # see: https://github.com/taf2/curb/issues/192, # to pass: # # c = Curl::Easy.new('http://127.0.0.1:9999/test') # c.on_progress do|x| # puts "we're in the progress callback" # false # end # c.perform # # notice no return keyword # c = Curl::Easy.new("http://127.0.0.1:#{@port}/test") did_progress = false c.on_progress do|x| did_progress = true return false end c.perform assert did_progress assert false, "should not reach this point" rescue => e assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s c.close end end curb-1.3.5/tests/leak_trace.rb0000644000004100000410000001367615203731642016304 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require 'fileutils' require 'objspace' require 'optparse' require 'rbconfig' require 'tmpdir' TOPDIR = File.expand_path('..', __dir__) LIBDIR = File.join(TOPDIR, 'lib') EXTDIR = File.join(TOPDIR, 'ext') $LOAD_PATH.unshift(LIBDIR) $LOAD_PATH.unshift(EXTDIR) require 'curb' module LeakTrace Record = Struct.new(:identifier, :created_location, :closed, keyword_init: true) module_function def install_multi_trace! return if @multi_trace_installed @multi_trace_installed = true @multi_records = {} multi_singleton = class << Curl::Multi self end multi_singleton.alias_method(:__leak_trace_new, :new) multi_singleton.define_method(:new) do |*args, **kwargs, &block| multi = if kwargs.empty? __leak_trace_new(*args, &block) else __leak_trace_new(*args, **kwargs, &block) end LeakTrace.multi_records[multi.object_id] ||= Record.new( identifier: multi.object_id, created_location: caller_locations(1, 6).map(&:to_s), closed: false ) multi end Curl::Multi.class_eval do alias_method :__leak_trace__close, :_close def _close(*args, **kwargs, &block) record = LeakTrace.multi_records[object_id] record.closed = true if record if kwargs.empty? __leak_trace__close(*args, &block) else __leak_trace__close(*args, **kwargs, &block) end end end end def multi_records @multi_records ||= {} end def live_multis ObjectSpace.each_object(Curl::Multi).to_a end def fixture_path File.expand_path('helper.rb', __dir__) end def fixture_url path = fixture_path.tr('\\', '/') "file://#{path.start_with?('/') ? '' : '/'}#{path}" end def full_gc(compact: false) GC.start(full_mark: true, immediate_sweep: true) GC.compact if compact && GC.respond_to?(:compact) GC.start(full_mark: true, immediate_sweep: true) end def close_multi(multi) return unless multi multi.instance_variable_set(:@requests, {}) if multi.respond_to?(:instance_variable_set) multi._close rescue StandardError multi.close rescue nil end def multi_perform(iterations:, handles:, close:, compact:) iterations.times do multi = Curl::Multi.new handles.times { multi.add(Curl::Easy.new(fixture_url)) } multi.perform ensure close_multi(multi) if close multi = nil full_gc(compact: compact) end end def multi_gc_cleanup(iterations:, handles:, compact:) iterations.times do multi = Curl::Multi.new handles.times { multi.add(Curl::Easy.new(fixture_url)) } multi.perform multi = nil full_gc(compact: compact) end end def easy_perform(iterations:, compact:) iterations.times do easy = Curl::Easy.new(fixture_url) easy.perform easy.close easy = nil full_gc(compact: compact) end end def download(iterations:, compact:) iterations.times do |i| Dir.mktmpdir("curb-leak-trace-#{i}") do |dir| Curl::Easy.download(fixture_url, File.join(dir, 'helper-copy.rb')) end full_gc(compact: compact) end end def report(io:, verbose:) live = live_multis tracked_live = live.filter_map { |multi| multi_records[multi.object_id] } io.puts "scanned live Curl::Multi objects: #{live.size}" io.puts "tracked Curl::Multi allocations: #{multi_records.size}" io.puts "tracked Curl::Multi closes: #{multi_records.count { |_id, record| record.closed }}" grouped = tracked_live.group_by { |record| record.created_location.first || '' } grouped.sort_by { |location, records| [-records.size, location] }.each do |location, records| io.puts "live #{records.size}: #{location}" next unless verbose records.each do |record| io.puts " object_id=#{record.identifier}" record.created_location.drop(1).each do |line| io.puts " #{line}" end end end end end options = { scenario: 'multi_perform', iterations: 25, handles: 3, close: true, compact: false, verbose: false, fail_on_live: false } OptionParser.new do |opts| opts.banner = 'Usage: ruby tests/leak_trace.rb [options]' opts.on('--scenario NAME', 'multi_perform, multi_gc_cleanup, easy_perform, download') do |value| options[:scenario] = value end opts.on('--iterations N', Integer, 'number of iterations to run') do |value| options[:iterations] = value end opts.on('--handles N', Integer, 'number of easy handles per multi iteration') do |value| options[:handles] = value end opts.on('--[no-]close', 'explicitly close multi handles when the scenario finishes') do |value| options[:close] = value end opts.on('--compact', 'run GC.compact between iterations when available') do options[:compact] = true end opts.on('--verbose', 'print full creation stacks for live multi handles') do options[:verbose] = true end opts.on('--fail-on-live', 'exit non-zero when any live Curl::Multi objects remain') do options[:fail_on_live] = true end end.parse! LeakTrace.install_multi_trace! case options[:scenario] when 'multi_perform' LeakTrace.multi_perform( iterations: options[:iterations], handles: options[:handles], close: options[:close], compact: options[:compact] ) when 'multi_gc_cleanup' LeakTrace.multi_gc_cleanup( iterations: options[:iterations], handles: options[:handles], compact: options[:compact] ) when 'easy_perform' LeakTrace.easy_perform( iterations: options[:iterations], compact: options[:compact] ) when 'download' LeakTrace.download( iterations: options[:iterations], compact: options[:compact] ) else warn "unknown scenario: #{options[:scenario]}" exit 64 end LeakTrace.full_gc(compact: options[:compact]) LeakTrace.report(io: $stdout, verbose: options[:verbose]) exit 1 if options[:fail_on_live] && LeakTrace.live_multis.any? curb-1.3.5/tests/bug_postfields_crash.rb0000644000004100000410000000134615203731642020372 0ustar www-datawww-data# From GICodeWarrior: # # $ ruby crash_curb.rb # crash_curb.rb:7: [BUG] Segmentation fault # ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux] # # Aborted # crash_curb.rb: # #!/usr/bin/ruby # require 'rubygems' # require 'curb' # # curl = Curl::Easy.new('http://example.com/') # curl.multipart_form_post = true # curl.http_post(Curl::PostField.file('test', 'test.xml'){'example data'}) # Ubuntu 9.10 # curb gem version 0.6.2.1 require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugPostFieldsCrash < Test::Unit::TestCase def test_crash curl = Curl::Easy.new('http://example.com/') curl.multipart_form_post = true curl.http_post(Curl::PostField.file('test', 'test.xml'){'example data'}) end end curb-1.3.5/tests/tc_curl_multi.rb0000644000004100000410000011170015203731642017042 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) require 'set' require 'tmpdir' class TestCurbCurlMulti < Test::Unit::TestCase def teardown # get a better read on memory loss when running in valgrind ObjectSpace.garbage_collect super end # for https://github.com/taf2/curb/issues/277 # must connect to an external def test_connection_keepalive omit('Not supported on Windows runners') if WINDOWS # this test fails with libcurl 7.22.0. I didn't investigate, but it may be related # to CURLOPT_MAXCONNECTS bug fixed in 7.30.0: # https://github.com/curl/curl/commit/e87e76e2dc108efb1cae87df496416f49c55fca0 omit("Skip, libcurl too old (< 7.22.0)") if Curl::CURL_VERSION.to_f < 8 && Curl::CURL_VERSION.split('.')[1].to_i <= 22 port_socket = TCPServer.new('127.0.0.1', 0) port = port_socket.addr[1] port_socket.close server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount_proc(TestServlet.path) do |req, res| res.body = "GET#{req.query_string}" res['Content-Type'] = 'text/plain' end server_thread = Thread.new(server) { |srv| srv.start } wait_for_server_ready(port, thread: server_thread) test_url = "http://127.0.0.1:#{port}#{TestServlet.path}" Curl::Multi.autoclose = true assert Curl::Multi.autoclose if `which ss`.strip.size == 0 # osx need lsof still :( open_fds = lambda do out = `/usr/sbin/lsof -nP -a -p #{Process.pid} -iTCP:#{port} -sTCP:ESTABLISHED` [out.lines.drop(1).size, 0].max end else ss = `which ss`.strip open_fds = lambda do `#{ss} -tn4 state established dport = :#{port} | wc -l`.strip.to_i end end Curl::Multi.autoclose = false before_open = open_fds.call #puts "before_open: #{before_open.inspect}" assert !Curl::Multi.autoclose multi = Curl::Multi.new multi.max_connects = 1 # limit to 1 connection within the multi handle did_complete = false 5.times do |n| easy = Curl::Easy.new(test_url) do |curl| curl.timeout = 5 # ensure we don't hang for ever connecting to an external host curl.on_complete { did_complete = true } end multi.add(easy) end multi.perform assert did_complete after_open = open_fds.call #puts "after_open: #{after_open} before_open: #{before_open.inspect}" # Some CI/libcurl combinations tear down the loopback connection before # ss/lsof observes it, so only assert that the multi cache does not retain # more than one extra connection here. assert (after_open - before_open) < 3, "with max connections set to 1 the multi handle should not retain more than one extra connection" multi.close after_open = open_fds.call #puts "after_open: #{after_open} before_open: #{before_open.inspect}" assert_equal 0, (after_open - before_open), "after closing the multi handle all connections should be closed" Curl::Multi.autoclose = true multi = Curl::Multi.new did_complete = false 5.times do |n| easy = Curl::Easy.new(test_url) do |curl| curl.timeout = 5 # ensure we don't hang for ever connecting to an external host curl.on_complete { did_complete = true } end multi.add(easy) end multi.perform assert did_complete after_open = open_fds.call #puts "after_open: #{after_open} before_open: #{before_open.inspect}" assert_equal 0, (after_open - before_open), "auto close the connections" ensure server.shutdown if defined?(server) && server server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread server_thread.kill if defined?(server_thread) && server_thread&.alive? Curl::Multi.autoclose = false # restore default end def test_connection_autoclose assert !Curl::Multi.autoclose Curl::Multi.autoclose = true assert Curl::Multi.autoclose ensure Curl::Multi.autoclose = false # restore default end def test_perform_can_reuse_multi_when_autoclose_is_enabled Curl::Multi.autoclose = true multi = Curl::Multi.new results = [] first = Curl::Easy.new(TestServlet.url) first.on_complete { results << first.code } multi.add(first) multi.perform second = Curl::Easy.new(TestServlet.url) second.on_complete { results << second.code } multi.add(second) multi.perform assert_equal [200, 200], results ensure multi.close if defined?(multi) && multi Curl::Multi.autoclose = false end def test_close_inside_completion_callback_is_rejected_without_invalidating_multi multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) easy.on_complete { multi.close } multi.add(easy) error = assert_raise(Curl::Err::AbortedByCallbackError) { multi.perform } assert_match(/Cannot close an active Curl::Multi handle/, error.message) assert multi.idle? assert_equal({}, multi.requests) easy.on_complete { |_curl| } multi.add(easy) multi.perform assert_equal "GET", easy.body_str ensure multi.close if defined?(multi) && multi end def test_close_inside_perform_block_is_rejected_without_invalidating_multi multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) multi.add(easy) error = assert_raise(RuntimeError) do multi.perform { multi.close } end assert_match(/Cannot close an active Curl::Multi handle/, error.message) assert_nothing_raised { multi.close } multi = nil ensure multi.close if defined?(multi) && multi end def test_active_easy_cannot_be_added_to_another_multi first_multi = Curl::Multi.new second_multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) first_multi.add(easy) error = assert_raise(RuntimeError) { second_multi.add(easy) } assert_match(/active Curl::Easy/, error.message) assert_same first_multi, easy.multi assert_equal easy, first_multi.requests[easy.object_id] assert_nothing_raised { first_multi.close } first_multi = nil ensure first_multi.close if defined?(first_multi) && first_multi second_multi.close if defined?(second_multi) && second_multi end def test_close_makes_multi_unusable multi = Curl::Multi.new multi.close error = assert_raise(Curl::Err::MultiBadHandle) do multi.add(Curl::Easy.new($TEST_URL)) end assert_match(/Invalid multi handle/i, error.message) assert_equal 0, multi.requests.length ensure multi.close if multi end def test_new_multi_01 d1 = String.new c1 = Curl::Easy.new($TEST_URL) do |curl| curl.headers["User-Agent"] = "myapp-0.0" curl.on_body {|d| d1 << d; d.length } end d2 = String.new c2 = Curl::Easy.new($TEST_URL) do |curl| curl.headers["User-Agent"] = "myapp-0.0" curl.on_body {|d| d2 << d; d.length } end m = Curl::Multi.new m.add( c1 ) m.add( c2 ) m.perform assert_match(/^# DO NOT REMOVE THIS COMMENT/, d1) assert_match(/^# DO NOT REMOVE THIS COMMENT/, d2) ensure m.close if m end def test_perform_block c1 = Curl::Easy.new($TEST_URL) c2 = Curl::Easy.new($TEST_URL) m = Curl::Multi.new m.add( c1 ) m.add( c2 ) m.perform do # idle #puts "idling..." end assert_match(/^# DO NOT REMOVE THIS COMMENT/, c1.body_str) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c2.body_str) ensure m.close if m end def test_multi_perform_runs_work_added_from_final_idle_yield with_queue_refill_test_server do |port, hits| previous_autoclose = Curl::Multi.autoclose Curl::Multi.autoclose = true multi = Curl::Multi.new slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow") queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued") completions = [] empty_yields = 0 queued_added = false slow.on_complete { completions << :slow } queued.on_complete { completions << :queued } multi.add(slow) multi.perform do |performing_multi| empty_yields += 1 if performing_multi.requests.empty? if !queued_added && empty_yields >= 2 queued_added = true performing_multi.add(queued) end end assert queued_added, "test should add queued work from the final idle yield" assert_equal [:slow, :queued], completions assert_equal 1, hits[:slow] assert_equal 1, hits[:queued] assert_equal 0, multi.requests.length ensure Curl::Multi.autoclose = previous_autoclose if defined?(previous_autoclose) multi.close if defined?(multi) && multi end end def test_multi_easy_get n = 1 urls = [] n.times { urls << $TEST_URL } Curl::Multi.get(urls, {timeout: 5}) {|easy| assert_match(/file:/, easy.last_effective_url) } end def test_multi_easy_get_with_error omit('Path/line parsing differs on Windows') if WINDOWS begin did_raise = false n = 3 urls = [] n.times { urls << $TEST_URL } error_line_number_should_be = nil Curl::Multi.get(urls, {timeout: 5}) {|easy| # if we got this right the error will be reported to be on the line below our error_line_number_should_be error_line_number_should_be = __LINE__ raise } rescue Curl::Err::AbortedByCallbackError => e did_raise = true in_file = e.backtrace.detect {|err| err.match?(File.basename(__FILE__)) } #in_file_stack = e.backtrace.select {|err| err.match?(File.basename(__FILE__)) } assert_match(__FILE__, in_file) in_file.gsub!(__FILE__) parts = in_file.split(':') parts.shift line_no = parts.shift.to_i assert_equal error_line_number_should_be+1, line_no.to_i end assert did_raise, "we should have raised an exception" end def test_multi_perform_reraises_on_body_exception multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) easy.on_body { raise "body blew up" } error = assert_raise(RuntimeError) do multi.add(easy) multi.perform end assert_equal "body blew up", error.message assert !easy.instance_variable_defined?(:@__curb_callback_error) ensure multi.close if multi end def test_multi_perform_reraises_on_body_exception_for_frozen_easy multi = Curl::Multi.new easy = Curl::Easy.new($TEST_URL) callback_error = Class.new(RuntimeError) easy.on_body { raise callback_error, "body blew up" } easy.freeze error = assert_raise(callback_error) do multi.add(easy) multi.perform end assert_equal "body blew up", error.message ensure begin multi.close if multi rescue StandardError multi.instance_variable_set(:@requests, {}) multi._close end end def test_multi_perform_reraises_on_header_exception multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) easy.on_header { raise "header blew up" } error = assert_raise(RuntimeError) do multi.add(easy) multi.perform end assert_equal "header blew up", error.message assert !easy.instance_variable_defined?(:@__curb_callback_error) ensure multi.close if multi end def test_multi_perform_drains_completed_siblings_before_reraising_on_body_exception multi = Curl::Multi.new failed = Curl::Easy.new($TEST_URL) completed = Curl::Easy.new($TEST_URL) completions = [] failed.on_body { raise "body blew up" } completed.on_complete { completions << :completed } error = assert_raise(RuntimeError) do multi.add(failed) multi.add(completed) multi.perform end assert_equal "body blew up", error.message assert_equal [:completed], completions assert multi.idle?, 'The multi handle should be idle after draining the completed batch' assert_equal 0, multi.requests.length assert_nil failed.multi assert_nil completed.multi assert !failed.instance_variable_defined?(:@__curb_callback_error) ensure multi.close if multi end def test_multi_perform_waits_until_idle_before_reraising_on_complete_exception port_socket = TCPServer.new('127.0.0.1', 0) port = port_socket.addr[1] port_socket.close server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount_proc('/fast') do |_req, res| res['Content-Type'] = 'text/plain' res.body = 'fast' end server.mount_proc('/slow') do |_req, res| res['Content-Type'] = 'text/plain' sleep 0.2 res.body = 'slow' end server_thread = Thread.new(server) { |srv| srv.start } wait_for_server_ready(port, thread: server_thread) multi = Curl::Multi.new fast = Curl::Easy.new("http://127.0.0.1:#{port}/fast") slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow") completions = [] fast.on_complete do completions << :fast raise "complete blew up" end slow.on_complete { completions << :slow } error = assert_raise(Curl::Err::AbortedByCallbackError) do multi.add(fast) multi.add(slow) multi.perform end assert_equal "complete blew up", error.message assert_equal [:fast, :slow], completions assert multi.idle?, 'The multi handle should be idle before the deferred exception is raised' assert_equal 0, multi.requests.length assert_nil fast.multi assert_nil slow.multi ensure multi.close if multi server.shutdown if defined?(server) && server server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread server_thread.kill if defined?(server_thread) && server_thread&.alive? end def test_multi_perform_keeps_yielding_block_while_draining_deferred_on_complete_exception with_queue_refill_test_server do |port, hits| multi = Curl::Multi.new failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail") slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow") failure_seen = false slow_completed = false yielded_during_drain = false failed.on_complete do failure_seen = true raise "complete blew up" end slow.on_complete { slow_completed = true } error = assert_raise(Curl::Err::AbortedByCallbackError) do multi.add(failed) multi.add(slow) multi.perform do yielded_during_drain ||= failure_seen && !slow_completed end end assert_equal "complete blew up", error.message assert yielded_during_drain, "perform block should continue yielding while draining a slow sibling after a deferred callback exception" assert_equal 1, hits[:fail] assert_equal 1, hits[:slow] ensure multi.close if multi end end def test_multi_perform_does_not_start_queued_work_after_on_complete_exception with_queue_refill_test_server do |port, hits| multi = Curl::Multi.new failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail") slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow") queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued") failure_seen = false failed.on_complete do failure_seen = true raise "complete blew up" end error = assert_raise(Curl::Err::AbortedByCallbackError) do multi.add(failed) multi.add(slow) multi.perform do next unless failure_seen multi.add(queued) failure_seen = false end end assert_equal "complete blew up", error.message assert_equal 1, hits[:fail] assert_equal 1, hits[:slow] assert_equal 0, hits[:queued], "queued request should not start once a deferred callback exception is pending" ensure multi.close if multi end end def test_multi_perform_does_not_start_work_added_within_failing_on_complete_callback with_queue_refill_test_server do |port, hits| multi = Curl::Multi.new failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail") queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued") failed.on_complete do multi.add(queued) raise "complete blew up" end error = assert_raise(Curl::Err::AbortedByCallbackError) do multi.add(failed) multi.perform end assert_equal "complete blew up", error.message assert_equal 1, hits[:fail] assert_equal 0, hits[:queued], "request added from a failing on_complete callback should not start once the multi begins aborting" ensure multi.close if multi end end def test_multi_perform_does_not_restart_sibling_removed_and_readded_from_failing_on_complete_callback with_queue_refill_test_server(wait_fail_until_slow: true) do |port, hits| multi = Curl::Multi.new failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail") slow = Curl::Easy.new("http://127.0.0.1:#{port}/slow") failed.on_complete do multi.remove(slow) multi.add(slow) raise "complete blew up" end error = assert_raise(Curl::Err::AbortedByCallbackError) do multi.add(failed) multi.add(slow) multi.perform end assert_equal "complete blew up", error.message assert_equal 1, hits[:fail] assert_equal 1, hits[:slow], "sibling removed and re-added from a failing on_complete callback should be treated as replacement work and not restarted" ensure multi.close if multi end end def test_multi_perform_does_not_start_work_added_within_on_complete_after_on_body_exception with_queue_refill_test_server do |port, hits| multi = Curl::Multi.new failed = Curl::Easy.new("http://127.0.0.1:#{port}/fail") queued = Curl::Easy.new("http://127.0.0.1:#{port}/queued") failed.on_body { raise "body blew up" } failed.on_complete { multi.add(queued) } error = assert_raise(RuntimeError) do multi.add(failed) multi.perform end assert_equal "body blew up", error.message assert_equal 1, hits[:fail] assert_equal 0, hits[:queued], "request added from on_complete after a body callback exception should not start once the multi begins aborting" ensure multi.close if multi end end def test_multi_http_does_not_fetch_queued_url_after_on_body_exception with_queue_refill_test_server do |port, hits| urls = [ { :url => "http://127.0.0.1:#{port}/queued", :method => :get }, { :url => "http://127.0.0.1:#{port}/slow", :method => :get }, { :url => "http://127.0.0.1:#{port}/fail", :method => :get, :on_body => proc { raise "body blew up" } } ] error = assert_raise(RuntimeError) do Curl::Multi.http(urls, {:max_connects => 2}) { |_easy, _code, _method| } end assert_equal "body blew up", error.message assert_equal 1, hits[:fail] assert_equal 1, hits[:slow] assert_equal 0, hits[:queued], "Curl::Multi.http should not refill queued urls after a callback abort" end end def test_multi_perform_runs_status_callbacks_after_on_body_exception multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) callbacks = [] easy.on_body { raise "body blew up" } easy.on_complete { callbacks << :complete } easy.on_failure do |_completed_easy, error_info| callbacks << :failure assert_equal Curl::Err::WriteError, error_info.first end error = assert_raise(RuntimeError) do multi.add(easy) multi.perform end assert_equal "body blew up", error.message assert_equal [:complete, :failure], callbacks assert multi.idle? assert_equal 0, multi.requests.length ensure multi.close if multi end def test_multi_perform_preserves_on_body_exception_when_status_callbacks_raise multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) callbacks = [] easy.on_body { raise "body blew up" } easy.on_complete do callbacks << :complete raise "complete blew up" end easy.on_failure do |_completed_easy, error_info| callbacks << :failure assert_equal Curl::Err::WriteError, error_info.first raise "failure blew up" end error = assert_raise(RuntimeError) do multi.add(easy) multi.perform end assert_equal "body blew up", error.message assert_equal [:complete, :failure], callbacks assert multi.idle? assert_equal 0, multi.requests.length ensure multi.close if multi end # NOTE: if this test runs slowly on Mac OSX, it is probably due to the use of a port install curl+ssl+ares install # on my MacBook, this causes curl_easy_init to take nearly 0.01 seconds / * 100 below is 1 second too many! def test_n_requests n = 100 m = Curl::Multi.new responses = [] n.times do|i| responses[i] = String.new c = Curl::Easy.new($TEST_URL) do|curl| curl.on_body{|data| responses[i] << data; data.size } end m.add c end m.perform assert_equal n, responses.size n.times do|i| assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}") end ensure m.close if m end def test_n_requests_with_break # process n requests then load the handle again and run it again n = 2 m = Curl::Multi.new 5.times do|it| responses = [] n.times do|i| responses[i] = String.new c = Curl::Easy.new($TEST_URL) do|curl| curl.on_body{|data| responses[i] << data; data.size } end m.add c end m.perform assert_equal n, responses.size n.times do|i| assert_match(/^# DO NOT REMOVE THIS COMMENT/, responses[i], "response #{i}") end end ensure m.close if m end def test_idle_check m = Curl::Multi.new e = Curl::Easy.new($TEST_URL) assert(m.idle?, 'A new Curl::Multi handle should be idle') assert_nil e.multi m.add(e) assert_not_nil e.multi assert((not m.idle?), 'A Curl::Multi handle with a request should not be idle') m.perform assert(m.idle?, 'A Curl::Multi handle should be idle after performing its requests') ensure m.close if m end def test_requests m = Curl::Multi.new assert_equal(0, m.requests.length, 'A new Curl::Multi handle should have no requests') 10.times do m.add(Curl::Easy.new($TEST_URL)) end assert_equal(10, m.requests.length, 'multi.requests should contain all the active requests') m.perform assert_equal(0, m.requests.length, 'A new Curl::Multi handle should have no requests after a perform') ensure m.close if m end def test_easy_multi_is_cleared_after_perform m = Curl::Multi.new c = Curl::Easy.new($TEST_URL) m.add(c) assert_equal m, c.multi m.perform assert_nil c.multi ensure m.close if m end def test_easy_multi_is_available_during_completion_callbacks multi = Curl::Multi.new easy = Curl::Easy.new($TEST_URL) seen_multi = {} easy.on_complete do |completed_easy| seen_multi[:complete] = completed_easy.multi end easy.on_success do |completed_easy| seen_multi[:success] = completed_easy.multi end multi.add(easy) multi.perform assert_same multi, seen_multi[:complete] assert_same multi, seen_multi[:success] assert_nil easy.multi ensure multi.close if multi end def test_cancel m = Curl::Multi.new m.cancel! # shouldn't raise anything 10.times do m.add(Curl::Easy.new($TEST_URL)) end m.cancel! assert_equal(0, m.requests.size, 'A new Curl::Multi handle should have no requests after being canceled') ensure m.close if m end def test_with_success c1 = Curl::Easy.new($TEST_URL) c2 = Curl::Easy.new($TEST_URL) success_called1 = false success_called2 = false c1.on_success do|c| success_called1 = true assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end c2.on_success do|c| success_called2 = true assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end m = Curl::Multi.new m.add( c1 ) m.add( c2 ) m.perform do # idle #puts "idling..." end assert success_called2 assert success_called1 ensure m.close if m end def test_with_success_cb_with_404 c1 = Curl::Easy.new("#{$TEST_URL.gsub(/file:\/\//,'')}/not_here") c2 = Curl::Easy.new($TEST_URL) success_called1 = false success_called2 = false c1.on_success do|c| success_called1 = true #puts "success 1 called: #{c.body_str.inspect}" assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end c1.on_failure do|c,rc| # rc => [Curl::Err::MalformedURLError, "URL using bad/illegal format or missing URL"] assert_equal Curl::Easy, c.class assert_equal Curl::Err::MalformedURLError, rc.first assert_equal "URL using bad/illegal format or missing URL", rc.last end c2.on_success do|c| # puts "success 2 called: #{c.body_str.inspect}" success_called2 = true assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end m = Curl::Multi.new #puts "c1: #{c1.url}" m.add( c1 ) #puts "c2: #{c2.url}" m.add( c2 ) #puts "calling" m.perform do # idle end assert success_called2 assert !success_called1 ensure m.close if m end # This tests whether, ruby's GC will trash an out of scope easy handle class TestForScope attr_reader :buf def t_method @buf = String.new @m = Curl::Multi.new 10.times do|i| c = Curl::Easy.new($TEST_URL) c.on_success{|b| @buf << b.body_str } ObjectSpace.garbage_collect @m.add(c) ObjectSpace.garbage_collect end ObjectSpace.garbage_collect end def t_call @m.perform do ObjectSpace.garbage_collect end ensure @m.close if @m end def self.test ObjectSpace.garbage_collect tfs = TestForScope.new ObjectSpace.garbage_collect tfs.t_method ObjectSpace.garbage_collect tfs.t_call ObjectSpace.garbage_collect tfs.buf end end def test_with_garbage_collect ObjectSpace.garbage_collect buf = TestForScope.test ObjectSpace.garbage_collect assert_match(/^# DO NOT REMOVE THIS COMMENT/, buf) end =begin def test_remote_requests responses = {} requests = ["http://google.co.uk/", "http://ruby-lang.org/"] m = Curl::Multi.new # add a few easy handles requests.each do |url| responses[url] = String.new responses["#{url}-header"] = String.new c = Curl::Easy.new(url) do|curl| curl.follow_location = true curl.on_header{|data| responses["#{url}-header"] << data; data.size } curl.on_body{|data| responses[url] << data; data.size } curl.on_success { puts curl.last_effective_url } end m.add(c) end m.perform requests.each do|url| puts responses["#{url}-header"].split("\r\n").inspect #puts responses[url].size end end =end def test_multi_easy_get_01 urls = [] root_uri = 'http://127.0.0.1:9129/ext/' # send a request to fetch all c files in the ext dir Dir[File.dirname(__FILE__) + "/../ext/*.c"].each do|path| urls << root_uri + File.basename(path) end urls = urls[0..(urls.size/2)] # keep it fast, webrick... Curl::Multi.get(urls, {:follow_location => true}, {:pipeline => true}) do|curl| assert_equal 200, curl.response_code end end def test_multi_easy_download_01 # test collecting response buffers to file e.g. on_body root_uri = 'http://127.0.0.1:9129/ext/' urls = [] downloads = [] file_info = {} Dir.mktmpdir('curb-download-') do |download_dir| # for each file store the size by file name Dir[File.dirname(__FILE__) + "/../ext/*.c"].each do|path| urls << (root_uri + File.basename(path)) downloads << File.join(download_dir, File.basename(path)) file_info[File.basename(path)] = {:size => File.size(path), :path => path} end # start downloads Curl::Multi.download(urls,{},{},downloads) do|curl,download_path| assert_equal 200, curl.response_code assert File.exist?(download_path) assert_equal file_info[File.basename(download_path)][:size], File.size(download_path), "incomplete download: #{download_path}" end end end def test_multi_easy_post_01 urls = [ { :url => TestServlet.url + '?q=1', :post_fields => {'field1' => 'value1', 'k' => 'j'}}, { :url => TestServlet.url + '?q=2', :post_fields => {'field2' => 'value2', 'foo' => 'bar', 'i' => 'j' }}, { :url => TestServlet.url + '?q=3', :post_fields => {'field3' => 'value3', 'field4' => 'value4'}} ] Curl::Multi.post(urls, {:follow_location => true, :multipart_form_post => true}, {:pipeline => true}) do|easy| str = easy.body_str assert_match(/POST/, str) fields = {} str.gsub(/POST\n/,'').split('&').map{|sv| k, v = sv.split('='); fields[k] = v } expected = urls.find{|s| s[:url] == easy.last_effective_url } assert_equal expected[:post_fields], fields #puts "#{easy.last_effective_url} #{fields.inspect}" end end def test_multi_easy_put_01 urls = [{ :url => TestServlet.url, :method => :put, :put_data => "message", :headers => {'Content-Type' => 'application/json' } }, { :url => TestServlet.url, :method => :put, :put_data => "message", :headers => {'Content-Type' => 'application/json' } }] Curl::Multi.put(urls, {}, {:pipeline => true}) do|easy| assert_match(/PUT/, easy.body_str) assert_match(/message/, easy.body_str) end end def test_multi_easy_http_01 urls = [ { :url => TestServlet.url + '?q=1', :method => :post, :post_fields => {'field1' => 'value1', 'k' => 'j'}}, { :url => TestServlet.url + '?q=2', :method => :post, :post_fields => {'field2' => 'value2', 'foo' => 'bar', 'i' => 'j' }}, { :url => TestServlet.url + '?q=3', :method => :post, :post_fields => {'field3' => 'value3', 'field4' => 'value4'}}, { :url => TestServlet.url, :method => :put, :put_data => "message", :headers => {'Content-Type' => 'application/json' } }, { :url => TestServlet.url, :method => :get } ] Curl::Multi.http(urls, {:pipeline => true}) do|easy, code, method| assert_equal 200, code case method when :post assert_match(/POST/, easy.body_str) when :get assert_match(/GET/, easy.body_str) when :put assert_match(/PUT/, easy.body_str) end #puts "#{easy.body_str.inspect}, #{method.inspect}, #{code.inspect}" end end def test_multi_easy_http_with_max_connects urls = [ { :url => TestServlet.url + '?q=1', :method => :get }, { :url => TestServlet.url + '?q=2', :method => :get }, { :url => TestServlet.url + '?q=3', :method => :get } ] Curl::Multi.http(urls, {:pipeline => true, :max_connects => 1}) do|easy, code, method| assert_equal 200, code case method when :post assert_match(/POST/, easy.body) when :get assert_match(/GET/, easy.body) when :put assert_match(/PUT/, easy.body) end end end def test_multi_easy_http_with_max_host_connections urls = [ { :url => TestServlet.url + '?q=1', :method => :get }, { :url => TestServlet.url + '?q=2', :method => :get }, { :url => TestServlet.url + '?q=3', :method => :get } ] Curl::Multi.http(urls, {:pipeline => true, :max_host_connections => 1}) do|easy, code, method| assert_equal 200, code case method when :post assert_match(/POST/, easy.body) when :get assert_match(/GET/, easy.body) when :put assert_match(/PUT/, easy.body) end end end # Regression test for issue #267 (2015): ensure that when reusing # easy handles with constrained concurrency, the callback receives # the correct URL for each completed request rather than repeating # the first URL. def test_multi_easy_http_urls_unique_across_max_connects urls = [ { :url => TestServlet.url + '?q=1', :method => :get }, { :url => TestServlet.url + '?q=2', :method => :get }, { :url => TestServlet.url + '?q=3', :method => :get } ] [1, 2, 3].each do |max| results = [] Curl::Multi.http(urls.dup, {:pipeline => true, :max_connects => max}) do |easy, code, method| assert_equal 200, code assert_equal :get, method results << easy.last_effective_url end # Ensure we saw one completion per input URL assert_equal urls.size, results.size, "expected #{urls.size} results with max_connects=#{max}" # And that each URL completed exactly once (no accidental reuse/mis-reporting) expected_urls = urls.map { |u| u[:url] } assert_equal expected_urls.to_set, results.to_set, "unexpected URLs for max_connects=#{max}: #{results.inspect}" assert_equal expected_urls.size, results.uniq.size, "duplicate URLs seen for max_connects=#{max}: #{results.inspect}" end end def test_multi_recieves_500 m = Curl::Multi.new e = Curl::Easy.new("http://127.0.0.1:9129/methods") failure = false e.post_body = "hello=world&s=500" e.on_failure{|c,r| failure = true } e.on_success{|c| failure = false } m.add(e) m.perform assert failure e2 = Curl::Easy.new(TestServlet.url) e2.post_body = "hello=world" e2.on_failure{|c,r| failure = true } m.add(e2) m.perform failure = false assert !failure assert_equal "POST\nhello=world", e2.body_str ensure m.close if m end def test_remove_exception_is_descriptive m = Curl::Multi.new c = Curl::Easy.new("http://127.9.9.9:999110") m.remove(c) rescue => e assert_equal 'CURLError: Invalid easy handle', e.message assert_equal 0, m.requests.size ensure m.close if m end def test_retry_easy_handle m = Curl::Multi.new tries = 10 c1 = Curl::Easy.new('http://127.1.1.1:99911') do |curl| curl.on_failure {|c,e| assert_equal [Curl::Err::MalformedURLError, "URL using bad/illegal format or missing URL"], e if tries > 0 tries -= 1 m.add(c) end } end tries -= 1 m.add(c1) m.perform assert_equal 0, tries assert_equal 0, m.requests.size ensure m.close if m end def test_close_in_on_missing_callback_is_blocked m = Curl::Multi.new c = Curl::Easy.new(TestServlet.url + '/not_here') did_raise = false c.on_missing do |easy, _error| begin easy.close rescue RuntimeError did_raise = true end end m.add(c) m.perform assert did_raise ensure m.close if m end def test_close_in_on_redirect_callback_is_blocked m = Curl::Multi.new c = Curl::Easy.new(TestServlet.url + '/redirect') did_raise = false c.on_redirect do |easy, _error| begin easy.close rescue RuntimeError did_raise = true end end m.add(c) m.perform assert did_raise ensure m.close if m end def test_reusing_handle m = Curl::Multi.new c = Curl::Easy.new('http://127.0.0.1') do|easy| easy.on_complete{|e,r| puts e.inspect } end m.add(c) m.add(c) rescue => e assert Curl::Err::MultiBadEasyHandle == e.class || Curl::Err::MultiAddedAlready == e.class ensure m.close if m end def test_multi_default_timeout assert_equal 100, Curl::Multi.default_timeout Curl::Multi.default_timeout = 12 assert_equal 12, Curl::Multi.default_timeout assert_equal 100, (Curl::Multi.default_timeout = 100) end def with_queue_refill_test_server(wait_fail_until_slow: false) port_socket = TCPServer.new('127.0.0.1', 0) port = port_socket.addr[1] port_socket.close hits = Hash.new(0) slow_started = false slow_started_lock = Mutex.new slow_started_condition = ConditionVariable.new server = WEBrick::HTTPServer.new(:Port => port, :Logger => WEBRICK_TEST_LOG, :AccessLog => []) server.mount_proc('/fail') do |_req, res| if wait_fail_until_slow slow_started_lock.synchronize do slow_started_condition.wait(slow_started_lock, 1) unless slow_started end end hits[:fail] += 1 res['Content-Type'] = 'text/plain' res.body = 'fail' end server.mount_proc('/slow') do |_req, res| slow_started_lock.synchronize do hits[:slow] += 1 slow_started = true slow_started_condition.broadcast end res['Content-Type'] = 'text/plain' sleep 0.3 res.body = 'slow' end server.mount_proc('/queued') do |_req, res| hits[:queued] += 1 res['Content-Type'] = 'text/plain' res.body = 'queued' end server_thread = Thread.new(server) { |srv| srv.start } wait_for_server_ready(port, thread: server_thread) yield port, hits ensure server.shutdown if defined?(server) && server server_thread.join(server_startup_timeout) if defined?(server_thread) && server_thread server_thread.kill if defined?(server_thread) && server_thread&.alive? end include TestServerMethods def setup server_setup end end curb-1.3.5/tests/bug_require_last_or_segfault.rb0000644000004100000410000000236715203731642022133 0ustar www-datawww-data# From Vlad Jebelev: # # - if I have a require statement after "require 'curb'" and there is a # POST with at least 1 field, the script will fail with a segmentation # fault, e.g. the following sequence fails every time for me (Ruby 1.8.5): # ----------------------------------------------------------------- # require 'curb' # require 'uri' # # url = 'https://www.google.com/accounts/ServiceLoginAuth' # # c = Curl::Easy.http_post( # 'https://www.google.com/accounts/ServiceLoginAuth', # [Curl:: PostField.content('ltmpl','m_blanco')] ) do |curl| # end # ------------------------------------------------------------------ # :..dev/util$ ruby seg.rb # seg.rb:6: [BUG] Segmentation fault # ruby 1.8.5 (2006-08-25) [i686-linux] # # Aborted # ------------------------------------------------------------------ # require 'test/unit' require 'rbconfig' $rubycmd = RbConfig::CONFIG['RUBY_INSTALL_NAME'] || 'ruby' class BugTestRequireLastOrSegfault < Test::Unit::TestCase def test_bug 5.times do |i| puts "Test ##{i}" # will be empty string if it segfaults... assert_equal 'success', `#$rubycmd #{File.dirname(__FILE__)}/require_last_or_segfault_script.rb`.chomp sleep 5 end end end curb-1.3.5/tests/tc_curl_easy.rb0000644000004100000410000014172215203731642016660 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class FooNoToS undef to_s end class TestCurbCurlEasy < Test::Unit::TestCase def test_global_reset Curl.get($TEST_URL) # in a Timeout block you should reset the thread current handle Curl.reset end def test_nested_easy_methods easy = Curl.get(TestServlet.url) {|http| res = Curl.get(TestServlet.url + '/not_here') assert_equal 404, res.code } assert_equal 200, easy.code end def test_curlopt_resolve require 'resolv' uri = URI.parse(TestServlet.url) resolved_ip = Resolv.getaddress(uri.host) # perform DNS lookup once mapping = "#{uri.host}:#{uri.port}:#{resolved_ip}" http = Curl::Easy.new(TestServlet.url) http.setopt(Curl::CURLOPT_RESOLVE, [mapping]) http.get assert_match(/GET/, http.body) end def test_resolve_clear_keeps_handle_usable host = "curb.invalid" url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}" mapping = "#{host}:#{TestServlet.port}:127.0.0.1" http = Curl::Easy.new(url) http.dns_cache_timeout = 0 http.resolve = [mapping] http.get assert_match(/GET/, http.body) http.resolve = nil http.url = TestServlet.url http.get assert_match(/GET/, http.body) end def test_curlopt_stderr_with_file # does not work with Tempfile directly path = Tempfile.new('curb_test_curlopt_stderr').path File.open(path, 'w') do |file| easy = Curl::Easy.new(TestServlet.url) easy.verbose = true easy.setopt(Curl::CURLOPT_STDERR, file) easy.perform end output = File.read(path) assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output) assert_match('Host: 127.0.0.1:9129', output) end def test_curlopt_stderr_with_io tmp = Tempfile.new('curb_test_curlopt_stderr') path = tmp.path fd = IO.sysopen(path, 'w') io = IO.new(fd, 'w') io.sync = true easy = Curl::Easy.new(TestServlet.url) easy.verbose = true easy.setopt(Curl::CURLOPT_STDERR, io) easy.perform io.flush io.close output = File.read(path) assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output) assert_match('Host: 127.0.0.1:9129', output) ensure tmp.close! if defined?(tmp) && tmp end def test_curlopt_stderr_gc_safe tmp = Tempfile.new('curb_test_curlopt_stderr_gc') path = tmp.path fd = IO.sysopen(path, 'w') io = IO.new(fd, 'w') easy = Curl::Easy.new(TestServlet.url) easy.verbose = true easy.setopt(Curl::CURLOPT_STDERR, io) # Drop our Ruby reference and force GC; curb should retain it internally io = nil GC.start easy.perform output = File.read(path) assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output) assert_match('Host: 127.0.0.1:9129', output) ensure tmp.close! if defined?(tmp) && tmp end def test_head_request_no_body_and_no_timeout # Ensure a HEAD request completes and does not attempt to read a body # even when the server advertises a Content-Length. easy = nil assert_nothing_raised do easy = Curl.http(:HEAD, TestServlet.url) end assert_not_nil easy # Header string should contain the HTTP status line. assert_match(/HTTP\/1\.1\s\d+\s/, easy.header_str.to_s) # Body should be empty for HEAD requests (libcurl won't call the write callback). assert_equal "", easy.body_str.to_s end def test_head_request_restores_easy_state_after_callback_exception easy = Curl::Easy.new(TestServlet.url) easy.on_complete { raise "head complete blew up" } error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.http("HEAD") } assert_equal "head complete blew up", error.message easy.on_complete { |_curl| } easy.perform assert_equal "GET", easy.body_str end def test_patch_request_does_not_leak_custom_method_to_next_perform easy = Curl::Easy.new(TestServlet.url) easy.http_patch("a=b") assert_equal "PATCH\na=b", easy.body_str easy.perform assert_equal "GET", easy.body_str end def test_put_request_does_not_leak_custom_method_to_next_perform easy = Curl::Easy.new(TestServlet.url) easy.http_put("payload") assert_equal "PUT\npayload", easy.body_str easy.perform assert_equal "GET", easy.body_str end def test_failed_put_data_setup_does_not_leave_easy_in_upload_mode easy = Curl::Easy.new(TestServlet.url) easy.headers = ["X-Test: true"] assert_raise(RuntimeError) { easy.put_data = "payload" } easy.headers = {} easy.perform assert_equal "GET", easy.body_str end def test_perform_releases_failed_implicit_multi_without_follow_up_easy_perform Curl::Easy.flush_deferred_multi_closes assert_equal 0, Curl::Easy.deferred_multi_closes.length easy, implicit_multi = capture_failed_implicit_multi_cleanup_state assert_nil easy.multi assert_equal({}, implicit_multi.requests) assert_equal false, implicit_multi.instance_variable_get(:@deferred_close) assert_equal 0, Curl::Easy.deferred_multi_closes.length ensure easy.multi = nil if defined?(easy) && easy implicit_multi.close if defined?(implicit_multi) && implicit_multi Curl::Easy.flush_deferred_multi_closes end def test_deferred_multi_closes_stay_with_their_owner_thread_across_retry Curl::Easy.flush_deferred_multi_closes(all_threads: true) multi = deferred_close_stub_multi(failures: 1) Curl::Easy.defer_multi_close(multi, nil) Curl::Easy.flush_deferred_multi_closes assert_equal 1, multi.close_attempts assert_equal true, multi.instance_variable_get(:@deferred_close) assert_equal 1, Curl::Easy.deferred_multi_closes.length Thread.new { Curl::Easy.flush_deferred_multi_closes }.join assert_equal 1, multi.close_attempts assert_equal true, multi.instance_variable_get(:@deferred_close) assert_equal 1, Curl::Easy.deferred_multi_closes.length Curl::Easy.flush_deferred_multi_closes assert_equal 2, multi.close_attempts assert_equal true, multi.closed? assert_equal false, multi.instance_variable_get(:@deferred_close) assert_equal 0, Curl::Easy.deferred_multi_closes.length ensure Curl::Easy.flush_deferred_multi_closes(all_threads: true) end def capture_created_multis created_multis = [] multi_singleton = class << Curl::Multi; self; end multi_singleton.__send__(:alias_method, :__curb_test_original_new, :new) multi_singleton.__send__(:define_method, :new) do |*args, &block| multi = __curb_test_original_new(*args, &block) created_multis << multi multi end yield created_multis ensure if defined?(multi_singleton) && multi_singleton.method_defined?(:__curb_test_original_new) multi_singleton.__send__(:remove_method, :new) multi_singleton.__send__(:alias_method, :new, :__curb_test_original_new) multi_singleton.__send__(:remove_method, :__curb_test_original_new) end end def capture_failed_implicit_multi_cleanup_state created_multis = nil easy = nil capture_created_multis do |captured_multis| created_multis = captured_multis easy = Curl::Easy.new(TestServlet.url) easy.on_complete { raise "complete blew up" } error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform } assert_equal "complete blew up", error.message end assert_equal 1, created_multis.length, "test should observe one implicit multi created by Easy#perform" [easy, created_multis.first] end def deferred_close_stub_multi(failures:) Object.new.tap do |multi| requests = {} remaining_failures = failures close_attempts = 0 closed = false multi.define_singleton_method(:requests) { requests } multi.define_singleton_method(:_close) do close_attempts += 1 if remaining_failures.positive? remaining_failures -= 1 raise "close failed" end closed = true end multi.define_singleton_method(:close_attempts) { close_attempts } multi.define_singleton_method(:closed?) { closed } end end def test_perform_restores_explicit_multi_after_callback_error multi = Curl::Multi.new easy = Curl::Easy.new(TestServlet.url) easy.multi = multi easy.on_complete { raise "complete blew up" } error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform } assert_equal "complete blew up", error.message assert_same multi, easy.multi assert_equal({}, multi.requests) easy.on_complete { |_curl| } easy.perform assert_same multi, easy.multi assert_equal "GET", easy.body_str ensure easy.multi = nil if defined?(easy) && easy && easy.multi.equal?(multi) multi.close if defined?(multi) && multi end def test_perform_restores_cached_implicit_multi_after_callback_error Curl::Multi.autoclose = false easy = Curl::Easy.new(TestServlet.url) easy.perform cached_multi = easy.multi easy.on_complete { raise "complete blew up" } error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform } assert_equal "complete blew up", error.message assert_same cached_multi, easy.multi assert_equal({}, cached_multi.requests) easy.on_complete { |_curl| } easy.perform assert_same cached_multi, easy.multi assert_equal "GET", easy.body_str ensure easy.multi = nil if defined?(easy) && easy cached_multi.close if defined?(cached_multi) && cached_multi Curl::Multi.autoclose = false end def test_close_clears_explicit_idle_easy_multi_reference easy = Curl::Easy.new($TEST_URL) multi = Curl::Multi.new easy.multi = multi assert_same multi, easy.multi assert_equal({}, multi.requests) multi.close assert_nil easy.multi end def test_close_clears_cached_idle_easy_multi_reference Curl::Multi.autoclose = false easy = Curl::Easy.new($TEST_URL) easy.perform cached_multi = easy.multi assert_not_nil cached_multi assert_equal({}, cached_multi.requests) cached_multi.close assert_nil easy.multi ensure easy.multi = nil if defined?(easy) && easy Curl::Multi.autoclose = false end def test_multi_assignment_rejects_non_multi_objects easy = Curl::Easy.new($TEST_URL) error = assert_raise(TypeError) { easy.multi = Object.new } assert_match(/Curl::Multi/, error.message) assert_nil easy.multi end def test_perform_can_reuse_explicit_multi_when_autoclose_is_enabled Curl::Multi.autoclose = true easy = Curl::Easy.new(TestServlet.url) multi = Curl::Multi.new easy.multi = multi easy.perform assert_nil easy.multi assert_equal({}, multi.requests) easy.multi = multi easy.perform assert_nil easy.multi assert_equal({}, multi.requests) assert_equal "GET", easy.body_str ensure easy.multi = nil if defined?(easy) && easy multi.close if defined?(multi) && multi Curl::Multi.autoclose = false end def test_perform_reraises_on_body_exception_for_frozen_easy easy = Curl::Easy.new($TEST_URL) callback_error = Class.new(RuntimeError) easy.on_body { raise callback_error, "body blew up" } easy.freeze error = assert_raise(callback_error) { easy.perform } assert_equal "body blew up", error.message end def test_perform_preserves_implicit_multi_when_autoclose_is_disabled Curl::Multi.autoclose = false easy = Curl::Easy.new(TestServlet.url) easy.perform first_multi = easy.multi assert_not_nil first_multi assert_equal({}, first_multi.requests) easy.perform assert_same first_multi, easy.multi assert_equal "GET", easy.body_str ensure easy.multi = nil if defined?(easy) && easy first_multi.close if defined?(first_multi) && first_multi Curl::Multi.autoclose = false end def test_curlopt_stderr_fails_with_tempdir Tempfile.open('curb_test_curlopt_stderr') do |tempfile| easy = Curl::Easy.new(TestServlet.url) assert_raise(TypeError) do easy.setopt(Curl::CURLOPT_STDERR, tempfile) end end end def test_curlopt_stderr_fails_with_stringio stringio = StringIO.new easy = Curl::Easy.new(TestServlet.url) assert_raise(TypeError) do easy.setopt(Curl::CURLOPT_STDERR, stringio) end end def test_curlopt_stderr_fails_with_string string = String.new easy = Curl::Easy.new(TestServlet.url) assert_raise(TypeError) do easy.setopt(Curl::CURLOPT_STDERR, string) end end def test_exception begin Curl.get('NOT_FOUND_URL') rescue assert true rescue Exception assert false, "We should raise StandardError" end end def test_threads t = [] 5.times do t << Thread.new do 5.times do c = Curl.get($TEST_URL) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body) end end end t.each {|x| x.join } end def test_class_perform_01 assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body) end def test_class_perform_02 data = String.new assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL) { |curl| curl.on_body { |d| data << d; d.length } } assert_nil c.body_str assert_match(/^# DO NOT REMOVE THIS COMMENT/, data) end def test_class_perform_03 assert_raise(Curl::Err::CouldntReadError) { Curl::Easy.perform($TEST_URL + "nonexistent") } end def test_new_01 c = Curl::Easy.new assert_equal Curl::Easy, c.class assert_nil c.url assert_nil c.body_str assert_nil c.header_str end def test_new_02 c = Curl::Easy.new($TEST_URL) assert_equal $TEST_URL, c.url end def test_new_03 blk = lambda { |i| i.length } c = Curl::Easy.new do |curl| curl.on_body(&blk) end assert_nil c.url assert_equal blk, c.on_body # sets handler nil, returns old handler assert_equal nil, c.on_body end def test_new_04 blk = lambda { |i| i.length } c = Curl::Easy.new($TEST_URL) do |curl| curl.on_body(&blk) end assert_equal $TEST_URL, c.url assert_equal blk, c.on_body # sets handler nil, returns old handler assert_equal nil, c.on_body end class Foo < Curl::Easy ; end def test_new_05 # can use Curl::Easy as a base class c = Foo.new assert_equal Foo, c.class c.url = $TEST_URL c.perform assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end # test invalid use of new def test_new_06 Curl::Easy.new(TestServlet.url) do|curl| curl.http_post assert_equal "POST\n", curl.body_str end end def test_escape c = Curl::Easy.new assert_equal "one%20two", c.escape('one two') assert_equal "one%00two%20three", c.escape("one\000two three") end def test_unescape c = Curl::Easy.new assert_equal "one two", c.unescape('one%20two') # prior to 7.15.4 embedded nulls cannot be unescaped if Curl::VERNUM >= 0x070f04 assert_equal "one\000two three", c.unescape("one%00two%20three") end end def test_headers c = Curl::Easy.new($TEST_URL) assert_equal({}, c.headers) c.headers = "Expect:" assert_equal "Expect:", c.headers c.headers = ["Expect:", "User-Agent: myapp-0.0.0"] assert_equal ["Expect:", "User-Agent: myapp-0.0.0"], c.headers end def test_get_01 c = Curl::Easy.new($TEST_URL) assert_equal true, c.http_get assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str) end def test_get_02 data = String.new c = Curl::Easy.new($TEST_URL) do |curl| curl.on_body { |d| data << d; d.length } end assert_equal true, c.http_get assert_nil c.body_str assert_match(/^# DO NOT REMOVE THIS COMMENT/, data) end def test_get_03 c = Curl::Easy.new($TEST_URL + "nonexistent") assert_raise(Curl::Err::CouldntReadError) { c.http_get } assert_equal "", c.body_str assert_equal "", c.header_str end def test_last_effective_url_01 omit('Windows file URL semantics differ') if WINDOWS c = Curl::Easy.new($TEST_URL) assert_equal $TEST_URL, c.url assert_nil c.last_effective_url assert c.http_get assert_equal c.url, c.last_effective_url c.url = "file://some/new.url" assert_not_equal c.last_effective_url, c.url end def test_http_get_block curl = Curl::Easy.http_get(TestServlet.url) do|c| c.follow_location = true c.max_redirects = 3 end assert_equal curl.url, curl.last_effective_url assert_equal 'GET', curl.body_str end def test_local_port_01 c = Curl::Easy.new($TEST_URL) assert_nil c.local_port assert_nil c.local_port_range assert_nil c.proxy_port c.local_port = 88 assert_equal 88, c.local_port assert_nil c.local_port_range assert_nil c.proxy_port c.local_port = nil assert_nil c.local_port assert_nil c.local_port_range assert_nil c.proxy_port end def test_local_port_02 c = Curl::Easy.new($TEST_URL) assert_nil c.local_port assert_raise(ArgumentError) { c.local_port = 0 } assert_raise(ArgumentError) { c.local_port = 65536 } assert_raise(ArgumentError) { c.local_port = -1 } end def test_local_port_range_01 c = Curl::Easy.new($TEST_URL) assert_nil c.local_port_range assert_nil c.local_port assert_nil c.proxy_port c.local_port_range = 88 assert_equal 88, c.local_port_range assert_nil c.local_port assert_nil c.proxy_port c.local_port_range = nil assert_nil c.local_port_range assert_nil c.local_port assert_nil c.proxy_port end def test_local_port_range_02 c = Curl::Easy.new($TEST_URL) assert_nil c.local_port_range assert_raise(ArgumentError) { c.local_port_range = 0 } assert_raise(ArgumentError) { c.local_port_range = 65536 } assert_raise(ArgumentError) { c.local_port_range = -1 } end def test_proxy_url_01 c = Curl::Easy.new($TEST_URL) assert_equal $TEST_URL, c.url assert_nil c.proxy_url c.proxy_url = "http://some.proxy" assert_equal $TEST_URL, c.url assert_equal "http://some.proxy", c.proxy_url c.proxy_url = nil assert_equal $TEST_URL, c.url assert_nil c.proxy_url end def test_proxy_port_01 c = Curl::Easy.new($TEST_URL) assert_nil c.local_port assert_nil c.local_port_range assert_nil c.proxy_port c.proxy_port = 88 assert_equal 88, c.proxy_port assert_nil c.local_port assert_nil c.local_port_range c.proxy_port = nil assert_nil c.proxy_port assert_nil c.local_port assert_nil c.local_port_range end def test_proxy_port_02 c = Curl::Easy.new($TEST_URL) assert_nil c.proxy_port assert_raise(ArgumentError) { c.proxy_port = 0 } assert_raise(ArgumentError) { c.proxy_port = 65536 } assert_raise(ArgumentError) { c.proxy_port = -1 } end def test_proxy_type_01 c = Curl::Easy.new($TEST_URL) assert_nil c.proxy_type c.proxy_type = 3 assert_equal 3, c.proxy_type c.proxy_type = nil assert_nil c.proxy_type end def test_http_auth_types_01 c = Curl::Easy.new($TEST_URL) assert_nil c.http_auth_types c.http_auth_types = 3 assert_equal 3, c.http_auth_types c.http_auth_types = nil assert_nil c.http_auth_types end def test_proxy_auth_types_01 c = Curl::Easy.new($TEST_URL) assert_nil c.proxy_auth_types c.proxy_auth_types = 3 assert_equal 3, c.proxy_auth_types c.proxy_auth_types = nil assert_nil c.proxy_auth_types end def test_max_redirects_01 c = Curl::Easy.new($TEST_URL) assert_nil c.max_redirects c.max_redirects = 3 assert_equal 3, c.max_redirects c.max_redirects = nil assert_nil c.max_redirects end def test_timeout_with_floats c = Curl::Easy.new($TEST_URL) c.timeout = 1.5 assert_equal 1500, c.timeout_ms assert_equal 1.5, c.timeout end def test_timeout_with_negative c = Curl::Easy.new($TEST_URL) c.timeout = -1.5 assert_equal 0, c.timeout assert_equal 0, c.timeout_ms c.timeout = -4.8 assert_equal 0, c.timeout assert_equal 0, c.timeout_ms end def test_timeout_01 c = Curl::Easy.new($TEST_URL) assert_equal 0, c.timeout c.timeout = 3 assert_equal 3, c.timeout c.timeout = 0 assert_equal 0, c.timeout end def test_timeout_ms_01 c = Curl::Easy.new($TEST_URL) assert_equal 0, c.timeout_ms c.timeout_ms = 100 assert_equal 100, c.timeout_ms c.timeout_ms = nil assert_equal 0, c.timeout_ms end def test_timeout_ms_with_floats c = Curl::Easy.new($TEST_URL) c.timeout_ms = 55.5 assert_equal 55, c.timeout_ms assert_equal 0.055, c.timeout end def test_timeout_ms_with_negative c = Curl::Easy.new($TEST_URL) c.timeout_ms = -1.5 assert_equal 0, c.timeout assert_equal 0, c.timeout_ms c.timeout_ms = -4.8 assert_equal 0, c.timeout assert_equal 0, c.timeout_ms end def test_connect_timeout_01 c = Curl::Easy.new($TEST_URL) assert_nil c.connect_timeout c.connect_timeout = 3 assert_equal 3, c.connect_timeout c.connect_timeout = nil assert_nil c.connect_timeout end def test_connect_timeout_ms_01 c = Curl::Easy.new($TEST_URL) assert_nil c.connect_timeout_ms c.connect_timeout_ms = 100 assert_equal 100, c.connect_timeout_ms c.connect_timeout_ms = nil assert_nil c.connect_timeout_ms end def test_ftp_response_timeout_01 c = Curl::Easy.new($TEST_URL) assert_nil c.ftp_response_timeout c.ftp_response_timeout = 3 assert_equal 3, c.ftp_response_timeout c.ftp_response_timeout = nil assert_nil c.ftp_response_timeout end def test_dns_cache_timeout_01 c = Curl::Easy.new($TEST_URL) assert_equal 60, c.dns_cache_timeout c.dns_cache_timeout = nil assert_nil c.dns_cache_timeout c.dns_cache_timeout = 30 assert_equal 30, c.dns_cache_timeout end def test_low_speed_limit_01 c = Curl::Easy.new($TEST_URL) assert_nil c.low_speed_limit c.low_speed_limit = 3 assert_equal 3, c.low_speed_limit c.low_speed_limit = nil assert_nil c.low_speed_limit end def test_low_speed_time_01 c = Curl::Easy.new($TEST_URL) assert_nil c.low_speed_time c.low_speed_time = 3 assert_equal 3, c.low_speed_time c.low_speed_time = nil assert_nil c.low_speed_time end def test_on_body blk = lambda { |i| i.length } c = Curl::Easy.new c.on_body(&blk) assert_equal blk, c.on_body # sets handler nil, returns old handler assert_equal nil, c.on_body end def test_inspect_with_no_url c = Curl::Easy.new assert_equal '#', c.inspect end def test_inspect_with_short_url c = Curl::Easy.new('http://www.google.com/') assert_equal "#", c.inspect end def test_inspect_truncates_to_64_chars base_url = 'http://www.google.com/' truncated_url = base_url + 'x' * (64 - '#'.size - base_url.size) long_url = truncated_url + 'yyyy' c = Curl::Easy.new(long_url) assert_equal 64, c.inspect.size assert_equal "#", c.inspect end def test_on_header blk = lambda { |i| i.length } c = Curl::Easy.new c.on_header(&blk) assert_equal blk, c.on_header # sets handler nil, returns old handler assert_equal nil, c.on_header end def test_on_progress blk = lambda { |*args| true } c = Curl::Easy.new c.on_progress(&blk) assert_equal blk, c.on_progress # sets handler nil, returns old handler assert_equal nil, c.on_progress end def test_on_debug blk = lambda { |*args| true } c = Curl::Easy.new c.on_debug(&blk) assert_equal blk, c.on_debug # sets handler nil, returns old handler assert_equal nil, c.on_debug end def test_proxy_tunnel c = Curl::Easy.new assert !c.proxy_tunnel? assert c.proxy_tunnel = true assert c.proxy_tunnel? end def test_fetch_file_time c = Curl::Easy.new assert !c.fetch_file_time? assert c.fetch_file_time = true assert c.fetch_file_time? end def test_ssl_verify_peer c = Curl::Easy.new assert c.ssl_verify_peer? assert !c.ssl_verify_peer = false assert !c.ssl_verify_peer? end def test_ssl_verify_host c = Curl::Easy.new assert c.ssl_verify_host? c.ssl_verify_host = 0 c.ssl_verify_host = false assert !c.ssl_verify_host? end def test_header_in_body c = Curl::Easy.new assert !c.header_in_body? assert c.header_in_body = true assert c.header_in_body? end def test_use_netrc c = Curl::Easy.new assert !c.use_netrc? assert c.use_netrc = true assert c.use_netrc? end def test_follow_location c = Curl::Easy.new assert !c.follow_location? assert c.follow_location = true assert c.follow_location? end def test_unrestricted_auth c = Curl::Easy.new assert !c.unrestricted_auth? assert c.unrestricted_auth = true assert c.unrestricted_auth? end def test_multipart_form_post c = Curl::Easy.new assert !c.multipart_form_post? assert c.multipart_form_post = true assert c.multipart_form_post? end def test_ignore_content_length c = Curl::Easy.new assert !c.ignore_content_length? assert c.ignore_content_length = true assert c.ignore_content_length? end def test_resolve_mode c = Curl::Easy.new assert_equal :auto, c.resolve_mode c.resolve_mode = :ipv4 assert_equal :ipv4, c.resolve_mode c.resolve_mode = :ipv6 assert_equal :ipv6, c.resolve_mode assert_raises(ArgumentError) { c.resolve_mode = :bad } end def test_http_version_accessors c = Curl::Easy.new assert_equal Curl::HTTP_NONE, c.http_version c.http_version = Curl::HTTP_1_1 assert_equal Curl::HTTP_1_1, c.http_version if Curl.const_defined?(:HTTP_2TLS) c.http_version = Curl::HTTP_2TLS assert_equal Curl::HTTP_2TLS, c.http_version end if Curl.const_defined?(:HTTP_2_PRIOR_KNOWLEDGE) c.http_version = Curl::HTTP_2_PRIOR_KNOWLEDGE assert_equal Curl::HTTP_2_PRIOR_KNOWLEDGE, c.http_version end c.http_version = nil assert_equal Curl::HTTP_NONE, c.http_version end def test_enable_cookies c = Curl::Easy.new assert !c.enable_cookies? assert c.enable_cookies = true assert c.enable_cookies? end def test_cookies_option c = Curl::Easy.new assert_nil c.cookies assert_equal "name1=content1; name2=content2;", c.cookies = "name1=content1; name2=content2;" assert_equal "name1=content1; name2=content2;", c.cookies end def test_cookiefile c = Curl::Easy.new assert_nil c.cookiefile assert_equal "some.file", c.cookiefile = "some.file" assert_equal "some.file", c.cookiefile end def test_cookiejar c = Curl::Easy.new assert_nil c.cookiejar assert_equal "some.file", c.cookiejar = "some.file" assert_equal "some.file", c.cookiejar end def test_cookielist c = Curl::Easy.new TestServlet.url c.enable_cookies = true c.post_body = URI.encode_www_form('c' => 'somename=somevalue') assert_nil c.cookielist c.perform assert_match(/somevalue/, c.cookielist.join('')) end def test_on_success curl = Curl::Easy.new($TEST_URL) on_success_called = false curl.on_success {|c| on_success_called = true assert_not_nil c.body assert_match(/Content-Length: /, c.head) assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body) } curl.perform assert on_success_called, "Success handler not called" end def test_on_success_with_on_failure curl = Curl::Easy.new(TestServlet.url + '/error') on_failure_called = false curl.on_success {|c| } # make sure we get the failure call even though this handler is defined curl.on_failure {|c,code| on_failure_called = true } curl.perform assert_equal 500, curl.response_code assert on_failure_called, "Failure handler not called" end def test_on_success_with_on_missing curl = Curl::Easy.new(TestServlet.url + '/not_here') on_missing_called = false curl.on_success {|c| } # make sure we get the missing call even though this handler is defined curl.on_missing {|c,code| on_missing_called = true } curl.perform assert_equal 404, curl.response_code assert on_missing_called, "Missing handler not called" end def test_on_success_with_on_redirect curl = Curl::Easy.new(TestServlet.url + '/redirect') on_redirect_called = false curl.on_success {|c| } # make sure we get the redirect call even though this handler is defined curl.on_redirect {|c,code| on_redirect_called = true } curl.perform assert_equal 302, curl.response_code assert on_redirect_called, "Redirect handler not called" end def test_get_remote curl = Curl::Easy.new(TestServlet.url) curl.http_get assert_equal 'GET', curl.body_str end def test_remote curl = Curl::Easy.new(TestServlet.url) curl.http_post([Curl::PostField.content('document_id', 5)]) assert_equal "POST\ndocument_id=5", curl.unescape(curl.body_str) curl.http_put([Curl::PostField.content('document_id', 5)]) assert_equal "PUT\ndocument_id=5", curl.unescape(curl.body_str) curl.http_patch([Curl::PostField.content('document_id', 5)]) assert_equal "PATCH\ndocument_id=5", curl.unescape(curl.body_str) end def test_remote_is_easy_handle # see: http://pastie.org/560852 and # http://groups.google.com/group/curb---ruby-libcurl-bindings/browse_thread/thread/216bb2d9b037f347?hl=en [:post, :patch, :put, :get, :head, :delete].each do |method| retries = 0 begin count = 0 m = "http_#{method}".to_sym Curl::Easy.send(m, TestServlet.url) do|c| count += 1 assert_equal Curl::Easy, c.class end assert_equal 1, count, "For request method: #{method.to_s.upcase}" rescue Curl::Err::HostResolutionError => e # travis-ci.org fails to resolve... try again? retries+=1 retry if retries < 3 raise e rescue ArgumentError => e assert false, "#{m} - #{e.message}" end end end # see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445 def test_multipart_array_remote curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true fields = [ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))), Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))) ] curl.http_post(fields) assert_match(/HTTP POST file upload/, curl.body_str) assert_match(/Content-Disposition: form-data/, curl.body_str) curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true fields = [ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))), Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))) ] curl.http_put(fields) assert_match(/HTTP POST file upload/, curl.body_str) assert_match(/Content-Disposition: form-data/, curl.body_str) curl.http_patch(fields) assert_match(/HTTP POST file upload/, curl.body_str) assert_match(/Content-Disposition: form-data/, curl.body_str) end def test_with_body_remote curl = Curl::Easy.new(TestServlet.url) curl.post_body = 'foo=bar&encoded%20string=val' curl.perform assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str assert_equal 'foo=bar&encoded%20string=val', curl.post_body curl = Curl::Easy.new(TestServlet.url) curl.put_data = 'foo=bar&encoded%20string=val' curl.perform assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str end def test_post_body_from_to_s_keeps_string_buffer_alive post_body = Object.new def post_body.to_s 'foo=bar&encoded%20string=val' end curl = Curl::Easy.new(TestServlet.url) curl.post_body = post_body assert_equal 'foo=bar&encoded%20string=val', curl.post_body GC.start GC.compact if GC.respond_to?(:compact) curl.perform assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str end def test_post_body_preserves_assigned_snapshot_when_original_string_is_mutated omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS) curl = Curl::Easy.new(TestServlet.url) post_body = String.new('alpha=1') curl.post_body = post_body post_body.replace('beta=2') assert_equal 'beta=2', post_body assert_equal 'alpha=1', curl.post_body curl.perform assert_equal "POST\nalpha=1", curl.body_str assert_equal 'alpha=1', curl.post_body end def test_setopt_postfields_keeps_string_buffer_alive curl = Curl::Easy.new(TestServlet.url) curl.set(Curl::CURLOPT_POSTFIELDS, 'foo=bar&encoded%20string=val') assert_equal 'foo=bar&encoded%20string=val', curl.post_body GC.start GC.compact if GC.respond_to?(:compact) curl.perform assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str end def test_setopt_postfields_preserves_assigned_snapshot_when_original_string_is_mutated omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS) curl = Curl::Easy.new(TestServlet.url) post_body = String.new('alpha=1') curl.set(Curl::CURLOPT_POSTFIELDS, post_body) post_body.replace('beta=2') assert_equal 'beta=2', post_body assert_equal 'alpha=1', curl.post_body curl.perform assert_equal "POST\nalpha=1", curl.body_str assert_equal 'alpha=1', curl.post_body end def test_setopt_postfields_nil_preserves_post_request curl = Curl::Easy.new(TestServlet.url) curl.set(Curl::CURLOPT_POST, true) curl.set(Curl::CURLOPT_POSTFIELDS, nil) curl.perform assert_nil curl.post_body assert_equal "POST\n", curl.body_str end def test_form_body_remote curl = Curl::Easy.new(TestServlet.url) curl.http_post('foo=bar', 'encoded%20string=val') assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str assert_equal 'foo=bar&encoded%20string=val', curl.post_body curl = Curl::Easy.new(TestServlet.url) curl.http_put('foo=bar', 'encoded%20string=val') assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str curl = Curl::Easy.new(TestServlet.url) curl.http_patch('foo=bar', 'encoded%20string=val') assert_equal "PATCH\nfoo=bar&encoded%20string=val", curl.body_str end def test_multipart_file_remote [:put, :post, :patch].each {|method| curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))) curl.send("http_#{method}", pf) assert_match(/HTTP POST file upload/, curl.body_str) assert_match(/Content-Disposition: form-data/, curl.body_str) } end def test_multipart_form_reuse_clears_previous_form curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true curl.http_post(Curl::PostField.content('document_id', '5')) assert_match(/document_id/, curl.body_str) curl.multipart_form_post = false curl.post_body = 'foo=bar' curl.http_post assert_equal "POST\nfoo=bar", curl.body_str end def test_multipart_patch_build_failure_restores_easy_request_state curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true field = Curl::PostField.content('document_id', '5') field.name = nil error = assert_raise(Curl::Err::InvalidPostFieldError) do # The first field allocates native multipart form state before the second field raises. curl.http_patch(Curl::PostField.content('keep', 'allocated'), field) end assert_match(/Cannot post unnamed field/, error.message) curl.multipart_form_post = false curl.perform assert_equal 'GET', curl.body_str end def test_delete_remote curl = Curl::Easy.new(TestServlet.url) curl.http_delete assert_equal 'DELETE', curl.body_str end def test_arbitrary_http_verb curl = Curl::Easy.new(TestServlet.url) curl.http('PURGE') assert_equal 'PURGE', curl.body_str end def test_head_remote curl = Curl::Easy.new(TestServlet.url) curl.http_head redirect = curl.header_str.match(/Location: (.*)/) assert_equal '', curl.body_str assert_match('/nonexistent', redirect[1]) end def test_head_accessor curl = Curl::Easy.new(TestServlet.url) curl.head = true curl.perform redirect = curl.header_str.match(/Location: (.*)/) assert_equal '', curl.body_str assert_match('/nonexistent', redirect[1]) curl.head = false curl.perform assert_equal 'GET', curl.body_str end def test_put_remote curl = Curl::Easy.new(TestServlet.url) curl.headers['Content-Type'] = 'application/json' assert curl.http_put("message") assert_match(/^PUT/, curl.body_str) assert_match(/message$/, curl.body_str) assert_match(/message$/, curl.body) assert_match(/application\/json/, curl.header_str) assert_match(/application\/json/, curl.head) end def test_put_data curl = Curl::Easy.new(TestServlet.url) curl.put_data = 'message' curl.perform assert_match(/^PUT/, curl.body_str) assert_match(/message$/, curl.body_str) end def test_put_data_from_to_s_object curl = Curl::Easy.new(TestServlet.url) curl.put_data = :message curl.perform assert_equal "PUT\nmessage", curl.body_str end def test_put_data_read_exception_sets_result_and_releases_callback_state error_class = Class.new(StandardError) reader_class = Class.new do define_method(:read) do |_len| raise error_class, 'upload read failed' end def seek(_offset, _whence = SEEK_SET) 0 end def stat Struct.new(:size).new(1) end end curl = Curl::Easy.new(TestServlet.url) curl.put_data = reader_class.new error = assert_raise(error_class) { curl.perform } assert_equal 'upload read failed', error.message assert_not_equal 0, curl.last_result curl.url = TestServlet.url curl.http_get assert_equal 'GET', curl.body_str end # https://github.com/taf2/curb/issues/101 def test_put_data_null_bytes curl = Curl::Easy.new(TestServlet.url) curl.put_data = "a\0b" curl.perform assert_match(/^PUT/, curl.body_str) assert_match("a\0b", curl.body_str) end def test_put_nil_data_no_crash curl = Curl::Easy.new(TestServlet.url) curl.put_data = nil curl.perform end def test_put_remote_file curl = Curl::Easy.new(TestServlet.url) File.open(__FILE__,'rb') do|f| assert curl.http_put(f) end assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '') end def test_put_class_method count = 0 curl = Curl::Easy.http_put(TestServlet.url,File.open(__FILE__,'rb')) do|c| count += 1 assert_equal Curl::Easy, c.class end assert_equal 1, count assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '') end # Generate a self-signed cert with # openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 \ # -keyout tests/cert.pem -out tests/cert.pem def test_cert curl = Curl::Easy.new(TestServlet.url) curl.cert= File.join(File.dirname(__FILE__),"cert.pem") assert_match(/cert.pem$/,curl.cert) end def test_cert_with_password curl = Curl::Easy.new(TestServlet.url) path = File.join(File.dirname(__FILE__),"cert.pem") curl.certpassword = 'password' curl.cert = path assert_match(/cert.pem$/,curl.cert) end def test_cert_type curl = Curl::Easy.new(TestServlet.url) curl.certtype= "DER" assert_equal "DER", curl.certtype end def test_default_certtype curl = Curl::Easy.new(TestServlet.url) assert_nil curl.certtype curl.certtype = "PEM" assert_equal "PEM", curl.certtype end # Generate a CA cert with instructions at # http://technocage.com/~caskey/openssl/ def test_ca_cert curl = Curl::Easy.new(TestServlet.url) curl.cacert= File.join(File.dirname(__FILE__),"cacert.pem") assert_match(/cacert.pem$/, curl.cacert) end def test_user_agent curl = Curl::Easy.new(TestServlet.url) curl.useragent= "Curb-Easy/Ruby" assert_equal "Curb-Easy/Ruby",curl.useragent end def test_username_password curl = Curl::Easy.new(TestServlet.url) curl.username = "foo" curl.password = "bar" if !curl.username.nil? assert_equal "foo", curl.username assert_equal "bar", curl.password else curl.userpwd = "foo:bar" end curl.http_auth_types = :basic #curl.verbose = true curl.perform assert_equal 'Basic Zm9vOmJhcg==', $auth_header $auth_header = nil # curl checks the auth type supported by the server, so we have to create a # new easy handle if we're going to change the auth type... if WINDOWS # On Windows, libcurl often uses SSPI for NTLM which yields a different # header value and encoding; skip the NTLM-specific assertion. return end # curl 8.20.0 disables NTLM by default and plans to remove it in # September 2026; libcurl silently downgrades to Basic when NTLM is # unavailable, so skip the NTLM-specific assertion in that case. return unless Curl.ntlm? curl = Curl::Easy.new(TestServlet.url) curl.username = "foo" curl.password = "bar" if curl.username.nil? curl.userpwd = "foo:bar" end curl.http_auth_types = :ntlm curl.perform assert_equal 'NTLM TlRMTVNTUAABAAAABoIIAAAAAAAAAAAAAAAAAAAAAAA=', $auth_header end def test_primary_ip curl = Curl::Easy.new(TestServlet.url) if curl.respond_to?(:primary_ip) curl.perform assert_equal '127.0.0.1', curl.primary_ip end end def test_post_streaming readme = File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')) pf = Curl::PostField.file("filename", readme) easy = Curl::Easy.new easy.url = TestServlet.url easy.multipart_form_post = true easy.http_post(pf) assert_not_equal(0,easy.body.size) assert_equal(Digest::MD5.hexdigest(easy.body), Digest::MD5.hexdigest(File.binread(readme))) end def test_easy_close easy = Curl::Easy.new easy.close easy.url = TestServlet.url easy.http_get end def test_easy_close_restores_error_buffer easy = Curl::Easy.new('http://127.0.0.1:1') assert_raise(Curl::Err::ConnectionFailedError) { easy.perform } assert_not_nil easy.last_error easy.close easy.url = 'http://127.0.0.1:1' assert_raise(Curl::Err::ConnectionFailedError) { easy.perform } assert_not_nil easy.last_error end def test_easy_reset easy = Curl::Easy.new easy.url = TestServlet.url + "?query=foo" easy.http_get settings = easy.reset assert settings.key?(:url) assert settings.key?(:body_data) assert settings.key?(:header_data) easy.url = TestServlet.url easy.http_get end def test_last_result_initialization # Test for issue #463 - ensure last_result is properly initialized to 0 easy = Curl::Easy.new assert_equal 0, easy.last_result, "last_result should be initialized to 0 for new instance" # Test that reset also sets last_result to 0 easy.url = TestServlet.url easy.http_get easy.reset assert_equal 0, easy.last_result, "last_result should be reset to 0 after calling reset" end def test_easy_use_http_versions easy = Curl::Easy.new easy.url = TestServlet.url + "?query=foo" #puts "http none: #{Curl::HTTP_NONE.inspect}" #puts "http1.0: #{Curl::HTTP_1_0.inspect}" #puts "http1.1: #{Curl::HTTP_1_1.inspect}" easy.version = Curl::HTTP_1_1 #easy.verbose = true easy.http_get end def test_easy_http_verbs curl = Curl::Easy.new(TestServlet.url) curl.http_delete assert_equal 'DELETE', curl.body_str curl.http_get assert_equal 'GET', curl.body_str curl.http_post assert_equal "POST\n", curl.body_str curl.http('PURGE') assert_equal 'PURGE', curl.body_str curl.http_put('hello') assert_equal "PUT\nhello", curl.body_str curl.http('COPY') assert_equal 'COPY', curl.body_str curl.http_patch assert_equal "PATCH\n", curl.body_str curl.http_put assert_equal "PUT\n", curl.body_str end def test_easy_http_verbs_must_respond_to_str # issue http://github.com/taf2/curb/issues/45 assert_nothing_raised do c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(:get) end assert_raise RuntimeError do c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(FooNoToS.new) end end # http://github.com/taf2/curb/issues/#issue/33 def test_easy_http_verbs_with_errors curl = Curl::Easy.new("http://127.0.0.1:9012/") # test will fail if http server on port 9012 assert_raise Curl::Err::ConnectionFailedError do curl.http_delete end curl.url = TestServlet.url curl.http_get assert_equal 'GET', curl.body_str end def test_easy_can_put_with_content_length curl = Curl::Easy.new(TestServlet.url) rd, wr = IO.pipe buf = (("hello")* (1000 / 5)) producer = Thread.new do 5.times do wr << buf sleep 0.1 # act as a slow producer end end consumer = Thread.new do #curl.verbose = true curl.headers['Content-Length'] = buf.size * 5 curl.headers['User-Agent'] = "Something else" curl.headers['Content-Type'] = "text/javascript" curl.headers['Date'] = Time.now.httpdate curl.headers['Host'] = 's3.amazonaws.com' curl.headers['Accept'] = '*/*' curl.headers['Authorization'] = 'Foo Bar Biz Baz' curl.http_put(rd) assert_match(/^PUT/, curl.body_str) assert_match(/hello$/, curl.body_str) curl.header_str curl.body_str end producer.join wr.close consumer.join end def test_get_set_multi_on_easy easy = Curl::Easy.new assert_nil easy.multi multi = Curl::Multi.new easy.multi = multi assert_not_nil easy.multi assert_equal multi, easy.multi end def test_raise_on_progress c = Curl::Easy.new($TEST_URL) c.on_progress {|w,x,y,z| raise "error" } c.perform rescue => e assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s c.close end def test_raise_on_success c = Curl::Easy.new($TEST_URL) c.on_success {|x| raise "error" } c.perform rescue Curl::Err::AbortedByCallbackError => e assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s c.close end def test_raise_on_debug c = Curl::Easy.new($TEST_URL) c.on_debug { raise "error" } c.perform assert true, "raise in on debug has no effect" end def test_status_codes curl = Curl::Easy.new(TestServlet.url) curl.perform assert_equal '200 OK', curl.status end def test_close_in_on_callbacks curl = Curl::Easy.new(TestServlet.url) curl.on_body {|d| curl.close; d.size } assert_raises RuntimeError do curl.perform end end def test_close_in_on_progress_is_blocked curl = Curl::Easy.new(TestServlet.url) did_raise = false curl.on_progress do |_dltotal, _dlnow, _ultotal, _ulnow| unless did_raise begin curl.close rescue RuntimeError did_raise = true end end true end curl.perform assert did_raise end def test_close_in_on_debug_is_blocked curl = Curl::Easy.new(TestServlet.url) did_raise = false curl.on_debug do |_type, _data| unless did_raise begin curl.close rescue RuntimeError did_raise = true end end end curl.perform assert did_raise end def test_close_in_upload_read_is_blocked curl = Curl::Easy.new(TestServlet.url) reader_class = Class.new do attr_reader :close_blocked def initialize(curl) @curl = curl @done = false @close_blocked = false end def read(_len) return nil if @done @done = true begin @curl.close rescue RuntimeError @close_blocked = true end 'hello' end def seek(_offset, _whence = SEEK_SET) 0 end def stat Struct.new(:size).new(5) end end reader = reader_class.new(curl) curl.http_put(reader) assert reader.close_blocked assert_equal "PUT\nhello", curl.body_str end def test_set_unsupported_options curl = Curl::Easy.new assert_raises TypeError do curl.set(99999, 1) end end include TestServerMethods def setup server_setup end end curb-1.3.5/tests/bug_follow_redirect_288.rb0000644000004100000410000000454615203731642020627 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugFollowRedirect288 < Test::Unit::TestCase include BugTestServerSetupTeardown def setup @port = unused_local_port super @server.mount_proc("/redirect_to_test") do|req,res| res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, "/test") end end def test_follow_redirect_with_no_redirect c = Curl::Easy.new("http://127.0.0.1:#{@port}/test") did_call_redirect = false c.on_redirect do|x| did_call_redirect = true end c.perform assert !did_call_redirect, "should reach this point redirect should not have been called" c = Curl::Easy.new("http://127.0.0.1:#{@port}/test") did_call_redirect = false c.on_redirect do|x| did_call_redirect = true end c.follow_location = true c.perform assert_equal 0, c.redirect_count assert !did_call_redirect, "should reach this point redirect should not have been called" c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test") did_call_redirect = false c.on_redirect do|x| did_call_redirect = true end c.perform assert_equal 307, c.response_code assert did_call_redirect, "we should have called on_redirect" c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test") did_call_redirect = false c.follow_location = true # NOTE: while this API is not supported by libcurl e.g. there is no redirect function callback in libcurl we could # add support in ruby for this by executing this callback if redirect_count is greater than 0 at the end of a request in curb_multi.c c.on_redirect do|x| did_call_redirect = true end c.perform assert_equal 1, c.redirect_count assert_equal 200, c.response_code assert did_call_redirect, "we should have called on_redirect" c.url = "http://127.0.0.1:#{@port}/test" c.perform assert_equal 0, c.redirect_count assert_equal 200, c.response_code puts "checking for raise support" did_raise = false begin c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test") did_call_redirect = false c.on_redirect do|x| raise "raise" did_call_redirect = true end c.perform rescue => e did_raise = true end assert_equal 307, c.response_code assert did_raise end end curb-1.3.5/tests/signals.rb0000644000004100000410000000141015203731642015631 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) # This test suite requires the timeout server to be running # See tests/timeout.rb for more info about the timeout server class TestCurbSignals < Test::Unit::TestCase # Testcase for https://github.com/taf2/curb/issues/117 def test_continue_after_signal trap("SIGUSR1") { } curl = Curl::Easy.new(wait_url(2)) pid = $$ Thread.new do sleep 1 Process.kill("SIGUSR1", pid) end assert_equal true, curl.http_get end private def wait_url(time) "#{server_base}/wait/#{time}" end def serve_url(chunk_size, time, count) "#{server_base}/serve/#{chunk_size}/every/#{time}/for/#{count}" end def server_base 'http://127.0.0.1:9128' end end curb-1.3.5/tests/tc_curl_easy_request_target.rb0000644000004100000410000000214015203731642021764 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlEasyRequestTarget < Test::Unit::TestCase include TestServerMethods def setup server_setup end def test_request_target_absolute_form unless Curl.const_defined?(:CURLOPT_REQUEST_TARGET) omit('libcurl lacks CURLOPT_REQUEST_TARGET support') end tmp = Tempfile.new('curb_test_request_target') path = tmp.path fd = IO.sysopen(path, 'w') io = IO.new(fd, 'w') io.sync = true easy = Curl::Easy.new(TestServlet.url) easy.verbose = true easy.setopt(Curl::CURLOPT_STDERR, io) # Force absolute-form request target, different from the URL host easy.request_target = "http://localhost:#{TestServlet.port}#{TestServlet.path}" easy.headers = { 'Host' => "example.com" } easy.perform io.flush io.close output = File.read(path) assert_match(/GET\s+http:\/\/localhost:#{TestServlet.port}#{Regexp.escape(TestServlet.path)}\s+HTTP\/1\.1/, output) assert_match(/Host:\s+example\.com/, output) ensure tmp.close! if defined?(tmp) && tmp end end curb-1.3.5/tests/tc_test_server_methods.rb0000644000004100000410000000525315203731642020760 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) require 'tmpdir' class TestServerMethodsWaitForServerReady < Test::Unit::TestCase include TestServerMethods def server_startup_timeout 0.05 end def unused_port server = TCPServer.new('127.0.0.1', 0) port = server.addr[1] server.close port end def test_wait_for_server_ready_returns_false_when_thread_dies thread = Thread.new {} thread.join started = Process.clock_gettime(Process::CLOCK_MONOTONIC) assert_equal false, wait_for_server_ready(unused_port, thread: thread) elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started assert_operator elapsed, :<, 0.5 end def test_wait_for_server_ready_times_out_when_server_never_starts error = assert_raise(RuntimeError) do wait_for_server_ready(unused_port) end assert_match(/Failed to startup test server/, error.message) end end class TestServerMethodsLockHandling < Test::Unit::TestCase include TestServerMethods def setup @lock_dir = Dir.mktmpdir('curb-server-lock') @__port = unused_port end def teardown super FileUtils.remove_entry(@lock_dir) if @lock_dir && File.exist?(@lock_dir) end def server_startup_timeout 0.05 end def locked_file File.join(@lock_dir, "server_lock-#{@__port}") end def unused_port server = TCPServer.new('127.0.0.1', 0) port = server.addr[1] server.close port end def test_clear_stale_server_lock_removes_legacy_lock_when_server_is_down File.write(locked_file, 'locked') clear_stale_server_lock(@__port) assert_equal false, File.exist?(locked_file) end def test_clear_stale_server_lock_removes_dead_pid_lock_when_server_is_down File.write(locked_file, "999999\n") clear_stale_server_lock(@__port) assert_equal false, File.exist?(locked_file) end def test_clear_stale_server_lock_keeps_live_pid_lock_during_startup_window write_server_lock(Process.pid) clear_stale_server_lock(@__port) assert_equal true, File.exist?(locked_file) end def test_clear_stale_server_lock_removes_non_responding_live_pid_lock_after_timeout write_server_lock(Process.pid) stale_time = Time.now - 1 File.utime(stale_time, stale_time, locked_file) clear_stale_server_lock(@__port) assert_equal false, File.exist?(locked_file) end def test_stop_test_server_removes_owned_lock_and_shuts_listener_down server_setup(@__port) assert_equal true, File.exist?(locked_file) assert_equal true, server_responding?(@__port) stop_test_server assert_equal false, File.exist?(locked_file) assert_equal false, server_responding?(@__port) end end curb-1.3.5/tests/bug_issue_spnego.rb0000644000004100000410000000320615203731642017536 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugIssueSpnego < Test::Unit::TestCase def test_spnego_detection_works # The fix for issue #227 ensures that Curl.spnego? checks for both # CURL_VERSION_SPNEGO (newer libcurl) and CURL_VERSION_GSSNEGOTIATE (older libcurl) # # We can't test that it returns true because that depends on how libcurl # was compiled on the system. We can only test that: # 1. The method exists and works # 2. If CURLAUTH_GSSNEGOTIATE is available, we can use it # Check if CURLAUTH_GSSNEGOTIATE is available if Curl.const_defined?(:CURLAUTH_GSSNEGOTIATE) && Curl.const_get(:CURLAUTH_GSSNEGOTIATE) != 0 # Test that we can use GSSNEGOTIATE auth type c = Curl::Easy.new('http://example.com') assert_nothing_raised do c.http_auth_types = Curl::CURLAUTH_GSSNEGOTIATE end # The fix ensures spnego? won't incorrectly return false when # older libcurl versions have GSSNEGOTIATE support # (The actual return value depends on system libcurl compilation) end # The important fix is that the method now checks both constants # This test passes if no exceptions are raised assert true, "SPNEGO detection is working correctly" end def test_spnego_method_exists # The method should always exist assert Curl.respond_to?(:spnego?), "Curl should respond to spnego?" end def test_spnego_returns_boolean # The method should return a boolean result = Curl.spnego? assert [true, false].include?(result), "Curl.spnego? should return true or false, got #{result.inspect}" end endcurb-1.3.5/tests/bug_instance_post_differs_from_class_post.rb0000644000004100000410000000266015203731642024666 0ustar www-datawww-data# From Vlad Jebelev: # # - Second thing - I think you just probably didn't have the time to update # instance methods yet but when I POST with a reusal of a previous curl # instance, it doesnt' work for me, e.g. when I create a curl previously and # then issue: # # c.http_post(login_url, *fields) # # instead of: # # c = Curl::Easy.http_post(login_url, *fields) do |curl| # ... # end # # then the result I am getting is quite different. # # ================ # # Update: # # It seems that class httpost is incorrectly passing arguments down to # instance httppost. This bug is intermittent, but results in an # exception from the first post when it occurs. # require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase def test_bug 5.times do |i| puts "Test ##{i}" do_test sleep 2 end end def do_test c = Curl::Easy.http_post('https://www.google.com/accounts/ServiceLoginAuth', Curl::PostField.content('ltmpl','m_blanco')) body_c, header_c = c.body_str, c.header_str sleep 2 c.http_post('https://www.google.com/accounts/ServiceLoginAuth', Curl::PostField.content('ltmpl','m_blanco')) body_i, header_i = c.body, c.head # timestamps will differ, just check first bit. We wont get here if # the bug bites anyway... assert_equal header_c[0..50], header_i[0..50] end end curb-1.3.5/tests/bug_multi_segfault.rb0000644000004100000410000000074715203731642020066 0ustar www-datawww-data# From safis http://github.com/taf2/curb/issues#issue/5 # irb: require 'curb' # irb: multi = Curl::Multi.new # irb: exit #
:47140: [BUG] Bus Error require File.expand_path(File.join(File.dirname(__FILE__), 'helper')) $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','ext')) $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib')) require 'curb' class BugMultiSegfault < Test::Unit::TestCase def test_bug multi = Curl::Multi.new end end curb-1.3.5/tests/timeout.rb0000644000004100000410000000633515203731642015672 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) # Run server with: ruby -rubygems timeout_server.rb -p 9128 # Note that curl requires all timeouts to be integers - # curl_easy_setopt does not have a provision for floating-point values class TestCurbTimeouts < Test::Unit::TestCase def test_no_timeout_by_default curl = Curl::Easy.new(wait_url(2)) start = Time.now assert_equal true, curl.http_get elapsed = Time.now - start assert elapsed > 2 end def test_overall_timeout_on_dead_transfer curl = Curl::Easy.new(wait_url(2)) curl.timeout = 1 exception = assert_raise(Curl::Err::TimeoutError) do curl.http_get end assert_match( /^Timeout was reached: Operation timed out after/, exception.message ) end def test_overall_timeout_ms_on_dead_transfer curl = Curl::Easy.new(wait_url(2)) curl.timeout_ms = 1000 assert_raise(Curl::Err::TimeoutError) do curl.http_get end end def test_clearing_timeout curl = Curl::Easy.new(wait_url(2)) curl.timeout = 1 curl.timeout = nil start = Time.now assert_equal true, curl.http_get elapsed = Time.now - start assert elapsed > 2 end def test_overall_timeout_on_slow_transfer curl = Curl::Easy.new(serve_url(100, 2, 3)) curl.timeout = 1 # transfer is aborted despite data being exchanged exception = assert_raise(Curl::Err::TimeoutError) do curl.http_get end assert_match( /^Timeout was reached: Operation timed out after/, exception.message ) end def test_low_speed_time_on_slow_transfer curl = Curl::Easy.new(serve_url(100, 1, 3)) curl.low_speed_time = 2 # use default low_speed_limit of 1 assert_equal true, curl.http_get end def test_low_speed_time_on_very_slow_transfer # send data slower than required curl = Curl::Easy.new(serve_url(10, 2, 3)) curl.low_speed_time = 1 # XXX for some reason this test fails if low speed limit is not specified curl.low_speed_limit = 1 # use default low_speed_limit of 1 exception = assert_raise(Curl::Err::TimeoutError) do curl.http_get end assert_match( /^Timeout was reached: Operation too slow/, exception.message ) end def test_low_speed_limit_on_slow_transfer curl = Curl::Easy.new(serve_url(10, 1, 3)) curl.low_speed_time = 2 curl.low_speed_limit = 1000 exception = assert_raise(Curl::Err::TimeoutError) do curl.http_get end assert_match( /^Timeout was reached: Operation too slow/, exception.message ) end def test_clearing_low_speed_time curl = Curl::Easy.new(serve_url(100, 2, 3)) curl.low_speed_time = 1 curl.low_speed_time = nil assert_equal true, curl.http_get end def test_clearing_low_speed_limit curl = Curl::Easy.new(serve_url(10, 1, 3)) curl.low_speed_time = 2 curl.low_speed_limit = 1000 curl.low_speed_limit = nil assert_equal true, curl.http_get end private def wait_url(time) "#{server_base}/wait/#{time}" end def serve_url(chunk_size, time, count) "#{server_base}/serve/#{chunk_size}/every/#{time}/for/#{count}" end def server_base 'http://127.0.0.1:9128' end end curb-1.3.5/tests/tc_curl_postfield.rb0000644000004100000410000002336115203731642017706 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlPostfield < Test::Unit::TestCase def test_private_new assert_raise(NoMethodError) { Curl::PostField.new } end def test_new_content_01 pf = Curl::PostField.content('foo', 'bar') assert_equal 'foo', pf.name assert_equal 'bar', pf.content assert_nil pf.content_type assert_nil pf.local_file assert_nil pf.remote_file assert_nil pf.set_content_proc end def test_new_content_02 pf = Curl::PostField.content('foo', 'bar', 'text/html') assert_equal 'foo', pf.name assert_equal 'bar', pf.content assert_equal 'text/html', pf.content_type assert_nil pf.local_file assert_nil pf.remote_file assert_nil pf.set_content_proc end def test_new_content_03 l = lambda { |field| "never gets run" } pf = Curl::PostField.content('foo', &l) assert_equal 'foo', pf.name assert_nil pf.content assert_nil pf.content_type assert_nil pf.local_file assert_nil pf.remote_file # N.B. This doesn't just get the proc, but also removes it. assert_equal l, pf.set_content_proc end def test_new_content_04 l = lambda { |field| "never gets run" } pf = Curl::PostField.content('foo', 'text/html', &l) assert_equal 'foo', pf.name assert_nil pf.content assert_equal 'text/html', pf.content_type assert_nil pf.local_file assert_nil pf.remote_file # N.B. This doesn't just get the proc, but also removes it. assert_equal l, pf.set_content_proc end def test_new_file_01 pf = Curl::PostField.file('foo', 'localname') pf.content_type = 'text/super' assert_equal 'foo', pf.name assert_equal 'localname', pf.local_file assert_equal 'localname', pf.remote_file assert_nothing_raised { pf.to_s } assert_equal 'text/super', pf.content_type assert_nil pf.content assert_nil pf.set_content_proc end def test_new_file_02 pf = Curl::PostField.file('foo', 'localname', 'remotename') assert_equal 'foo', pf.name assert_equal 'localname', pf.local_file assert_equal 'remotename', pf.remote_file assert_nil pf.content_type assert_nil pf.content assert_nil pf.set_content_proc end def test_new_file_03 l = lambda { |field| "never gets run" } pf = Curl::PostField.file('foo', 'remotename', &l) assert_equal 'foo', pf.name assert_equal 'remotename', pf.remote_file assert_nil pf.local_file assert_nil pf.content_type assert_nil pf.content # N.B. This doesn't just get the proc, but also removes it. assert_equal l, pf.set_content_proc end def test_new_file_04 assert_raise(ArgumentError) do # no local name, no block Curl::PostField.file('foo') end assert_raise(ArgumentError) do # no remote name with block Curl::PostField.file('foo') { |field| "never runs" } end end def test_new_file_05 # local gets ignored when supplying a block, but remote # is still set up properly. l = lambda { |field| "never runs" } pf = Curl::PostField.file('foo', 'local', 'remote', &l) assert_equal 'foo', pf.name assert_equal 'remote', pf.remote_file assert_nil pf.local_file assert_nil pf.content_type assert_nil pf.content assert_equal l, pf.set_content_proc end def test_to_s_01 pf = Curl::PostField.content('foo', 'bar') assert_equal "foo=bar", pf.to_s end def test_to_s_02 pf = Curl::PostField.content('foo', 'bar ton') assert_equal "foo=bar%20ton", pf.to_s end def test_to_s_03 pf = Curl::PostField.content('foo') { |field| field.name.upcase + "BAR" } assert_equal "foo=FOOBAR", pf.to_s end def test_content_proc_survives_gc pf = postfield_with_only_native_proc_reference 10.times do GC.start(full_mark: true, immediate_sweep: true) GC.compact if GC.respond_to?(:compact) end assert_equal "foo=FOOBAR", pf.to_s end def test_to_s_04 pf = Curl::PostField.file('foo.file', 'bar.file') assert_nothing_raised { pf.to_s } #assert_raise(Curl::Err::InvalidPostFieldError) { pf.to_s } end def postfield_with_only_native_proc_reference Curl::PostField.content('foo') { |field| field.name.upcase + "BAR" } end end class TestCurbCurlPostfieldNativeCoverage < Test::Unit::TestCase def test_attribute_writers_round_trip pf = Curl::PostField.content('foo', 'bar') assert_equal 'renamed', pf.name = 'renamed' assert_equal 'payload', pf.content = 'payload' assert_equal 'text/plain', pf.content_type = 'text/plain' assert_equal 'local.txt', pf.local_file = 'local.txt' assert_equal 'remote.txt', pf.remote_file = 'remote.txt' assert_equal 'renamed', pf.name assert_equal 'payload', pf.content assert_equal 'text/plain', pf.content_type assert_equal 'local.txt', pf.local_file assert_equal 'remote.txt', pf.remote_file end def test_to_s_accepts_non_string_name_via_to_s name_like = Object.new def name_like.to_s 'fancy name' end pf = Curl::PostField.content(name_like, 'value') assert_equal 'fancy%20name=value', pf.to_s end def test_to_s_uses_remote_file_when_local_file_is_missing pf = Curl::PostField.file('upload', 'local.txt', 'remote.txt') pf.local_file = nil assert_equal 'upload=remote.txt', pf.to_s end def test_to_s_rejects_name_without_to_s name_like = Class.new do undef to_s end.new pf = Curl::PostField.content(name_like, 'value') error = assert_raise(Curl::Err::InvalidPostFieldError) { pf.to_s } assert_match(/Cannot convert unnamed field to string/, error.message) end def test_to_s_rejects_content_without_to_s content_like = Class.new do undef to_s end.new pf = Curl::PostField.content('name', content_like) error = assert_raise(RuntimeError) { pf.to_s } assert_match(/does not respond_to to_s/, error.message) end def test_multipart_rejects_unnamed_field curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true pf = Curl::PostField.content('name', 'value') pf.name = nil error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) } assert_match(/Cannot post unnamed field/, error.message) end def test_multipart_rejects_content_field_without_data curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true pf = Curl::PostField.content('name', 'value') pf.content = nil error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) } assert_match(/Cannot post content field with no data/, error.message) end def test_multipart_rejects_file_field_without_filename curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true pf = Curl::PostField.file('upload', 'remote.txt') { 'payload' } pf.local_file = 'dummy.txt' pf.remote_file = nil error = assert_raise(Curl::Err::InvalidPostFieldError) { curl.http_post(pf) } assert_match(/Cannot post file upload field with no filename/, error.message) end end class TestCurbCurlPostfieldMultipartCoverage < Test::Unit::TestCase include TestServerMethods def setup server_setup end def test_multipart_content_variants_include_dynamic_and_typed_fields curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true proc_without_type = Curl::PostField.content('proc_without_type') { 'alpha' } proc_with_type = Curl::PostField.content('proc_with_type', 'text/plain') { 'beta' } direct_with_type = Curl::PostField.content('direct_with_type', 'gamma', 'text/plain') curl.http_post([proc_without_type, proc_with_type, direct_with_type]) body = curl.body_str assert_match(/name="proc_without_type"/, body) assert_match(/alpha/, body) assert_match(/name="proc_with_type"/, body) assert_match(/beta/, body) assert_match(/name="direct_with_type"/, body) assert_match(/gamma/, body) assert_match(/Content-Type: text\/plain/, body) end def test_multipart_file_variants_include_buffered_and_local_uploads readme = File.expand_path(File.join(File.dirname(__FILE__), '..', 'README.md')) proc_without_type = Curl::PostField.file('proc_without_type', 'proc_without_type.txt') { 'alpha' } proc_with_type = Curl::PostField.file('proc_with_type', 'proc_with_type.txt') { 'beta' } proc_with_type.content_type = 'text/plain' direct_without_type = Curl::PostField.file('direct_without_type', 'ignored.txt', 'direct_without_type.txt') direct_without_type.content = 'gamma' direct_without_type.local_file = nil direct_with_type = Curl::PostField.file('direct_with_type', 'ignored.txt', 'direct_with_type.txt') direct_with_type.content = 'delta' direct_with_type.local_file = nil direct_with_type.content_type = 'text/plain' local_with_type = Curl::PostField.file('local_with_type', readme) local_with_type.content_type = 'text/plain' curl = Curl::Easy.new(TestServlet.url) curl.multipart_form_post = true curl.http_post([proc_without_type, proc_with_type, direct_without_type, direct_with_type, local_with_type]) body = curl.body_str assert_match(/name="proc_without_type"/, body) assert_match(/filename="proc_without_type.txt"/, body) assert_match(/alpha/, body) assert_match(/name="proc_with_type"/, body) assert_match(/filename="proc_with_type.txt"/, body) assert_match(/beta/, body) assert_match(/name="direct_without_type"/, body) assert_match(/filename="direct_without_type.txt"/, body) assert_match(/gamma/, body) assert_match(/name="direct_with_type"/, body) assert_match(/filename="direct_with_type.txt"/, body) assert_match(/delta/, body) assert_match(/name="local_with_type"/, body) assert_match(/Curb - Libcurl bindings for Ruby/, body) assert_match(/Content-Type: text\/plain/, body) end end curb-1.3.5/tests/tc_curl_easy_resolve.rb0000644000004100000410000000237115203731642020413 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlEasyResolve < Test::Unit::TestCase include TestServerMethods def setup server_setup @easy = Curl::Easy.new end def test_resolve @easy.resolve = [ "example.com:80:127.0.0.1" ] assert_equal @easy.resolve, [ "example.com:80:127.0.0.1" ] end def test_empty_resolve assert_equal @easy.resolve, nil end def test_setopt_resolve_persists_across_performs host = "curb-setopt-resolve.invalid" mapping = "#{host}:#{TestServlet.port}:127.0.0.1" @easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}" @easy.dns_cache_timeout = 0 @easy.set(:resolve, [mapping]) @easy.perform assert_match(/GET/, @easy.body_str) @easy.perform assert_match(/GET/, @easy.body_str) end def test_resolve_entries_can_be_objects_that_convert_to_string host = "curb-resolve-object.invalid" mapping = "#{host}:#{TestServlet.port}:127.0.0.1" entry = Object.new entry.define_singleton_method(:to_s) { mapping } @easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}" @easy.dns_cache_timeout = 0 @easy.resolve = [entry] @easy.perform assert_match(/GET/, @easy.body_str) end end curb-1.3.5/tests/tc_curl_native_coverage.rb0000644000004100000410000001163515203731642021057 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), 'helper')) class TestCurbCurlErrorMappings < Test::Unit::TestCase def assert_easy_error_mapping(code, expected_class) actual_class, message = Curl::Easy.error(code) assert_equal expected_class, actual_class assert_kind_of String, message assert_not_empty message end def test_easy_error_known_mappings assert_easy_error_mapping(0, Curl::Err::CurlOK) assert_easy_error_mapping(1, Curl::Err::UnsupportedProtocolError) assert_easy_error_mapping(2, Curl::Err::FailedInitError) assert_easy_error_mapping(3, Curl::Err::MalformedURLError) assert_easy_error_mapping(5, Curl::Err::ProxyResolutionError) assert_easy_error_mapping(6, Curl::Err::HostResolutionError) assert_easy_error_mapping(7, Curl::Err::ConnectionFailedError) assert_easy_error_mapping(23, Curl::Err::WriteError) assert_easy_error_mapping(26, Curl::Err::ReadError) assert_easy_error_mapping(28, Curl::Err::TimeoutError) assert_easy_error_mapping(42, Curl::Err::AbortedByCallbackError) assert_easy_error_mapping(47, Curl::Err::TooManyRedirectsError) assert_easy_error_mapping(52, Curl::Err::GotNothingError) assert_easy_error_mapping(55, Curl::Err::SendError) assert_easy_error_mapping(56, Curl::Err::RecvError) assert_easy_error_mapping(57, Curl::Err::ShareInUseError) assert_easy_error_mapping(58, Curl::Err::SSLCertificateError) assert_easy_error_mapping(59, Curl::Err::SSLCypherError) assert_easy_error_mapping(61, Curl::Err::BadContentEncodingError) assert_easy_error_mapping(63, Curl::Err::FileSizeExceededError) assert_easy_error_mapping(64, Curl::Err::FTPSSLFailed) ssl_peer_or_ca_error = if Gem::Version.new(Curl::CURL_VERSION) >= Gem::Version.new('7.62.0') Curl::Err::SSLPeerCertificateError else Curl::Err::SSLCACertificateError end assert_easy_error_mapping(60, ssl_peer_or_ca_error) end def test_easy_error_returns_error_info_for_known_numeric_range 0.upto(92) do |code| error_class, message = Curl::Easy.error(code) assert_kind_of Class, error_class assert error_class <= Curl::Err::CurlError assert_kind_of String, message assert_not_empty message end end def test_easy_error_uses_generic_mapping_for_unknown_codes error_class, message = Curl::Easy.error(9_999) assert_equal Curl::Err::CurlError, error_class assert_equal 'Unknown error result from libcurl', message end end class TestCurbCurlNativeCoverage < Test::Unit::TestCase include TestServerMethods def setup server_setup end def test_clone_preserves_native_lists_after_original_handle_closes easy = Curl::Easy.new("http://curb.invalid:#{TestServlet.port}#{TestServlet.path}") easy.headers['X-Test'] = '1' easy.proxy_headers['X-Proxy'] = '2' easy.ftp_commands = ['PWD'] easy.resolve = ["curb.invalid:#{TestServlet.port}:127.0.0.1"] clone = easy.clone easy.close clone.http_get assert_equal 'GET', clone.body_str assert_equal '1', clone.headers['X-Test'] assert_equal '2', clone.proxy_headers['X-Proxy'] assert_equal ['PWD'], clone.ftp_commands assert_equal ["curb.invalid:#{TestServlet.port}:127.0.0.1"], clone.resolve ensure clone.close if defined?(clone) && clone easy.close if defined?(easy) && easy end def test_clone_rebinds_upload_callbacks_to_clone_state easy = Curl::Easy.new(TestServlet.url) easy.put_data = 'clone-data' clone = easy.clone easy.put_data = 'other-data' clone.perform assert_equal "PUT\nclone-data", clone.body_str ensure clone.close if defined?(clone) && clone easy.close if defined?(easy) && easy end def test_native_accessors_round_trip easy = Curl::Easy.new(TestServlet.url) assert_equal '127.0.0.1', easy.interface = '127.0.0.1' assert_equal 'user:pass', easy.userpwd = 'user:pass' assert_equal 'proxy:pass', easy.proxypwd = 'proxy:pass' assert_equal 'tests/cert.pem', easy.cert_key = 'tests/cert.pem' assert_equal 'gzip', easy.encoding = 'gzip' if easy.respond_to?(:max_send_speed_large=) assert_equal 123, easy.max_send_speed_large = 123 assert_equal 123, easy.max_send_speed_large end if easy.respond_to?(:max_recv_speed_large=) assert_equal 456, easy.max_recv_speed_large = 456 assert_equal 456, easy.max_recv_speed_large end assert_equal '127.0.0.1', easy.interface assert_equal 'user:pass', easy.userpwd assert_equal 'proxy:pass', easy.proxypwd assert_equal 'tests/cert.pem', easy.cert_key assert_equal 'gzip', easy.encoding ensure easy.close if defined?(easy) && easy end def test_upload_round_trips_stream_and_offset upload = Curl::Upload.new stream = StringIO.new('payload') assert_same stream, upload.stream = stream assert_same stream, upload.stream assert_equal 7, upload.offset = 7 assert_equal 7, upload.offset end end curb-1.3.5/doc.rb0000644000004100000410000000166115203731642013604 0ustar www-datawww-datarequire 'fileutils' include FileUtils begin incflags = File.read('ext/Makefile')[/INCFLAGS\s*=\s*(.*)$/,1] rescue Errno::ENOENT $stderr.puts("No makefile found; run `rake ext/Makefile' first.") end pp_srcdir = 'ext' rm_rf(tmpdir = '.doc-tmp') mkdir(tmpdir) begin if ARGV.include?('--cpp') begin if `cpp --version` =~ /\(GCC\)/ # gnu cpp $stderr.puts "Running GNU cpp over source" Dir['ext/*.c'].each do |fn| system("cpp -DRDOC_NEVER_DEFINED -C #{incflags} -o " + "#{File.join(tmpdir, File.basename(fn))} #{fn}") end pp_srcdir = tmpdir else $stderr.puts "Not running cpp (non-GNU)" end rescue # no cpp $stderr.puts "No cpp found" end end system("rdoc --title='Curb - libcurl bindings for ruby' --main=README #{pp_srcdir}/*.c README LICENSE lib/curb.rb") ensure rm_rf(tmpdir) end curb-1.3.5/Rakefile0000644000004100000410000002531115203731642014155 0ustar www-datawww-data# $Id$ # require 'rake/clean' require 'rake/testtask' require "ruby_memcheck" require 'shellwords' begin require 'mixlib/shellout' rescue LoadError end CLEAN.include '**/*.o' CLEAN.include "**/*.#{(defined?(RbConfig) ? RbConfig : Config)::MAKEFILE_CONFIG['DLEXT']}" CLOBBER.include 'doc' CLOBBER.include '**/*.log' CLOBBER.include '**/Makefile' CLOBBER.include '**/extconf.h' # Load support ruby and rake files (in this order) Dir.glob('tasks/*.rb').each { |r| load r} Dir.glob('tasks/*.rake').each { |r| load r} desc 'Print Ruby major version (ie "2_5")' task :ruby_version do print current_ruby_major end def announce(msg='') $stderr.puts msg end desc "Default Task (Test project)" task :default => :test # Determine the current version of the software if File.read('ext/curb.h') =~ /\s*CURB_VERSION\s*['"](\d.+)['"]/ CURRENT_VERSION = $1 else CURRENT_VERSION = "0.0.0" end if ENV['REL'] PKG_VERSION = ENV['REL'] else PKG_VERSION = CURRENT_VERSION end task :test_ver do puts PKG_VERSION end # Make tasks ----------------------------------------------------- make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' MAKECMD = ENV['MAKE_CMD'] || make_program MAKEOPTS = ENV['MAKE_OPTS'] || '' CURB_SO = "ext/curb_core.#{(defined?(RbConfig) ? RbConfig : Config)::MAKEFILE_CONFIG['DLEXT']}" file 'ext/Makefile' => 'ext/extconf.rb' do shell(['ruby', 'extconf.rb', ENV['EXTCONF_OPTS'].to_s], { :live_stdout => STDOUT , :cwd => "#{Dir.pwd}/ext" } ).error! end def make(target = '') shell(["#{MAKECMD}", "#{MAKEOPTS}", "#{target}"].reject(&:empty?), { :live_stdout => STDOUT, :cwd => "#{Dir.pwd}/ext" } ).error! end # Let make handle dependencies between c/o/so - we'll just run it. file CURB_SO => (['ext/Makefile'] + Dir['ext/*.c'] + Dir['ext/*.h']) do make end desc "Compile the shared object" task :compile => [CURB_SO] desc "Install to your site_ruby directory" task :install do make 'install' end # Test Tasks --------------------------------------------------------- task :ta => :alltests task :tu => :unittests task :test => [:rmpid,:unittests] task :rmpid do FileUtils.rm_rf Dir.glob("tests/server_lock-*") end at_exit do next unless defined?(Rake.application) top_level_tasks = Rake.application.top_level_tasks test_tasks = %w[default test ta tu alltests unittests bugtests test:valgrind] should_cleanup = top_level_tasks.empty? || top_level_tasks.any? { |task| test_tasks.include?(task) } FileUtils.rm_rf Dir.glob("tests/server_lock-*") if should_cleanup end if ENV['RELTEST'] announce "Release task testing - not running regression tests on alltests" task :alltests => [:unittests] else task :alltests => [:unittests, :bugtests] end ruby_memcheck_config = { binary_name: 'curb_core' } if RUBY_ENGINE == 'ruby' && RUBY_VERSION == '4.0.4' # Ruby 4.0.4 reports fiber/block-handler VM stack accesses under Valgrind. # Keep reporting errors that originate in curb_core, but filter Ruby-side noise. ruby_memcheck_config[:filter_all_errors] = true if RubyMemcheck::Configuration.instance_method(:initialize).parameters.any? { |type, name| type == :key && name == :use_only_ruby_free_at_exit } ruby_memcheck_config[:use_only_ruby_free_at_exit] = false end ruby_memcheck_config[:skipped_ruby_functions] = RubyMemcheck::Configuration::DEFAULT_SKIPPED_RUBY_FUNCTIONS + [ /\Arb_vm_frame_block_handler\z/ ] end RubyMemcheck.config(**ruby_memcheck_config) namespace :test do RubyMemcheck::TestTask.new(valgrind: :compile) do|t| t.test_files = FileList['tests/tc_*.rb'] t.verbose = false end desc 'Run the standalone leak trace helper' task :leak_trace => :compile do ruby_opts = Shellwords.split(ENV['LEAK_TRACE_OPTS'].to_s) sh RbConfig.ruby, '-Ilib', '-Iext', 'tests/leak_trace.rb', *ruby_opts end end Rake::Task['test:valgrind'].enhance([:rmpid]) do Rake::Task[:rmpid].reenable Rake::Task[:rmpid].invoke end Rake::TestTask.new(:unittests) do |t| t.test_files = FileList['tests/tc_*.rb'] t.verbose = false end Rake::TestTask.new(:bugtests) do |t| t.test_files = FileList['tests/bug_*.rb'] t.verbose = false end #Rake::TestTask.new(:funtests) do |t| # t.test_files = FileList['test/func_*.rb'] #t.warning = true #t.warning = true #end task :unittests => :compile task :bugtests => :compile def has_gem?(file,name) begin require file has_http_persistent = true rescue LoadError => e puts "Skipping #{name}" end end desc "Benchmark curl against http://127.0.0.1/zeros-2k - will fail if /zeros-2k or 127.0.0.1 are missing" task :bench do sh "ruby bench/curb_easy.rb" sh "ruby bench/curb_multi.rb" sh "ruby bench/nethttp_test.rb" if has_gem?("net/http/persistent","net-http-persistent") sh "ruby bench/patron_test.rb" if has_gem?("patron","patron") sh "ruby bench/typhoeus_test.rb" if has_gem?("typhoeus","typhoeus") sh "ruby bench/typhoeus_hydra_test.rb" if has_gem?("typhoeus","typhoeus") end # RDoc Tasks --------------------------------------------------------- desc "Create the RDOC documentation" task :doc do ruby "doc.rb #{ENV['DOC_OPTS']}" end desc "Publish the RDoc documentation to project web site" task :doc_upload => [ :doc ] do begin require 'rdoc/task' rescue LoadError => e require 'rake/rdoctask' end if ENV['RELTEST'] announce "Release Task Testing, skipping doc upload" else unless ENV['RUBYFORGE_ACCT'] raise "Need to set RUBYFORGE_ACCT to your rubyforge.org user name (e.g. 'fred')" end require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new( "#{ENV['RUBYFORGE_ACCT']}@rubyforge.org", "/var/www/gforge-projects/curb", "doc" ).upload end end if ! defined?(Gem) warn "Package Target requires RubyGEMs" else desc 'Generate gem specification' task :gemspec do require 'erb' tspec = ERB.new(File.read(File.join(File.dirname(__FILE__),'lib','curb.gemspec.erb'))) File.open(File.join(File.dirname(__FILE__),'curb.gemspec'),'wb') do|f| f << tspec.result end end desc 'Build gem' task :package => :gemspec do require 'rubygems/package' spec_source = File.read File.join(File.dirname(__FILE__),'curb.gemspec') spec = nil # see: http://gist.github.com/16215 Thread.new { spec = eval("#{spec_source}") }.join spec.validate Gem::Package.build(spec) end task :static do ENV['STATIC_BUILD'] = '1' end task :binary_gemspec => [:static, :compile] do require 'erb' ENV['BINARY_PACKAGE'] = '1' tspec = ERB.new(File.read(File.join(File.dirname(__FILE__),'lib','curb.gemspec.erb'))) File.open(File.join(File.dirname(__FILE__),'curb-binary.gemspec'),'wb') do|f| f << tspec.result end end desc 'Strip extra strings from Binary' task :binary_strip do strip = '/usr/bin/strip' if File.exist?(strip) and `#{strip} -h 2>&1`.match(/GNU/) sh "#{strip} #{CURB_SO}" end end desc 'Build gem' task :binary_package => [:binary_gemspec, :binary_strip] do require 'rubygems/specification' spec_source = File.read File.join(File.dirname(__FILE__),'curb-binary.gemspec') spec = nil # see: http://gist.github.com/16215 Thread.new { spec = eval("$SAFE = 3\n#{spec_source}") }.join spec.validate Gem::Builder.new(spec).build end end # -------------------------------------------------------------------- # Creating a release desc "Make a new release (Requires SVN commit / webspace access)" task :release => [ :prerelease, :clobber, :alltests, :update_version, :package, :tag, :doc_upload] do announce announce "**************************************************************" announce "* Release #{PKG_VERSION} Complete." announce "* Packages ready to upload." announce "**************************************************************" announce end # Validate that everything is ready to go for a release. task :prerelease do announce announce "**************************************************************" announce "* Making RubyGem Release #{PKG_VERSION}" announce "* (current version #{CURRENT_VERSION})" announce "**************************************************************" announce # Is a release number supplied? unless ENV['REL'] fail "Usage: rake release REL=x.y.z [REUSE=tag_suffix]" end # Is the release different than the current release. # (or is REUSE set?) if PKG_VERSION == CURRENT_VERSION && ! ENV['REUSE'] fail "Current version is #{PKG_VERSION}, must specify REUSE=tag_suffix to reuse version" end # Are all source files checked in? if ENV['RELTEST'] announce "Release Task Testing, skipping checked-in file test" else announce "Checking for unchecked-in files..." data = `svn status` unless data =~ /^$/ fail "SVN status is not clean ... do you have unchecked-in files?" end announce "No outstanding checkins found ... OK" end announce "Doc will try to use GNU cpp if available" ENV['DOC_OPTS'] = "--cpp" end # Used during release packaging if a REL is supplied task :update_version do unless PKG_VERSION == CURRENT_VERSION pkg_vernum = PKG_VERSION.tr('.','').sub(/^0*/,'') pkg_vernum << '0' until pkg_vernum.length > 2 File.open('ext/curb.h.new','w+') do |f| maj, min, mic, patch = /(\d+)\.(\d+)(?:\.(\d+))?(?:\.(\d+))?/.match(PKG_VERSION).captures f << File.read('ext/curb.h'). gsub(/CURB_VERSION\s+"(\d.+)"/) { "CURB_VERSION \"#{PKG_VERSION}\"" }. gsub(/CURB_VER_NUM\s+\d+/) { "CURB_VER_NUM #{pkg_vernum}" }. gsub(/CURB_VER_MAJ\s+\d+/) { "CURB_VER_MAJ #{maj}" }. gsub(/CURB_VER_MIN\s+\d+/) { "CURB_VER_MIN #{min}" }. gsub(/CURB_VER_MIC\s+\d+/) { "CURB_VER_MIC #{mic || 0}" }. gsub(/CURB_VER_PATCH\s+\d+/) { "CURB_VER_PATCH #{patch || 0}" } end mv('ext/curb.h.new', 'ext/curb.h') if ENV['RELTEST'] announce "Release Task Testing, skipping commiting of new version" else sh %{svn commit -m "Updated to version #{PKG_VERSION}" ext/curb.h} end end end # "Create a new SVN tag with the latest release number (REL=x.y.z)" task :tag => [:prerelease] do reltag = "curb-#{PKG_VERSION}" reltag << ENV['REUSE'] if ENV['REUSE'] announce "Tagging SVN with [#{reltag}]" if ENV['RELTEST'] announce "Release Task Testing, skipping SVN tagging" else # need to get current base URL s = `svn info` if s =~ /URL:\s*([^\n]*)\n/ svnroot = $1 if svnroot =~ /^(.*)\/trunk/i svnbase = $1 sh %{svn cp #{svnroot} #{svnbase}/TAGS/#{reltag} -m "Release #{PKG_VERSION}"} else fail "Please merge to trunk before making a release" end else fail "Unable to determine repository URL from 'svn info' - is this a working copy?" end end end curb-1.3.5/LICENSE0000644000004100000410000000426015203731642013515 0ustar www-datawww-dataCopyright (c) 2006 Ross Bamford (rosco AT roscopeco DOT co DOT uk). Curb is free software licensed under the following terms: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a) distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. curb-1.3.5/ext/0000755000004100000410000000000015203731642013306 5ustar www-datawww-datacurb-1.3.5/ext/curb_postfield.c0000644000004100000410000005274315203731642016471 0ustar www-datawww-data/* curb_postfield.c - Field class for POST method * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_postfield.c 30 2006-12-09 12:30:24Z roscopeco $ */ #include "curb_postfield.h" #include "curb_errors.h" extern VALUE mCurl; static VALUE idCall; #ifdef RDOC_NEVER_DEFINED mCurl = rb_define_module("Curl"); #endif VALUE cCurlPostField; /* ================= APPEND FORM FUNC ================ */ /* This gets called by the post method on Curl::Easy for each postfield * supplied in the arguments. It's job is to add the supplied field to * the list that's being built for a perform. * * THIS FUNC MODIFIES ITS ARGUMENTS. See curl_formadd(3) for details. */ void append_to_form(VALUE self, struct curl_httppost **first, struct curl_httppost **last) { ruby_curl_postfield *rbcpf; CURLFORMcode result = -1; TypedData_Get_Struct(self, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf); if (rbcpf->name == Qnil) { rb_raise(eCurlErrInvalidPostField, "Cannot post unnamed field"); } else { if ((rbcpf->local_file != Qnil) || (rbcpf->remote_file != Qnil)) { // is a file upload field if (rbcpf->content_proc != Qnil) { // with content proc rbcpf->buffer_str = rb_funcall(rbcpf->content_proc, idCall, 1, self); if (rbcpf->remote_file == Qnil) { rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no filename"); } else { if (rbcpf->content_type == Qnil) { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file), CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->buffer_str), CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->buffer_str), CURLFORM_END); } else { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file), CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->buffer_str), CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->buffer_str), CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type), CURLFORM_END); } } } else if (rbcpf->content != Qnil) { // with content if (rbcpf->remote_file == Qnil) { rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no filename"); } else { if (rbcpf->content_type == Qnil) { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file), CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->content), CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->content), CURLFORM_END); } else { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file), CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->content), CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->content), CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type), CURLFORM_END); } } } else if (rbcpf->local_file != Qnil) { // with local filename if (rbcpf->local_file == Qnil) { rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field no filename"); } else { if (rbcpf->remote_file == Qnil) { rbcpf->remote_file = rbcpf->local_file; } if (rbcpf->content_type == Qnil) { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_FILE, StringValuePtr(rbcpf->local_file), CURLFORM_FILENAME, StringValuePtr(rbcpf->remote_file), CURLFORM_END); } else { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_FILE, StringValuePtr(rbcpf->local_file), CURLFORM_FILENAME, StringValuePtr(rbcpf->remote_file), CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type), CURLFORM_END); } } } else { rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no data"); } } else { // is a content field if (rbcpf->content_proc != Qnil) { rbcpf->buffer_str = rb_funcall(rbcpf->content_proc, idCall, 1, self); if (rbcpf->content_type == Qnil) { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->buffer_str), CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->buffer_str), CURLFORM_END); } else { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->buffer_str), CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->buffer_str), CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type), CURLFORM_END); } } else if (rbcpf->content != Qnil) { if (rbcpf->content_type == Qnil) { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->content), CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->content), CURLFORM_END); } else { result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name), CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->content), CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->content), CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type), CURLFORM_END); } } else { rb_raise(eCurlErrInvalidPostField, "Cannot post content field with no data"); } } } if (result != 0) { const char *reason = NULL; switch (result) { case CURL_FORMADD_MEMORY: reason = "Memory allocation failed"; break; case CURL_FORMADD_OPTION_TWICE: reason = "Duplicate option"; break; case CURL_FORMADD_NULL: reason = "Unexpected NULL string"; break; case CURL_FORMADD_UNKNOWN_OPTION: reason = "Unknown option"; break; case CURL_FORMADD_INCOMPLETE: reason = "Incomplete form data"; break; case CURL_FORMADD_ILLEGAL_ARRAY: reason = "Illegal array [BINDING BUG]"; break; case CURL_FORMADD_DISABLED: reason = "Installed libcurl cannot support requested feature(s)"; break; default: reason = "Unknown error"; } rb_raise(eCurlErrInvalidPostField, "Failed to add field (%s)", reason); } } /* ================== MARK/FREE FUNC ==================*/ static void curl_postfield_mark(void *ptr) { ruby_curl_postfield *rbcpf = (ruby_curl_postfield *)ptr; if (rbcpf) { rb_gc_mark(rbcpf->name); rb_gc_mark(rbcpf->content); rb_gc_mark(rbcpf->content_type); rb_gc_mark(rbcpf->content_proc); rb_gc_mark(rbcpf->local_file); rb_gc_mark(rbcpf->remote_file); rb_gc_mark(rbcpf->buffer_str); } } static void curl_postfield_free(void *ptr) { if (ptr) free(ptr); } static size_t curl_postfield_memsize(const void *ptr) { (void)ptr; return sizeof(ruby_curl_postfield); } const rb_data_type_t ruby_curl_postfield_data_type = { "Curl::PostField", { curl_postfield_mark, curl_postfield_free, curl_postfield_memsize, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, /* compact */ #endif }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, NULL, /* parent, data */ RUBY_TYPED_FREE_IMMEDIATELY #endif }; /* ================= ALLOC METHODS ====================*/ /* * call-seq: * Curl::PostField.content(name, content) => # * Curl::PostField.content(name, content, content_type = nil) => # * Curl::PostField.content(name, content_type = nil) { |field| ... } => # * * Create a new Curl::PostField, supplying the field name, content, * and, optionally, Content-type (curl will attempt to determine this if * not specified). * * The block form allows a block to supply the content for this field, called * during the perform. The block should return a ruby string with the field * data. */ static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass) { VALUE self; ruby_curl_postfield *rbcpf; self = TypedData_Make_Struct(klass, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf); MEMZERO(rbcpf, ruby_curl_postfield, 1); // wierdness - we actually require two args, unless a block is provided, but // we have to work that out below. rb_scan_args(argc, argv, "12&", &rbcpf->name, &rbcpf->content, &rbcpf->content_type, &rbcpf->content_proc); // special handling if theres a block, second arg is actually content_type if (rbcpf->content_proc != Qnil) { if (rbcpf->content != Qnil) { // we were given a content-type rbcpf->content_type = rbcpf->content; rbcpf->content = Qnil; } else { // default content type rbcpf->content_type = Qnil; } } else { // no block, so make sure content was provided if (rbcpf->content == Qnil) { rb_raise(rb_eArgError, "Incorrect number of arguments (expected 2 or 3)"); } } /* assoc objects */ rbcpf->local_file = Qnil; rbcpf->remote_file = Qnil; rbcpf->buffer_str = Qnil; return self; } /* * call-seq: * Curl::PostField.file(name, local_file_name) => # * Curl::PostField.file(name, local_file_name, remote_file_name = local_file_name) => # * Curl::PostField.file(name, remote_file_name) { |field| ... } => # * * Create a new Curl::PostField for a file upload field, supplying the local filename * to read from, and optionally the remote filename (defaults to the local name). * * The block form allows a block to supply the content for this field, called * during the perform. The block should return a ruby string with the field * data. */ static VALUE ruby_curl_postfield_new_file(int argc, VALUE *argv, VALUE klass) { // TODO needs to handle content-type too VALUE self; ruby_curl_postfield *rbcpf; self = TypedData_Make_Struct(klass, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf); MEMZERO(rbcpf, ruby_curl_postfield, 1); rb_scan_args(argc, argv, "21&", &rbcpf->name, &rbcpf->local_file, &rbcpf->remote_file, &rbcpf->content_proc); // special handling if theres a block, second arg is actually remote name. if (rbcpf->content_proc != Qnil) { if (rbcpf->local_file != Qnil) { // we were given a local file if (rbcpf->remote_file == Qnil) { // we weren't given a remote, so local is actually remote // (correct block call form) rbcpf->remote_file = rbcpf->local_file; } // Shouldn't get a local file, so can ignore it. rbcpf->local_file = Qnil; } } else { if (rbcpf->remote_file == Qnil) { rbcpf->remote_file = rbcpf->local_file; } } /* assoc objects */ rbcpf->content = Qnil; rbcpf->content_type = Qnil; rbcpf->buffer_str = Qnil; return self; } /* ================= ATTRIBUTES ====================*/ /* * call-seq: * field.name = "name" => "name" * * Set the POST field name for this PostField. */ static VALUE ruby_curl_postfield_name_set(VALUE self, VALUE name) { CURB_OBJECT_SETTER(ruby_curl_postfield, name); } /* * call-seq: * field.name => "name" * * Obtain the POST field name for this PostField. */ static VALUE ruby_curl_postfield_name_get(VALUE self) { CURB_OBJECT_GETTER(ruby_curl_postfield, name); } /* * call-seq: * field.content = "content" => "content" * * Set the POST field content for this PostField. Ignored when a * content_proc is supplied via either +Curl::PostField.file+ or * +set_content_proc+. */ static VALUE ruby_curl_postfield_content_set(VALUE self, VALUE content) { CURB_OBJECT_SETTER(ruby_curl_postfield, content); } /* * call-seq: * field.content => "content" * * Obtain the POST field content for this PostField. */ static VALUE ruby_curl_postfield_content_get(VALUE self) { CURB_OBJECT_GETTER(ruby_curl_postfield, content); } /* * call-seq: * field.content_type = "content_type" => "content_type" * * Set the POST field Content-type for this PostField. */ static VALUE ruby_curl_postfield_content_type_set(VALUE self, VALUE content_type) { CURB_OBJECT_SETTER(ruby_curl_postfield, content_type); } /* * call-seq: * field.content_type => "content_type" * * Get the POST field Content-type for this PostField. */ static VALUE ruby_curl_postfield_content_type_get(VALUE self) { CURB_OBJECT_GETTER(ruby_curl_postfield, content_type); } /* * call-seq: * field.local_file = "filename" => "filename" * * Set the POST field local filename for this PostField (when performing * a file upload). Ignored when a content_proc is supplied via either * +Curl::PostField.file+ or +set_content_proc+. */ static VALUE ruby_curl_postfield_local_file_set(VALUE self, VALUE local_file) { CURB_OBJECT_SETTER(ruby_curl_postfield, local_file); } /* * call-seq: * field.local_file => "filename" * * Get the POST field local filename for this PostField (when performing * a file upload). */ static VALUE ruby_curl_postfield_local_file_get(VALUE self) { CURB_OBJECT_GETTER(ruby_curl_postfield, local_file); } /* * call-seq: * field.remote_file = "filename" => "filename" * * Set the POST field remote filename for this PostField (when performing * a file upload). If no remote filename is provided, and no content_proc * is supplied, the local filename is used. If no remote filename is * specified when a content_proc is used, an exception will be raised * during the perform. */ static VALUE ruby_curl_postfield_remote_file_set(VALUE self, VALUE remote_file) { CURB_OBJECT_SETTER(ruby_curl_postfield, remote_file); } /* * call-seq: * field.local_file => "filename" * * Get the POST field remote filename for this PostField (when performing * a file upload). */ static VALUE ruby_curl_postfield_remote_file_get(VALUE self) { CURB_OBJECT_GETTER(ruby_curl_postfield, remote_file); } /* * call-seq: * field.set_content_proc { |field| ... } => * * Set a content proc for this field. This proc will be called during the * perform to supply the content for this field, overriding any setting * of +content+ or +local_file+. */ static VALUE ruby_curl_postfield_content_proc_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_SETTER(ruby_curl_postfield, content_proc); } /* * call-seq: * field.to_str => "name=value" * field.to_s => "name=value" * * Obtain a String representation of this PostField in url-encoded * format. This is used to construct the post data for non-multipart * POSTs. * * Only content fields may be converted to strings. */ static VALUE ruby_curl_postfield_to_str(VALUE self) { ruby_curl_postfield *rbcpf; VALUE result = Qnil; VALUE name = Qnil; char *tmpchrs; #ifdef HAVE_CURL_EASY_ESCAPE CURL *curl_handle = NULL; #endif TypedData_Get_Struct(self, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf); if (rbcpf->name != Qnil) { name = rbcpf->name; if (TYPE(name) != T_STRING) { if (rb_respond_to(name, rb_intern("to_s"))) name = rb_funcall(name, rb_intern("to_s"), 0); else name = Qnil; } } if (name == Qnil) { rb_raise(eCurlErrInvalidPostField, "Cannot convert unnamed field to string %s:%d, make sure your field name responds_to :to_s", __FILE__, __LINE__); } /* Force field name to UTF-8 before escaping */ VALUE name_utf8 = rb_str_export_to_enc(name, rb_utf8_encoding()); #ifdef HAVE_CURL_EASY_ESCAPE curl_handle = curl_easy_init(); if (!curl_handle) { rb_raise(eCurlErrInvalidPostField, "Failed to initialize curl handle for escaping"); } tmpchrs = curl_easy_escape(curl_handle, StringValuePtr(name_utf8), (int)RSTRING_LEN(name_utf8)); if (!tmpchrs) { curl_easy_cleanup(curl_handle); rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name"); } #else tmpchrs = curl_escape(StringValuePtr(name_utf8), (int)RSTRING_LEN(name_utf8)); if (!tmpchrs) { rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name"); } #endif VALUE escd_name = rb_str_new2(tmpchrs); #ifdef HAVE_CURL_EASY_ESCAPE curl_free(tmpchrs); #else curl_free(tmpchrs); #endif VALUE tmpcontent = Qnil; if (rbcpf->content_proc != Qnil) { tmpcontent = rb_funcall(rbcpf->content_proc, idCall, 1, self); } else if (rbcpf->content != Qnil) { tmpcontent = rbcpf->content; } else if (rbcpf->local_file != Qnil) { tmpcontent = rbcpf->local_file; } else if (rbcpf->remote_file != Qnil) { tmpcontent = rbcpf->remote_file; } else { tmpcontent = rb_str_new2(""); } if (TYPE(tmpcontent) != T_STRING) { if (rb_respond_to(tmpcontent, rb_intern("to_s"))) tmpcontent = rb_funcall(tmpcontent, rb_intern("to_s"), 0); else { #ifdef HAVE_CURL_EASY_ESCAPE curl_easy_cleanup(curl_handle); #endif rb_raise(rb_eRuntimeError, "postfield(%s) is not a string and does not respond_to to_s", RSTRING_PTR(escd_name)); } } /* Force content to UTF-8 before escaping */ VALUE content_utf8 = rb_str_export_to_enc(tmpcontent, rb_utf8_encoding()); #ifdef HAVE_CURL_EASY_ESCAPE tmpchrs = curl_easy_escape(curl_handle, StringValuePtr(content_utf8), (int)RSTRING_LEN(content_utf8)); if (!tmpchrs) { curl_easy_cleanup(curl_handle); rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content"); } #else tmpchrs = curl_escape(StringValuePtr(content_utf8), (int)RSTRING_LEN(content_utf8)); if (!tmpchrs) { rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content"); } #endif VALUE escd_content = rb_str_new2(tmpchrs); #ifdef HAVE_CURL_EASY_ESCAPE curl_free(tmpchrs); curl_easy_cleanup(curl_handle); #else curl_free(tmpchrs); #endif result = escd_name; rb_str_cat(result, "=", 1); rb_str_concat(result, escd_content); return result; } /* =================== INIT LIB =====================*/ void init_curb_postfield() { VALUE sc; idCall = rb_intern("call"); cCurlPostField = rb_define_class_under(mCurl, "PostField", rb_cObject); rb_undef_alloc_func(cCurlPostField); /* Class methods */ rb_define_singleton_method(cCurlPostField, "content", ruby_curl_postfield_new_content, -1); rb_define_singleton_method(cCurlPostField, "file", ruby_curl_postfield_new_file, -1); sc = rb_singleton_class(cCurlPostField); rb_undef(sc, rb_intern("new")); rb_define_method(cCurlPostField, "name=", ruby_curl_postfield_name_set, 1); rb_define_method(cCurlPostField, "name", ruby_curl_postfield_name_get, 0); rb_define_method(cCurlPostField, "content=", ruby_curl_postfield_content_set, 1); rb_define_method(cCurlPostField, "content", ruby_curl_postfield_content_get, 0); rb_define_method(cCurlPostField, "content_type=", ruby_curl_postfield_content_type_set, 1); rb_define_method(cCurlPostField, "content_type", ruby_curl_postfield_content_type_get, 0); rb_define_method(cCurlPostField, "local_file=", ruby_curl_postfield_local_file_set, 1); rb_define_method(cCurlPostField, "local_file", ruby_curl_postfield_local_file_get, 0); rb_define_method(cCurlPostField, "remote_file=", ruby_curl_postfield_remote_file_set, 1); rb_define_method(cCurlPostField, "remote_file", ruby_curl_postfield_remote_file_get, 0); rb_define_method(cCurlPostField, "set_content_proc", ruby_curl_postfield_content_proc_set, -1); rb_define_method(cCurlPostField, "to_str", ruby_curl_postfield_to_str, 0); rb_define_alias(cCurlPostField, "to_s", "to_str"); } curb-1.3.5/ext/curb_upload.c0000644000004100000410000000571715203731642015763 0ustar www-datawww-data/* curb_upload.c - Curl upload handle * Copyright (c)2009 Todd A Fisher. * Licensed under the Ruby License. See LICENSE for details. */ #include "curb_upload.h" extern VALUE mCurl; VALUE cCurlUpload; #ifdef RDOC_NEVER_DEFINED mCurl = rb_define_module("Curl"); #endif static void curl_upload_mark(void *ptr) { ruby_curl_upload *rbcu = (ruby_curl_upload *)ptr; if (rbcu && rbcu->stream && !NIL_P(rbcu->stream)) rb_gc_mark(rbcu->stream); } static void curl_upload_free(void *ptr) { if (ptr) free(ptr); } static size_t curl_upload_memsize(const void *ptr) { (void)ptr; return sizeof(ruby_curl_upload); } const rb_data_type_t ruby_curl_upload_data_type = { "Curl::Upload", { curl_upload_mark, curl_upload_free, curl_upload_memsize, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, /* compact */ #endif }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, NULL, /* parent, data */ RUBY_TYPED_FREE_IMMEDIATELY #endif }; /* * call-seq: * internal class for sending large file uploads */ VALUE ruby_curl_upload_new(VALUE klass) { VALUE upload; ruby_curl_upload *rbcu = ALLOC(ruby_curl_upload); if (!rbcu) { rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Upload"); } rbcu->stream = Qnil; rbcu->offset = 0; upload = TypedData_Wrap_Struct(klass, &ruby_curl_upload_data_type, rbcu); return upload; } /* * call-seq: * internal class for sending large file uploads */ VALUE ruby_curl_upload_stream_set(VALUE self, VALUE stream) { ruby_curl_upload *rbcu; TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); rbcu->stream = stream; return stream; } /* * call-seq: * internal class for sending large file uploads */ VALUE ruby_curl_upload_stream_get(VALUE self) { ruby_curl_upload *rbcu; TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); return rbcu->stream; } /* * call-seq: * internal class for sending large file uploads */ VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset) { ruby_curl_upload *rbcu; TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); rbcu->offset = NUM2LONG(offset); return offset; } /* * call-seq: * internal class for sending large file uploads */ VALUE ruby_curl_upload_offset_get(VALUE self) { ruby_curl_upload *rbcu; TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); return LONG2NUM(rbcu->offset); } /* =================== INIT LIB =====================*/ void init_curb_upload() { cCurlUpload = rb_define_class_under(mCurl, "Upload", rb_cObject); rb_undef_alloc_func(cCurlUpload); rb_define_singleton_method(cCurlUpload, "new", ruby_curl_upload_new, 0); rb_define_method(cCurlUpload, "stream=", ruby_curl_upload_stream_set, 1); rb_define_method(cCurlUpload, "stream", ruby_curl_upload_stream_get, 0); rb_define_method(cCurlUpload, "offset=", ruby_curl_upload_offset_set, 1); rb_define_method(cCurlUpload, "offset", ruby_curl_upload_offset_get, 0); } curb-1.3.5/ext/curb_errors.c0000644000004100000410000007300615203731642016007 0ustar www-datawww-data/* curb_errors.c - Ruby exception types for curl errors * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_errors.c 10 2006-11-20 00:17:30Z roscopeco $ */ #include "curb_errors.h" extern VALUE mCurl; #ifdef RDOC_NEVER_DEFINED mCurl = rb_define_module("Curl"); #endif /* base errors */ VALUE mCurlErr; VALUE eCurlErrError; VALUE eCurlErrFTPError; VALUE eCurlErrHTTPError; VALUE eCurlErrFileError; VALUE eCurlErrLDAPError; VALUE eCurlErrTelnetError; VALUE eCurlErrTFTPError; VALUE eCurlErrRTSPError; /* Specific libcurl errors */ VALUE eCurlErrOK; /* not really an error but a return code */ VALUE eCurlErrUnsupportedProtocol; VALUE eCurlErrFailedInit; VALUE eCurlErrMalformedURL; VALUE eCurlErrMalformedURLUser; VALUE eCurlErrNotBuiltIn; VALUE eCurlErrProxyResolution; VALUE eCurlErrHostResolution; VALUE eCurlErrConnectFailed; VALUE eCurlErrFTPWeirdReply; VALUE eCurlErrFTPAccessDenied; VALUE eCurlErrFTPBadPassword; VALUE eCurlErrFTPWeirdPassReply; VALUE eCurlErrFTPWeirdUserReply; VALUE eCurlErrFTPWeirdPasvReply; VALUE eCurlErrFTPWeird227Format; VALUE eCurlErrFTPCantGetHost; VALUE eCurlErrFTPCantReconnect; VALUE eCurlErrFTPCouldntSetBinary; VALUE eCurlErrPartialFile; VALUE eCurlErrFTPCouldntRetrFile; VALUE eCurlErrFTPWrite; VALUE eCurlErrFTPQuote; VALUE eCurlErrHTTPFailed; VALUE eCurlErrWriteError; VALUE eCurlErrMalformedUser; VALUE eCurlErrFTPCouldntStorFile; VALUE eCurlErrReadError; VALUE eCurlErrOutOfMemory; VALUE eCurlErrTimeout; VALUE eCurlErrFTPCouldntSetASCII; VALUE eCurlErrFTPPortFailed; VALUE eCurlErrFTPCouldntUseRest; VALUE eCurlErrFTPCouldntGetSize; VALUE eCurlErrHTTPRange; VALUE eCurlErrHTTPPost; VALUE eCurlErrSSLConnectError; VALUE eCurlErrBadResume; VALUE eCurlErrFileCouldntRead; VALUE eCurlErrLDAPCouldntBind; VALUE eCurlErrLDAPSearchFailed; VALUE eCurlErrLibraryNotFound; VALUE eCurlErrFunctionNotFound; VALUE eCurlErrAbortedByCallback; VALUE eCurlErrBadFunctionArgument; VALUE eCurlErrBadCallingOrder; VALUE eCurlErrInterfaceFailed; VALUE eCurlErrBadPasswordEntered; VALUE eCurlErrTooManyRedirects; VALUE eCurlErrTelnetUnknownOption; VALUE eCurlErrTelnetBadOptionSyntax; VALUE eCurlErrObsolete; VALUE eCurlErrSSLPeerCertificate; VALUE eCurlErrGotNothing; VALUE eCurlErrSSLEngineNotFound; VALUE eCurlErrSSLEngineSetFailed; VALUE eCurlErrSendError; VALUE eCurlErrRecvError; VALUE eCurlErrShareInUse; VALUE eCurlErrSSLCertificate; VALUE eCurlErrSSLCipher; VALUE eCurlErrSSLCACertificate; VALUE eCurlErrBadContentEncoding; VALUE eCurlErrLDAPInvalidURL; VALUE eCurlErrFileSizeExceeded; VALUE eCurlErrFTPSSLFailed; VALUE eCurlErrSendFailedRewind; VALUE eCurlErrSSLEngineInitFailed; VALUE eCurlErrLoginDenied; VALUE eCurlErrTFTPNotFound; VALUE eCurlErrTFTPPermission; VALUE eCurlErrTFTPDiskFull; VALUE eCurlErrTFTPIllegalOperation; VALUE eCurlErrTFTPUnknownID; VALUE eCurlErrTFTPFileExists; VALUE eCurlErrTFTPNoSuchUser; VALUE eCurlErrConvFailed; VALUE eCurlErrConvReqd; VALUE eCurlErrSSLCacertBadfile; VALUE eCurlErrRemoteFileNotFound; VALUE eCurlErrSSH; VALUE eCurlErrSSLShutdownFailed; VALUE eCurlErrAgain; VALUE eCurlErrSSLCRLBadfile; VALUE eCurlErrSSLIssuerError; /* multi errors */ VALUE mCurlErrFailedInit; VALUE mCurlErrCallMultiPerform; VALUE mCurlErrBadHandle; VALUE mCurlErrBadEasyHandle; VALUE mCurlErrOutOfMemory; VALUE mCurlErrInternalError; VALUE mCurlErrBadSocket; #ifdef HAVE_CURLM_ADDED_ALREADY VALUE mCurlErrAddedAlready; #endif VALUE mCurlErrUnknownOption; /* binding errors */ VALUE eCurlErrInvalidPostField; /* new errors */ VALUE eCurlErrFTPPRETFailed; VALUE eCurlErrRTSPCseqError; VALUE eCurlErrRTSPSessionError; VALUE eCurlErrFTPBadFileList; VALUE eCurlErrChunkFailed; VALUE eCurlErrNoConnectionAvailable; VALUE eCurlErrSSLPinnedPubKeyNotMatch; VALUE eCurlErrSSLInvalidCertStatus; VALUE eCurlErrHTTP2Stream; VALUE rb_curl_easy_error(CURLcode code) { VALUE exclz; const char *exmsg = NULL; VALUE results; switch (code) { case CURLE_OK: /* 0 */ exclz = eCurlErrOK; break; case CURLE_UNSUPPORTED_PROTOCOL: /* 1 */ exclz = eCurlErrUnsupportedProtocol; break; case CURLE_FAILED_INIT: /* 2 */ exclz = eCurlErrFailedInit; break; case CURLE_URL_MALFORMAT: /* 3 */ exclz = eCurlErrMalformedURL; break; #ifdef HAVE_CURLE_NOT_BUILT_IN case CURLE_NOT_BUILT_IN: /* 4 - [was obsoleted in August 2007 for 7.17.0, reused in April 2011 for 7.21.5] */ exclz = eCurlErrNotBuiltIn; break; #else case CURLE_URL_MALFORMAT_USER: /* 4 (NOT USED) */ exclz = eCurlErrMalformedURLUser; break; #endif case CURLE_COULDNT_RESOLVE_PROXY: /* 5 */ exclz = eCurlErrProxyResolution; break; case CURLE_COULDNT_RESOLVE_HOST: /* 6 */ exclz = eCurlErrHostResolution; break; case CURLE_COULDNT_CONNECT: /* 7 */ exclz = eCurlErrConnectFailed; break; case CURLE_FTP_WEIRD_SERVER_REPLY: /* 8 */ exclz = eCurlErrFTPWeirdReply; break; case CURLE_FTP_ACCESS_DENIED: /* 9 denied due to lack of access. */ exclz = eCurlErrFTPAccessDenied; break; case CURLE_FTP_USER_PASSWORD_INCORRECT: /* 10 */ exclz = eCurlErrFTPBadPassword; break; case CURLE_FTP_WEIRD_PASS_REPLY: /* 11 */ exclz = eCurlErrFTPWeirdPassReply; break; case CURLE_FTP_WEIRD_USER_REPLY: /* 12 */ exclz = eCurlErrFTPWeirdUserReply; break; case CURLE_FTP_WEIRD_PASV_REPLY: /* 13 */ exclz = eCurlErrFTPWeirdPasvReply; break; case CURLE_FTP_WEIRD_227_FORMAT: /* 14 */ exclz = eCurlErrFTPWeird227Format; break; case CURLE_FTP_CANT_GET_HOST: /* 15 */ exclz = eCurlErrFTPCantGetHost; break; case CURLE_FTP_CANT_RECONNECT: /* 16 */ exclz = eCurlErrFTPCantReconnect; break; case CURLE_FTP_COULDNT_SET_BINARY: /* 17 */ exclz = eCurlErrFTPCouldntSetBinary; break; case CURLE_PARTIAL_FILE: /* 18 */ exclz = eCurlErrPartialFile; break; case CURLE_FTP_COULDNT_RETR_FILE: /* 19 */ exclz = eCurlErrFTPCouldntRetrFile; break; case CURLE_FTP_WRITE_ERROR: /* 20 */ exclz = eCurlErrFTPWrite; break; case CURLE_FTP_QUOTE_ERROR: /* 21 */ exclz = eCurlErrFTPQuote; break; case CURLE_HTTP_RETURNED_ERROR: /* 22 */ exclz = eCurlErrHTTPFailed; break; case CURLE_WRITE_ERROR: /* 23 */ exclz = eCurlErrWriteError; break; case CURLE_MALFORMAT_USER: /* 24 - NOT USED */ exclz = eCurlErrMalformedUser; break; case CURLE_FTP_COULDNT_STOR_FILE: /* 25 - failed FTP upload */ exclz = eCurlErrFTPCouldntStorFile; break; case CURLE_READ_ERROR: /* 26 - could open/read from file */ exclz = eCurlErrReadError; break; case CURLE_OUT_OF_MEMORY: /* 27 */ exclz = eCurlErrOutOfMemory; break; case CURLE_OPERATION_TIMEOUTED: /* 28 - the timeout time was reached */ exclz = eCurlErrTimeout; break; case CURLE_FTP_COULDNT_SET_ASCII: /* 29 - TYPE A failed */ exclz = eCurlErrFTPCouldntSetASCII; break; case CURLE_FTP_PORT_FAILED: /* 30 - FTP PORT operation failed */ exclz = eCurlErrFTPPortFailed; break; case CURLE_FTP_COULDNT_USE_REST: /* 31 - the REST command failed */ exclz = eCurlErrFTPCouldntUseRest; break; case CURLE_FTP_COULDNT_GET_SIZE: /* 32 - the SIZE command failed */ exclz = eCurlErrFTPCouldntGetSize; break; case CURLE_HTTP_RANGE_ERROR: /* 33 - RANGE "command" didn't work */ exclz = eCurlErrHTTPRange; break; case CURLE_HTTP_POST_ERROR: /* 34 */ exclz = eCurlErrHTTPPost; break; case CURLE_SSL_CONNECT_ERROR: /* 35 - wrong when connecting with SSL */ exclz = eCurlErrSSLConnectError; break; case CURLE_BAD_DOWNLOAD_RESUME: /* 36 - couldn't resume download */ exclz = eCurlErrBadResume; break; case CURLE_FILE_COULDNT_READ_FILE: /* 37 */ exclz = eCurlErrFileCouldntRead; break; case CURLE_LDAP_CANNOT_BIND: /* 38 */ exclz = eCurlErrLDAPCouldntBind; break; case CURLE_LDAP_SEARCH_FAILED: /* 39 */ exclz = eCurlErrLDAPSearchFailed; break; case CURLE_LIBRARY_NOT_FOUND: /* 40 */ exclz = eCurlErrLibraryNotFound; break; case CURLE_FUNCTION_NOT_FOUND: /* 41 */ exclz = eCurlErrFunctionNotFound; break; case CURLE_ABORTED_BY_CALLBACK: /* 42 */ exclz = eCurlErrAbortedByCallback; break; case CURLE_BAD_FUNCTION_ARGUMENT: /* 43 */ exclz = eCurlErrBadFunctionArgument; break; case CURLE_BAD_CALLING_ORDER: /* 44 - NOT USED */ exclz = eCurlErrBadCallingOrder; break; case CURLE_INTERFACE_FAILED: /* 45 - CURLOPT_INTERFACE failed */ exclz = eCurlErrInterfaceFailed; break; case CURLE_BAD_PASSWORD_ENTERED: /* 46 - NOT USED */ exclz = eCurlErrBadPasswordEntered; break; case CURLE_TOO_MANY_REDIRECTS: /* 47 - catch endless re-direct loops */ exclz = eCurlErrTooManyRedirects; break; case CURLE_UNKNOWN_TELNET_OPTION: /* 48 - User specified an unknown option */ exclz = eCurlErrTelnetUnknownOption; break; case CURLE_TELNET_OPTION_SYNTAX: /* 49 - Malformed telnet option */ exclz = eCurlErrTelnetBadOptionSyntax; break; #ifdef HAVE_CURLE_OBSOLETE case CURLE_OBSOLETE: /* 50 - NOT USED */ exclz = eCurlErrObsolete; break; #endif #if LIBCURL_VERSION_NUM < 0x073e00 case CURLE_SSL_PEER_CERTIFICATE: /* 51 - peer's certificate wasn't ok */ exclz = eCurlErrSSLPeerCertificate; break; #endif case CURLE_GOT_NOTHING: /* 52 - when this is a specific error */ exclz = eCurlErrGotNothing; break; case CURLE_SSL_ENGINE_NOTFOUND: /* 53 - SSL crypto engine not found */ exclz = eCurlErrSSLEngineNotFound; break; case CURLE_SSL_ENGINE_SETFAILED: /* 54 - can not set SSL crypto engine as default */ exclz = eCurlErrSSLEngineSetFailed; break; case CURLE_SEND_ERROR: /* 55 - failed sending network data */ exclz = eCurlErrSendError; break; case CURLE_RECV_ERROR: /* 56 - failure in receiving network data */ exclz = eCurlErrRecvError; break; case CURLE_SHARE_IN_USE: /* 57 - share is in use */ exclz = eCurlErrShareInUse; break; case CURLE_SSL_CERTPROBLEM: /* 58 - problem with the local certificate */ exclz = eCurlErrSSLCertificate; break; case CURLE_SSL_CIPHER: /* 59 - couldn't use specified cipher */ exclz = eCurlErrSSLCipher; break; #if LIBCURL_VERSION_NUM >= 0x073e00 case CURLE_PEER_FAILED_VERIFICATION: /* 60 - problem with the CA cert (path?) */ exclz = eCurlErrSSLPeerCertificate; #else case CURLE_SSL_CACERT: /* 60 - problem with the CA cert (path?) */ exclz = eCurlErrSSLCACertificate; #endif break; case CURLE_BAD_CONTENT_ENCODING: /* 61 - Unrecognized transfer encoding */ exclz = eCurlErrBadContentEncoding; break; case CURLE_LDAP_INVALID_URL: /* 62 - Invalid LDAP URL */ exclz = eCurlErrLDAPInvalidURL; break; case CURLE_FILESIZE_EXCEEDED: /* 63 - Maximum file size exceeded */ exclz = eCurlErrFileSizeExceeded; break; case CURLE_FTP_SSL_FAILED: /* 64 - Requested FTP SSL level failed */ exclz = eCurlErrFTPSSLFailed; break; #ifdef HAVE_CURLE_SEND_FAIL_REWIND case CURLE_SEND_FAIL_REWIND: /* 65 - Sending the data requires a rewind that failed */ exclz = eCurlErrSendFailedRewind; break; #endif #ifdef HAVE_CURLE_SSL_ENGINE_INITFAILED case CURLE_SSL_ENGINE_INITFAILED: /* 66 - failed to initialise ENGINE */ exclz = eCurlErrSSLEngineInitFailed; break; #endif #ifdef HAVE_CURLE_LOGIN_DENIED case CURLE_LOGIN_DENIED: /* 67 - user, password or similar was not accepted and we failed to login */ exclz = eCurlErrLoginDenied; break; #endif // recent additions, may not be present in all supported versions #ifdef HAVE_CURLE_TFTP_NOTFOUND case CURLE_TFTP_NOTFOUND: /* 68 - file not found on server */ exclz = eCurlErrTFTPNotFound; break; #endif #ifdef HAVE_CURLE_TFTP_PERM case CURLE_TFTP_PERM: /* 69 - permission problem on server */ exclz = eCurlErrTFTPPermission; break; #endif #ifdef HAVE_CURLE_TFTP_DISKFULL case CURLE_TFTP_DISKFULL: /* 70 - out of disk space on server */ exclz = eCurlErrTFTPDiskFull; break; #endif #ifdef HAVE_CURLE_TFTP_ILLEGAL case CURLE_TFTP_ILLEGAL: /* 71 - Illegal TFTP operation */ exclz = eCurlErrTFTPIllegalOperation; break; #endif #ifdef HAVE_CURLE_TFTP_UNKNOWNID case CURLE_TFTP_UNKNOWNID: /* 72 - Unknown transfer ID */ exclz = eCurlErrTFTPUnknownID; break; #endif #ifdef HAVE_CURLE_TFTP_EXISTS case CURLE_TFTP_EXISTS: /* 73 - File already exists */ exclz = eCurlErrTFTPFileExists; break; #endif #ifdef HAVE_CURLE_TFTP_NOSUCHUSER case CURLE_TFTP_NOSUCHUSER: /* 74 - No such user */ exclz = eCurlErrTFTPNotFound; break; #endif #ifdef HAVE_CURLE_CONV_FAILED case CURLE_CONV_FAILED: /* 75 - conversion failed */ exclz = eCurlErrConvFailed; break; #endif #ifdef HAVE_CURLE_CONV_REQD case CURLE_CONV_REQD: /* 76 - caller must register conversion callbacks using curl_easy_setopt options CURLOPT_CONV_FROM_NETWORK_FUNCTION, CURLOPT_CONV_TO_NETWORK_FUNCTION, and CURLOPT_CONV_FROM_UTF8_FUNCTION */ exclz = eCurlErrConvReqd; break; #endif #ifdef HAVE_CURLE_SSL_CACERT_BADFILE case CURLE_SSL_CACERT_BADFILE: /* 77 - could not load CACERT file, missing or wrong format */ exclz = eCurlErrSSLCacertBadfile; break; #endif #ifdef HAVE_CURLE_REMOTE_FILE_NOT_FOUND case CURLE_REMOTE_FILE_NOT_FOUND: /* 78 - remote file not found */ exclz = eCurlErrRemoteFileNotFound; break; #endif #ifdef HAVE_CURLE_SSH case CURLE_SSH: /* 79 - error from the SSH layer, somewhat generic so the error message will be of interest when this has happened */ exclz = eCurlErrSSH; break; #endif #ifdef HAVE_CURLE_SSL_SHUTDOWN_FAILED case CURLE_SSL_SHUTDOWN_FAILED: /* 80 - Failed to shut down the SSL connection */ exclz = eCurlErrSSLShutdownFailed; break; #endif #ifdef HAVE_CURLE_AGAIN case CURLE_AGAIN: /* 81 - socket is not ready for send/recv, wait till it's ready and try again (Added in 7.18.2) */ exclz = eCurlErrAgain; break; #endif #ifdef HAVE_CURLE_SSL_CRL_BADFILE case CURLE_SSL_CRL_BADFILE: /* 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */ exclz = eCurlErrSSLCRLBadfile; break; #endif #ifdef HAVE_CURLE_SSL_ISSUER_ERROR case CURLE_SSL_ISSUER_ERROR: /* 83 - Issuer check failed. (Added in 7.19.0) */ exclz = eCurlErrSSLIssuerError; break; #endif #ifdef HAVE_CURLE_FTP_PRET_FAILED case CURLE_FTP_PRET_FAILED: /* 84 */ exclz = eCurlErrFTPPRETFailed; break; #endif #ifdef HAVE_CURLE_RTSP_CSEQ_ERROR case CURLE_RTSP_CSEQ_ERROR: /* 85 */ exclz = eCurlErrRTSPCseqError; break; #endif #ifdef HAVE_CURLE_RTSP_SESSION_ERROR case CURLE_RTSP_SESSION_ERROR: /* 86 */ exclz = eCurlErrRTSPSessionError; break; #endif #ifdef HAVE_CURLE_FTP_BAD_FILE_LIST case CURLE_FTP_BAD_FILE_LIST: /* 87 */ exclz = eCurlErrFTPBadFileList; break; #endif #ifdef HAVE_CURLE_CHUNK_FAILED case CURLE_CHUNK_FAILED: /* 88 */ exclz = eCurlErrChunkFailed; break; #endif #ifdef HAVE_CURLE_NO_CONNECTION_AVAILABLE case CURLE_NO_CONNECTION_AVAILABLE: /* 89 */ exclz = eCurlErrNoConnectionAvailable; break; #endif #ifdef HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH case CURLE_SSL_PINNEDPUBKEYNOTMATCH: /* 90 */ exclz = eCurlErrSSLPinnedPubKeyNotMatch; break; #endif #ifdef HAVE_CURLE_SSL_INVALIDCERTSTATUS case CURLE_SSL_INVALIDCERTSTATUS: /* 91 */ exclz = eCurlErrSSLInvalidCertStatus; break; #endif #ifdef HAVE_CURLE_HTTP2_STREAM case CURLE_HTTP2_STREAM: /* 92 */ exclz = eCurlErrHTTP2Stream; break; #endif default: exclz = eCurlErrError; exmsg = "Unknown error result from libcurl"; } if (!exmsg) { exmsg = curl_easy_strerror(code); } results = rb_ary_new2(2); rb_ary_push(results, exclz); rb_ary_push(results, rb_str_new2(exmsg)); return results; } /* rb_raise an approriate exception for the supplied CURLcode */ void raise_curl_easy_error_exception(CURLcode code) { VALUE obj = rb_curl_easy_error(code); VALUE exmsg = rb_ary_entry(obj,1); rb_raise(rb_ary_entry(obj,0), "CURLError: %s", StringValueCStr(exmsg)); } VALUE rb_curl_multi_error(CURLMcode code) { VALUE exclz; const char *exmsg = NULL; VALUE results; switch(code) { case CURLM_CALL_MULTI_PERFORM: /* -1 */ exclz = mCurlErrCallMultiPerform; break; case CURLM_BAD_HANDLE: /* 1 */ exclz = mCurlErrBadHandle; break; case CURLM_BAD_EASY_HANDLE: /* 2 */ exclz = mCurlErrBadEasyHandle; break; case CURLM_OUT_OF_MEMORY: /* 3 */ exclz = mCurlErrOutOfMemory; break; case CURLM_INTERNAL_ERROR: /* 4 */ exclz = mCurlErrInternalError; break; #ifdef HAVE_CURLM_BAD_SOCKET case CURLM_BAD_SOCKET: /* 5 */ exclz = mCurlErrBadSocket; break; #endif #ifdef HAVE_CURLM_UNKNOWN_OPTION case CURLM_UNKNOWN_OPTION: /* 6 */ exclz = mCurlErrUnknownOption; break; #endif #ifdef HAVE_CURLM_ADDED_ALREADY case CURLM_ADDED_ALREADY: /* 7 */ exclz = mCurlErrAddedAlready; break; #endif default: exclz = eCurlErrError; exmsg = "Unknown error result from libcurl"; } if (!exmsg) { exmsg = curl_multi_strerror(code); } results = rb_ary_new2(2); rb_ary_push(results, exclz); rb_ary_push(results, rb_str_new2(exmsg)); return results; } void raise_curl_multi_error_exception(CURLMcode code) { VALUE obj = rb_curl_multi_error(code); VALUE exmsg = rb_ary_entry(obj,1); rb_raise(rb_ary_entry(obj,0), "CURLError: %s", StringValueCStr(exmsg)); } void init_curb_errors() { mCurlErr = rb_define_module_under(mCurl, "Err"); eCurlErrError = rb_define_class_under(mCurlErr, "CurlError", rb_eRuntimeError); eCurlErrFTPError = rb_define_class_under(mCurlErr, "FTPError", eCurlErrError); eCurlErrHTTPError = rb_define_class_under(mCurlErr, "HTTPError", eCurlErrError); eCurlErrFileError = rb_define_class_under(mCurlErr, "FileError", eCurlErrError); eCurlErrLDAPError = rb_define_class_under(mCurlErr, "LDAPError", eCurlErrError); eCurlErrTelnetError = rb_define_class_under(mCurlErr, "TelnetError", eCurlErrError); eCurlErrTFTPError = rb_define_class_under(mCurlErr, "TFTPError", eCurlErrError); eCurlErrRTSPError = rb_define_class_under(mCurlErr, "RTSPError", eCurlErrError); eCurlErrOK = rb_define_class_under(mCurlErr, "CurlOK", eCurlErrError); eCurlErrUnsupportedProtocol = rb_define_class_under(mCurlErr, "UnsupportedProtocolError", eCurlErrError); eCurlErrFailedInit = rb_define_class_under(mCurlErr, "FailedInitError", eCurlErrError); eCurlErrMalformedURL = rb_define_class_under(mCurlErr, "MalformedURLError", eCurlErrError); eCurlErrNotBuiltIn = rb_define_class_under(mCurlErr, "NotBuiltInError", eCurlErrError); eCurlErrMalformedURLUser = rb_define_class_under(mCurlErr, "MalformedURLUserError", eCurlErrError); eCurlErrProxyResolution = rb_define_class_under(mCurlErr, "ProxyResolutionError", eCurlErrError); eCurlErrHostResolution = rb_define_class_under(mCurlErr, "HostResolutionError", eCurlErrError); eCurlErrConnectFailed = rb_define_class_under(mCurlErr, "ConnectionFailedError", eCurlErrError); eCurlErrFTPWeirdReply = rb_define_class_under(mCurlErr, "WeirdReplyError", eCurlErrFTPError); eCurlErrFTPAccessDenied = rb_define_class_under(mCurlErr, "AccessDeniedError", eCurlErrFTPError); eCurlErrFTPBadPassword = rb_define_class_under(mCurlErr, "BadPasswordError", eCurlErrFTPError); eCurlErrFTPWeirdPassReply = rb_define_class_under(mCurlErr, "WeirdPassReplyError", eCurlErrFTPError); eCurlErrFTPWeirdUserReply = rb_define_class_under(mCurlErr, "WeirdUserReplyError", eCurlErrFTPError); eCurlErrFTPWeirdPasvReply = rb_define_class_under(mCurlErr, "WeirdPasvReplyError", eCurlErrFTPError); eCurlErrFTPWeird227Format = rb_define_class_under(mCurlErr, "Weird227FormatError", eCurlErrFTPError); eCurlErrFTPCantGetHost = rb_define_class_under(mCurlErr, "CantGetHostError", eCurlErrFTPError); eCurlErrFTPCantReconnect = rb_define_class_under(mCurlErr, "CantReconnectError", eCurlErrFTPError); eCurlErrFTPCouldntSetBinary = rb_define_class_under(mCurlErr, "CouldntSetBinaryError", eCurlErrFTPError); eCurlErrPartialFile = rb_define_class_under(mCurlErr, "PartialFileError", eCurlErrError); eCurlErrFTPCouldntRetrFile = rb_define_class_under(mCurlErr, "CouldntRetrFileError", eCurlErrFTPError); eCurlErrFTPWrite = rb_define_class_under(mCurlErr, "FTPWriteError", eCurlErrFTPError); eCurlErrFTPQuote = rb_define_class_under(mCurlErr, "FTPQuoteError", eCurlErrFTPError); eCurlErrHTTPFailed = rb_define_class_under(mCurlErr, "HTTPFailedError", eCurlErrHTTPError); eCurlErrWriteError = rb_define_class_under(mCurlErr, "WriteError", eCurlErrError); eCurlErrMalformedUser = rb_define_class_under(mCurlErr, "MalformedUserError", eCurlErrError); eCurlErrFTPCouldntStorFile = rb_define_class_under(mCurlErr, "CouldntStorFileError", eCurlErrFTPError); eCurlErrReadError = rb_define_class_under(mCurlErr, "ReadError", eCurlErrError); eCurlErrOutOfMemory = rb_define_class_under(mCurlErr, "OutOfMemoryError", eCurlErrError); eCurlErrTimeout = rb_define_class_under(mCurlErr, "TimeoutError", eCurlErrError); eCurlErrFTPCouldntSetASCII = rb_define_class_under(mCurlErr, "CouldntSetASCIIError", eCurlErrFTPError); eCurlErrFTPPortFailed = rb_define_class_under(mCurlErr, "PortFailedError", eCurlErrFTPError); eCurlErrFTPCouldntUseRest = rb_define_class_under(mCurlErr, "CouldntUseRestError", eCurlErrFTPError); eCurlErrFTPCouldntGetSize = rb_define_class_under(mCurlErr, "CouldntGetSizeError", eCurlErrFTPError); eCurlErrHTTPRange = rb_define_class_under(mCurlErr, "HTTPRangeError", eCurlErrHTTPError); eCurlErrHTTPPost = rb_define_class_under(mCurlErr, "HTTPPostError", eCurlErrHTTPError); eCurlErrSSLConnectError = rb_define_class_under(mCurlErr, "SSLConnectError", eCurlErrError); eCurlErrBadResume = rb_define_class_under(mCurlErr, "BadResumeError", eCurlErrError); eCurlErrFileCouldntRead = rb_define_class_under(mCurlErr, "CouldntReadError", eCurlErrFileError); eCurlErrLDAPCouldntBind = rb_define_class_under(mCurlErr, "CouldntBindError", eCurlErrLDAPError); eCurlErrLDAPSearchFailed = rb_define_class_under(mCurlErr, "SearchFailedError", eCurlErrLDAPError); eCurlErrLibraryNotFound = rb_define_class_under(mCurlErr, "LibraryNotFoundError", eCurlErrError); eCurlErrFunctionNotFound = rb_define_class_under(mCurlErr, "FunctionNotFoundError", eCurlErrError); eCurlErrAbortedByCallback = rb_define_class_under(mCurlErr, "AbortedByCallbackError", eCurlErrError); eCurlErrBadFunctionArgument = rb_define_class_under(mCurlErr, "BadFunctionArgumentError", eCurlErrError); eCurlErrBadCallingOrder = rb_define_class_under(mCurlErr, "BadCallingOrderError", eCurlErrError); eCurlErrInterfaceFailed = rb_define_class_under(mCurlErr, "InterfaceFailedError", eCurlErrError); eCurlErrBadPasswordEntered = rb_define_class_under(mCurlErr, "BadPasswordEnteredError", eCurlErrError); eCurlErrTooManyRedirects = rb_define_class_under(mCurlErr, "TooManyRedirectsError", eCurlErrError); eCurlErrTelnetUnknownOption = rb_define_class_under(mCurlErr, "UnknownOptionError", eCurlErrTelnetError); eCurlErrTelnetBadOptionSyntax = rb_define_class_under(mCurlErr, "BadOptionSyntaxError", eCurlErrTelnetError); eCurlErrObsolete = rb_define_class_under(mCurlErr, "ObsoleteError", eCurlErrError); eCurlErrSSLPeerCertificate = rb_define_class_under(mCurlErr, "SSLPeerCertificateError", eCurlErrError); eCurlErrGotNothing = rb_define_class_under(mCurlErr, "GotNothingError", eCurlErrError); eCurlErrSSLEngineNotFound = rb_define_class_under(mCurlErr, "SSLEngineNotFoundError", eCurlErrError); eCurlErrSSLEngineSetFailed = rb_define_class_under(mCurlErr, "SSLEngineSetFailedError", eCurlErrError); eCurlErrSendError = rb_define_class_under(mCurlErr, "SendError", eCurlErrError); eCurlErrRecvError = rb_define_class_under(mCurlErr, "RecvError", eCurlErrError); eCurlErrShareInUse = rb_define_class_under(mCurlErr, "ShareInUseError", eCurlErrError); eCurlErrConvFailed = rb_define_class_under(mCurlErr, "ConvFailed", eCurlErrError); eCurlErrConvReqd = rb_define_class_under(mCurlErr, "ConvReqd", eCurlErrError); eCurlErrRemoteFileNotFound = rb_define_class_under(mCurlErr, "RemoteFileNotFound", eCurlErrError); eCurlErrAgain = rb_define_class_under(mCurlErr, "Again", eCurlErrError); eCurlErrSSLCertificate = rb_define_class_under(mCurlErr, "SSLCertificateError", eCurlErrError); eCurlErrSSLCipher = rb_define_class_under(mCurlErr, "SSLCypherError", eCurlErrError); eCurlErrSSLCACertificate = rb_define_class_under(mCurlErr, "SSLCACertificateError", eCurlErrError); eCurlErrBadContentEncoding = rb_define_class_under(mCurlErr, "BadContentEncodingError", eCurlErrError); eCurlErrSSLCacertBadfile = rb_define_class_under(mCurlErr, "SSLCaertBadFile", eCurlErrError); eCurlErrSSLCRLBadfile = rb_define_class_under(mCurlErr, "SSLCRLBadfile", eCurlErrError); eCurlErrSSLIssuerError = rb_define_class_under(mCurlErr, "SSLIssuerError", eCurlErrError); eCurlErrSSLShutdownFailed = rb_define_class_under(mCurlErr, "SSLShutdownFailed", eCurlErrError); eCurlErrSSH = rb_define_class_under(mCurlErr, "SSH", eCurlErrError); mCurlErrFailedInit = rb_define_class_under(mCurlErr, "MultiInitError", eCurlErrError); mCurlErrCallMultiPerform = rb_define_class_under(mCurlErr, "MultiPerform", eCurlErrError); mCurlErrBadHandle = rb_define_class_under(mCurlErr, "MultiBadHandle", eCurlErrError); mCurlErrBadEasyHandle = rb_define_class_under(mCurlErr, "MultiBadEasyHandle", eCurlErrError); mCurlErrOutOfMemory = rb_define_class_under(mCurlErr, "MultiOutOfMemory", eCurlErrError); mCurlErrInternalError = rb_define_class_under(mCurlErr, "MultiInternalError", eCurlErrError); mCurlErrBadSocket = rb_define_class_under(mCurlErr, "MultiBadSocket", eCurlErrError); #ifdef HAVE_CURLM_ADDED_ALREADY mCurlErrAddedAlready = rb_define_class_under(mCurlErr, "MultiAddedAlready", eCurlErrError); #endif mCurlErrUnknownOption = rb_define_class_under(mCurlErr, "MultiUnknownOption", eCurlErrError); eCurlErrLDAPInvalidURL = rb_define_class_under(mCurlErr, "InvalidLDAPURLError", eCurlErrLDAPError); eCurlErrFileSizeExceeded = rb_define_class_under(mCurlErr, "FileSizeExceededError", eCurlErrError); eCurlErrFTPSSLFailed = rb_define_class_under(mCurlErr, "FTPSSLFailed", eCurlErrFTPError); eCurlErrSendFailedRewind = rb_define_class_under(mCurlErr, "SendFailedRewind", eCurlErrError); eCurlErrSSLEngineInitFailed = rb_define_class_under(mCurlErr, "SSLEngineInitFailedError", eCurlErrError); eCurlErrLoginDenied = rb_define_class_under(mCurlErr, "LoginDeniedError", eCurlErrError); eCurlErrTFTPNotFound = rb_define_class_under(mCurlErr, "NotFoundError", eCurlErrTFTPError); eCurlErrTFTPPermission = rb_define_class_under(mCurlErr, "PermissionError", eCurlErrTFTPError); eCurlErrTFTPDiskFull = rb_define_class_under(mCurlErr, "DiskFullError", eCurlErrTFTPError); eCurlErrTFTPIllegalOperation = rb_define_class_under(mCurlErr, "IllegalOperationError", eCurlErrTFTPError); eCurlErrTFTPUnknownID = rb_define_class_under(mCurlErr, "UnknownIDError", eCurlErrTFTPError); eCurlErrTFTPFileExists = rb_define_class_under(mCurlErr, "FileExistsError", eCurlErrTFTPError); eCurlErrTFTPNoSuchUser = rb_define_class_under(mCurlErr, "NoSuchUserError", eCurlErrTFTPError); eCurlErrInvalidPostField = rb_define_class_under(mCurlErr, "InvalidPostFieldError", eCurlErrError); eCurlErrFTPPRETFailed = rb_define_class_under(mCurlErr, "PPRETFailedError", eCurlErrFTPError); eCurlErrRTSPCseqError = rb_define_class_under(mCurlErr, "CseqError", eCurlErrRTSPError); eCurlErrRTSPSessionError = rb_define_class_under(mCurlErr, "SessionError", eCurlErrRTSPError); eCurlErrFTPBadFileList = rb_define_class_under(mCurlErr, "BadFileListError", eCurlErrFTPError); eCurlErrChunkFailed = rb_define_class_under(mCurlErr, "ChunkFailedError", eCurlErrError); eCurlErrNoConnectionAvailable = rb_define_class_under(mCurlErr, "NoConnectionAvailableError", eCurlErrError); eCurlErrSSLPinnedPubKeyNotMatch = rb_define_class_under(mCurlErr, "SSLPinnedPubKeyNotMatchError", eCurlErrError); eCurlErrSSLInvalidCertStatus = rb_define_class_under(mCurlErr, "SSLInvalidCertStatusError", eCurlErrError); eCurlErrHTTP2Stream = rb_define_class_under(mCurlErr, "HTTP2StreamError", eCurlErrHTTPError); } curb-1.3.5/ext/curb.h0000644000004100000410000000222215203731642014410 0ustar www-datawww-data/* Curb - Libcurl(3) bindings for Ruby. * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb.h 39 2006-12-23 15:28:45Z roscopeco $ */ #ifndef __CURB_H #define __CURB_H #include #ifdef HAVE_RUBY_IO_H #include "ruby/io.h" #else #include "rubyio.h" // ruby 1.8 #endif #include #include "banned.h" #include "curb_config.h" #include "curb_easy.h" #include "curb_errors.h" #include "curb_postfield.h" #include "curb_multi.h" #include "curb_macros.h" // These should be managed from the Rake 'release' task. #define CURB_VERSION "1.3.5" #define CURB_VER_NUM 1035 #define CURB_VER_MAJ 1 #define CURB_VER_MIN 3 #define CURB_VER_MIC 5 #define CURB_VER_PATCH 0 // Maybe not yet defined in Ruby #ifndef RSTRING_LEN #define RSTRING_LEN(x) RSTRING(x)->len #endif #ifndef RSTRING_PTR #define RSTRING_PTR(x) RSTRING(x)->ptr #endif #ifndef RHASH_SIZE #define RHASH_SIZE(hash) RHASH(hash)->tbl->num_entries #endif // ruby 1.8 does not provide the macro #ifndef DBL2NUM #define DBL2NUM(dbl) rb_float_new(dbl) #endif extern VALUE mCurl; extern void Init_curb_core(); #endif curb-1.3.5/ext/curb_postfield.h0000644000004100000410000000207515203731642016467 0ustar www-datawww-data/* curb_postfield.h - Field class for POST method * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_postfield.h 4 2006-11-17 18:35:31Z roscopeco $ */ #ifndef __CURB_POSTFIELD_H #define __CURB_POSTFIELD_H #include "curb.h" /* * postfield doesn't actually wrap a curl_httppost - instead, * it just holds together some ruby objects and has a C-side * method to add it to a given form list during the perform. */ typedef struct { /* Objects we associate */ VALUE name; VALUE content; VALUE content_type; VALUE content_proc; VALUE local_file; VALUE remote_file; /* this will sometimes hold a string, which is the result * of the content_proc invocation. We need it to hang around. */ VALUE buffer_str; } ruby_curl_postfield; extern VALUE cCurlPostField; extern const rb_data_type_t ruby_curl_postfield_data_type; void append_to_form(VALUE self, struct curl_httppost **first, struct curl_httppost **last); void init_curb_postfield(); #endif curb-1.3.5/ext/banned.h0000644000004100000410000000143315203731642014707 0ustar www-datawww-data#ifndef BANNED_H #define BANNED_H /* * This header lists functions that have been banned from our code base, * because they're too easy to misuse (and even if used correctly, * complicate audits). Including this header turns them into compile-time * errors. */ #define BANNED(func) sorry_##func##_is_a_banned_function #undef strcpy #define strcpy(x,y) BANNED(strcpy) #undef strcat #define strcat(x,y) BANNED(strcat) #undef strncpy #define strncpy(x,y,n) BANNED(strncpy) #undef strncat #define strncat(x,y,n) BANNED(strncat) #undef sprintf #undef vsprintf #ifdef HAVE_VARIADIC_MACROS #define sprintf(...) BANNED(sprintf) #define vsprintf(...) BANNED(vsprintf) #else #define sprintf(buf,fmt,arg) BANNED(sprintf) #define vsprintf(buf,fmt,arg) BANNED(sprintf) #endif #endif /* BANNED_H */ curb-1.3.5/ext/curb_errors.h0000644000004100000410000001035115203731642016006 0ustar www-datawww-data/* curb_errors.h - Ruby exception types for curl errors * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_errors.h 4 2006-11-17 18:35:31Z roscopeco $ */ #ifndef __CURB_ERRORS_H #define __CURB_ERRORS_H #include "curb.h" /* base errors */ extern VALUE cCurlErr; /* easy errors */ extern VALUE mCurlErr; extern VALUE eCurlErrError; extern VALUE eCurlErrFTPError; extern VALUE eCurlErrHTTPError; extern VALUE eCurlErrFileError; extern VALUE eCurlErrLDAPError; extern VALUE eCurlErrTelnetError; extern VALUE eCurlErrTFTPError; /* libcurl errors */ extern VALUE eCurlErrUnsupportedProtocol; extern VALUE eCurlErrFailedInit; extern VALUE eCurlErrMalformedURL; extern VALUE eCurlErrMalformedURLUser; extern VALUE eCurlErrProxyResolution; extern VALUE eCurlErrHostResolution; extern VALUE eCurlErrConnectFailed; extern VALUE eCurlErrFTPWeirdReply; extern VALUE eCurlErrFTPAccessDenied; extern VALUE eCurlErrFTPBadPassword; extern VALUE eCurlErrFTPWeirdPassReply; extern VALUE eCurlErrFTPWeirdUserReply; extern VALUE eCurlErrFTPWeirdPasvReply; extern VALUE eCurlErrFTPWeird227Format; extern VALUE eCurlErrFTPCantGetHost; extern VALUE eCurlErrFTPCantReconnect; extern VALUE eCurlErrFTPCouldntSetBinary; extern VALUE eCurlErrPartialFile; extern VALUE eCurlErrFTPCouldntRetrFile; extern VALUE eCurlErrFTPWrite; extern VALUE eCurlErrFTPQuote; extern VALUE eCurlErrHTTPFailed; extern VALUE eCurlErrWriteError; extern VALUE eCurlErrMalformedUser; extern VALUE eCurlErrFTPCouldntStorFile; extern VALUE eCurlErrReadError; extern VALUE eCurlErrOutOfMemory; extern VALUE eCurlErrTimeout; extern VALUE eCurlErrFTPCouldntSetASCII; extern VALUE eCurlErrFTPPortFailed; extern VALUE eCurlErrFTPCouldntUseRest; extern VALUE eCurlErrFTPCouldntGetSize; extern VALUE eCurlErrHTTPRange; extern VALUE eCurlErrHTTPPost; extern VALUE eCurlErrSSLConnectError; extern VALUE eCurlErrBadResume; extern VALUE eCurlErrFileCouldntRead; extern VALUE eCurlErrLDAPCouldntBind; extern VALUE eCurlErrLDAPSearchFailed; extern VALUE eCurlErrLibraryNotFound; extern VALUE eCurlErrFunctionNotFound; extern VALUE eCurlErrAbortedByCallback; extern VALUE eCurlErrBadFunctionArgument; extern VALUE eCurlErrBadCallingOrder; extern VALUE eCurlErrInterfaceFailed; extern VALUE eCurlErrBadPasswordEntered; extern VALUE eCurlErrTooManyRedirects; extern VALUE eCurlErrTelnetUnknownOption; extern VALUE eCurlErrTelnetBadOptionSyntax; extern VALUE eCurlErrObsolete; extern VALUE eCurlErrSSLPeerCertificate; extern VALUE eCurlErrGotNothing; extern VALUE eCurlErrSSLEngineNotFound; extern VALUE eCurlErrSSLEngineSetFailed; extern VALUE eCurlErrSendError; extern VALUE eCurlErrRecvError; extern VALUE eCurlErrShareInUse; extern VALUE eCurlErrSSLCertificate; extern VALUE eCurlErrSSLCipher; extern VALUE eCurlErrSSLCACertificate; extern VALUE eCurlErrBadContentEncoding; extern VALUE eCurlErrLDAPInvalidURL; extern VALUE eCurlErrFileSizeExceeded; extern VALUE eCurlErrFTPSSLFailed; extern VALUE eCurlErrSendFailedRewind; extern VALUE eCurlErrSSLEngineInitFailed; extern VALUE eCurlErrLoginDenied; extern VALUE eCurlErrTFTPNotFound; extern VALUE eCurlErrTFTPPermission; extern VALUE eCurlErrTFTPDiskFull; extern VALUE eCurlErrTFTPIllegalOperation; extern VALUE eCurlErrTFTPUnknownID; extern VALUE eCurlErrTFTPFileExists; extern VALUE eCurlErrTFTPNoSuchUser; extern VALUE eCurlErrConvFailed; extern VALUE eCurlErrConvReqd; extern VALUE eCurlErrSSLCacertBadfile; extern VALUE eCurlErrRemoteFileNotFound; extern VALUE eCurlErrSSH; extern VALUE eCurlErrSSLShutdownFailed; extern VALUE eCurlErrAgain; extern VALUE eCurlErrSSLCRLBadfile; extern VALUE eCurlErrSSLIssuerError; /* multi errors */ extern VALUE mCurlErrFailedInit; extern VALUE mCurlErrCallMultiPerform; extern VALUE mCurlErrBadHandle; extern VALUE mCurlErrBadEasyHandle; extern VALUE mCurlErrOutOfMemory; extern VALUE mCurlErrInternalError; extern VALUE mCurlErrBadSocket; extern VALUE mCurlErrUnknownOption; #ifdef HAVE_CURLM_ADDED_ALREADY extern VALUE mCurlErrAddedAlready; #endif /* binding errors */ extern VALUE eCurlErrInvalidPostField; void init_curb_errors(); void raise_curl_easy_error_exception(CURLcode code); void raise_curl_multi_error_exception(CURLMcode code); VALUE rb_curl_easy_error(CURLcode code); VALUE rb_curl_multi_error(CURLMcode code); #endif curb-1.3.5/ext/curb_multi.c0000644000004100000410000022176715203731642015636 0ustar www-datawww-data/* curb_multi.c - Curl multi mode * Copyright (c)2008 Todd A. Fisher. * Licensed under the Ruby License. See LICENSE for details. * */ #include "curb_config.h" #include #ifdef HAVE_RUBY_IO_H #include #endif #ifdef HAVE_RUBY_ST_H #include #else #include #endif #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL #include #endif #ifdef HAVE_RUBY_FIBER_SCHEDULER_H #include #endif #include "curb_easy.h" #include "curb_errors.h" #include "curb_postfield.h" #include "curb_multi.h" #include #include #ifndef _WIN32 #include #include #endif #include #include /* * Optional socket-action debug logging. Enabled by defining CURB_SOCKET_DEBUG=1 * at compile time (e.g. via environment variable passed to extconf.rb). */ #ifndef CURB_SOCKET_DEBUG #define CURB_SOCKET_DEBUG 0 #endif #if !CURB_SOCKET_DEBUG #define curb_debugf(...) ((void)0) #endif #ifdef RBIMPL_ATTR_MAYBE_UNUSED #define CURB_MAYBE_UNUSED_DECL RBIMPL_ATTR_MAYBE_UNUSED() #else #define CURB_MAYBE_UNUSED_DECL #endif #ifdef _WIN32 // for O_RDWR and O_BINARY #include #endif #if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT) struct wait_args { CURLM *handle; long timeout_ms; int numfds; }; static void *curl_multi_wait_wrapper(void *p) { struct wait_args *args = p; CURLMcode code = curl_multi_wait(args->handle, NULL, 0, args->timeout_ms, &args->numfds); return (void *)(intptr_t)code; } #endif extern VALUE mCurl; static VALUE idCall; static ID id_deferred_exception_ivar; static ID id_deferred_exception_source_id_ivar; static ID id_socket_io_cache_ivar; #ifdef RDOC_NEVER_DEFINED mCurl = rb_define_module("Curl"); #endif VALUE cCurlMulti; static long cCurlMutiDefaulttimeout = 100; /* milliseconds */ static char cCurlMutiAutoClose = 0; static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result); static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy); static void rb_curl_multi_read_info(VALUE self, CURLM *mptr); static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running); static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg); static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm); static void curl_multi_mark(void *ptr); static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm); static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce); static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy); static VALUE ruby_curl_multi_mark_closed(VALUE self); static VALUE ruby_curl_multi_alloc(VALUE klass); static VALUE ruby_curl_multi_initialize(VALUE self); static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self); #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32) static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self); #endif static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) { if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) { return NULL; } #if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA) if (!RTYPEDDATA_P(multi_val)) { return NULL; } if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) { return NULL; } return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val); #else if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) { return NULL; } return DATA_PTR(multi_val); #endif } static VALUE callback_exception(VALUE did_raise, VALUE exception) { // TODO: we could have an option to enable exception reporting /* VALUE ret = rb_funcall(exception, rb_intern("message"), 0); VALUE trace = rb_funcall(exception, rb_intern("backtrace"), 0); if (RB_TYPE_P(trace, T_ARRAY) && RARRAY_LEN(trace) > 0) { printf("we got an exception: %s:%d\n", StringValueCStr(ret), RARRAY_LEN(trace)); VALUE sep = rb_str_new_cstr("\n"); VALUE trace_lines = rb_ary_join(trace, sep); if (RB_TYPE_P(trace_lines, T_STRING)) { printf("%s\n", StringValueCStr(trace_lines)); } else { printf("trace is not a string??\n"); } } else { printf("we got an exception: %s\nno stack available\n", StringValueCStr(ret)); } */ rb_hash_aset(did_raise, rb_easy_hkey("error"), exception); return exception; } static VALUE take_easy_callback_error_if_any(VALUE easy) { ruby_curl_easy *rbce = NULL; if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) { return Qnil; } TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return rb_curl_easy_take_callback_error(rbce); } static VALUE build_aborted_by_callback_exception(VALUE exception) { VALUE message = rb_funcall(exception, rb_intern("message"), 0); VALUE aborted_exception = rb_exc_new_str(eCurlErrAbortedByCallback, message); VALUE backtrace = rb_funcall(exception, rb_intern("backtrace"), 0); rb_funcall(aborted_exception, rb_intern("set_backtrace"), 1, backtrace); return aborted_exception; } static void stash_multi_exception_if_unset(VALUE self, VALUE exception, VALUE source_easy) { if (rb_ivar_defined(self, id_deferred_exception_ivar)) { return; } rb_ivar_set(self, id_deferred_exception_ivar, exception); if (!NIL_P(source_easy)) { rb_ivar_set(self, id_deferred_exception_source_id_ivar, rb_obj_id(source_easy)); } } static void clear_multi_deferred_exception_source_id_if_any(VALUE self) { if (rb_ivar_defined(self, id_deferred_exception_source_id_ivar)) { rb_funcall(self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_deferred_exception_source_id_ivar)); } } static VALUE clear_multi_deferred_exception_if_any(VALUE self) { VALUE exception = Qnil; if (rb_ivar_defined(self, id_deferred_exception_ivar)) { exception = rb_funcall(self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_deferred_exception_ivar)); } return exception; } static void rb_curl_multi_yield_if_given(VALUE self, VALUE block) { if (block == Qnil) { return; } rb_funcall(block, idCall, 1, self); } static void raise_multi_deferred_exception_if_idle(VALUE self) { ruby_curl_multi *rbcm = NULL; if (!rb_ivar_defined(self, id_deferred_exception_ivar)) { return; } TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); if (rbcm && rbcm->active > 0) { return; } VALUE exception = clear_multi_deferred_exception_if_any(self); rb_exc_raise(exception); } static VALUE take_status_callback_exception_if_any(VALUE did_raise) { VALUE exception = rb_hash_aref(did_raise, rb_easy_hkey("error")); if (FIX2INT(rb_hash_size(did_raise)) > 0 && exception != Qnil) { rb_hash_clear(did_raise); return build_aborted_by_callback_exception(exception); } return Qnil; } static void raise_completion_callback_error_if_any(VALUE did_raise, VALUE easy_callback_error) { if (!NIL_P(easy_callback_error)) { rb_exc_raise(easy_callback_error); } VALUE exception = take_status_callback_exception_if_any(did_raise); if (!NIL_P(exception)) { rb_exc_raise(exception); } } struct multi_handle_complete_args { VALUE self; CURL *easy_handle; int result; }; static VALUE rb_curl_mutli_handle_complete_protected(VALUE argp) { struct multi_handle_complete_args *args = (struct multi_handle_complete_args *)argp; rb_curl_mutli_handle_complete(args->self, args->easy_handle, args->result); return Qnil; } static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg) { ruby_curl_multi *rbcm = (ruby_curl_multi *)arg; VALUE easy = (VALUE)val; ruby_curl_easy *rbce = NULL; if (RB_TYPE_P(easy, T_DATA)) { TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); } if (!rbce) { return ST_CONTINUE; } if (rbcm && rbcm->handle && rbce->curl) { curl_multi_remove_handle(rbcm->handle, rbce->curl); } rbce->multi = Qnil; return ST_CONTINUE; } void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) { ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr; if (!rbcm || !rbce || !rbcm->attached) { return; } st_data_t key = (st_data_t)rbce; st_delete(rbcm->attached, &key, NULL); } static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) { if (!rbcm || !rbcm->attached) { return; } st_table *attached = rbcm->attached; rbcm->attached = NULL; st_foreach(attached, detach_easy_entry, (st_data_t)rbcm); st_free_table(attached); rbcm->active = 0; rbcm->running = 0; } static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) { st_data_t value = 0; if (!rbcm || !rbce || !rbcm->attached) { return 0; } return st_lookup(rbcm->attached, (st_data_t)rbce, &value); } static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) { VALUE requests; if (NIL_P(self) || NIL_P(easy)) { return; } requests = rb_funcall(self, rb_intern("requests"), 0); if (!RB_TYPE_P(requests, T_HASH)) { return; } rb_hash_delete(requests, rb_obj_id(easy)); } /* TypedData-compatible free function */ static void curl_multi_free(void *ptr) { ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr; if (!rbcm) { return; } rb_curl_multi_detach_all(rbcm); if (rbcm->handle) { curl_multi_cleanup(rbcm->handle); rbcm->handle = NULL; } free(rbcm); } static size_t curl_multi_memsize(const void *ptr) { (void)ptr; return sizeof(ruby_curl_multi); } const rb_data_type_t ruby_curl_multi_data_type = { "Curl::Multi", { curl_multi_mark, curl_multi_free, curl_multi_memsize, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, /* compact */ #endif }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, NULL, /* parent, data */ RUBY_TYPED_FREE_IMMEDIATELY #endif }; static void ruby_curl_multi_init(ruby_curl_multi *rbcm) { rbcm->handle = curl_multi_init(); if (!rbcm->handle) { rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle"); } rbcm->active = 0; rbcm->running = 0; rbcm->closed = 0; rbcm->perform_active = 0; rbcm->callback_active = 0; rbcm->allow_close_during_perform = 0; if (rbcm->attached) { st_free_table(rbcm->attached); rbcm->attached = NULL; } rbcm->attached = st_init_numtable(); if (!rbcm->attached) { curl_multi_cleanup(rbcm->handle); rbcm->handle = NULL; rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table"); } } static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm) { if (!rbcm->handle) { if (rbcm->closed) { raise_curl_multi_error_exception(CURLM_BAD_HANDLE); } ruby_curl_multi_init(rbcm); } } /* * call-seq: * Curl::Multi.new => # * * Create a new Curl::Multi instance */ static VALUE ruby_curl_multi_alloc(VALUE klass) { VALUE self; ruby_curl_multi *rbcm; self = TypedData_Make_Struct(klass, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); MEMZERO(rbcm, ruby_curl_multi, 1); return self; } static VALUE ruby_curl_multi_initialize(VALUE self) { ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_init(rbcm); /* * The mark routine will be called by the garbage collector during its ``mark'' phase. * If your structure references other Ruby objects, then your mark function needs to * identify these objects using rb_gc_mark(value). If the structure doesn't reference * other Ruby objects, you can simply pass 0 as a function pointer. */ return self; } /* * call-seq: * Curl::Multi.default_timeout = 4 => 4 * * Set the global default time out for all Curl::Multi Handles. This value is used * when libcurl cannot determine a timeout value when calling curl_multi_timeout. * */ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) { cCurlMutiDefaulttimeout = NUM2LONG(timeout); return timeout; } /* * call-seq: * Curl::Multi.default_timeout = 4 => 4 * * Get the global default time out for all Curl::Multi Handles. * */ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) { return LONG2NUM(cCurlMutiDefaulttimeout); } /* * call-seq: * Curl::Multi.autoclose = true => true * * Automatically close open connections after each request. Otherwise, the connection will remain open * for reuse until the next GC * */ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) { cCurlMutiAutoClose = ((onoff == Qtrue) ? 1 : 0); return onoff; } /* * call-seq: * Curl::Multi.autoclose => true|false * * Get the global default autoclose setting for all Curl::Multi Handles. * */ VALUE ruby_curl_multi_get_autoclose(VALUE klass) { return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse; } /* * call-seq: * multi.requests => [#, ...] * * Returns an array containing all the active requests on this Curl::Multi object. */ /* * call-seq: * multi = Curl::Multi.new * multi.max_connects = 800 * * Set the max connections in the cache for a multi handle */ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) { #ifdef HAVE_CURLMOPT_MAXCONNECTS ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_ensure_handle(rbcm); curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2LONG(count)); #endif return count; } /* * call-seq: * multi = Curl::Multi.new * multi.max_host_connections = 1 * * Set the max number of connections per host */ static VALUE ruby_curl_multi_max_host_connections(VALUE self, VALUE count) { #ifdef HAVE_CURLMOPT_MAX_HOST_CONNECTIONS ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_ensure_handle(rbcm); curl_multi_setopt(rbcm->handle, CURLMOPT_MAX_HOST_CONNECTIONS, NUM2LONG(count)); #endif return count; } /* * call-seq: * multi = Curl::Multi.new * multi.pipeline = true * * Pass a long set to 1 for HTTP/1.1 pipelining, 2 for HTTP/2 multiplexing, or 0 to disable. * Enabling pipelining on a multi handle will make it attempt to perform HTTP Pipelining as * far as possible for transfers using this handle. This means that if you add a second request * that can use an already existing connection, the second request will be "piped" on the same * connection rather than being executed in parallel. (Added in 7.16.0, multiplex added in 7.43.0) * */ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) { #ifdef HAVE_CURLMOPT_PIPELINING ruby_curl_multi *rbcm; long value; if (method == Qtrue) { value = 1; } else if (method == Qfalse) { value = 0; } else { value = NUM2LONG(method); } TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_ensure_handle(rbcm); curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, value); #endif return method == Qtrue ? 1 : 0; } /* * call-seq: * multi = Curl::Multi.new * easy = Curl::Easy.new('url') * * multi.add(easy) * * Add an easy handle to the multi stack */ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) { CURLMcode mcode; ruby_curl_easy *rbce; ruby_curl_multi *rbcm; ruby_curl_multi *existing_rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); ruby_curl_multi_ensure_handle(rbcm); if (rb_curl_multi_has_easy(rbcm, rbce)) { return self; } existing_rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi); if (existing_rbcm && existing_rbcm != rbcm && rb_curl_multi_has_easy(existing_rbcm, rbce)) { rb_raise(rb_eRuntimeError, "Cannot add an active Curl::Easy handle to another Curl::Multi"); } /* setup the easy handle */ ruby_curl_easy_setup( rbce ); mcode = curl_multi_add_handle(rbcm->handle, rbce->curl); if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) { ruby_curl_easy_cleanup(easy, rbce); raise_curl_multi_error_exception(mcode); } rbcm->active++; /* Increase the running count, so that the perform loop keeps running. * If this number is not correct, the next call to curl_multi_perform will correct it. */ rbcm->running++; if (!rbcm->attached) { rbcm->attached = st_init_numtable(); if (!rbcm->attached) { curl_multi_remove_handle(rbcm->handle, rbce->curl); ruby_curl_easy_cleanup(easy, rbce); rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table"); } } rbce->multi_attachment_generation++; st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy); /* track a reference to associated multi handle */ rbce->multi = self; return self; } /* * call-seq: * multi = Curl::Multi.new * easy = Curl::Easy.new('url') * * multi.add(easy) * * # sometime later * multi.remove(easy) * * Remove an easy handle from a multi stack. * * Will raise an exception if the easy handle is not found */ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) { ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); rb_curl_multi_remove(rbcm, rb_easy_handle); return self; } static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) { CURLMcode result; ruby_curl_easy *rbce; TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); result = curl_multi_remove_handle(rbcm->handle, rbce->curl); if (result != 0) { raise_curl_multi_error_exception(result); } if (rbcm->active > 0) { rbcm->active--; } rbce->multi = Qnil; ruby_curl_easy_cleanup( easy, rbce ); rb_curl_multi_forget_easy(rbcm, rbce); } // on_success, on_failure, on_complete static VALUE call_status_handler1(VALUE ary) { return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1)); } static VALUE call_status_handler2(VALUE ary) { return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2)); } static void flush_stderr_if_any(ruby_curl_easy *rbce) { VALUE stderr_io = rb_easy_get("stderr_io"); if (stderr_io != Qnil) { /* Flush via Ruby IO API */ rb_funcall(stderr_io, rb_intern("flush"), 0); #ifdef HAVE_RUBY_IO_H /* Additionally flush underlying FILE* to be extra safe. */ rb_io_t *open_f_ptr; if (RB_TYPE_P(stderr_io, T_FILE)) { GetOpenFile(stderr_io, open_f_ptr); FILE *fp = rb_io_stdio_file(open_f_ptr); if (fp) fflush(fp); } #endif } } /* Helper to locate the Ruby Easy VALUE from the attached table using the * underlying CURL* handle when CURLINFO_PRIVATE is unavailable or stale. */ struct find_easy_ctx { CURL *handle; VALUE easy; }; static int find_easy_by_handle_i(st_data_t key, st_data_t val, st_data_t arg) { ruby_curl_easy *rbce = (ruby_curl_easy *)key; struct find_easy_ctx *ctx = (struct find_easy_ctx *)arg; if (rbce && rbce->curl == ctx->handle) { ctx->easy = (VALUE)val; return ST_STOP; } return ST_CONTINUE; } static VALUE find_easy_by_handle(ruby_curl_multi *rbcm, CURL *easy_handle) { if (!rbcm || !rbcm->attached) return Qnil; struct find_easy_ctx ctx; ctx.handle = easy_handle; ctx.easy = Qnil; st_foreach(rbcm->attached, find_easy_by_handle_i, (st_data_t)&ctx); return ctx.easy; } static VALUE find_easy_value_for_handle(ruby_curl_multi *rbcm, CURL *easy_handle) { VALUE easy = Qnil; ruby_curl_easy *rbce = NULL; CURLcode private_rc = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char **)&rbce); if (private_rc == CURLE_OK && rbce) { easy = rbce->self; } if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) { easy = find_easy_by_handle(rbcm, easy_handle); } return easy; } struct multi_complete_callback_args { VALUE self; VALUE easy; ruby_curl_multi *rbcm; ruby_curl_easy *rbce; int result; st_table *attached_snapshot; }; static int snapshot_attached_easy_i(st_data_t key, st_data_t val, st_data_t arg) { st_table *snapshot = (st_table *)arg; ruby_curl_easy *rbce = (ruby_curl_easy *)key; if (!rbce) { return ST_CONTINUE; } st_insert(snapshot, key, (st_data_t)rbce->multi_attachment_generation); return ST_CONTINUE; } static st_table *capture_attached_easy_snapshot(ruby_curl_multi *rbcm) { st_table *snapshot = st_init_numtable(); if (!snapshot) { rb_raise(rb_eNoMemError, "Failed to allocate multi callback snapshot table"); } if (rbcm && rbcm->attached) { st_foreach(rbcm->attached, snapshot_attached_easy_i, (st_data_t)snapshot); } return snapshot; } struct collect_new_attached_easies_ctx { st_table *snapshot; VALUE easies; }; static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t arg) { st_data_t existing = 0; ruby_curl_easy *rbce = (ruby_curl_easy *)key; struct collect_new_attached_easies_ctx *ctx = (struct collect_new_attached_easies_ctx *)arg; if (!rbce) { return ST_CONTINUE; } if (!ctx->snapshot || !st_lookup(ctx->snapshot, key, &existing) || existing != (st_data_t)rbce->multi_attachment_generation) { rb_ary_push(ctx->easies, (VALUE)val); } return ST_CONTINUE; } static void rb_curl_multi_remove_added_easies_since_snapshot(VALUE self, ruby_curl_multi *rbcm, st_table *snapshot) { struct collect_new_attached_easies_ctx ctx; VALUE easies = rb_ary_new(); long index; if (!rbcm || !snapshot || !rbcm->attached) { return; } ctx.snapshot = snapshot; ctx.easies = easies; st_foreach(rbcm->attached, collect_new_attached_easies_i, (st_data_t)&ctx); for (index = 0; index < RARRAY_LEN(easies); index++) { VALUE easy = rb_ary_entry(easies, index); ruby_curl_easy *rbce = NULL; if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) { continue; } TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (!rbce || rbce->multi != self || !rb_curl_multi_has_easy(rbcm, rbce)) { continue; } rb_curl_multi_remove_request_reference(self, easy); rb_curl_multi_remove(rbcm, easy); } RB_GC_GUARD(easies); } static void stash_and_raise_status_callback_error_if_unmasked(struct multi_complete_callback_args *args, VALUE did_raise, VALUE easy_callback_error) { VALUE exception; if (!NIL_P(easy_callback_error)) { return; } exception = take_status_callback_exception_if_any(did_raise); if (NIL_P(exception)) { return; } stash_multi_exception_if_unset(args->self, exception, args->easy); rb_curl_multi_remove_added_easies_since_snapshot(args->self, args->rbcm, args->attached_snapshot); rb_exc_raise(exception); } static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) { struct multi_complete_callback_args *args = (struct multi_complete_callback_args *)argp; ruby_curl_easy *rbce = args->rbce; long response_code = -1; VALUE easy_callback_error = Qnil; VALUE callargs; VALUE did_raise = rb_hash_new(); long redirect_count; args->rbcm->callback_active = 1; easy_callback_error = take_easy_callback_error_if_any(args->easy); if (!NIL_P(easy_callback_error)) { stash_multi_exception_if_unset(args->self, easy_callback_error, args->easy); } if (!rb_easy_nil("complete_proc")) { callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), args->easy); args->rbce->callback_active = 1; rb_rescue(call_status_handler1, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } #ifdef HAVE_CURLINFO_RESPONSE_CODE curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code); #else /* use fdsets path for waiting */ // old libcurl curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code); #endif curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count); if (args->result != 0) { if (!rb_easy_nil("failure_proc")) { callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), args->easy, rb_curl_easy_error(args->result)); args->rbce->callback_active = 1; rb_rescue(call_status_handler2, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } } else if (!rb_easy_nil("success_proc") && ((response_code >= 200 && response_code < 300) || response_code == 0)) { /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */ callargs = rb_ary_new3(2, rb_easy_get("success_proc"), args->easy); args->rbce->callback_active = 1; rb_rescue(call_status_handler1, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } else if (!rb_easy_nil("redirect_proc") && ((response_code >= 300 && response_code < 400) || redirect_count > 0) ) { /* Skip on_redirect callback if follow_location is false AND max_redirects is 0 */ if (!args->rbce->follow_location && args->rbce->max_redirs == 0) { // Do nothing - skip the callback } else { args->rbce->callback_active = 1; callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), args->easy, rb_curl_easy_error(args->result)); rb_rescue(call_status_handler2, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } } else if (!rb_easy_nil("missing_proc") && (response_code >= 400 && response_code < 500)) { args->rbce->callback_active = 1; callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), args->easy, rb_curl_easy_error(args->result)); rb_rescue(call_status_handler2, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } else if (!rb_easy_nil("failure_proc") && (response_code >= 500 && response_code <= 999)) { callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), args->easy, rb_curl_easy_error(args->result)); args->rbce->callback_active = 1; rb_rescue(call_status_handler2, callargs, callback_exception, did_raise); args->rbce->callback_active = 0; stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error); } raise_completion_callback_error_if_any(did_raise, easy_callback_error); return Qnil; } static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) { struct multi_complete_callback_args *args = (struct multi_complete_callback_args *)argp; if (!args->rbce) { return Qnil; } if (args->rbce->callback_active) { args->rbce->callback_active = 0; } if (args->rbcm) { args->rbcm->callback_active = 0; } if (args->rbce->multi == args->self && !rb_curl_multi_has_easy(args->rbcm, args->rbce)) { args->rbce->multi = Qnil; } if (args->attached_snapshot) { st_free_table(args->attached_snapshot); args->attached_snapshot = NULL; } return Qnil; } static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) { VALUE easy = Qnil; ruby_curl_easy *rbce = NULL; ruby_curl_multi *rbcm = NULL; CURLMcode mcode; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); easy = find_easy_value_for_handle(rbcm, easy_handle); if (!NIL_P(easy) && RB_TYPE_P(easy, T_DATA)) { TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); } /* If we still cannot identify the easy handle, remove it and bail. */ if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA) || !rbce) { if (rbcm && rbcm->handle && easy_handle) { curl_multi_remove_handle(rbcm->handle, easy_handle); } return; } rbce->last_result = result; /* save the last easy result code */ /* Ensure any verbose output redirected via CURLOPT_STDERR is flushed * before we tear down handler state. */ flush_stderr_if_any(rbce); /* Detach the easy from libcurl and the Ruby requests table before running * status callbacks, but preserve easy.multi until the callbacks finish. */ mcode = curl_multi_remove_handle(rbcm->handle, rbce->curl); if (mcode != CURLM_OK) { raise_curl_multi_error_exception(mcode); } if (rbcm->active > 0) { rbcm->active--; } rb_curl_multi_remove_request_reference(self, easy); rb_curl_multi_forget_easy(rbcm, rbce); ruby_curl_easy_cleanup(easy, rbce); /* after running a request cleanup the headers, these are set before each request */ if (rbce->curl_headers) { curl_slist_free_all(rbce->curl_headers); rbce->curl_headers = NULL; } /* Flush again after removal to cover any last buffered data. */ flush_stderr_if_any(rbce); struct multi_complete_callback_args args = { self, easy, rbcm, rbce, result, capture_attached_easy_snapshot(rbcm) }; rb_ensure(rb_curl_multi_run_completion_callbacks, (VALUE)&args, rb_curl_multi_finish_completion_callbacks, (VALUE)&args); } static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) { int msgs_left; CURLcode c_easy_result; CURLMsg *c_multi_result; // for picking up messages with the transfer status CURL *c_easy_handle; ruby_curl_multi *rbcm = NULL; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); /* Check for finished easy handles and remove from the multi handle. * curl_multi_info_read will query for messages from individual handles. * * The messages fetched with this function are removed from the curl internal * queue and when there are no messages left it will return NULL (and break * the loop effectively). */ while ((c_multi_result = curl_multi_info_read(multi_handle, &msgs_left))) { // A message is there, but we really care only about transfer completetion. if (c_multi_result->msg != CURLMSG_DONE) continue; c_easy_handle = c_multi_result->easy_handle; c_easy_result = c_multi_result->data.result; /* return code for transfer */ struct multi_handle_complete_args args = { self, c_easy_handle, c_easy_result }; int state = 0; rb_protect(rb_curl_mutli_handle_complete_protected, (VALUE)&args, &state); if (state) { stash_multi_exception_if_unset(self, rb_errinfo(), find_easy_value_for_handle(rbcm, c_easy_handle)); rb_set_errinfo(Qnil); } } raise_multi_deferred_exception_if_idle(self); } /* called within ruby_curl_multi_perform */ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) { CURLMcode mcode; /* * curl_multi_perform will return CURLM_CALL_MULTI_PERFORM only when it wants to be called again immediately. * When things are fine and there is nothing immediate it wants done, it'll return CURLM_OK. * * It will perform all pending actions on all added easy handles attached to this multi handle. We will loop * here as long as mcode is CURLM_CALL_MULTIPERFORM. */ do { mcode = curl_multi_perform(multi_handle, still_running); } while (mcode == CURLM_CALL_MULTI_PERFORM); /* * Nothing more to do, check if an error occured in the loop above and raise an exception if necessary. */ if (mcode != CURLM_OK) { raise_curl_multi_error_exception(mcode); } /* * Everything is ok, but this does not mean all the transfers are completed. * There is no data to read or write available for Curl at the moment. * * At this point we can return control to the caller to do something else while * curl is waiting for more actions to queue. */ } #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32) /* ---- socket-action implementation (scheduler-friendly) ---- */ typedef struct { st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */ long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */ VALUE io_cache; /* fd -> IO wrapper for fiber-scheduler waits */ } multi_socket_ctx; static long long multi_socket_current_time_ms(void) { #if defined(CLOCK_MONOTONIC) struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { return ((long long)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); } #endif struct timeval tv; gettimeofday(&tv, NULL); return ((long long)tv.tv_sec * 1000) + (tv.tv_usec / 1000); } static int multi_socket_timer_due(multi_socket_ctx *ctx) { return ctx && ctx->timeout_deadline_ms >= 0 && multi_socket_current_time_ms() >= ctx->timeout_deadline_ms; } #if CURB_SOCKET_DEBUG static void curb_debugf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fputc('\n', stderr); fflush(stderr); va_end(ap); } static const char *poll_what_str(int what, char *buf, size_t n) { /* what is one of CURL_POLL_*, not a bitmask except INOUT */ if (what == CURL_POLL_REMOVE) snprintf(buf, n, "REMOVE"); else if (what == CURL_POLL_IN) snprintf(buf, n, "IN"); else if (what == CURL_POLL_OUT) snprintf(buf, n, "OUT"); else if (what == CURL_POLL_INOUT) snprintf(buf, n, "INOUT"); else snprintf(buf, n, "WHAT=%d", what); return buf; } static const char *cselect_flags_str(int flags, char *buf, size_t n) { char tmp[32]; tmp[0] = 0; int off = 0; if (flags & CURL_CSELECT_IN) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sIN", off?"|":""); if (flags & CURL_CSELECT_OUT) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sOUT", off?"|":""); if (flags & CURL_CSELECT_ERR) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sERR", off?"|":""); if (off == 0) snprintf(tmp, sizeof(tmp), "0"); snprintf(buf, n, "%s", tmp); return buf; } #else #define poll_what_str(...) "" #define cselect_flags_str(...) "" #endif #if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) /* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */ struct fiber_io_wait_args { VALUE scheduler; VALUE io; VALUE events; VALUE timeout; }; static VALUE fiber_io_wait_protected(VALUE argp) { struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp; return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout); } #endif #if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) struct fiber_io_select_args { VALUE scheduler; VALUE readables; VALUE writables; VALUE exceptables; VALUE timeout; }; static VALUE fiber_io_select_protected(VALUE argp) { struct fiber_io_select_args *a = (struct fiber_io_select_args *)argp; return rb_fiber_scheduler_io_select(a->scheduler, a->readables, a->writables, a->exceptables, a->timeout); } #endif #if defined(RB_INTEGER_TYPE_P) #define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value) #else #define CURB_INTEGER_P(value) (FIXNUM_P(value) || RB_TYPE_P((value), T_BIGNUM)) #endif static int multi_socket_wait_events_for_curl_poll(int what) { if (what == CURL_POLL_IN) return RB_WAITFD_IN; if (what == CURL_POLL_OUT) return RB_WAITFD_OUT; if (what == CURL_POLL_INOUT) return RB_WAITFD_IN | RB_WAITFD_OUT; return RB_WAITFD_IN | RB_WAITFD_OUT; } static int multi_socket_cselect_flags_for_curl_poll(int what) { int flags = 0; if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN; if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT; if (flags == 0) flags = CURL_CSELECT_IN | CURL_CSELECT_OUT; return flags; } static int multi_socket_cselect_flags_for_wait_events(int events) { int flags = 0; if (events & RB_WAITFD_IN) flags |= CURL_CSELECT_IN; if (events & RB_WAITFD_OUT) flags |= CURL_CSELECT_OUT; #ifdef RB_WAITFD_PRI if (events & RB_WAITFD_PRI) flags |= CURL_CSELECT_ERR; #endif return flags; } static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) { st_data_t key = (st_data_t)fd; st_data_t rec; if (!ctx) return; if (ctx->sock_map) st_delete(ctx->sock_map, &key, &rec); if (!NIL_P(ctx->io_cache)) rb_hash_delete(ctx->io_cache, INT2NUM(fd)); } static int multi_socket_fd_valid_p(int fd) { if (fd < 0) return 0; errno = 0; return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) { multi_socket_ctx *ctx = (multi_socket_ctx *)userp; (void)easy; (void)socketp; int fd = (int)s; if (!ctx || !ctx->sock_map) return 0; if (fd < 0) return 0; if (what == CURL_POLL_REMOVE) { multi_socket_forget_fd(ctx, fd); #if CURB_SOCKET_DEBUG { char b[16]; curb_debugf("[curb.socket] sock_cb fd=%d what=%s (removed)", fd, poll_what_str(what, b, sizeof(b))); } #endif } else { /* store current interest mask for this fd */ st_data_t key = (st_data_t)fd; st_data_t old_what; if (st_lookup(ctx->sock_map, key, &old_what) && (int)old_what != what && !NIL_P(ctx->io_cache)) { rb_hash_delete(ctx->io_cache, INT2NUM(fd)); } st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what); #if CURB_SOCKET_DEBUG { char b[16]; curb_debugf("[curb.socket] sock_cb fd=%d what=%s (tracked)", fd, poll_what_str(what, b, sizeof(b))); } #endif } return 0; } static int multi_timer_cb(CURLM *multi, long timeout_ms, void *userp) { (void)multi; multi_socket_ctx *ctx = (multi_socket_ctx *)userp; if (ctx) { ctx->timeout_deadline_ms = timeout_ms < 0 ? -1 : multi_socket_current_time_ms() + timeout_ms; } curb_debugf("[curb.socket] timer_cb timeout_ms=%ld", timeout_ms); return 0; } struct build_fdset_args { rb_fdset_t *r; rb_fdset_t *w; rb_fdset_t *e; int maxfd; }; static int rb_fdset_from_sockmap_i(st_data_t key, st_data_t val, st_data_t argp) { struct build_fdset_args *a = (struct build_fdset_args *)argp; int fd = (int)key; int what = (int)val; if (what & CURL_POLL_IN) rb_fd_set(fd, a->r); if (what & CURL_POLL_OUT) rb_fd_set(fd, a->w); rb_fd_set(fd, a->e); if (fd > a->maxfd) a->maxfd = fd; return ST_CONTINUE; } CURB_MAYBE_UNUSED_DECL static void rb_fdset_from_sockmap(st_table *map, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, int *maxfd_out) { if (!map) { *maxfd_out = -1; return; } struct build_fdset_args a; a.r = rfds; a.w = wfds; a.e = efds; a.maxfd = -1; st_foreach(map, rb_fdset_from_sockmap_i, (st_data_t)&a); *maxfd_out = a.maxfd; } struct ready_fd { int fd; int flags; }; struct collect_ready_fd_args { rb_fdset_t *r; rb_fdset_t *w; rb_fdset_t *e; struct ready_fd *fds; int capacity; int count; }; static int collect_ready_fd_i(st_data_t key, st_data_t val, st_data_t argp) { (void)val; struct collect_ready_fd_args *a = (struct collect_ready_fd_args *)argp; int fd = (int)key; int flags = 0; if (rb_fd_isset(fd, a->r)) flags |= CURL_CSELECT_IN; if (rb_fd_isset(fd, a->w)) flags |= CURL_CSELECT_OUT; if (rb_fd_isset(fd, a->e)) flags |= CURL_CSELECT_ERR; if (flags && a->count < a->capacity) { a->fds[a->count].fd = fd; a->fds[a->count].flags = flags; a->count++; } return ST_CONTINUE; } /* Helpers used with st_foreach to avoid compiler-specific nested functions. */ struct pick_one_state { int fd; int what; int found; }; static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) { struct pick_one_state *s = (struct pick_one_state *)argp; s->fd = (int)key; s->what = (int)val; s->found = 1; return ST_STOP; } struct counter_state { int count; }; static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) { (void)k; (void)v; struct counter_state *c = (struct counter_state *)argp; c->count++; return ST_CONTINUE; } static const char *multi_socket_io_mode_for_curl_poll(int what) { if (what == CURL_POLL_IN) return "r"; if (what == CURL_POLL_OUT) return "w"; return "r+"; } static VALUE multi_socket_io_for_fd(multi_socket_ctx *ctx, int fd, int what) { VALUE key = INT2NUM(fd); VALUE io = rb_hash_aref(ctx->io_cache, key); if (NIL_P(io)) { io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, key, rb_str_new_cstr(multi_socket_io_mode_for_curl_poll(what))); rb_funcall(io, rb_intern("autoclose="), 1, Qfalse); rb_hash_aset(ctx->io_cache, key, io); } return io; } struct io_for_fd_args { multi_socket_ctx *ctx; int fd; int what; }; static VALUE multi_socket_io_for_fd_protected(VALUE argp) { struct io_for_fd_args *a = (struct io_for_fd_args *)argp; return multi_socket_io_for_fd(a->ctx, a->fd, a->what); } #if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) struct build_io_select_arrays_args { multi_socket_ctx *ctx; VALUE readables; VALUE writables; VALUE exceptables; int failed; }; static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) { struct build_io_select_arrays_args *a = (struct build_io_select_arrays_args *)argp; int fd = (int)key; int what = (int)val; struct io_for_fd_args io_args = { a->ctx, fd, what }; int io_state = 0; VALUE io; if (!multi_socket_fd_valid_p(fd)) { a->failed = 1; return ST_STOP; } io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state); if (io_state || NIL_P(io)) { if (io_state) { #if CURB_SOCKET_DEBUG VALUE err = rb_errinfo(); VALUE msg = rb_obj_as_string(err); curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg)); #endif rb_set_errinfo(Qnil); } a->failed = 1; return ST_STOP; } if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) rb_ary_push(a->readables, io); if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) rb_ary_push(a->writables, io); rb_ary_push(a->exceptables, io); return ST_CONTINUE; } struct collect_io_select_ready_args { multi_socket_ctx *ctx; VALUE readables; VALUE writables; VALUE exceptables; struct ready_fd *fds; int capacity; int count; }; static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t argp) { (void)val; struct collect_io_select_ready_args *a = (struct collect_io_select_ready_args *)argp; VALUE io = rb_hash_aref(a->ctx->io_cache, INT2NUM((int)key)); int flags = 0; if (NIL_P(io)) return ST_CONTINUE; if (RTEST(rb_ary_includes(a->readables, io))) flags |= CURL_CSELECT_IN; if (RTEST(rb_ary_includes(a->writables, io))) flags |= CURL_CSELECT_OUT; if (RTEST(rb_ary_includes(a->exceptables, io))) flags |= CURL_CSELECT_ERR; if (flags && a->count < a->capacity) { a->fds[a->count].fd = (int)key; a->fds[a->count].flags = flags; a->count++; } return ST_CONTINUE; } #endif static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) { /* prime the state: let libcurl act on timeouts to setup sockets */ CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running); if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc); curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running); rb_curl_multi_read_info(self, rbcm->handle); rb_curl_multi_yield_if_given(self, block); while (rbcm->running) { struct timeval tv = {0, 0}; long wait_ms = cCurlMutiDefaulttimeout; if (multi_socket_timer_due(ctx)) { mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running); curb_debugf("[curb.socket] socket_action timeout(due) -> mrc=%d running=%d", mrc, rbcm->running); if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc); rb_curl_multi_read_info(self, rbcm->handle); rb_curl_multi_yield_if_given(self, block); continue; } if (ctx->timeout_deadline_ms >= 0) { long long remaining_ms = ctx->timeout_deadline_ms - multi_socket_current_time_ms(); if (remaining_ms < wait_ms) wait_ms = remaining_ms < 0 ? 0 : (long)remaining_ms; } tv.tv_sec = wait_ms / 1000; tv.tv_usec = (wait_ms % 1000) * 1000; /* Find a representative fd to wait on (if any). */ int wait_fd = -1; int wait_what = 0; if (ctx->sock_map) { struct pick_one_state st = { -1, 0, 0 }; st_foreach(ctx->sock_map, st_pick_one_i, (st_data_t)&st); if (st.found) { wait_fd = st.fd; wait_what = st.what; } } /* Count tracked fds for logging */ int count_tracked = 0; if (ctx->sock_map) { struct counter_state cs = { 0 }; st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs); count_tracked = cs.count; } curb_debugf("[curb.socket] wait phase: tracked_fds=%d fd=%d what=%d tv=%ld.%06ld", count_tracked, wait_fd, wait_what, (long)tv.tv_sec, (long)tv.tv_usec); int did_timeout = 0; int any_ready = 0; int ready_flags = 0; int handled_wait = 0; if (count_tracked > 1) { #if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { VALUE readables = rb_ary_new(); VALUE writables = rb_ary_new(); VALUE exceptables = rb_ary_new(); struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0 }; st_foreach(ctx->sock_map, build_io_select_arrays_i, (st_data_t)&build_args); if (!build_args.failed) { double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6); VALUE timeout = rb_float_new(timeout_s); struct fiber_io_select_args select_args = { scheduler, readables, writables, exceptables, timeout }; int state = 0; VALUE ready = rb_protect(fiber_io_select_protected, (VALUE)&select_args, &state); if (state) { #if CURB_SOCKET_DEBUG VALUE err = rb_errinfo(); VALUE msg = rb_obj_as_string(err); curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg)); #endif rb_set_errinfo(Qnil); } else { handled_wait = 1; any_ready = RB_TYPE_P(ready, T_ARRAY); did_timeout = !any_ready && multi_socket_timer_due(ctx); if (any_ready) { VALUE ready_readables = rb_ary_entry(ready, 0); VALUE ready_writables = rb_ary_entry(ready, 1); VALUE ready_exceptables = rb_ary_entry(ready, 2); struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked); struct collect_io_select_ready_args d; int i; if (!RB_TYPE_P(ready_readables, T_ARRAY)) ready_readables = rb_ary_new(); if (!RB_TYPE_P(ready_writables, T_ARRAY)) ready_writables = rb_ary_new(); if (!RB_TYPE_P(ready_exceptables, T_ARRAY)) ready_exceptables = rb_ary_new(); d.ctx = ctx; d.readables = ready_readables; d.writables = ready_writables; d.exceptables = ready_exceptables; d.fds = ready_fds; d.capacity = count_tracked; d.count = 0; st_foreach(ctx->sock_map, collect_io_select_ready_i, (st_data_t)&d); any_ready = (d.count > 0); did_timeout = !any_ready && multi_socket_timer_due(ctx); for (i = 0; i < d.count; i++) { mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running); if (mrc != CURLM_OK) { xfree(ready_fds); raise_curl_multi_error_exception(mrc); } } xfree(ready_fds); } } } } } #endif if (!handled_wait) { /* Multi-fd wait using scheduler-aware rb_thread_fd_select. */ rb_fdset_t rfds, wfds, efds; rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds); int maxfd = -1; rb_fdset_from_sockmap(ctx->sock_map, &rfds, &wfds, &efds, &maxfd); int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv); curb_debugf("[curb.socket] rb_thread_fd_select(multi) rc=%d maxfd=%d", rc, maxfd); if (rc < 0) { rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno)); continue; } any_ready = (rc > 0); did_timeout = (rc == 0 && multi_socket_timer_due(ctx)); if (any_ready) { struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked); struct collect_ready_fd_args d; int i; d.r = &rfds; d.w = &wfds; d.e = &efds; d.fds = ready_fds; d.capacity = count_tracked; d.count = 0; st_foreach(ctx->sock_map, collect_ready_fd_i, (st_data_t)&d); for (i = 0; i < d.count; i++) { mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running); if (mrc != CURLM_OK) { xfree(ready_fds); rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); raise_curl_multi_error_exception(mrc); } } xfree(ready_fds); } rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); handled_wait = 1; } } else if (count_tracked == 1) { #if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { int scheduler_wait_handled = 0; int events = 0; if (wait_fd >= 0) { events = multi_socket_wait_events_for_curl_poll(wait_what); } double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6); VALUE timeout = rb_float_new(timeout_s); if (wait_fd < 0) { #ifdef HAVE_RB_THREAD_FD_SELECT rb_thread_fd_select(0, NULL, NULL, NULL, &tv); #else rb_thread_wait_for(tv); #endif did_timeout = multi_socket_timer_due(ctx); scheduler_wait_handled = 1; } else if (!multi_socket_fd_valid_p(wait_fd)) { multi_socket_forget_fd(ctx, wait_fd); did_timeout = 1; scheduler_wait_handled = 1; } else { struct io_for_fd_args io_args = { ctx, wait_fd, wait_what }; int io_state = 0; VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state); if (io_state || NIL_P(io)) { if (io_state) { #if CURB_SOCKET_DEBUG VALUE err = rb_errinfo(); VALUE msg = rb_obj_as_string(err); curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg)); #endif rb_set_errinfo(Qnil); } any_ready = 0; } else { struct fiber_io_wait_args args = { scheduler, io, INT2NUM(events), timeout }; int state = 0; VALUE ready = rb_protect(fiber_io_wait_protected, (VALUE)&args, &state); if (state) { #if CURB_SOCKET_DEBUG VALUE err = rb_errinfo(); VALUE msg = rb_obj_as_string(err); curb_debugf("[curb.socket] scheduler io_wait failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg)); #endif rb_set_errinfo(Qnil); any_ready = 0; } else { scheduler_wait_handled = 1; any_ready = (ready != Qfalse && !NIL_P(ready)); did_timeout = !any_ready && multi_socket_timer_due(ctx); if (any_ready) { if (ready == Qtrue) { ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what); } else if (CURB_INTEGER_P(ready)) { ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready)); if (ready_flags == 0) { any_ready = 0; did_timeout = multi_socket_timer_due(ctx); } } else { ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what); } } } } } if (scheduler_wait_handled) handled_wait = 1; } } #endif #if defined(HAVE_RB_WAIT_FOR_SINGLE_FD) if (!handled_wait && wait_fd >= 0) { int ev = multi_socket_wait_events_for_curl_poll(wait_what); int rc = rb_wait_for_single_fd(wait_fd, ev, &tv); curb_debugf("[curb.socket] rb_wait_for_single_fd rc=%d fd=%d ev=%d", rc, wait_fd, ev); if (rc < 0) { if (errno != EINTR) rb_raise(rb_eRuntimeError, "wait_for_single_fd(): %s", strerror(errno)); continue; } any_ready = (rc != 0); did_timeout = (rc == 0 && multi_socket_timer_due(ctx)); if (any_ready) ready_flags = multi_socket_cselect_flags_for_wait_events(rc); handled_wait = 1; } #endif if (!handled_wait) { /* Fallback: single-fd select. */ rb_fdset_t rfds, wfds, efds; rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds); int maxfd = -1; if (wait_fd >= 0) { if (wait_what == CURL_POLL_IN || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &rfds); if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &wfds); rb_fd_set(wait_fd, &efds); maxfd = wait_fd; } int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv); curb_debugf("[curb.socket] rb_thread_fd_select(single) rc=%d fd=%d", rc, wait_fd); if (rc < 0) { rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno)); continue; } any_ready = (rc > 0); did_timeout = (rc == 0 && multi_socket_timer_due(ctx)); if (any_ready && wait_fd >= 0) { if (rb_fd_isset(wait_fd, &rfds)) ready_flags |= CURL_CSELECT_IN; if (rb_fd_isset(wait_fd, &wfds)) ready_flags |= CURL_CSELECT_OUT; if (rb_fd_isset(wait_fd, &efds)) ready_flags |= CURL_CSELECT_ERR; } rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); } } else { /* count_tracked == 0 */ #ifdef HAVE_RB_THREAD_FD_SELECT rb_thread_fd_select(0, NULL, NULL, NULL, &tv); #else rb_thread_wait_for(tv); #endif did_timeout = multi_socket_timer_due(ctx); } if (did_timeout) { mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running); curb_debugf("[curb.socket] socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running); if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc); } else if (any_ready) { if (count_tracked == 1 && wait_fd >= 0) { int flags = ready_flags; if (flags == 0) flags = multi_socket_cselect_flags_for_curl_poll(wait_what); #if CURB_SOCKET_DEBUG { char b[32]; curb_debugf("[curb.socket] socket_action fd=%d flags=%s", wait_fd, cselect_flags_str(flags, b, sizeof(b))); } #endif mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)wait_fd, flags, &rbcm->running); curb_debugf("[curb.socket] socket_action -> mrc=%d running=%d", mrc, rbcm->running); if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc); } } rb_curl_multi_read_info(self, rbcm->handle); curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running); rb_curl_multi_yield_if_given(self, block); } } struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; }; static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) { struct socket_drive_args *a = (struct socket_drive_args *)argp; rb_curl_multi_socket_drive(a->self, a->rbcm, a->ctx, a->block); return Qtrue; } struct socket_cleanup_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; }; static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) { struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp; if (c->rbcm && c->rbcm->handle) { curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETFUNCTION, NULL); curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETDATA, NULL); curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERFUNCTION, NULL); curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERDATA, NULL); } if (c->ctx && c->ctx->sock_map) { st_free_table(c->ctx->sock_map); c->ctx->sock_map = NULL; } if (c->ctx) { if (!NIL_P(c->ctx->io_cache)) { rb_hash_clear(c->ctx->io_cache); } c->ctx->io_cache = Qnil; } if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) { rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar)); } return Qnil; } static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self) { ruby_curl_multi *rbcm; VALUE block = Qnil; rb_scan_args(argc, argv, "0&", &block); TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_ensure_handle(rbcm); if (!rb_ivar_defined(self, id_deferred_exception_ivar)) { clear_multi_deferred_exception_source_id_if_any(self); } multi_socket_ctx ctx; ctx.sock_map = st_init_numtable(); ctx.timeout_deadline_ms = -1; ctx.io_cache = rb_hash_new(); rb_ivar_set(self, id_socket_io_cache_ivar, ctx.io_cache); /* install socket/timer callbacks */ curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb); curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETDATA, &ctx); curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERFUNCTION, multi_timer_cb); curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERDATA, &ctx); /* run using socket action loop with ensure-cleanup */ struct socket_drive_args body_args = { self, rbcm, &ctx, block }; struct socket_cleanup_args ensure_args = { self, rbcm, &ctx }; rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args); /* finalize */ rb_curl_multi_read_info(self, rbcm->handle); rb_curl_multi_yield_if_given(self, block); if (cCurlMutiAutoClose == 1) { rbcm->allow_close_during_perform = 1; rb_funcall(self, rb_intern("_autoclose"), 0); rbcm->allow_close_during_perform = 0; } return Qtrue; } #endif /* socket-action implementation */ #ifdef _WIN32 void create_crt_fd(fd_set *os_set, fd_set *crt_set) { int i; crt_set->fd_count = os_set->fd_count; for (i = 0; i < os_set->fd_count; i++) { WSAPROTOCOL_INFO wsa_pi; // dupicate the SOCKET int r = WSADuplicateSocket(os_set->fd_array[i], GetCurrentProcessId(), &wsa_pi); SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0); // create the CRT fd so ruby can get back to the SOCKET int fd = _open_osfhandle(s, O_RDWR|O_BINARY); os_set->fd_array[i] = s; crt_set->fd_array[i] = fd; } } void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set) { int i; for (i = 0; i < os_set->fd_count; i++) { // cleanup the CRT fd _close(crt_set->fd_array[i]); // cleanup the duplicated SOCKET closesocket(os_set->fd_array[i]); } } #endif /* curb_select is only needed when rb_thread_fd_select is NOT available */ #if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)) struct _select_set { int maxfd; fd_set *fdread, *fdwrite, *fdexcep; struct timeval *tv; }; static VALUE curb_select(void *args) { struct _select_set* set = args; int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv); return INT2FIX(rc); } #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL static void *curb_select_without_gvl(void *args) { struct _select_set* set = args; int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv); return (void *)(intptr_t)rc; } #endif #endif /* * call-seq: * multi = Curl::Multi.new * easy1 = Curl::Easy.new('url') * easy2 = Curl::Easy.new('url') * * multi.add(easy1) * multi.add(easy2) * * multi.perform do * # while idle other code my execute here * end * * Run multi handles, looping selecting when data can be transfered */ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) { CURLMcode mcode; ruby_curl_multi *rbcm; int maxfd, rc = -1; fd_set fdread, fdwrite, fdexcep; #ifdef _WIN32 fd_set crt_fdread, crt_fdwrite, crt_fdexcep; #endif long timeout_milliseconds; struct timeval tv = {0, 0}; struct timeval tv_100ms = {0, 100000}; VALUE block = Qnil; #if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)) struct _select_set fdset_args; #endif rb_scan_args(argc, argv, "0&", &block); TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); ruby_curl_multi_ensure_handle(rbcm); if (!rb_ivar_defined(self, id_deferred_exception_ivar)) { clear_multi_deferred_exception_source_id_if_any(self); } timeout_milliseconds = cCurlMutiDefaulttimeout; // Run curl_multi_perform for the first time to get the ball rolling rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) ); // Check the easy handles for new messages one more time before yielding // control to passed ruby block. // // This call will block until all queued messages are processed and if any // handle completed the transfer we will run the on_complete callback here too. rb_curl_multi_read_info( self, rbcm->handle ); // There are no more messages to handle by curl and we can run the ruby block // passed to perform method. // When the block completes curl will resume. rb_curl_multi_yield_if_given(self, block); do { while (rbcm->running) { #ifdef HAVE_CURL_MULTI_TIMEOUT /* get the curl suggested time out */ mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds); if (mcode != CURLM_OK) { raise_curl_multi_error_exception(mcode); } #else /* libcurl doesn't have a timeout method defined, initialize to -1 we'll pick up the default later */ timeout_milliseconds = -1; #endif if (timeout_milliseconds == 0) { /* no delay */ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) ); rb_curl_multi_read_info( self, rbcm->handle ); rb_curl_multi_yield_if_given(self, block); #if defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) if (rb_fiber_scheduler_current() != Qnil) { rb_thread_schedule(); } #endif continue; } if (timeout_milliseconds < 0 || timeout_milliseconds > cCurlMutiDefaulttimeout) { timeout_milliseconds = cCurlMutiDefaulttimeout; /* libcurl doesn't know how long to wait, use a default timeout */ /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */ } #if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT) { struct wait_args wait_args; wait_args.handle = rbcm->handle; wait_args.timeout_ms = timeout_milliseconds; wait_args.numfds = 0; /* * When a Fiber scheduler is available (Ruby >= 3.x), rb_thread_fd_select * integrates with it. If we have rb_thread_fd_select available at build * time, we avoid curl_multi_wait entirely (see preprocessor guard above) * and use the fdset branch below. Otherwise, we use curl_multi_wait and * release the GVL so Ruby threads can continue to run. */ CURLMcode wait_rc; #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) wait_rc = (CURLMcode)(intptr_t)rb_thread_call_without_gvl( curl_multi_wait_wrapper, &wait_args, RUBY_UBF_IO, NULL ); #else wait_rc = curl_multi_wait(rbcm->handle, NULL, 0, timeout_milliseconds, &wait_args.numfds); #endif if (wait_rc != CURLM_OK) { raise_curl_multi_error_exception(wait_rc); } if (wait_args.numfds == 0) { #ifdef HAVE_RB_THREAD_FD_SELECT struct timeval tv_sleep = tv_100ms; /* Sleep in a scheduler-aware way. */ rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep); #else rb_thread_wait_for(tv_100ms); #endif } /* Process pending transfers after waiting */ rb_curl_multi_run(self, rbcm->handle, &(rbcm->running)); rb_curl_multi_read_info(self, rbcm->handle); rb_curl_multi_yield_if_given(self, block); } #else tv.tv_sec = 0; /* never wait longer than 1 second */ tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */ FD_ZERO(&fdread); FD_ZERO(&fdwrite); FD_ZERO(&fdexcep); /* load the fd sets from the multi handle */ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd); if (mcode != CURLM_OK) { raise_curl_multi_error_exception(mcode); } if (maxfd == -1) { /* libcurl recommends sleeping for 100ms */ #ifdef HAVE_RB_THREAD_FD_SELECT struct timeval tv_sleep = tv_100ms; rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep); #else rb_thread_wait_for(tv_100ms); #endif rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) ); rb_curl_multi_read_info( self, rbcm->handle ); rb_curl_multi_yield_if_given(self, block); continue; } #ifdef _WIN32 create_crt_fd(&fdread, &crt_fdread); create_crt_fd(&fdwrite, &crt_fdwrite); create_crt_fd(&fdexcep, &crt_fdexcep); #endif #if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)) fdset_args.maxfd = maxfd+1; fdset_args.fdread = &fdread; fdset_args.fdwrite = &fdwrite; fdset_args.fdexcep = &fdexcep; fdset_args.tv = &tv; #endif #ifdef HAVE_RB_THREAD_FD_SELECT /* Prefer scheduler-aware waiting when available. Build rb_fdset_t sets. */ { rb_fdset_t rfds, wfds, efds; rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds); #ifdef _WIN32 /* On Windows, iterate explicit fd arrays for CRT fds. */ int i; for (i = 0; i < crt_fdread.fd_count; i++) rb_fd_set(crt_fdread.fd_array[i], &rfds); for (i = 0; i < crt_fdwrite.fd_count; i++) rb_fd_set(crt_fdwrite.fd_array[i], &wfds); for (i = 0; i < crt_fdexcep.fd_count; i++) rb_fd_set(crt_fdexcep.fd_array[i], &efds); rc = rb_thread_fd_select(0, &rfds, &wfds, &efds, &tv); #else int fd; for (fd = 0; fd <= maxfd; fd++) { if (FD_ISSET(fd, &fdread)) rb_fd_set(fd, &rfds); if (FD_ISSET(fd, &fdwrite)) rb_fd_set(fd, &wfds); if (FD_ISSET(fd, &fdexcep)) rb_fd_set(fd, &efds); } rc = rb_thread_fd_select(maxfd+1, &rfds, &wfds, &efds, &tv); #endif rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds); } #elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) rc = (int)(intptr_t) rb_thread_call_without_gvl(curb_select_without_gvl, &fdset_args, RUBY_UBF_IO, 0); #elif HAVE_RB_THREAD_BLOCKING_REGION rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0); #else rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv); #endif #ifdef _WIN32 cleanup_crt_fd(&fdread, &crt_fdread); cleanup_crt_fd(&fdwrite, &crt_fdwrite); cleanup_crt_fd(&fdexcep, &crt_fdexcep); #endif switch(rc) { case -1: if(errno != EINTR) { rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno)); break; } case 0: /* timeout */ default: /* action */ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) ); rb_curl_multi_read_info( self, rbcm->handle ); rb_curl_multi_yield_if_given(self, block); break; } #endif /* disabled curl_multi_wait: use fdsets */ } rb_curl_multi_read_info( self, rbcm->handle ); rb_curl_multi_yield_if_given(self, block); } while( rbcm->running ); if (cCurlMutiAutoClose == 1) { rbcm->allow_close_during_perform = 1; rb_funcall(self, rb_intern("_autoclose"), 0); rbcm->allow_close_during_perform = 0; } return Qtrue; } struct multi_perform_call_args { int argc; VALUE *argv; VALUE self; VALUE result; VALUE (*func)(int, VALUE *, VALUE); }; static VALUE ruby_curl_multi_perform_guard_body(VALUE argp) { struct multi_perform_call_args *args = (struct multi_perform_call_args *)argp; args->result = args->func(args->argc, args->argv, args->self); return args->result; } static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) { ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); rbcm->perform_active = 0; rbcm->callback_active = 0; rbcm->allow_close_during_perform = 0; return Qnil; } static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE self, VALUE (*func)(int, VALUE *, VALUE)) { ruby_curl_multi *rbcm; struct multi_perform_call_args args; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); if (rbcm->perform_active) { rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle"); } rbcm->perform_active = 1; args.argc = argc; args.argv = argv; args.self = self; args.result = Qnil; args.func = func; return rb_ensure(ruby_curl_multi_perform_guard_body, (VALUE)&args, ruby_curl_multi_perform_guard_ensure, self); } VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) { return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl); } #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32) VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) { return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl); } #endif /* * call-seq: * * multi.close * after closing the multi handle all connections will be closed and the handle will no longer be usable * */ VALUE ruby_curl_multi_close(VALUE self) { ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) { rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform"); } rb_curl_multi_detach_all(rbcm); if (rbcm->handle) { curl_multi_cleanup(rbcm->handle); rbcm->handle = NULL; } rbcm->active = 0; rbcm->running = 0; clear_multi_deferred_exception_if_any(self); clear_multi_deferred_exception_source_id_if_any(self); return self; } static VALUE ruby_curl_multi_mark_closed(VALUE self) { ruby_curl_multi *rbcm; TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm); rbcm->closed = 1; return self; } /* GC mark: keep attached easy VALUEs alive while associated. */ static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) { VALUE easy = (VALUE)val; if (!NIL_P(easy)) rb_gc_mark(easy); return ST_CONTINUE; } static void curl_multi_mark(void *ptr) { ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr; if (!rbcm) return; if (rbcm->attached) { st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0); } } /* =================== INIT LIB =====================*/ void init_curb_multi() { idCall = rb_intern("call"); id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception"); id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id"); id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache"); cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject); rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc); /* Class methods */ rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1); rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0); rb_define_singleton_method(cCurlMulti, "autoclose=", ruby_curl_multi_set_autoclose, 1); rb_define_singleton_method(cCurlMulti, "autoclose", ruby_curl_multi_get_autoclose, 0); /* Instance methods */ rb_define_method(cCurlMulti, "initialize", ruby_curl_multi_initialize, 0); rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1); rb_define_method(cCurlMulti, "max_host_connections=", ruby_curl_multi_max_host_connections, 1); rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1); rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1); rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1); /* * The legacy fdset loop is the stable default. The newer socket-action path * is kept in-tree, but it has shown scheduler regressions for one-handle * multi usage (for example Curl::Easy#perform under Async). */ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1); #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32) rb_define_private_method(cCurlMulti, "_socket_perform", ruby_curl_multi_socket_perform, -1); #endif rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0); rb_define_private_method(cCurlMulti, "_mark_closed", ruby_curl_multi_mark_closed, 0); } curb-1.3.5/ext/curb_multi.h0000644000004100000410000000121015203731642015616 0ustar www-datawww-data/* curb_multi.h - Curl easy mode * Copyright (c)2008 Todd A. Fisher. * Licensed under the Ruby License. See LICENSE for details. * * $Id$ */ #ifndef __CURB_MULTI_H #define __CURB_MULTI_H #include "curb.h" #include struct st_table; typedef struct { int active; int running; char closed; char perform_active; char callback_active; char allow_close_during_perform; CURLM *handle; struct st_table *attached; } ruby_curl_multi; extern VALUE cCurlMulti; extern const rb_data_type_t ruby_curl_multi_data_type; void init_curb_multi(); void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr); #endif curb-1.3.5/ext/curb_easy.h0000644000004100000410000000626115203731642015440 0ustar www-datawww-data/* curb_easy.h - Curl easy mode * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_easy.h 25 2006-12-07 23:38:25Z roscopeco $ */ #ifndef __CURB_EASY_H #define __CURB_EASY_H #include "curb.h" #include #ifdef CURL_VERSION_SSL #if LIBCURL_VERSION_NUM >= 0x070b00 # if LIBCURL_VERSION_NUM <= 0x071004 # define CURB_FTPSSL CURLOPT_FTP_SSL # define CURB_FTPSSL_ALL CURLFTPSSL_ALL # define CURB_FTPSSL_TRY CURLFTPSSL_TRY # define CURB_FTPSSL_CONTROL CURLFTPSSL_CONTROL # define CURB_FTPSSL_NONE CURLFTPSSL_NONE # else # define CURB_FTPSSL CURLOPT_USE_SSL # define CURB_FTPSSL_ALL CURLUSESSL_ALL # define CURB_FTPSSL_TRY CURLUSESSL_TRY # define CURB_FTPSSL_CONTROL CURLUSESSL_CONTROL # define CURB_FTPSSL_NONE CURLUSESSL_NONE # endif #endif #endif /* a lot of this *could* be kept in the handler itself, * but then we lose the ability to query it's status. */ typedef struct { /* The handler */ CURL *curl; /* Buffer for error details from CURLOPT_ERRORBUFFER */ char err_buf[CURL_ERROR_SIZE]; VALUE self; /* owning Ruby object */ VALUE opts; /* rather then allocate everything we might need to store, allocate a Hash and only store objects we actually use... */ VALUE multi; /* keep a multi handle alive for each easy handle not being used by a multi handle. This improves easy performance when not within a multi context */ VALUE callback_error; /* preserves body/header callback exceptions without mutating the Ruby object */ /* Other opts */ unsigned short local_port; // 0 is no port unsigned short local_port_range; // " " " " unsigned short proxy_port; // " " " " int proxy_type; long http_auth_types; long proxy_auth_types; long max_redirs; unsigned long timeout; unsigned long timeout_ms; unsigned long connect_timeout; unsigned long connect_timeout_ms; long dns_cache_timeout; unsigned long ftp_response_timeout; long low_speed_limit; long low_speed_time; long max_send_speed_large; long max_recv_speed_large; long ssl_version; long use_ssl; long ftp_filemethod; long http_version; unsigned short resolve_mode; /* bool flags */ char proxy_tunnel; char fetch_file_time; char ssl_verify_peer; char ssl_verify_host; char header_in_body; char use_netrc; char follow_location; char unrestricted_auth; char verbose; char multipart_form_post; char enable_cookies; char cookielist_engine_enabled; /* track if CURLOPT_COOKIELIST was used with a non-command to enable engine */ char ignore_content_length; char callback_active; struct curl_slist *curl_headers; struct curl_slist *curl_proxy_headers; struct curl_slist *curl_ftp_commands; struct curl_slist *curl_resolve; unsigned long multi_attachment_generation; int last_result; /* last result code from multi loop */ } ruby_curl_easy; extern VALUE cCurlEasy; extern const rb_data_type_t ruby_curl_easy_data_type; VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce); VALUE ruby_curl_easy_cleanup(VALUE self, ruby_curl_easy *rbce); VALUE rb_curl_easy_take_callback_error(ruby_curl_easy *rbce); void init_curb_easy(); #endif curb-1.3.5/ext/curb_upload.h0000644000004100000410000000145315203731642015761 0ustar www-datawww-data/* curb_upload.h - Curl upload handle * Copyright (c)2009 Todd A Fisher. * Licensed under the Ruby License. See LICENSE for details. */ #ifndef __CURB_UPLOAD_H #define __CURB_UPLOAD_H #include "curb.h" #include /* * Maintain the state of an upload e.g. for putting large streams with very little memory * out to a server. via PUT requests */ typedef struct { VALUE stream; size_t offset; } ruby_curl_upload; extern VALUE cCurlUpload; extern const rb_data_type_t ruby_curl_upload_data_type; void init_curb_upload(); VALUE ruby_curl_upload_new(VALUE klass); VALUE ruby_curl_upload_stream_set(VALUE self, VALUE stream); VALUE ruby_curl_upload_stream_get(VALUE self); VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset); VALUE ruby_curl_upload_offset_get(VALUE self); #endif curb-1.3.5/ext/curb_easy.c0000644000004100000410000050573415203731642015444 0ustar www-datawww-data/* curb_easy.c - Curl easy mode * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_easy.c 30 2006-12-09 12:30:24Z roscopeco $ */ #include "curb_easy.h" #include "curb_errors.h" #include "curb_postfield.h" #include "curb_upload.h" #include "curb_multi.h" #include #include #ifndef _WIN32 #include #endif extern VALUE mCurl; static VALUE idCall; static VALUE idJoin; static VALUE rbstrAmp; #ifdef RDOC_NEVER_DEFINED mCurl = rb_define_module("Curl"); #endif VALUE cCurlEasy; /* Internal wrapper type for passing pointers through rb_iterate callbacks. * No mark/free needed - these are temporary wrappers that don't own memory. */ static const rb_data_type_t curl_slist_ptr_type = { "curl_slist_ptr_wrapper", { NULL, NULL, NULL }, NULL, NULL, 0 }; // for Ruby 1.8 #ifndef HAVE_RB_IO_STDIO_FILE static FILE * rb_io_stdio_file(rb_io_t *fptr) { return fptr->f; } #endif static struct curl_slist *duplicate_curl_slist(struct curl_slist *list); static size_t proc_data_handler(char *stream, size_t size, size_t nmemb, VALUE proc); /* ================== CURL HANDLER FUNCS ==============*/ static VALUE callback_exception(VALUE unused, VALUE exception) { return Qfalse; } static VALUE callback_exception_store_on_easy(VALUE arg, VALUE exception) { ruby_curl_easy *rbce = (ruby_curl_easy *)arg; if (rbce && NIL_P(rbce->callback_error)) { rbce->callback_error = exception; } return Qfalse; } VALUE rb_curl_easy_take_callback_error(ruby_curl_easy *rbce) { VALUE exception = Qnil; if (!rbce) { return Qnil; } exception = rbce->callback_error; rbce->callback_error = Qnil; return exception; } static VALUE ruby_curl_easy_take_callback_error(VALUE self) { ruby_curl_easy *rbce = NULL; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return rb_curl_easy_take_callback_error(rbce); } static VALUE ensure_clear_easy_callback_active(VALUE arg) { ruby_curl_easy *rbce = (ruby_curl_easy *)arg; if (rbce) { rbce->callback_active = 0; } return Qnil; } static VALUE with_easy_callback_active(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) { rbce->callback_active = 1; return rb_ensure(func, arg, ensure_clear_easy_callback_active, (VALUE)rbce); } struct stream_read_call_args { VALUE stream; size_t read_bytes; }; static VALUE call_stream_read(VALUE argp) { struct stream_read_call_args *args = (struct stream_read_call_args *)argp; return rb_funcall(args->stream, rb_intern("read"), 1, ULONG2NUM((unsigned long)args->read_bytes)); } static VALUE call_stream_to_s(VALUE stream) { return rb_funcall(stream, rb_intern("to_s"), 0); } static VALUE call_string_value(VALUE str) { StringValue(str); return str; } struct stream_seek_call_args { VALUE stream; curl_off_t offset; int origin; }; static VALUE call_stream_seek(VALUE argp) { struct stream_seek_call_args *args = (struct stream_seek_call_args *)argp; return rb_funcall(args->stream, rb_intern("seek"), 2, LL2NUM(args->offset), INT2NUM(args->origin)); } struct proc_data_call_args { char *stream; size_t size; size_t nmemb; VALUE proc; }; static VALUE call_proc_data_handler_wrapped(VALUE argp) { struct proc_data_call_args *args = (struct proc_data_call_args *)argp; return ULONG2NUM((unsigned long)proc_data_handler(args->stream, args->size, args->nmemb, args->proc)); } struct easy_callback_dispatch_args { ruby_curl_easy *rbce; VALUE (*func)(VALUE); VALUE arg; }; static VALUE call_with_easy_callback_active(VALUE argp) { struct easy_callback_dispatch_args *args = (struct easy_callback_dispatch_args *)argp; return with_easy_callback_active(args->rbce, args->func, args->arg); } static VALUE rescue_easy_callback(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) { struct easy_callback_dispatch_args dispatch_args; dispatch_args.rbce = rbce; dispatch_args.func = func; dispatch_args.arg = arg; return rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce); } static size_t curl_read_abort_result(void) { #ifdef CURL_READFUNC_ABORT return CURL_READFUNC_ABORT; #else return 0; #endif } static int curl_seek_fail_result(void) { #ifdef CURL_SEEKFUNC_FAIL return CURL_SEEKFUNC_FAIL; #else return 1; #endif } /* Default body handler appends to easy.body_data buffer */ static size_t default_body_handler(char *stream, size_t size, size_t nmemb, void *userdata) { ruby_curl_easy *rbce = (ruby_curl_easy *)userdata; size_t total = size * nmemb; VALUE out = rb_easy_get("body_data"); if (NIL_P(out)) { out = rb_easy_set("body_data", rb_str_buf_new(32768)); } rb_str_buf_cat(out, stream, total); return total; } /* Default header handler appends to easy.header_data buffer */ static size_t default_header_handler(char *stream, size_t size, size_t nmemb, void *userdata) { ruby_curl_easy *rbce = (ruby_curl_easy *)userdata; size_t total = size * nmemb; VALUE out = rb_easy_get("header_data"); if (NIL_P(out)) { out = rb_easy_set("header_data", rb_str_buf_new(16384)); } rb_str_buf_cat(out, stream, total); return total; } // size_t function( void *ptr, size_t size, size_t nmemb, void *stream); static size_t read_data_handler(void *ptr, size_t size, size_t nmemb, ruby_curl_easy *rbce) { VALUE upload = rb_easy_get("upload"); size_t read_bytes = (size*nmemb); VALUE stream; if (NIL_P(upload)) { return curl_read_abort_result(); } stream = ruby_curl_upload_stream_get(upload); if (rb_respond_to(stream, rb_intern("read"))) {//if (rb_respond_to(stream, rb_intern("to_s"))) { /* copy read_bytes from stream into ptr */ struct stream_read_call_args args; args.stream = stream; args.read_bytes = read_bytes; VALUE str = rescue_easy_callback(rbce, call_stream_read, (VALUE)&args); if( str != Qnil ) { size_t str_len; str = rescue_easy_callback(rbce, call_string_value, str); if (str == Qfalse || str == Qnil) { return curl_read_abort_result(); } str_len = (size_t)RSTRING_LEN(str); if (str_len > read_bytes) { snprintf(rbce->err_buf, CURL_ERROR_SIZE, "read callback returned more data than requested"); return curl_read_abort_result(); } memcpy(ptr, RSTRING_PTR(str), str_len); return str_len; } else { return 0; } } else if (rb_respond_to(stream, rb_intern("to_s"))) { ruby_curl_upload *rbcu; VALUE str; size_t len; size_t remaining; char *str_ptr; TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); str = rescue_easy_callback(rbce, call_stream_to_s, stream); str = rescue_easy_callback(rbce, call_string_value, str); if (str == Qfalse || str == Qnil) { return curl_read_abort_result(); } len = RSTRING_LEN(str); if (rbcu->offset >= len) { return 0; } remaining = len - rbcu->offset; str_ptr = RSTRING_PTR(str); if( remaining <= read_bytes ) { if( remaining > 0 ) { memcpy(ptr, str_ptr+rbcu->offset, remaining); read_bytes = remaining; rbcu->offset += remaining; } return remaining; } else { // read_bytes < remaining - send what we can fit in the buffer(ptr) memcpy(ptr, str_ptr+rbcu->offset, read_bytes); rbcu->offset += read_bytes; } return read_bytes; } else { return 0; } } int seek_data_handler(ruby_curl_easy *rbce, curl_off_t offset, int origin) { VALUE upload = rb_easy_get("upload"); VALUE stream; if (NIL_P(upload)) { return curl_seek_fail_result(); } stream = ruby_curl_upload_stream_get(upload); if (rb_respond_to(stream, rb_intern("seek"))) { struct stream_seek_call_args args; args.stream = stream; args.offset = offset; args.origin = origin; rescue_easy_callback(rbce, call_stream_seek, (VALUE)&args); if (!NIL_P(rbce->callback_error)) { return curl_seek_fail_result(); } } else { ruby_curl_upload *rbcu; TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); // This OK because curl only uses SEEK_SET as per the documentation rbcu->offset = offset; } return 0; } static size_t proc_data_handler(char *stream, size_t size, size_t nmemb, VALUE proc) { VALUE procret; procret = rb_funcall(proc, idCall, 1, rb_str_new(stream, size * nmemb)); switch (rb_type(procret)) { case T_FIXNUM: return FIX2LONG(procret); case T_BIGNUM: return NUM2LONG(procret); default: rb_warn("Curl data handlers should return the number of bytes read as an Integer"); return size * nmemb; } } static size_t proc_data_handler_body(char *stream, size_t size, size_t nmemb, ruby_curl_easy *rbce) { struct proc_data_call_args args; struct easy_callback_dispatch_args dispatch_args; VALUE procret; args.stream = stream; args.size = size; args.nmemb = nmemb; args.proc = rb_easy_get("body_proc"); dispatch_args.rbce = rbce; dispatch_args.func = call_proc_data_handler_wrapped; dispatch_args.arg = (VALUE)&args; procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce); return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret); } static size_t proc_data_handler_header(char *stream, size_t size, size_t nmemb, ruby_curl_easy *rbce) { struct proc_data_call_args args; struct easy_callback_dispatch_args dispatch_args; VALUE procret; args.stream = stream; args.size = size; args.nmemb = nmemb; args.proc = rb_easy_get("header_proc"); dispatch_args.rbce = rbce; dispatch_args.func = call_proc_data_handler_wrapped; dispatch_args.arg = (VALUE)&args; procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce); return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret); } static VALUE call_progress_handler(VALUE ary) { return rb_funcall(rb_ary_entry(ary, 0), idCall, 4, rb_ary_entry(ary, 1), // rb_float_new(dltotal), rb_ary_entry(ary, 2), // rb_float_new(dlnow), rb_ary_entry(ary, 3), // rb_float_new(ultotal), rb_ary_entry(ary, 4)); // rb_float_new(ulnow)); } /* CURLOPT_PROGRESSFUNCTION callback (deprecated since 7.32.0) */ #ifndef HAVE_CURLOPT_XFERINFOFUNCTION static int proc_progress_handler(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { ruby_curl_easy *rbce = (ruby_curl_easy *)clientp; VALUE proc = rb_easy_get("progress_proc"); if (proc == Qnil) { return 0; } VALUE procret; VALUE callargs = rb_ary_new2(5); rb_ary_store(callargs, 0, proc); rb_ary_store(callargs, 1, rb_float_new(dltotal)); rb_ary_store(callargs, 2, rb_float_new(dlnow)); rb_ary_store(callargs, 3, rb_float_new(ultotal)); rb_ary_store(callargs, 4, rb_float_new(ulnow)); struct easy_callback_dispatch_args dispatch_args; dispatch_args.rbce = rbce; dispatch_args.func = call_progress_handler; dispatch_args.arg = callargs; procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil); return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0); } #endif /* CURLOPT_XFERINFOFUNCTION callback (since 7.32.0, replaces PROGRESSFUNCTION) */ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION static int proc_xferinfo_handler(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { ruby_curl_easy *rbce = (ruby_curl_easy *)clientp; VALUE proc = rb_easy_get("progress_proc"); if (proc == Qnil) { return 0; } VALUE procret; VALUE callargs = rb_ary_new2(5); rb_ary_store(callargs, 0, proc); rb_ary_store(callargs, 1, LL2NUM(dltotal)); rb_ary_store(callargs, 2, LL2NUM(dlnow)); rb_ary_store(callargs, 3, LL2NUM(ultotal)); rb_ary_store(callargs, 4, LL2NUM(ulnow)); struct easy_callback_dispatch_args dispatch_args; dispatch_args.rbce = rbce; dispatch_args.func = call_progress_handler; dispatch_args.arg = callargs; procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil); return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0); } #endif static VALUE call_debug_handler(VALUE ary) { return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), // INT2NUM(type), rb_ary_entry(ary, 2)); // rb_str_new(data, data_len) } static int proc_debug_handler(CURL *curl, curl_infotype type, char *data, size_t data_len, void *clientp) { ruby_curl_easy *rbce = (ruby_curl_easy *)clientp; VALUE proc = rb_easy_get("debug_proc"); if (proc == Qnil) { return 0; } VALUE callargs = rb_ary_new2(3); rb_ary_store(callargs, 0, proc); rb_ary_store(callargs, 1, INT2NUM(type)); rb_ary_store(callargs, 2, rb_str_new(data, data_len)); struct easy_callback_dispatch_args dispatch_args; dispatch_args.rbce = rbce; dispatch_args.func = call_debug_handler; dispatch_args.arg = callargs; rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil); /* no way to indicate to libcurl that we should break out given an exception in the on_debug handler... * this means exceptions will be swallowed */ //rb_funcall(proc, idCall, 2, INT2NUM(type), rb_str_new(data, data_len)); return 0; } /* ================== MARK/FREE/SIZE FUNCS ==================*/ static void curl_easy_mark(void *ptr) { ruby_curl_easy *rbce = (ruby_curl_easy *)ptr; if (rbce) { if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); } if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); } if (!NIL_P(rbce->callback_error)) { rb_gc_mark(rbce->callback_error); } } } static void ruby_curl_easy_clear_headers_list(ruby_curl_easy *rbce) { if (!rbce || !rbce->curl_headers) { return; } if (rbce->curl) { curl_easy_setopt(rbce->curl, CURLOPT_HTTPHEADER, NULL); } curl_slist_free_all(rbce->curl_headers); rbce->curl_headers = NULL; } static void ruby_curl_easy_clear_proxy_headers_list(ruby_curl_easy *rbce) { if (!rbce || !rbce->curl_proxy_headers) { return; } #ifdef HAVE_CURLOPT_PROXYHEADER if (rbce->curl) { curl_easy_setopt(rbce->curl, CURLOPT_PROXYHEADER, NULL); } #endif curl_slist_free_all(rbce->curl_proxy_headers); rbce->curl_proxy_headers = NULL; } static void ruby_curl_easy_clear_ftp_commands_list(ruby_curl_easy *rbce) { if (!rbce || !rbce->curl_ftp_commands) { return; } if (rbce->curl) { curl_easy_setopt(rbce->curl, CURLOPT_QUOTE, NULL); } curl_slist_free_all(rbce->curl_ftp_commands); rbce->curl_ftp_commands = NULL; } static void ruby_curl_easy_clear_resolve_list(ruby_curl_easy *rbce) { if (!rbce || !rbce->curl_resolve) { return; } #ifdef HAVE_CURLOPT_RESOLVE if (rbce->curl) { curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, NULL); } #endif curl_slist_free_all(rbce->curl_resolve); rbce->curl_resolve = NULL; } /* Legacy wrapper for external callers */ void ruby_curl_easy_mark(ruby_curl_easy *rbce) { curl_easy_mark((void *)rbce); } static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) { if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) { return NULL; } #if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA) if (!RTYPEDDATA_P(multi_val)) { return NULL; } if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) { return NULL; } return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val); #else if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) { return NULL; } return DATA_PTR(multi_val); #endif } static void ruby_curl_easy_free(ruby_curl_easy *rbce) { if (!rbce) { return; } if (!NIL_P(rbce->multi)) { VALUE multi_val = rbce->multi; ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(multi_val); rbce->multi = Qnil; if (rbcm) { /* Best-effort: ensure the handle is detached from the multi to avoid * libcurl retaining a dangling pointer to a soon-to-be cleaned-up easy * handle. This path runs during GC, so it must not invoke Ruby APIs that * can allocate or raise. */ if (rbcm->handle && rbce->curl) { curl_multi_remove_handle(rbcm->handle, rbce->curl); } rb_curl_multi_forget_easy(rbcm, rbce); } } ruby_curl_easy_clear_headers_list(rbce); ruby_curl_easy_clear_proxy_headers_list(rbce); ruby_curl_easy_clear_ftp_commands_list(rbce); ruby_curl_easy_clear_resolve_list(rbce); if (rbce->curl) { /* disable any progress or debug events */ curl_easy_setopt(rbce->curl, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(rbce->curl, CURLOPT_WRITEDATA, NULL); curl_easy_setopt(rbce->curl, CURLOPT_HEADERFUNCTION, NULL); curl_easy_setopt(rbce->curl, CURLOPT_HEADERDATA, NULL); curl_easy_setopt(rbce->curl, CURLOPT_DEBUGFUNCTION, NULL); curl_easy_setopt(rbce->curl, CURLOPT_DEBUGDATA, NULL); curl_easy_setopt(rbce->curl, CURLOPT_VERBOSE, 0); #ifdef HAVE_CURLOPT_XFERINFOFUNCTION curl_easy_setopt(rbce->curl, CURLOPT_XFERINFOFUNCTION, NULL); #else curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL); #endif curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1); curl_easy_cleanup(rbce->curl); rbce->curl = NULL; } rbce->self = Qnil; } /* TypedData-compatible free function */ static void curl_easy_free(void *ptr) { ruby_curl_easy *rbce = (ruby_curl_easy *)ptr; if (rbce) { ruby_curl_easy_free(rbce); free(rbce); } } /* Legacy wrapper for external callers (e.g., curb_multi) */ void ruby_curl_easy_free_wrapper(ruby_curl_easy *rbce) { curl_easy_free((void *)rbce); } static size_t curl_easy_memsize(const void *ptr) { const ruby_curl_easy *rbce = (const ruby_curl_easy *)ptr; size_t size = sizeof(ruby_curl_easy); /* Note: We don't count curl_slist or CURL handle memory as they're * managed by libcurl and would require complex introspection */ (void)rbce; /* silence unused warning */ return size; } const rb_data_type_t ruby_curl_easy_data_type = { "Curl::Easy", { curl_easy_mark, curl_easy_free, curl_easy_memsize, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, /* compact - not needed */ #endif }, #ifdef RUBY_TYPED_FREE_IMMEDIATELY NULL, NULL, /* parent, data */ RUBY_TYPED_FREE_IMMEDIATELY #endif }; /* ================= ALLOC METHODS ====================*/ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) { rbce->opts = rb_hash_new(); memset(rbce->err_buf, 0, CURL_ERROR_SIZE); rbce->self = Qnil; rbce->curl_headers = NULL; rbce->curl_proxy_headers = NULL; rbce->curl_ftp_commands = NULL; rbce->curl_resolve = NULL; /* various-typed opts */ rbce->local_port = 0; rbce->local_port_range = 0; rbce->proxy_port = 0; rbce->proxy_type = -1; rbce->http_auth_types = 0; rbce->proxy_auth_types = 0; rbce->max_redirs = -1; rbce->timeout = 0; rbce->timeout_ms = 0; rbce->connect_timeout = 0; rbce->connect_timeout_ms = 0; rbce->dns_cache_timeout = 60; rbce->ftp_response_timeout = 0; rbce->low_speed_limit = 0; rbce->low_speed_time = 0; rbce->max_send_speed_large = 0; rbce->max_recv_speed_large = 0; rbce->ssl_version = -1; rbce->use_ssl = -1; rbce->ftp_filemethod = -1; rbce->http_version = CURL_HTTP_VERSION_NONE; rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER; /* bool opts */ rbce->proxy_tunnel = 0; rbce->fetch_file_time = 0; rbce->ssl_verify_peer = 1; rbce->ssl_verify_host = 2; rbce->header_in_body = 0; rbce->use_netrc = 0; rbce->follow_location = 0; rbce->unrestricted_auth = 0; rbce->verbose = 0; rbce->multipart_form_post = 0; rbce->enable_cookies = 0; rbce->cookielist_engine_enabled = 0; rbce->ignore_content_length = 0; rbce->callback_active = 0; rbce->callback_error = Qnil; rbce->last_result = 0; } /* * Allocate space for a Curl::Easy instance. */ static VALUE ruby_curl_easy_allocate(VALUE klass) { ruby_curl_easy *rbce; rbce = ALLOC(ruby_curl_easy); if (!rbce) { rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy"); } rbce->curl = NULL; rbce->opts = Qnil; rbce->multi = Qnil; ruby_curl_easy_zero(rbce); return TypedData_Wrap_Struct(klass, &ruby_curl_easy_data_type, rbce); } /* * call-seq: * Curl::Easy.new => # * Curl::Easy.new(url = nil) => # * Curl::Easy.new(url = nil) { |self| ... } => # * * Initialize a new Curl::Easy instance, optionally supplying the URL. * The block form allows further configuration to be supplied before * the instance is returned. */ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) { CURLcode ecode; VALUE url, blk; ruby_curl_easy *rbce; rb_scan_args(argc, argv, "01&", &url, &blk); TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); /* handler */ rbce->curl = curl_easy_init(); if (!rbce->curl) { rb_raise(eCurlErrFailedInit, "Failed to initialize easy handle"); } rbce->multi = Qnil; rbce->opts = Qnil; ruby_curl_easy_zero(rbce); rbce->self = self; curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf); rb_easy_set("url", url); /* set the pointer to the curl handle */ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce); if (ecode != CURLE_OK) { raise_curl_easy_error_exception(ecode); } if (blk != Qnil) { rb_funcall(blk, idCall, 1, self); } return self; } /* Helper to duplicate a curl_slist */ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) { struct curl_slist *dup = NULL; struct curl_slist *tmp; struct curl_slist *new_list; for (tmp = list; tmp; tmp = tmp->next) { new_list = curl_slist_append(dup, tmp->data); if (!new_list) { if (dup) { curl_slist_free_all(dup); } rb_raise(rb_eNoMemError, "Failed to duplicate curl_slist"); } dup = new_list; } return dup; } static VALUE duplicate_upload(VALUE upload) { ruby_curl_upload *rbcu, *newrbcu; VALUE new_upload; if (NIL_P(upload)) { return Qnil; } TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu); new_upload = ruby_curl_upload_new(cCurlUpload); TypedData_Get_Struct(new_upload, ruby_curl_upload, &ruby_curl_upload_data_type, newrbcu); newrbcu->stream = rbcu->stream; newrbcu->offset = rbcu->offset; return new_upload; } /* * call-seq: * easy.clone => * easy.dup => * * Clone this Curl::Easy instance, creating a new instance. * This method duplicates the underlying CURL* handle. */ static VALUE ruby_curl_easy_clone(VALUE self) { ruby_curl_easy *rbce, *newrbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); newrbce = ALLOC(ruby_curl_easy); if (!newrbce) { rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy clone"); } /* shallow copy */ memcpy(newrbce, rbce, sizeof(ruby_curl_easy)); /* now deep copy */ newrbce->curl = curl_easy_duphandle(rbce->curl); if (!newrbce->curl) { free(newrbce); rb_raise(rb_eNoMemError, "Failed to duplicate Curl::Easy handle"); } newrbce->curl_headers = (rbce->curl_headers) ? duplicate_curl_slist(rbce->curl_headers) : NULL; newrbce->curl_proxy_headers = (rbce->curl_proxy_headers) ? duplicate_curl_slist(rbce->curl_proxy_headers) : NULL; newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL; newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL; /* A cloned easy should not retain ownership reference to the original multi. */ newrbce->multi = Qnil; newrbce->callback_error = Qnil; if (rbce->opts != Qnil) { newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0); } /* Set the error buffer on the new curl handle using the new err_buf */ curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf); if (newrbce->opts != Qnil) { VALUE upload = rb_hash_aref(newrbce->opts, rb_easy_hkey("upload")); if (!NIL_P(upload)) { rb_hash_aset(newrbce->opts, rb_easy_hkey("upload"), duplicate_upload(upload)); curl_easy_setopt(newrbce->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler); curl_easy_setopt(newrbce->curl, CURLOPT_READDATA, newrbce); #ifdef HAVE_CURLOPT_SEEKFUNCTION curl_easy_setopt(newrbce->curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler); #endif #ifdef HAVE_CURLOPT_SEEKDATA curl_easy_setopt(newrbce->curl, CURLOPT_SEEKDATA, newrbce); #endif } } VALUE clone = TypedData_Wrap_Struct(cCurlEasy, &ruby_curl_easy_data_type, newrbce); newrbce->self = clone; curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce); return clone; } /* * call-seq: * easy.close => nil * * Close the Curl::Easy instance. Any open connections are closed * The easy handle is reinitialized. If a previous multi handle was * open it is set to nil and will be cleared after a GC. */ static VALUE ruby_curl_easy_close(VALUE self) { CURLcode ecode; ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (rbce->callback_active) { rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback"); } ruby_curl_easy_free(rbce); /* reinit the handle */ rbce->curl = curl_easy_init(); if (!rbce->curl) { rb_raise(eCurlErrFailedInit, "Failed to initialize easy handle"); } rbce->multi = Qnil; ruby_curl_easy_zero(rbce); rbce->self = self; curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf); /* give the new curl handle a reference back to the ruby object */ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce); if (ecode != CURLE_OK) { raise_curl_easy_error_exception(ecode); } return Qnil; } /* * call-seq: * easy.reset => Hash * * Reset the Curl::Easy instance, clears out all settings. * * from http://curl.haxx.se/libcurl/c/curl_easy_reset.html * Re-initializes all options previously set on a specified CURL handle to the default values. This puts back the handle to the same state as it was in when it was just created with curl_easy_init(3). * It does not change the following information kept in the handle: live connections, the Session ID cache, the DNS cache, the cookies and shares. * * The return value contains all settings stored. */ static VALUE ruby_curl_easy_reset(VALUE self) { CURLcode ecode; ruby_curl_easy *rbce; VALUE opts_dup; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (rbce->callback_active) { rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback"); } opts_dup = rb_funcall(rbce->opts, rb_intern("dup"), 0); ruby_curl_easy_cleanup(self, rbce); curl_easy_reset(rbce->curl); ruby_curl_easy_zero(rbce); rbce->self = self; curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf); /* reset clobbers the private setting, so reset it to self */ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce); if (ecode != CURLE_OK) { raise_curl_easy_error_exception(ecode); } return opts_dup; } /* ================ OBJ ATTRIBUTES ==================*/ /* * call-seq: * easy.url => string * * Obtain the URL that will be used by subsequent calls to +perform+. */ static VALUE ruby_curl_easy_url_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, url); } /* * call-seq: * easy.proxy_url => string * * Obtain the HTTP Proxy URL that will be used by subsequent calls to +perform+. */ static VALUE ruby_curl_easy_proxy_url_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, proxy_url); } /* * call-seq: * easy.headers = "Header: val" => "Header: val" * easy.headers = {"Header" => "val" ..., "Header" => "val"} => {"Header: val", ...} * easy.headers = ["Header: val" ..., "Header: val"] => ["Header: val", ...] * * Set custom HTTP headers for following requests. This can be used to add * custom headers, or override standard headers used by libcurl. It defaults to a * Hash. * * For example to set a standard or custom header: * * easy.headers["MyHeader"] = "myval" * * To remove a standard header (this is useful when removing libcurls default * 'Expect: 100-Continue' header when using HTTP form posts): * * easy.headers["Expect"] = '' * * Anything passed to libcurl as a header will be converted to a string during * the perform step. */ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) { CURB_OBJECT_HSETTER(ruby_curl_easy, headers); } static VALUE ruby_curl_easy_proxy_headers_set(VALUE self, VALUE proxy_headers) { CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_headers); } /* * call-seq: * easy.headers => Hash, Array or Str * * Obtain the custom HTTP headers for following requests. */ static VALUE ruby_curl_easy_headers_get(VALUE self) { ruby_curl_easy *rbce; VALUE headers; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers")); if (headers == Qnil) { headers = rb_easy_set("headers", rb_hash_new()); } return headers; } /* * call-seq: * easy.proxy_headers = "Header: val" => "Header: val" * easy.proxy_headers = {"Header" => "val" ..., "Header" => "val"} => {"Header: val", ...} * easy.proxy_headers = ["Header: val" ..., "Header: val"] => ["Header: val", ...] * * * For example to set a standard or custom header: * * easy.proxy_headers["MyHeader"] = "myval" * * To remove a standard header (this is useful when removing libcurls default * 'Expect: 100-Continue' header when using HTTP form posts): * * easy.proxy_headers["Expect"] = '' * * Anything passed to libcurl as a header will be converted to a string during * the perform step. */ /* * call-seq: * easy.proxy_headers => Hash, Array or Str * * Obtain the custom HTTP proxy_headers for following requests. */ static VALUE ruby_curl_easy_proxy_headers_get(VALUE self) { ruby_curl_easy *rbce; VALUE proxy_headers; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); proxy_headers = rb_easy_get("proxy_headers");//rb_hash_aref(rbce->opts, rb_intern("proxy_headers")); if (proxy_headers == Qnil) { proxy_headers = rb_easy_set("proxy_headers", rb_hash_new()); } return proxy_headers; } /* * call-seq: * easy.interface => string * * Obtain the interface name that is used as the outgoing network interface. * The name can be an interface name, an IP address or a host name. */ static VALUE ruby_curl_easy_interface_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, interface_hm); } /* * call-seq: * easy.userpwd => string * * Obtain the username/password string that will be used for subsequent * calls to +perform+. */ static VALUE ruby_curl_easy_userpwd_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, userpwd); } /* * call-seq: * easy.proxypwd => string * * Obtain the username/password string that will be used for proxy * connection during subsequent calls to +perform+. The supplied string * should have the form "username:password" */ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, proxypwd); } /* * call-seq: * easy.cookies => "name1=content1; name2=content2;" * * Obtain the manually set Cookie header string for this Curl::Easy instance. * * Notes: * - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing * Cookie request header. It does NOT modify the internal libcurl cookie engine * that stores cookies received via Set-Cookie. * - To inspect or modify cookies stored in the cookie engine, use * +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter). * - To clear a previously set manual Cookie header, assign an empty string. * Assigning +nil+ currently has no effect. */ static VALUE ruby_curl_easy_cookies_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cookies); } /* * call-seq: * easy.cookiefile => string * * Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the * cookie engine is enabled). */ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile); } /* * call-seq: * easy.cookiejar => string * * Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the * cookie engine is enabled). */ static VALUE ruby_curl_easy_cookiejar_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar); } /* * call-seq: * easy.cert = string => "" * * Set a cert file to use for this Curl::Easy instance. This file * will be used to validate SSL connections. * */ static VALUE ruby_curl_easy_cert_set(VALUE self, VALUE cert) { CURB_OBJECT_HSETTER(ruby_curl_easy, cert); } /* * call-seq: * easy.cert => string * * Obtain the cert file to use for this Curl::Easy instance. */ static VALUE ruby_curl_easy_cert_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cert); } /* * call-seq: * easy.cert_key = "cert_key.file" => "" * * Set a cert key to use for this Curl::Easy instance. This file * will be used to validate SSL certificates. * */ static VALUE ruby_curl_easy_cert_key_set(VALUE self, VALUE cert_key) { CURB_OBJECT_HSETTER(ruby_curl_easy, cert_key); } /* * call-seq: * easy.cert_key => "cert_key.file" * * Obtain the cert key file to use for this Curl::Easy instance. */ static VALUE ruby_curl_easy_cert_key_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cert_key); } /* * call-seq: * easy.cacert = string => "" * * Set a cacert bundle to use for this Curl::Easy instance. This file * will be used to validate SSL certificates. * */ static VALUE ruby_curl_easy_cacert_set(VALUE self, VALUE cacert) { CURB_OBJECT_HSETTER(ruby_curl_easy, cacert); } /* * call-seq: * easy.cacert => string * * Obtain the cacert file to use for this Curl::Easy instance. */ static VALUE ruby_curl_easy_cacert_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, cacert); } /* * call-seq: * easy.certpassword = string => "" * * Set a password used to open the specified cert */ static VALUE ruby_curl_easy_certpassword_set(VALUE self, VALUE certpassword) { CURB_OBJECT_HSETTER(ruby_curl_easy, certpassword); } /* * call-seq: * easy.certtype = "PEM|DER" => "" * * Set a cert type to use for this Curl::Easy instance. * Default is PEM * */ static VALUE ruby_curl_easy_certtype_set(VALUE self, VALUE certtype) { CURB_OBJECT_HSETTER(ruby_curl_easy, certtype); } /* * call-seq: * easy.certtype => string * * Obtain the cert type used for this Curl::Easy instance */ static VALUE ruby_curl_easy_certtype_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, certtype); } /* * call-seq: * easy.encoding = string => string * * Set the accepted encoding types, curl will handle all of the decompression * */ static VALUE ruby_curl_easy_encoding_set(VALUE self, VALUE encoding) { CURB_OBJECT_HSETTER(ruby_curl_easy, encoding); } /* * call-seq: * easy.encoding => string * * Get the set encoding types * */ static VALUE ruby_curl_easy_encoding_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, encoding); } /* * call-seq: * easy.useragent = "Ruby/Curb" => "" * * Set the user agent string for this Curl::Easy instance * */ static VALUE ruby_curl_easy_useragent_set(VALUE self, VALUE useragent) { CURB_OBJECT_HSETTER(ruby_curl_easy, useragent); } /* * call-seq: * easy.useragent => "Ruby/Curb" * * Obtain the user agent string used for this Curl::Easy instance */ static VALUE ruby_curl_easy_useragent_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, useragent); } /* * call-seq: * easy.post_body = "some=form%20data&to=send" => string or nil * * Sets the POST body of this Curl::Easy instance. This is expected to be * URL encoded; no additional processing or encoding is done on the string. * The content-type header will be set to application/x-www-form-urlencoded. * * This is handy if you want to perform a POST against a Curl::Multi instance. */ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) { ruby_curl_easy *rbce; CURL *curl; VALUE body_str; VALUE retained_body_str; char *data; long len; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl = rbce->curl; if ( post_body == Qnil ) { rb_easy_del("postdata_buffer"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); if (force_http_get_on_nil) { curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); } } else { if (rb_type(post_body) == T_STRING) { body_str = post_body; } else if (rb_respond_to(post_body, rb_intern("to_s"))) { body_str = rb_funcall(post_body, rb_intern("to_s"), 0); } else { rb_raise(rb_eRuntimeError, "post data must respond_to .to_s"); } StringValue(body_str); // Store the string, since it has to hang around for the duration of the // request. See CURLOPT_POSTFIELDS in the libcurl docs. #ifdef HAVE_CURLOPT_COPYPOSTFIELDS /* * libcurl copies the bytes immediately for COPYPOSTFIELDS, so retain a * matching Ruby snapshot for post_body instead of the caller's mutable * source string. */ retained_body_str = rb_str_dup(body_str); #else retained_body_str = body_str; #endif data = StringValuePtr(retained_body_str); len = RSTRING_LEN(retained_body_str); rb_easy_set("postdata_buffer", retained_body_str); curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len); #ifdef HAVE_CURLOPT_COPYPOSTFIELDS curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data); #else curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); #endif return post_body; } return Qnil; } static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) { return ruby_curl_easy_post_body_set_with_mode(self, post_body, 1); } /* * call-seq: * easy.post_body => string or nil * * Obtain the POST body used in this Curl::Easy instance. */ static VALUE ruby_curl_easy_post_body_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, postdata_buffer); } /* * call-seq: * easy.put_data = data => "" * * Points this Curl::Easy instance to data to be uploaded via PUT. This * sets the request to a PUT type request - useful if you want to PUT via * a multi handle. */ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) { ruby_curl_easy *rbce; CURL *curl; VALUE upload; VALUE upload_stream = data; VALUE headers; VALUE infile_size = Qnil; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); /* * Validate and prepare Ruby-visible state before mutating the CURL handle. * Several branches below can raise (header type, stat, size, to_s). */ if (!rb_easy_nil("headers")) { if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_STRING)) { rb_raise(rb_eRuntimeError, "Must set headers as a HASH to modify the headers in an PUT request"); } } if (!NIL_P(data) && !rb_respond_to(data, rb_intern("read"))) { if (rb_respond_to(data, rb_intern("to_s"))) { upload_stream = rb_obj_as_string(data); } else { rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s"); } } headers = rb_easy_get("headers"); if( headers == Qnil ) { headers = rb_hash_new(); } if (!NIL_P(data) && rb_respond_to(data, rb_intern("read"))) { VALUE stat = Qnil; if (rb_respond_to(data, rb_intern("stat"))) { stat = rb_funcall(data, rb_intern("stat"), 0); } if(!NIL_P(stat) && stat != Qfalse && rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil) { VALUE size; if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) { rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2("")); } size = rb_funcall(stat, rb_intern("size"), 0); infile_size = size; } else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil && rb_hash_aref(headers, rb_str_new2("Transfer-Encoding")) == Qnil ) { rb_hash_aset(headers, rb_str_new2("Transfer-Encoding"), rb_str_new2("chunked")); } else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) ) { VALUE size = rb_funcall(rb_hash_aref(headers, rb_str_new2("Content-Length")), rb_intern("to_i"), 0); infile_size = size; } } else if (!NIL_P(data) && rb_respond_to(data, rb_intern("to_s"))) { infile_size = LONG2NUM(RSTRING_LEN(upload_stream)); if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) { rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2("")); } } else if (NIL_P(data)) { /* Preserve legacy nil handling: configure an upload with no payload. */ } else { rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s"); } rb_easy_set("headers",headers); upload = ruby_curl_upload_new(cCurlUpload); ruby_curl_upload_stream_set(upload, upload_stream); curl = rbce->curl; rb_easy_set("upload", upload); /* keep the upload object alive as long as the easy handle is active or until the upload is complete or terminated... */ curl_easy_setopt(curl, CURLOPT_NOBODY, 0); curl_easy_setopt(curl, CURLOPT_POST, 0); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler); #ifdef HAVE_CURLOPT_SEEKFUNCTION curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler); #endif curl_easy_setopt(curl, CURLOPT_READDATA, rbce); #ifdef HAVE_CURLOPT_SEEKDATA curl_easy_setopt(curl, CURLOPT_SEEKDATA, rbce); #endif if (!NIL_P(infile_size)) { curl_easy_setopt(curl, CURLOPT_INFILESIZE, NUM2LONG(infile_size)); } // if we made it this far, all should be well. return data; } /* * call-seq: * easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...] * * Explicitly sets the list of commands to execute on the FTP server when calling perform. * * NOTE: * - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection. * - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not * parse PASV/EPSV replies from QUOTE commands and will not establish the required data * connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`) * and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV * and the data connection for you. */ static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) { CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands); } /* * call-seq: * easy.ftp_commands => array or nil */ static VALUE ruby_curl_easy_ftp_commands_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_commands); } /* * call-seq: * easy.resolve = [ "example.com:80:127.0.0.1" ] => [ "example.com:80:127.0.0.1" ] * * Set the resolve list to statically resolve hostnames to IP addresses, * bypassing DNS for matching hostname/port combinations. */ static VALUE ruby_curl_easy_resolve_set(VALUE self, VALUE resolve) { CURB_OBJECT_HSETTER(ruby_curl_easy, resolve); } /* * call-seq: * easy.resolve => array or nil */ static VALUE ruby_curl_easy_resolve_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, resolve); } /* ================== IMMED ATTRS ==================*/ /* * call-seq: * easy.local_port = fixnum or nil => fixnum or nil * * Set the local port that will be used for the following +perform+ calls. * * Passing +nil+ will return to the default behaviour (no local port * preference). * * This option is ignored if compiled against libcurl < 7.15.2. */ static VALUE ruby_curl_easy_local_port_set(VALUE self, VALUE local_port) { CURB_IMMED_PORT_SETTER(ruby_curl_easy, local_port, "port"); } /* * call-seq: * easy.local_port => fixnum or nil * * Obtain the local port that will be used for the following +perform+ calls. * * This option is ignored if compiled against libcurl < 7.15.2. */ static VALUE ruby_curl_easy_local_port_get(VALUE self) { CURB_IMMED_PORT_GETTER(ruby_curl_easy, local_port); } /* * call-seq: * easy.local_port_range = fixnum or nil => fixnum or nil * * Set the local port range that will be used for the following +perform+ * calls. This is a number (between 0 and 65535) that determines how far * libcurl may deviate from the supplied +local_port+ in order to find * an available port. * * If you set +local_port+ it's also recommended that you set this, since * it is fairly likely that your specified port will be unavailable. * * This option is ignored if compiled against libcurl < 7.15.2. */ static VALUE ruby_curl_easy_local_port_range_set(VALUE self, VALUE local_port_range) { CURB_IMMED_PORT_SETTER(ruby_curl_easy, local_port_range, "port range"); } /* * call-seq: * easy.local_port_range => fixnum or nil * * Obtain the local port range that will be used for the following +perform+ * calls. * * This option is ignored if compiled against libcurl < 7.15.2. */ static VALUE ruby_curl_easy_local_port_range_get(VALUE self) { CURB_IMMED_PORT_GETTER(ruby_curl_easy, local_port_range); } /* * call-seq: * easy.proxy_port = fixnum or nil => fixnum or nil * * Set the proxy port that will be used for the following +perform+ calls. */ static VALUE ruby_curl_easy_proxy_port_set(VALUE self, VALUE proxy_port) { CURB_IMMED_PORT_SETTER(ruby_curl_easy, proxy_port, "port"); } /* * call-seq: * easy.proxy_port => fixnum or nil * * Obtain the proxy port that will be used for the following +perform+ calls. */ static VALUE ruby_curl_easy_proxy_port_get(VALUE self) { CURB_IMMED_PORT_GETTER(ruby_curl_easy, proxy_port); } /* * call-seq: * easy.proxy_type = fixnum or nil => fixnum or nil * * Set the proxy type that will be used for the following +perform+ calls. * This should be one of the Curl::CURLPROXY constants. */ static VALUE ruby_curl_easy_proxy_type_set(VALUE self, VALUE proxy_type) { CURB_IMMED_SETTER(ruby_curl_easy, proxy_type, -1); } /* * call-seq: * easy.proxy_type => fixnum or nil * * Obtain the proxy type that will be used for the following +perform+ calls. */ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, proxy_type, -1); } #if defined(HAVE_CURLAUTH_DIGEST_IE) #define CURL_HTTPAUTH_STR_TO_NUM(node) \ (!strncmp("basic",node,5)) ? CURLAUTH_BASIC : \ (!strncmp("digest_ie",node,9)) ? CURLAUTH_DIGEST_IE : \ (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \ (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \ (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0 #else #define CURL_HTTPAUTH_STR_TO_NUM(node) \ (!strncmp("basic",node,5)) ? CURLAUTH_BASIC : \ (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \ (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \ (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0 #endif /* * call-seq: * easy.http_auth_types = fixnum or nil => fixnum or nil * easy.http_auth_types = [:basic,:digest,:digest_ie,:gssnegotiate, :ntlm, :any, :anysafe] * * Set the HTTP authentication types that may be used for the following * +perform+ calls. This is a bitmap made by ORing together the * Curl::CURLAUTH constants. */ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE self) {//VALUE self, VALUE http_auth_types) { ruby_curl_easy *rbce; VALUE args_ary; long i, len; char* node = NULL; long mask = 0; rb_scan_args(argc, argv, "*", &args_ary); TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); len = RARRAY_LEN(args_ary); if (len == 1 && (rb_ary_entry(args_ary,0) == Qnil || TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM || TYPE(rb_ary_entry(args_ary,0)) == T_BIGNUM)) { if (rb_ary_entry(args_ary,0) == Qnil) { rbce->http_auth_types = 0; } else { rbce->http_auth_types = NUM2LONG(rb_ary_entry(args_ary,0)); } } else { // we could have multiple values, but they should be symbols node = RSTRING_PTR(rb_funcall(rb_ary_entry(args_ary,0),rb_intern("to_s"),0)); mask = CURL_HTTPAUTH_STR_TO_NUM(node); for( i = 1; i < len; ++i ) { node = RSTRING_PTR(rb_funcall(rb_ary_entry(args_ary,i),rb_intern("to_s"),0)); mask |= CURL_HTTPAUTH_STR_TO_NUM(node); } rbce->http_auth_types = mask; } return LONG2NUM(rbce->http_auth_types); } /* * call-seq: * easy.http_auth_types => fixnum or nil * * Obtain the HTTP authentication types that may be used for the following * +perform+ calls. */ static VALUE ruby_curl_easy_http_auth_types_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, http_auth_types, 0); } /* * call-seq: * easy.proxy_auth_types = fixnum or nil => fixnum or nil * * Set the proxy authentication types that may be used for the following * +perform+ calls. This is a bitmap made by ORing together the * Curl::CURLAUTH constants. */ static VALUE ruby_curl_easy_proxy_auth_types_set(VALUE self, VALUE proxy_auth_types) { CURB_IMMED_SETTER(ruby_curl_easy, proxy_auth_types, 0); } /* * call-seq: * easy.proxy_auth_types => fixnum or nil * * Obtain the proxy authentication types that may be used for the following * +perform+ calls. */ static VALUE ruby_curl_easy_proxy_auth_types_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, proxy_auth_types, 0); } /* * call-seq: * easy.max_redirects = fixnum or nil => fixnum or nil * * Set the maximum number of redirections to follow in the following +perform+ * calls. Set to nil or -1 allow an infinite number (the default). Setting this * option only makes sense if +follow_location+ is also set true. * * With libcurl >= 7.15.1, setting this to 0 will cause libcurl to refuse any * redirect. */ static VALUE ruby_curl_easy_max_redirects_set(VALUE self, VALUE max_redirs) { CURB_IMMED_SETTER(ruby_curl_easy, max_redirs, -1); } /* * call-seq: * easy.max_redirects => fixnum or nil * * Obtain the maximum number of redirections to follow in the following * +perform+ calls. */ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, max_redirs, -1); } /* * call-seq: * easy.timeout = float, fixnum or nil => numeric * * Set the maximum time in seconds that you allow the libcurl transfer * operation to take. Normally, name lookups can take a considerable time * and limiting operations to less than a few minutes risk aborting * perfectly normal operations. * * Set to nil (or zero) to disable timeout (it will then only timeout * on the system's internal timeouts). * * Uses timeout_ms internally instead of timeout because it allows for * better precision and libcurl will use the last set value when both * timeout and timeout_ms are set. * */ static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (Qnil == timeout_s || NUM2DBL(timeout_s) <= 0.0) { rbce->timeout_ms = 0; } else { rbce->timeout_ms = (unsigned long)(NUM2DBL(timeout_s) * 1000); } return DBL2NUM(rbce->timeout_ms / 1000.0); } /* * call-seq: * easy.timeout => numeric * * Obtain the maximum time in seconds that you allow the libcurl transfer * operation to take. * * Uses timeout_ms internally instead of timeout. * */ static VALUE ruby_curl_easy_timeout_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return DBL2NUM(rbce->timeout_ms / 1000.0); } /* * call-seq: * easy.timeout_ms = fixnum or nil => fixnum or nil * * Set the maximum time in milliseconds that you allow the libcurl transfer * operation to take. Normally, name lookups can take a considerable time * and limiting operations to less than a few minutes risk aborting * perfectly normal operations. * * Set to nil (or zero) to disable timeout (it will then only timeout * on the system's internal timeouts). */ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (Qnil == timeout_ms || NUM2DBL(timeout_ms) <= 0.0) { rbce->timeout_ms = 0; } else { rbce->timeout_ms = NUM2ULONG(timeout_ms); } return ULONG2NUM(rbce->timeout_ms); } /* * call-seq: * easy.timeout_ms => fixnum or nil * * Obtain the maximum time in milliseconds that you allow the libcurl transfer * operation to take. */ static VALUE ruby_curl_easy_timeout_ms_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return LONG2NUM(rbce->timeout_ms); } /* * call-seq: * easy.connect_timeout = fixnum or nil => fixnum or nil * * Set the maximum time in seconds that you allow the connection to the * server to take. This only limits the connection phase, once it has * connected, this option is of no more use. * * Set to nil (or zero) to disable connection timeout (it will then only * timeout on the system's internal timeouts). */ static VALUE ruby_curl_easy_connect_timeout_set(VALUE self, VALUE connect_timeout) { CURB_IMMED_SETTER(ruby_curl_easy, connect_timeout, 0); } /* * call-seq: * easy.connect_timeout => fixnum or nil * * Obtain the maximum time in seconds that you allow the connection to the * server to take. */ static VALUE ruby_curl_easy_connect_timeout_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout, 0); } /* * call-seq: * easy.connect_timeout_ms = fixnum or nil => fixnum or nil * * Set the maximum time in milliseconds that you allow the connection to the * server to take. This only limits the connection phase, once it has * connected, this option is of no more use. * * Set to nil (or zero) to disable connection timeout (it will then only * timeout on the system's internal timeouts). */ static VALUE ruby_curl_easy_connect_timeout_ms_set(VALUE self, VALUE connect_timeout_ms) { CURB_IMMED_SETTER(ruby_curl_easy, connect_timeout_ms, 0); } /* * call-seq: * easy.connect_timeout_ms => fixnum or nil * * Obtain the maximum time in milliseconds that you allow the connection to the * server to take. */ static VALUE ruby_curl_easy_connect_timeout_ms_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout_ms, 0); } /* * call-seq: * easy.dns_cache_timeout = fixnum or nil => fixnum or nil * * Set the dns cache timeout in seconds. Name resolves will be kept in * memory for this number of seconds. Set to zero (0) to completely disable * caching, or set to nil (or -1) to make the cached entries remain forever. * By default, libcurl caches this info for 60 seconds. */ static VALUE ruby_curl_easy_dns_cache_timeout_set(VALUE self, VALUE dns_cache_timeout) { CURB_IMMED_SETTER(ruby_curl_easy, dns_cache_timeout, -1); } /* * call-seq: * easy.dns_cache_timeout => fixnum or nil * * Obtain the dns cache timeout in seconds. */ static VALUE ruby_curl_easy_dns_cache_timeout_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, dns_cache_timeout, -1); } /* * call-seq: * easy.ftp_response_timeout = fixnum or nil => fixnum or nil * * Set a timeout period (in seconds) on the amount of time that the server * is allowed to take in order to generate a response message for a command * before the session is considered hung. While curl is waiting for a * response, this value overrides +timeout+. It is recommended that if used * in conjunction with +timeout+, you set +ftp_response_timeout+ to a value * smaller than +timeout+. * * Ignored if libcurl version is < 7.10.8. */ static VALUE ruby_curl_easy_ftp_response_timeout_set(VALUE self, VALUE ftp_response_timeout) { CURB_IMMED_SETTER(ruby_curl_easy, ftp_response_timeout, 0); } /* * call-seq: * easy.ftp_response_timeout => fixnum or nil * * Obtain the maximum time that libcurl will wait for FTP command responses. */ static VALUE ruby_curl_easy_ftp_response_timeout_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, ftp_response_timeout, 0); } /* * call-seq: * easy.low_speed_limit = fixnum or nil => fixnum or nil * * Set the transfer speed (in bytes per second) that the transfer should be * below during +low_speed_time+ seconds for the library to consider it too * slow and abort. */ static VALUE ruby_curl_easy_low_speed_limit_set(VALUE self, VALUE low_speed_limit) { CURB_IMMED_SETTER(ruby_curl_easy, low_speed_limit, 0); } /* * call-seq: * easy.low_speed_limit => fixnum or nil * * Obtain the minimum transfer speed over +low_speed+time+ below which the * transfer will be aborted. */ static VALUE ruby_curl_easy_low_speed_limit_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, low_speed_limit, 0); } /* * call-seq: * easy.low_speed_time = fixnum or nil => fixnum or nil * * Set the time (in seconds) that the transfer should be below the * +low_speed_limit+ for the library to consider it too slow and abort. */ static VALUE ruby_curl_easy_low_speed_time_set(VALUE self, VALUE low_speed_time) { CURB_IMMED_SETTER(ruby_curl_easy, low_speed_time, 0); } /* * call-seq: * easy.low_speed_time => fixnum or nil * * Obtain the time that the transfer should be below +low_speed_limit+ for * the library to abort it. */ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, low_speed_time, 0); } /* * call-seq: * easy.max_send_speed_large = fixnum or nil => fixnum or nil * * Set the maximal sending transfer speed (in bytes per second) */ static VALUE ruby_curl_easy_max_send_speed_large_set(VALUE self, VALUE max_send_speed_large) { CURB_IMMED_SETTER(ruby_curl_easy, max_send_speed_large, 0); } /* * call-seq: * easy.max_send_speed_large = fixnum or nil => fixnum or nil * * Get the maximal sending transfer speed (in bytes per second) */ static VALUE ruby_curl_easy_max_send_speed_large_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, max_send_speed_large, 0); } /* * call-seq: * easy.max_recv_speed_large = fixnum or nil => fixnum or nil * * Set the maximal receiving transfer speed (in bytes per second) */ static VALUE ruby_curl_easy_max_recv_speed_large_set(VALUE self, VALUE max_recv_speed_large) { CURB_IMMED_SETTER(ruby_curl_easy, max_recv_speed_large, 0); } /* * call-seq: * easy.max_recv_speed_large = fixnum or nil => fixnum or nil * * Get the maximal receiving transfer speed (in bytes per second) */ static VALUE ruby_curl_easy_max_recv_speed_large_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, max_recv_speed_large, 0); } /* * call-seq: * easy.username = string => string * * Set the HTTP Authentication username. */ static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) { #ifdef HAVE_CURLOPT_USERNAME CURB_OBJECT_HSETTER(ruby_curl_easy, username); #else return Qnil; #endif } /* * call-seq: * easy.username => string * * Get the current username */ static VALUE ruby_curl_easy_username_get(VALUE self) { #ifdef HAVE_CURLOPT_USERNAME CURB_OBJECT_HGETTER(ruby_curl_easy, username); #else return Qnil; #endif } /* * call-seq: * easy.password = string => string * * Set the HTTP Authentication password. */ static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) { #ifdef HAVE_CURLOPT_PASSWORD CURB_OBJECT_HSETTER(ruby_curl_easy, password); #else return Qnil; #endif } /* * call-seq: * easy.password => string * * Get the current password */ static VALUE ruby_curl_easy_password_get(VALUE self) { #ifdef HAVE_CURLOPT_PASSWORD CURB_OBJECT_HGETTER(ruby_curl_easy, password); #else return Qnil; #endif } /* * call-seq: * easy.ssl_version = value => fixnum or nil * * Sets the version of SSL/TLS that libcurl will attempt to use. Valid * options are: * * Curl::CURL_SSLVERSION_DEFAULT * Curl::CURL_SSLVERSION_TLSv1 (TLS 1.x) * Curl::CURL_SSLVERSION_SSLv2 * Curl::CURL_SSLVERSION_SSLv3 * Curl::CURL_SSLVERSION_TLSv1_0 * Curl::CURL_SSLVERSION_TLSv1_1 * Curl::CURL_SSLVERSION_TLSv1_2 * Curl::CURL_SSLVERSION_TLSv1_3 */ static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) { CURB_IMMED_SETTER(ruby_curl_easy, ssl_version, -1); } /* * call-seq: * easy.ssl_version => fixnum * * Get the version of SSL/TLS that libcurl will attempt to use. */ static VALUE ruby_curl_easy_ssl_version_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, ssl_version, -1); } /* * call-seq: * easy.use_ssl = value => fixnum or nil * * Ensure libcurl uses SSL for FTP connections. Valid options are Curl::CURL_USESSL_NONE, * Curl::CURL_USESSL_TRY, Curl::CURL_USESSL_CONTROL, and Curl::CURL_USESSL_ALL. */ static VALUE ruby_curl_easy_use_ssl_set(VALUE self, VALUE use_ssl) { CURB_IMMED_SETTER(ruby_curl_easy, use_ssl, -1); } /* * call-seq: * easy.use_ssl => fixnum * * Get the desired level for using SSL on FTP connections. */ static VALUE ruby_curl_easy_use_ssl_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, use_ssl, -1); } /* * call-seq: * easy.ftp_filemethod = value => fixnum or nil * * Controls how libcurl reaches files on the server. Valid options are Curl::CURL_MULTICWD, * Curl::CURL_NOCWD, and Curl::CURL_SINGLECWD (see libcurl docs for CURLOPT_FTP_METHOD). */ static VALUE ruby_curl_easy_ftp_filemethod_set(VALUE self, VALUE ftp_filemethod) { CURB_IMMED_SETTER(ruby_curl_easy, ftp_filemethod, -1); } /* * call-seq: * easy.ftp_filemethod => fixnum * * Get the configuration for how libcurl will reach files on the server. */ static VALUE ruby_curl_easy_ftp_filemethod_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, ftp_filemethod, -1); } /* ================== BOOL ATTRS ===================*/ /* * call-seq: * easy.proxy_tunnel = boolean => boolean * * Configure whether this Curl instance will use proxy tunneling. */ static VALUE ruby_curl_easy_proxy_tunnel_set(VALUE self, VALUE proxy_tunnel) { CURB_BOOLEAN_SETTER(ruby_curl_easy, proxy_tunnel); } /* * call-seq: * easy.proxy_tunnel? => boolean * * Determine whether this Curl instance will use proxy tunneling. */ static VALUE ruby_curl_easy_proxy_tunnel_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, proxy_tunnel); } /* * call-seq: * easy.fetch_file_time = boolean => boolean * * Configure whether this Curl instance will fetch remote file * times, if available. */ static VALUE ruby_curl_easy_fetch_file_time_set(VALUE self, VALUE fetch_file_time) { CURB_BOOLEAN_SETTER(ruby_curl_easy, fetch_file_time); } /* * call-seq: * easy.fetch_file_time? => boolean * * Determine whether this Curl instance will fetch remote file * times, if available. */ static VALUE ruby_curl_easy_fetch_file_time_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, fetch_file_time); } /* * call-seq: * easy.ssl_verify_peer = boolean => boolean * * Configure whether this Curl instance will verify the SSL peer * certificate. When true (the default), and the verification fails to * prove that the certificate is authentic, the connection fails. When * false, the connection succeeds regardless. * * Authenticating the certificate is not by itself very useful. You * typically want to ensure that the server, as authentically identified * by its certificate, is the server you mean to be talking to. * The ssl_verify_host? options controls that. */ static VALUE ruby_curl_easy_ssl_verify_peer_set(VALUE self, VALUE ssl_verify_peer) { CURB_BOOLEAN_SETTER(ruby_curl_easy, ssl_verify_peer); } /* * call-seq: * easy.ssl_verify_peer? => boolean * * Determine whether this Curl instance will verify the SSL peer * certificate. */ static VALUE ruby_curl_easy_ssl_verify_peer_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, ssl_verify_peer); } /* * call-seq: * easy.ssl_verify_host = [0, 1, 2] => [0, 1, 2] * * Configure whether this Curl instance will verify that the server cert * is for the server it is known as. When true (the default) the server * certificate must indicate that the server is the server to which you * meant to connect, or the connection fails. When false, the connection * will succeed regardless of the names in the certificate. * * this option controls is of the identity that the server claims. * The server could be lying. To control lying, see ssl_verify_peer? . */ static VALUE ruby_curl_easy_ssl_verify_host_set(VALUE self, VALUE ssl_verify_host) { CURB_IMMED_SETTER(ruby_curl_easy, ssl_verify_host, 0); } /* * call-seq: * easy.ssl_verify_host => number * * Determine whether this Curl instance will verify that the server cert * is for the server it is known as. */ static VALUE ruby_curl_easy_ssl_verify_host_get(VALUE self) { CURB_IMMED_GETTER(ruby_curl_easy, ssl_verify_host, 0); } /* * call-seq: * easy.header_in_body = boolean => boolean * * Configure whether this Curl instance will return HTTP headers * combined with body data. If this option is set true, both header * and body data will go to +body_str+ (or the configured +on_body+ handler). */ static VALUE ruby_curl_easy_header_in_body_set(VALUE self, VALUE header_in_body) { CURB_BOOLEAN_SETTER(ruby_curl_easy, header_in_body); } /* * call-seq: * easy.header_in_body? => boolean * * Determine whether this Curl instance will return HTTP headers * combined with body data. */ static VALUE ruby_curl_easy_header_in_body_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, header_in_body); } /* * call-seq: * easy.use_netrc = boolean => boolean * * Configure whether this Curl instance will use data from the user's * .netrc file for FTP connections. */ static VALUE ruby_curl_easy_use_netrc_set(VALUE self, VALUE use_netrc) { CURB_BOOLEAN_SETTER(ruby_curl_easy, use_netrc); } /* * call-seq: * easy.use_netrc? => boolean * * Determine whether this Curl instance will use data from the user's * .netrc file for FTP connections. */ static VALUE ruby_curl_easy_use_netrc_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, use_netrc); } /* * call-seq: * * easy = Curl::Easy.new * easy.autoreferer=true */ static VALUE ruby_curl_easy_autoreferer_set(VALUE self, VALUE autoreferer) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (Qtrue == autoreferer) { curl_easy_setopt(rbce->curl, CURLOPT_AUTOREFERER, 1); } else { curl_easy_setopt(rbce->curl, CURLOPT_AUTOREFERER, 0); } return autoreferer; } /* * call-seq: * easy.follow_location? => boolean * * Determine whether this Curl instance will follow Location: headers * in HTTP responses. */ static VALUE ruby_curl_easy_follow_location_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, follow_location); } /* * call-seq: * easy.unrestricted_auth = boolean => boolean * * Configure whether this Curl instance may use any HTTP authentication * method available when necessary. */ static VALUE ruby_curl_easy_unrestricted_auth_set(VALUE self, VALUE unrestricted_auth) { CURB_BOOLEAN_SETTER(ruby_curl_easy, unrestricted_auth); } /* * call-seq: * easy.unrestricted_auth? => boolean * * Determine whether this Curl instance may use any HTTP authentication * method available when necessary. */ static VALUE ruby_curl_easy_unrestricted_auth_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, unrestricted_auth); } /* * call-seq: * easy.verbose = boolean => boolean * * Configure whether this Curl instance gives verbose output to STDERR * during transfers. Ignored if this instance has an on_debug handler. */ static VALUE ruby_curl_easy_verbose_set(VALUE self, VALUE verbose) { CURB_BOOLEAN_SETTER(ruby_curl_easy, verbose); } /* * call-seq: * easy.verbose? => boolean * * Determine whether this Curl instance gives verbose output to STDERR * during transfers. */ static VALUE ruby_curl_easy_verbose_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, verbose); } /* * call-seq: * easy.multipart_form_post = boolean => boolean * * Configure whether this Curl instance uses multipart/formdata content * type for HTTP POST requests. If this is false (the default), then the * application/x-www-form-urlencoded content type is used for the form * data. * * If this is set true, you must pass one or more PostField instances * to the http_post method - no support for posting multipart forms from * a string is provided. */ static VALUE ruby_curl_easy_multipart_form_post_set(VALUE self, VALUE multipart_form_post) { CURB_BOOLEAN_SETTER(ruby_curl_easy, multipart_form_post); } /* * call-seq: * easy.multipart_form_post? => boolean * * Determine whether this Curl instance uses multipart/formdata content * type for HTTP POST requests. */ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, multipart_form_post); } /* * call-seq: * easy.enable_cookies = boolean => boolean * * Configure whether the libcurl cookie engine is enabled for this Curl::Easy * instance. When enabled, cookies received via Set-Cookie are stored by libcurl * and automatically sent on subsequent matching requests. Use +easy.cookiefile+ * to load cookies and +easy.cookiejar+ to persist them. * * This setting is independent from the manual Cookie header set via +easy.cookies+. * The manual header is additive and can be cleared by assigning an empty string. */ static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies) { CURB_BOOLEAN_SETTER(ruby_curl_easy, enable_cookies); } /* * call-seq: * easy.enable_cookies? => boolean * * Determine whether the libcurl cookie engine is enabled for this * Curl::Easy instance. */ static VALUE ruby_curl_easy_enable_cookies_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, enable_cookies); } /* * call-seq: * easy.ignore_content_length = boolean * * Configure whether this Curl::Easy instance should ignore the content * length header. */ static VALUE ruby_curl_easy_ignore_content_length_set(VALUE self, VALUE ignore_content_length) { CURB_BOOLEAN_SETTER(ruby_curl_easy, ignore_content_length); } /* * call-seq: * easy.ignore_content_length? => boolean * * Determine whether this Curl::Easy instance ignores the content * length header. */ static VALUE ruby_curl_easy_ignore_content_length_q(VALUE self) { CURB_BOOLEAN_GETTER(ruby_curl_easy, ignore_content_length); } /* * call-seq: * easy.resolve_mode => symbol * * Determines what type of IP address this Curl::Easy instance * resolves DNS names to. */ static VALUE ruby_curl_easy_resolve_mode(VALUE self) { ruby_curl_easy *rbce; unsigned short rm; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); rm = rbce->resolve_mode; switch(rm) { case CURL_IPRESOLVE_V4: return rb_easy_sym("ipv4"); case CURL_IPRESOLVE_V6: return rb_easy_sym("ipv6"); default: return rb_easy_sym("auto"); } } /* * call-seq: * easy.resolve_mode = symbol => symbol * * Configures what type of IP address this Curl::Easy instance * resolves DNS names to. Valid options are: * * [:auto] resolves DNS names to all IP versions your system allows * [:ipv4] resolves DNS names to IPv4 only * [:ipv6] resolves DNS names to IPv6 only */ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) { if (TYPE(resolve_mode) != T_SYMBOL) { rb_raise(rb_eTypeError, "Must pass a symbol"); return Qnil; } else { ruby_curl_easy *rbce; ID resolve_mode_id; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); resolve_mode_id = rb_to_id(resolve_mode); if (resolve_mode_id == rb_intern("auto")) { rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER; return resolve_mode; } else if (resolve_mode_id == rb_intern("ipv4")) { rbce->resolve_mode = CURL_IPRESOLVE_V4; return resolve_mode; } else if (resolve_mode_id == rb_intern("ipv6")) { rbce->resolve_mode = CURL_IPRESOLVE_V6; return resolve_mode; } else { rb_raise(rb_eArgError, "Must set to one of :auto, :ipv4, :ipv6"); return Qnil; } } } /* * call-seq: * easy.http_version = Curl::HTTP_1_1 => Curl::HTTP_1_1 * * Force libcurl to use a specific HTTP protocol version. By default libcurl * negotiates the highest version supported by both peers. Supported constants * include Curl::HTTP_NONE, Curl::HTTP_1_0, Curl::HTTP_1_1, Curl::HTTP_2_0, * Curl::HTTP_2TLS, and Curl::HTTP_2_PRIOR_KNOWLEDGE (when provided by libcurl). */ static VALUE ruby_curl_easy_http_version_set(VALUE self, VALUE version) { ruby_curl_easy *rbce; long http_version; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (NIL_P(version)) { http_version = CURL_HTTP_VERSION_NONE; } else { http_version = NUM2LONG(version); } rbce->http_version = http_version; return version; } /* * call-seq: * easy.http_version => integer * * Returns the HTTP protocol version currently configured. */ static VALUE ruby_curl_easy_http_version_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return LONG2NUM(rbce->http_version); } /* ================= EVENT PROCS ================== */ /* * call-seq: * easy.on_body { |body_data| ... } => * * Assign or remove the +on_body+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_body+ handler is called for each chunk of response body passed back * by libcurl during +perform+. It should perform any processing necessary, * and return the actual number of bytes handled. Normally, this will * equal the length of the data string, and CURL will continue processing. * If the returned length does not equal the input length, CURL will abort * the processing with a Curl::Err::AbortedByCallbackError. */ static VALUE ruby_curl_easy_on_body_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, body_proc); } /* * call-seq: * easy.on_success { |easy| ... } => * * Assign or remove the +on_success+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_success+ handler is called when the request is finished with a * status of 20x */ static VALUE ruby_curl_easy_on_success_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, success_proc); } /* * call-seq: * easy.on_failure {|easy,code| ... } => * * Assign or remove the +on_failure+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_failure+ handler is called when the request is finished with a * status of 50x */ static VALUE ruby_curl_easy_on_failure_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, failure_proc); } /* * call-seq: * easy.on_missing {|easy,code| ... } => * * Assign or remove the on_missing handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no attached * block. * * The +on_missing+ handler is called when request is finished with a * status of 40x */ static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, missing_proc); } /* * call-seq: * easy.on_redirect {|easy,code| ... } => * * Assign or remove the on_redirect handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no attached * block. * * The +on_redirect+ handler is called when request is finished with a * status of 30x */ static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, redirect_proc); } /* * call-seq: * easy.on_complete {|easy| ... } => * * Assign or remove the +on_complete+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_complete+ handler is called when the request is finished. */ static VALUE ruby_curl_easy_on_complete_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, complete_proc); } /* * call-seq: * easy.on_header { |header_data| ... } => * * Assign or remove the +on_header+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_header+ handler is called for each chunk of response header passed * back by libcurl during +perform+. The semantics are the same as for the * block supplied to +on_body+. */ static VALUE ruby_curl_easy_on_header_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, header_proc); } /* * call-seq: * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => * * Assign or remove the +on_progress+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_progress+ handler is called regularly by libcurl (approximately once * per second) during transfers to allow the application to receive progress * information. There is no guarantee that the reported progress will change * between calls. * * The result of the block call determines whether libcurl continues the transfer. * Returning a non-true value (i.e. nil or false) will cause the transfer to abort, * throwing a Curl::Err::AbortedByCallbackError. */ static VALUE ruby_curl_easy_on_progress_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, progress_proc); } /* * call-seq: * easy.on_debug { |type, data| ... } => * * Assign or remove the +on_debug+ handler for this Curl::Easy instance. * To remove a previously-supplied handler, call this method with no * attached block. * * The +on_debug+ handler, if configured, will receive detailed information * from libcurl during the perform call. This can be useful for debugging. * Setting a debug handler overrides libcurl's internal handler, disabling * any output from +verbose+, if set. * * The type argument will match one of the Curl::Easy::CURLINFO_XXXX * constants, and specifies the kind of information contained in the * data. The data is passed as a String. */ static VALUE ruby_curl_easy_on_debug_set(int argc, VALUE *argv, VALUE self) { CURB_HANDLER_PROC_HSETTER(ruby_curl_easy, debug_proc); } /* =================== PERFORM =====================*/ /*********************************************** * This is an rb_iterate callback used to set up http headers. */ static VALUE cb_each_http_header(VALUE header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) { struct curl_slist **list; VALUE header_str = Qnil; TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list); //rb_p(header); if (rb_type(header) == T_ARRAY) { // we're processing a hash, header is [name, val] VALUE name, value; name = rb_obj_as_string(rb_ary_entry(header, 0)); value = rb_obj_as_string(rb_ary_entry(header, 1)); if (rb_str_strlen(value) == 0) { // removing the header e.g. Accept: with nothing trailing should remove it see: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html header_str = rb_str_plus(name, rb_str_new2(":")); } else { // This is a bit inefficient, but we don't want to be modifying // the actual values in the original hash. header_str = rb_str_plus(name, rb_str_new2(": ")); header_str = rb_str_plus(header_str, value); } } else { header_str = rb_obj_as_string(header); } //rb_p(header_str); struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(header_str)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to header list"); } *list = new_list; return header_str; } /*********************************************** * This is an rb_iterate callback used to set up http proxy headers. */ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) { struct curl_slist **list; VALUE proxy_header_str = Qnil; TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list); //rb_p(proxy_header); if (rb_type(proxy_header) == T_ARRAY) { // we're processing a hash, proxy header is [name, val] VALUE name, value; name = rb_obj_as_string(rb_ary_entry(proxy_header, 0)); value = rb_obj_as_string(rb_ary_entry(proxy_header, 1)); // This is a bit inefficient, but we don't want to be modifying // the actual values in the original hash. proxy_header_str = rb_str_plus(name, rb_str_new2(": ")); proxy_header_str = rb_str_plus(proxy_header_str, value); } else { proxy_header_str = rb_obj_as_string(proxy_header); } //rb_p(header_str); struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(proxy_header_str)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to proxy header list"); } *list = new_list; return proxy_header_str; } /*********************************************** * This is an rb_iterate callback used to set up ftp commands. */ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) { struct curl_slist **list; VALUE ftp_command_string; TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list); ftp_command_string = rb_obj_as_string(ftp_command); struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(ftp_command_string)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to FTP command list"); } *list = new_list; return ftp_command_string; } /*********************************************** * This is an rb_iterate callback used to set up the resolve list. */ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) { struct curl_slist **list; VALUE resolve_string; TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list); resolve_string = rb_obj_as_string(resolve); struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(resolve_string)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to resolve list"); } *list = new_list; return resolve_string; } /*********************************************** * * Setup a connection * * Always returns Qtrue, rb_raise on error. */ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) { // TODO this could do with a bit of refactoring... CURL *curl; VALUE url, _url = rb_easy_get("url"); struct curl_slist **hdrs = &(rbce->curl_headers); struct curl_slist **phdrs = &(rbce->curl_proxy_headers); struct curl_slist **cmds = &(rbce->curl_ftp_commands); struct curl_slist **rslv = &(rbce->curl_resolve); curl = rbce->curl; rbce->callback_error = Qnil; if (_url == Qnil) { rb_raise(eCurlErrError, "No URL supplied"); } url = rb_check_string_type(_url); curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url)); // network stuff and auth if (!rb_easy_nil("interface_hm")) { curl_easy_setopt(curl, CURLOPT_INTERFACE, rb_easy_get_str("interface_hm")); } else { curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL); } #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD) if (!rb_easy_nil("username")) { curl_easy_setopt(curl, CURLOPT_USERNAME, rb_easy_get_str("username")); } else { curl_easy_setopt(curl, CURLOPT_USERNAME, NULL); } if (!rb_easy_nil("password")) { curl_easy_setopt(curl, CURLOPT_PASSWORD, rb_easy_get_str("password")); } else { curl_easy_setopt(curl, CURLOPT_PASSWORD, NULL); } #endif if (!rb_easy_nil("userpwd")) { curl_easy_setopt(curl, CURLOPT_USERPWD, rb_easy_get_str("userpwd")); #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD) } else if (rb_easy_nil("username") && rb_easy_nil("password")) { /* don't set this even to NULL if we have set username and password */ #else } else { #endif curl_easy_setopt(curl, CURLOPT_USERPWD, NULL); } if (rb_easy_nil("proxy_url")) { curl_easy_setopt(curl, CURLOPT_PROXY, NULL); } else { curl_easy_setopt(curl, CURLOPT_PROXY, rb_easy_get_str("proxy_url")); } if (rb_easy_nil("proxypwd")) { curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL); } else { curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd")); } #ifdef HAVE_CURLOPT_NOPROXY if (rb_easy_nil("noproxy")) { curl_easy_setopt(curl, CURLOPT_NOPROXY, NULL); } else { curl_easy_setopt(curl, CURLOPT_NOPROXY, rb_easy_get_str("noproxy")); } #endif // body/header procs if (!rb_easy_nil("body_proc")) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&proc_data_handler_body); curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce); /* clear out the body_data if it was set */ rb_easy_del("body_data"); } else { rb_easy_set("body_data", rb_str_buf_new(32768)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_body_handler); curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce); } if (!rb_easy_nil("header_proc")) { curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&proc_data_handler_header); curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce); /* clear out the header_data if it was set */ rb_easy_del("header_data"); } else { rb_easy_set("header_data", rb_str_buf_new(16384)); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_header_handler); curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce); } /* encoding */ if (!rb_easy_nil("encoding")) { curl_easy_setopt(curl, CURLOPT_ENCODING, rb_easy_get_str("encoding")); } // progress and debug procs if (!rb_easy_nil("progress_proc")) { #ifdef HAVE_CURLOPT_XFERINFOFUNCTION curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &proc_xferinfo_handler); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce); #else curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce); #endif curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); } else { curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); #ifdef HAVE_CURLOPT_XFERINFOFUNCTION curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce); #else curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce); #endif } if (!rb_easy_nil("debug_proc")) { curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, (curl_debug_callback)&proc_debug_handler); curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } else { // have to remove handler to re-enable standard verbosity curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce); curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose); } /* general opts */ curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location); #if LIBCURL_VERSION_NUM == 0x081000 /* Work around 8.16.0 regression that clamps -1 (infinite) to zero */ if (rbce->max_redirs >= 0) { curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs); } #else curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs); #endif curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel); curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, rbce->ssl_verify_peer); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, rbce->ssl_verify_host); if ((rbce->use_netrc != Qnil) && (rbce->use_netrc != Qfalse)) { curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); } else { curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_IGNORED); } curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth); #ifdef HAVE_CURLOPT_TIMEOUT_MS curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms); #endif curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout); #ifdef HAVE_CURLOPT_CONNECTTIMEOUT_MS curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms); #endif curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout); curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, rbce->resolve_mode); #if HAVE_CURLOPT_HTTP_VERSION curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, rbce->http_version); #endif #if LIBCURL_VERSION_NUM >= 0x070a08 curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, rbce->ftp_response_timeout); #else if (rbce->ftp_response_timeout > 0) { rb_warn("Installed libcurl is too old to support ftp_response_timeout"); } #endif curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, rbce->low_speed_limit); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, rbce->low_speed_time); curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, rbce->max_recv_speed_large); curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, rbce->max_send_speed_large); // Set up localport / proxy port // FIXME these won't get returned to default if they're unset Ruby if (rbce->proxy_port > 0) { curl_easy_setopt(curl, CURLOPT_PROXYPORT, rbce->proxy_port); } if (rbce->local_port > 0) { #if LIBCURL_VERSION_NUM >= 0x070f02 curl_easy_setopt(curl, CURLOPT_LOCALPORT, rbce->local_port); if (rbce->local_port_range > 0) { curl_easy_setopt(curl, CURLOPT_LOCALPORTRANGE, rbce->local_port_range); } #else rb_warn("Installed libcurl is too old to support local_port"); #endif } if (rbce->proxy_type != -1) { #if LIBCURL_VERSION_NUM >= 0x070a00 if (rbce->proxy_type == -2) { rb_warn("Installed libcurl is too old to support the selected proxy type"); } else { curl_easy_setopt(curl, CURLOPT_PROXYTYPE, rbce->proxy_type); } } else { curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); #else rb_warn("Installed libcurl is too old to support proxy_type"); #endif } /* * NOTE: we used to set CURLAUTH_ANY but see: http://curl.haxx.se/mail/lib-2015-06/0033.html */ if (rbce->http_auth_types != 0) { #if LIBCURL_VERSION_NUM >= 0x070a06 curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rbce->http_auth_types); #else rb_warn("Installed libcurl is too old to support http_auth_types"); #endif } if (rbce->proxy_auth_types != 0) { #if LIBCURL_VERSION_NUM >= 0x070a07 curl_easy_setopt(curl, CURLOPT_PROXYAUTH, rbce->proxy_auth_types); #else rb_warn("Installed libcurl is too old to support proxy_auth_types"); #endif } /* Set up HTTP cookie handling if necessary */ /* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */ if (rbce->enable_cookies) { if (!rb_easy_nil("cookiejar")) { curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar")); } if (!rb_easy_nil("cookiefile")) { curl_easy_setopt(curl, CURLOPT_COOKIEFILE, rb_easy_get_str("cookiefile")); } else { curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */ } } else if (rbce->cookielist_engine_enabled) { /* Ensure cookie engine is enabled even if enable_cookies? is false. */ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); } if (!rb_easy_nil("cookies")) { curl_easy_setopt(curl, CURLOPT_COOKIE, rb_easy_get_str("cookies")); } /* Set up HTTPS cert handling if necessary */ if (!rb_easy_nil("cert")) { if (!rb_easy_nil("certtype")) { curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, rb_easy_get_str("certtype")); } curl_easy_setopt(curl, CURLOPT_SSLCERT, rb_easy_get_str("cert")); if (!rb_easy_nil("certpassword")) { curl_easy_setopt(curl, CURLOPT_SSLCERTPASSWD, rb_easy_get_str("certpassword")); } if (!rb_easy_nil("cert_key")) { curl_easy_setopt(curl, CURLOPT_SSLKEY, rb_easy_get_str("cert_key")); } } if (!rb_easy_nil("cacert")) { curl_easy_setopt(curl, CURLOPT_CAINFO, rb_easy_get_str("cacert")); } #ifdef HAVE_CURL_CONFIG_CA else { curl_easy_setopt(curl, CURLOPT_CAINFO, CURL_CONFIG_CA); } #endif #ifdef CURL_VERSION_SSL if (rbce->ssl_version > 0) { curl_easy_setopt(curl, CURLOPT_SSLVERSION, rbce->ssl_version); } if (rbce->use_ssl > 0) { curl_easy_setopt(curl, CURB_FTPSSL, rbce->use_ssl); } #else if (rbce->ssl_version > 0 || rbce->use_ssl > 0) { rb_warn("libcurl is not configured with SSL support"); } #endif if (rbce->ftp_filemethod > 0) { curl_easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod); } /* Set the user-agent string if specified */ if (!rb_easy_nil("useragent")) { curl_easy_setopt(curl, CURLOPT_USERAGENT, rb_easy_get_str("useragent")); } /* Setup HTTP headers if necessary */ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // XXX: maybe we shouldn't be clearing this? if (!rb_easy_nil("headers")) { if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_HASH)) { VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, hdrs); rb_block_call(rb_easy_get("headers"), rb_intern("each"), 0, NULL, cb_each_http_header, wrap); } else { VALUE headers_str = rb_obj_as_string(rb_easy_get("headers")); struct curl_slist *new_list = curl_slist_append(*hdrs, StringValuePtr(headers_str)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to headers list"); } *hdrs = new_list; } if (*hdrs) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, *hdrs); } } #ifdef HAVE_CURLOPT_PROXYHEADER /* Setup HTTP proxy headers if necessary */ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, NULL); // XXX: maybe we shouldn't be clearing this? if (!rb_easy_nil("proxy_headers")) { if (rb_easy_type_check("proxy_headers", T_ARRAY) || rb_easy_type_check("proxy_headers", T_HASH)) { VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, phdrs); rb_block_call(rb_easy_get("proxy_headers"), rb_intern("each"), 0, NULL, cb_each_http_proxy_header, wrap); } else { VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers")); struct curl_slist *new_list = curl_slist_append(*phdrs, StringValuePtr(proxy_headers_str)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to proxy headers list"); } *phdrs = new_list; } if (*phdrs) { curl_easy_setopt(curl, CURLOPT_PROXYHEADER, *phdrs); } } #endif /* Setup FTP commands if necessary */ if (!rb_easy_nil("ftp_commands")) { if (rb_easy_type_check("ftp_commands", T_ARRAY)) { VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cmds); rb_block_call(rb_easy_get("ftp_commands"), rb_intern("each"), 0, NULL, cb_each_ftp_command, wrap); } if (*cmds) { curl_easy_setopt(curl, CURLOPT_QUOTE, *cmds); } } #ifdef HAVE_CURLOPT_RESOLVE /* Setup resolve list if necessary */ if (!rb_easy_nil("resolve")) { if (rb_easy_type_check("resolve", T_ARRAY)) { VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, rslv); rb_block_call(rb_easy_get("resolve"), rb_intern("each"), 0, NULL, cb_each_resolve, wrap); } else { VALUE resolve_str = rb_obj_as_string(rb_easy_get("resolve")); struct curl_slist *new_list = curl_slist_append(*rslv, StringValuePtr(resolve_str)); if (!new_list) { rb_raise(rb_eNoMemError, "Failed to append to resolve list"); } *rslv = new_list; } if (*rslv) { curl_easy_setopt(curl, CURLOPT_RESOLVE, *rslv); } } #endif return Qnil; } /*********************************************** * * Clean up a connection * * Always returns Qnil. */ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) { CURL *curl = rbce->curl; struct curl_slist *ftp_commands; struct curl_slist *resolve; ruby_curl_easy_clear_headers_list(rbce); ruby_curl_easy_clear_proxy_headers_list(rbce); ftp_commands = rbce->curl_ftp_commands; if (ftp_commands) { ruby_curl_easy_clear_ftp_commands_list(rbce); } resolve = rbce->curl_resolve; if (resolve) { ruby_curl_easy_clear_resolve_list(rbce); } /* clean up a PUT request's curl options. */ if (!rb_easy_nil("upload")) { rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up curl_easy_setopt(curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_READDATA, NULL); #ifdef HAVE_CURLOPT_SEEKFUNCTION curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, NULL); #endif #ifdef HAVE_CURLOPT_SEEKDATA curl_easy_setopt(curl, CURLOPT_SEEKDATA, NULL); #endif curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0); } // set values on cleanup to nil //rb_easy_del("multi"); return Qnil; } struct easy_perform_request_restore_args { VALUE self; CURL *curl; ruby_curl_easy *rbce; int clear_customrequest; int clear_nobody; int clear_postfields; }; static VALUE perform_with_request_restore_body(VALUE argp) { struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp; return rb_funcall(args->self, rb_intern("perform"), 0); } static VALUE perform_with_request_restore_ensure(VALUE argp) { struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp; if (args->curl) { if (args->clear_nobody) { curl_easy_setopt(args->curl, CURLOPT_NOBODY, 0L); } if (args->clear_customrequest) { curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL); } if (args->clear_postfields) { curl_easy_setopt(args->curl, CURLOPT_POST, 0L); curl_easy_setopt(args->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(args->curl, CURLOPT_POSTFIELDSIZE, 0L); curl_easy_setopt(args->curl, CURLOPT_HTTPGET, 1L); if (args->rbce && !NIL_P(args->rbce->opts)) { rb_hash_delete(args->rbce->opts, rb_easy_hkey("postdata_buffer")); } } } return Qnil; } /* * Common implementation of easy.http(verb) and easy.http_delete */ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) { ruby_curl_easy *rbce; CURL *curl; VALUE retval; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl = rbce->curl; memset(rbce->err_buf, 0, CURL_ERROR_SIZE); /* Use method override and adjust related options for special verbs. */ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb); /* For HEAD, ensure no body is requested/downloaded, as some servers * include a Content-Length header which should not cause libcurl to * wait for a body that will never arrive. */ int is_head = (verb && ( #ifdef _WIN32 _stricmp(verb, "HEAD") == 0 #else strcasecmp(verb, "HEAD") == 0 #endif )); if (is_head) { curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); curl_easy_setopt(curl, CURLOPT_HTTPGET, 0L); curl_easy_setopt(curl, CURLOPT_POST, 0L); } struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, is_head, 0 }; retval = rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args, perform_with_request_restore_ensure, (VALUE)&restore_args); return retval; } /* * call-seq: * easy.http(verb) * * Send an HTTP request with method set to verb, using the current options set for this Curl::Easy instance. * This method always returns true or raises an exception (defined under Curl::Err) on error. */ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) { VALUE str_verb; if (rb_type(verb) == T_STRING) { return ruby_curl_easy_perform_verb_str(self, StringValueCStr(verb)); } else if (rb_respond_to(verb,rb_intern("to_s"))) { str_verb = rb_funcall(verb, rb_intern("to_s"), 0); return ruby_curl_easy_perform_verb_str(self, StringValueCStr(str_verb)); } else { rb_raise(rb_eRuntimeError, "Invalid HTTP VERB, must response to 'to_s'"); } } static VALUE call_easy_perform(VALUE self) { return rb_funcall(self, rb_intern("perform"), 0); } struct easy_form_perform_args { VALUE self; CURL *curl; int argc; VALUE *argv; struct curl_httppost *first; struct curl_httppost *last; int clear_customrequest; int form_set_on_curl; }; static void append_multipart_form_argument(VALUE arg, struct curl_httppost **first, struct curl_httppost **last) { if (rb_obj_is_instance_of(arg, cCurlPostField)) { append_to_form(arg, first, last); } else if (rb_type(arg) == T_ARRAY) { long j, argv_len = RARRAY_LEN(arg); for (j = 0; j < argv_len; ++j) { VALUE field = rb_ary_entry(arg, j); if (rb_obj_is_instance_of(field, cCurlPostField)) { append_to_form(field, first, last); } else { rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts"); } } } else { rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts"); } } static VALUE build_and_perform_multipart_form(VALUE argp) { struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp; int i; for (i = 0; i < args->argc; i++) { append_multipart_form_argument(args->argv[i], &args->first, &args->last); } curl_easy_setopt(args->curl, CURLOPT_POST, 0); curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, args->first); args->form_set_on_curl = 1; return call_easy_perform(args->self); } static VALUE ensure_free_form_post(VALUE argp) { struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp; if (args->curl) { if (args->form_set_on_curl) { curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, NULL); } if (args->clear_customrequest) { curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL); } } if (args->first) { curl_formfree(args->first); args->first = NULL; } args->last = NULL; return Qnil; } /* * call-seq: * easy.http_post("url=encoded%20form%20data;and=so%20on") => true * easy.http_post("url=encoded%20form%20data", "and=so%20on", ...) => true * easy.http_post("url=encoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true * easy.http_post(Curl::PostField, Curl::PostField ..., Curl::PostField) => true * * POST the specified formdata to the currently configured URL using * the current options set for this Curl::Easy instance. This method * always returns true, or raises an exception (defined under * Curl::Err) on error. * * The Content-type of the POST is determined by the current setting * of multipart_form_post? , according to the following rules: * * When false (the default): the form will be POSTed with a * content-type of 'application/x-www-form-urlencoded', and any of the * four calling forms may be used. * * When true: the form will be POSTed with a content-type of * 'multipart/formdata'. Only the last calling form may be used, * i.e. only PostField instances may be POSTed. In this mode, * individual fields' content-types are recognised, and file upload * fields are supported. * */ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) { ruby_curl_easy *rbce; CURL *curl; VALUE args_ary; rb_scan_args(argc, argv, "*", &args_ary); TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl = rbce->curl; memset(rbce->err_buf, 0, CURL_ERROR_SIZE); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL); if (rbce->multipart_form_post) { VALUE ret; struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 0, 0 }; ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args); return ret; } else { VALUE post_body = Qnil; /* TODO: check for PostField.file and raise error before to_s fails */ if ((post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp)) == Qnil) { rb_raise(eCurlErrError, "Failed to join arguments"); return Qnil; } else { /* if the function call above returns an empty string because no additional arguments were passed this makes sure a previously set easy.post_body = "arg=foo&bar=bin" will be honored */ if( post_body != Qnil && rb_type(post_body) == T_STRING && RSTRING_LEN(post_body) > 0 ) { ruby_curl_easy_post_body_set(self, post_body); } /* if post body is not defined, set it so we enable POST header, even though the request body is empty */ if( rb_easy_nil("postdata_buffer") ) { ruby_curl_easy_post_body_set(self, post_body); } struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 }; return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args, perform_with_request_restore_ensure, (VALUE)&restore_args); } } } /* * call-seq: * easy.http_patch("url=encoded%20form%20data;and=so%20on") => true * easy.http_patch("url=encoded%20form%20data", "and=so%20on", ...) => true * easy.http_patch("url=encoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true * easy.http_patch(Curl::PostField, Curl::PostField ..., Curl::PostField) => true * * PATCH the specified formdata to the currently configured URL using * the current options set for this Curl::Easy instance. This method * always returns true, or raises an exception (defined under * Curl::Err) on error. * * When multipart_form_post is true the multipart logic is used; otherwise, * the arguments are joined into a raw PATCH body. */ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) { ruby_curl_easy *rbce; CURL *curl; VALUE args_ary; rb_scan_args(argc, argv, "*", &args_ary); TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl = rbce->curl; /* Clear the error buffer */ memset(rbce->err_buf, 0, CURL_ERROR_SIZE); /* Set the custom HTTP method to PATCH */ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); if (rbce->multipart_form_post) { VALUE ret; struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 }; ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args); return ret; } else { /* Join arguments into a raw PATCH body */ VALUE patch_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp); if (patch_body == Qnil) { rb_raise(eCurlErrError, "Failed to join arguments"); return Qnil; } else { if (rb_type(patch_body) == T_STRING && RSTRING_LEN(patch_body) > 0) { ruby_curl_easy_post_body_set(self, patch_body); } /* If postdata_buffer is still nil, set it so that the PATCH header is enabled */ if (rb_easy_nil("postdata_buffer")) { ruby_curl_easy_post_body_set(self, patch_body); } struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 1 }; return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args, perform_with_request_restore_ensure, (VALUE)&restore_args); } } } /* * call-seq: * easy.http_put(data) => true * * PUT the supplied data to the currently configured URL using the * current options set for this Curl::Easy instance. This method always * returns true, or raises an exception (defined under Curl::Err) on error. */ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) { ruby_curl_easy *rbce; CURL *curl; VALUE args_ary; rb_scan_args(argc, argv, "*", &args_ary); TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl = rbce->curl; memset(rbce->err_buf, 0, CURL_ERROR_SIZE); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); /* New: if no arguments were provided, treat as an empty PUT */ if (RARRAY_LEN(args_ary) == 0) { /* Option 1: explicitly set an empty body */ ruby_curl_easy_put_data_set(self, rb_str_new2("")); } /* If a single argument is given and it is a String or responds to read, use legacy behavior */ else if (RARRAY_LEN(args_ary) == 1 && (rb_type(rb_ary_entry(args_ary, 0)) == T_STRING || rb_respond_to(rb_ary_entry(args_ary, 0), rb_intern("read")))) { ruby_curl_easy_put_data_set(self, rb_ary_entry(args_ary, 0)); } /* Otherwise, if multipart_form_post is true, use multipart logic */ else if (rbce->multipart_form_post) { VALUE ret; struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 }; ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args); return ret; } /* Fallback: join all arguments */ else { VALUE post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp); if (post_body != Qnil && rb_type(post_body) == T_STRING && RSTRING_LEN(post_body) > 0) { ruby_curl_easy_put_data_set(self, post_body); } } struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 }; return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args, perform_with_request_restore_ensure, (VALUE)&restore_args); } /* =================== DATA FUNCS =============== */ /* * call-seq: * easy.body_str => "response body" * * Return the response body from the previous call to +perform+. This * is populated by the default +on_body+ handler - if you supply * your own body handler, this string will be empty. */ static VALUE ruby_curl_easy_body_str_get(VALUE self) { /* TODO: can we force_encoding on the return here if we see charset=utf-8 in the content-type header? Content-Type: application/json; charset=utf-8 */ CURB_OBJECT_HGETTER(ruby_curl_easy, body_data); } /* * call-seq: * easy.header_str => "response header" * * Return the response header from the previous call to +perform+. This * is populated by the default +on_header+ handler - if you supply * your own header handler, this string will be empty. */ static VALUE ruby_curl_easy_header_str_get(VALUE self) { CURB_OBJECT_HGETTER(ruby_curl_easy, header_data); } /* ============== LASTCONN INFO FUNCS ============ */ /* * call-seq: * easy.last_effective_url => "http://some.url" or nil * * Retrieve the last effective URL used by this instance. * This is the URL used in the last +perform+ call, * and may differ from the value of easy.url. */ static VALUE ruby_curl_easy_last_effective_url_get(VALUE self) { ruby_curl_easy *rbce; char* url; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_EFFECTIVE_URL, &url); if (url && url[0]) { // curl returns empty string if none return rb_str_new2(url); } else { return Qnil; } } /* * call-seq: * easy.response_code => fixnum * * Retrieve the last received HTTP or FTP code. This will be zero * if no server response code has been received. Note that a proxy's * CONNECT response should be read with +http_connect_code+ * and not this method. */ static VALUE ruby_curl_easy_response_code_get(VALUE self) { ruby_curl_easy *rbce; long code; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_RESPONSE_CODE curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &code); #else // old libcurl curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CODE, &code); #endif return LONG2NUM(code); } #if defined(HAVE_CURLINFO_PRIMARY_IP) /* * call-seq: * easy.primary_ip => "xx.xx.xx.xx" or nil * * Retrieve the resolved IP of the most recent connection * done with this curl handle. This string may be IPv6 if * that's enabled. This feature requires curl 7.19.x and above */ static VALUE ruby_curl_easy_primary_ip_get(VALUE self) { ruby_curl_easy *rbce; char* ip; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_PRIMARY_IP, &ip); if (ip && ip[0]) { // curl returns empty string if none return rb_str_new2(ip); } else { return Qnil; } } #endif /* * call-seq: * easy.http_connect_code => fixnum * * Retrieve the last received proxy response code to a CONNECT request. */ static VALUE ruby_curl_easy_http_connect_code_get(VALUE self) { ruby_curl_easy *rbce; long code; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CONNECTCODE, &code); return LONG2NUM(code); } /* * call-seq: * easy.file_time => fixnum * * Retrieve the remote time of the retrieved document (in number of * seconds since 1 jan 1970 in the GMT/UTC time zone). If you get -1, * it can be because of many reasons (unknown, the server hides it * or the server doesn't support the command that tells document time * etc) and the time of the document is unknown. * * Note that you must tell the server to collect this information * before the transfer is made, by setting +fetch_file_time?+ to true, * or you will unconditionally get a -1 back. * * This requires libcurl 7.5 or higher - otherwise -1 is unconditionally * returned. */ static VALUE ruby_curl_easy_file_time_get(VALUE self) { #ifdef HAVE_CURLINFO_FILETIME ruby_curl_easy *rbce; long time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_FILETIME, &time); return LONG2NUM(time); #else rb_warn("Installed libcurl is too old to support file_time"); return LONG2NUM(0); #endif } /* * call-seq: * easy.total_time => float * * Retrieve the total time in seconds for the previous transfer, * including name resolving, TCP connect etc. */ static VALUE ruby_curl_easy_total_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_TOTAL_TIME, &time); return rb_float_new(time); } /* * call-seq: * easy.name_lookup_time => float * * Retrieve the time, in seconds, it took from the start until the * name resolving was completed. */ static VALUE ruby_curl_easy_name_lookup_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_NAMELOOKUP_TIME, &time); return rb_float_new(time); } /* * call-seq: * easy.connect_time => float * * Retrieve the time, in seconds, it took from the start until the * connect to the remote host (or proxy) was completed. */ static VALUE ruby_curl_easy_connect_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_CONNECT_TIME, &time); return rb_float_new(time); } /* * call-seq: * easy.app_connect_time => float * * Retrieve the time, in seconds, it took from the start until the SSL/SSH * connect/handshake to the remote host was completed. This time is most often * very near to the pre transfer time, except for cases such as HTTP * pipelining where the pretransfer time can be delayed due to waits in line * for the pipeline and more. */ #if defined(HAVE_CURLINFO_APPCONNECT_TIME) static VALUE ruby_curl_easy_app_connect_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_APPCONNECT_TIME, &time); return rb_float_new(time); } #endif /* * call-seq: * easy.pre_transfer_time => float * * Retrieve the time, in seconds, it took from the start until the * file transfer is just about to begin. This includes all pre-transfer * commands and negotiations that are specific to the particular protocol(s) * involved. */ static VALUE ruby_curl_easy_pre_transfer_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_PRETRANSFER_TIME, &time); return rb_float_new(time); } /* * call-seq: * easy.start_transfer_time => float * * Retrieve the time, in seconds, it took from the start until the first byte * is just about to be transferred. This includes the +pre_transfer_time+ and * also the time the server needs to calculate the result. */ static VALUE ruby_curl_easy_start_transfer_time_get(VALUE self) { ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_STARTTRANSFER_TIME, &time); return rb_float_new(time); } /* * call-seq: * easy.redirect_time => float * * Retrieve the total time, in seconds, it took for all redirection steps * include name lookup, connect, pretransfer and transfer before final * transaction was started. +redirect_time+ contains the complete * execution time for multiple redirections. * * Requires libcurl 7.9.7 or higher, otherwise -1 is always returned. */ static VALUE ruby_curl_easy_redirect_time_get(VALUE self) { #ifdef HAVE_CURLINFO_REDIRECT_TIME ruby_curl_easy *rbce; double time; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_TIME, &time); return rb_float_new(time); #else rb_warn("Installed libcurl is too old to support redirect_time"); return rb_float_new(-1); #endif } /* * call-seq: * easy.redirect_count => integer * * Retrieve the total number of redirections that were actually followed. * * Requires libcurl 7.9.7 or higher, otherwise -1 is always returned. */ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) { #ifdef HAVE_CURLINFO_REDIRECT_COUNT ruby_curl_easy *rbce; long count; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &count); return LONG2NUM(count); #else rb_warn("Installed libcurl is too old to support redirect_count"); return LONG2NUM(-1); #endif } /* * call-seq: * easy.redirect_url => "http://some.url" or nil * * Retrieve the URL a redirect would take you to if you * would enable CURLOPT_FOLLOWLOCATION. * * Requires libcurl 7.18.2 or higher, otherwise -1 is always returned. */ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) { #ifdef HAVE_CURLINFO_REDIRECT_URL ruby_curl_easy *rbce; char* url; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_URL, &url); if (url && url[0]) { // curl returns empty string if none return rb_str_new2(url); } else { return Qnil; } #else rb_warn("Installed libcurl is too old to support redirect_url"); return LONG2NUM(-1); #endif } /* * call-seq: * easy.uploaded_bytes => float * * Retrieve the total amount of bytes that were uploaded in the * preceeding transfer. */ static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_SIZE_UPLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.downloaded_bytes => float * * Retrieve the total amount of bytes that were downloaded in the * preceeding transfer. */ static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_SIZE_DOWNLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.upload_speed => float * * Retrieve the average upload speed that curl measured for the * preceeding complete upload. */ static VALUE ruby_curl_easy_upload_speed_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_SPEED_UPLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.download_speed => float * * Retrieve the average download speed that curl measured for * the preceeding complete download. */ static VALUE ruby_curl_easy_download_speed_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_SPEED_DOWNLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.header_size => fixnum * * Retrieve the total size of all the headers received in the * preceeding transfer. */ static VALUE ruby_curl_easy_header_size_get(VALUE self) { ruby_curl_easy *rbce; long size; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_HEADER_SIZE, &size); return LONG2NUM(size); } /* * call-seq: * easy.request_size => fixnum * * Retrieve the total size of the issued requests. This is so far * only for HTTP requests. Note that this may be more than one request * if +follow_location?+ is true. */ static VALUE ruby_curl_easy_request_size_get(VALUE self) { ruby_curl_easy *rbce; long size; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_REQUEST_SIZE, &size); return LONG2NUM(size); } /* * call-seq: * easy.ssl_verify_result => integer * * Retrieve the result of the certification verification that was requested * (by setting +ssl_verify_peer?+ to +true+). */ static VALUE ruby_curl_easy_ssl_verify_result_get(VALUE self) { ruby_curl_easy *rbce; long result; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_SSL_VERIFYRESULT, &result); return LONG2NUM(result); } /* TODO CURLINFO_SSL_ENGINES Pass the address of a 'struct curl_slist *' to receive a linked-list of OpenSSL crypto-engines supported. Note that engines are normally implemented in separate dynamic libraries. Hence not all the returned engines may be available at run-time. NOTE: you must call curl_slist_free_all(3) on the list pointer once you're done with it, as libcurl will not free the data for you. (Added in 7.12.3) */ /* * call-seq: * easy.downloaded_content_length => float * * Retrieve the content-length of the download. This is the value read * from the Content-Length: field. */ static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.uploaded_content_length => float * * Retrieve the content-length of the upload. */ static VALUE ruby_curl_easy_uploaded_content_length_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #ifdef HAVE_CURLINFO_CONTENT_LENGTH_UPLOAD_T curl_off_t bytes; curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD_T, &bytes); return LL2NUM(bytes); #else double bytes; curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &bytes); return rb_float_new(bytes); #endif } /* * call-seq: * easy.content_type => "content/type" or nil * * Retrieve the content-type of the downloaded object. This is the value read * from the Content-Type: field. If you get +nil+, it means that the server * didn't send a valid Content-Type header or that the protocol used doesn't * support this. */ static VALUE ruby_curl_easy_content_type_get(VALUE self) { ruby_curl_easy *rbce; char* type; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_TYPE, &type); if (type && type[0]) { // curl returns empty string if none return rb_str_new2(type); } else { return Qnil; } } /* NOT REQUIRED? CURLINFO_PRIVATE Pass a pointer to a 'char *' to receive the pointer to the private data associated with the curl handle (set with the CURLOPT_PRIVATE option to curl_easy_setopt(3)). (Added in 7.10.3) */ /* TODO these will need constants setting up too for checking the bits. * * Alternatively, could return an object that wraps the long, and has * question methods to query the auth types. Could return long from to_i(nt) * CURLINFO_HTTPAUTH_AVAIL Pass a pointer to a long to receive a bitmask indicating the authentication method(s) available. The meaning of the bits is explained in the CURLOPT_HTTPAUTH option for curl_easy_setopt(3). (Added in 7.10.8) CURLINFO_PROXYAUTH_AVAIL Pass a pointer to a long to receive a bitmask indicating the authentication method(s) available for your proxy authentication. (Added in 7.10.8) */ /* * call-seq: * easy.os_errno => integer * * Retrieve the errno variable from a connect failure (requires * libcurl 7.12.2 or higher, otherwise 0 is always returned). */ static VALUE ruby_curl_easy_os_errno_get(VALUE self) { #ifdef HAVE_CURLINFO_OS_ERRNO ruby_curl_easy *rbce; long result; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_OS_ERRNO, &result); return LONG2NUM(result); #else rb_warn("Installed libcurl is too old to support os_errno"); return LONG2NUM(0); #endif } /* * call-seq: * easy.num_connects => integer * * Retrieve the number of new connections libcurl had to create to achieve * the previous transfer (only the successful connects are counted). * Combined with +redirect_count+ you are able to know how many times libcurl * successfully reused existing connection(s) or not. * * See the Connection Options of curl_easy_setopt(3) to see how libcurl tries * to make persistent connections to save time. * * (requires libcurl 7.12.3 or higher, otherwise -1 is always returned). */ static VALUE ruby_curl_easy_num_connects_get(VALUE self) { #ifdef HAVE_CURLINFO_NUM_CONNECTS ruby_curl_easy *rbce; long result; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_NUM_CONNECTS, &result); return LONG2NUM(result); #else rb_warn("Installed libcurl is too old to support num_connects"); return LONG2NUM(-1); #endif } /* * call-seq: * easy.cookielist => cookielist * * Retrieves the cookies curl knows in an array of strings. * Returned strings are in Netscape cookiejar format or in Set-Cookie format. * Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported. * * To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+ * or +easy.set(:cookielist, string)+ with one of the following accepted inputs: * - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..." * - One or more lines in Netscape cookie file format (tab-separated fields) * - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file) * * @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option CURLINFO_COOKIELIST of * curl_easy_getopt(3) to see how libcurl behaves. * @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned * @return [Array, nil, -1] array of strings, or +nil+ if there are no cookies, or +-1+ if the libcurl version is too old */ static VALUE ruby_curl_easy_cookielist_get(VALUE self) { #ifdef HAVE_CURLINFO_COOKIELIST ruby_curl_easy *rbce; struct curl_slist *cookies; struct curl_slist *cookie; VALUE rb_cookies; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_COOKIELIST, &cookies); if (!cookies) return Qnil; rb_cookies = rb_ary_new(); for (cookie = cookies; cookie; cookie = cookie->next) rb_ary_push(rb_cookies, rb_str_new2(cookie->data)); curl_slist_free_all(cookies); return rb_cookies; #else rb_warn("Installed libcurl is too old to support cookielist"); return INT2FIX(-1); #endif } /* TODO this needs to be implemented. Could probably support CONNECT_ONLY by having this * return an open Socket or something. * CURLINFO_LASTSOCKET Pass a pointer to a long to receive the last socket used by this curl session. If the socket is no longer valid, -1 is returned. When you finish working with the socket, you must call curl_easy_cleanup() as usual and let libcurl close the socket and cleanup other resources associated with the handle. This is typically used in combination with CURLOPT_CONNECT_ONLY. (Added in 7.15.2) */ /* * call-seq: * easy.ftp_entry_path => "C:\ftp\root\" or nil * * Retrieve the path of the entry path. That is the initial path libcurl ended * up in when logging on to the remote FTP server. This returns +nil+ if * something is wrong. * * (requires libcurl 7.15.4 or higher, otherwise +nil+ is always returned). */ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) { #ifdef HAVE_CURLINFO_FTP_ENTRY_PATH ruby_curl_easy *rbce; char* path = NULL; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); curl_easy_getinfo(rbce->curl, CURLINFO_FTP_ENTRY_PATH, &path); if (path && path[0]) { // curl returns NULL or empty string if none return rb_str_new2(path); } else { return Qnil; } #else rb_warn("Installed libcurl is too old to support ftp_entry_path"); return Qnil; #endif } /* * call-seq: * easy.multi => "#" */ static VALUE ruby_curl_easy_multi_get(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return rbce->multi; } /* * call-seq: * easy.multi=multi => "#" */ static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (!NIL_P(multi) && rb_obj_is_kind_of(multi, cCurlMulti) != Qtrue) { rb_raise(rb_eTypeError, "expected Curl::Multi or nil"); } rbce->multi = multi; return rbce->multi; } /* * call-seq: * easy.last_result => 0 */ static VALUE ruby_curl_easy_last_result(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); return LONG2NUM(rbce->last_result); } /* * call-seq: * easy.last_error => "Error details" or nil */ static VALUE ruby_curl_easy_last_error(VALUE self) { ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); if (rbce->err_buf[0]) { // curl returns NULL or empty string if none return rb_str_new2(rbce->err_buf); } else { return Qnil; } } /* * call-seq: * easy.setopt(opt, val) => val * * Initial access to libcurl curl_easy_setopt * * @param [Fixnum] opt The option to set, see +Curl::CURLOPT_*+ constants * @param [Object] val * @return [Object] val * @raise [TypeError] if the option is not supported * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state. * @note When +curl_easy_setopt+ is called, return value is not checked here. */ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) { ruby_curl_easy *rbce; long option = NUM2LONG(opt); rb_io_t *open_f_ptr; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); switch (option) { /* BEHAVIOR OPTIONS */ case CURLOPT_VERBOSE: { VALUE verbose = val; CURB_BOOLEAN_SETTER(ruby_curl_easy, verbose); } break; case CURLOPT_FOLLOWLOCATION: { VALUE follow_location = val; CURB_BOOLEAN_SETTER(ruby_curl_easy, follow_location); } break; /* TODO: CALLBACK OPTIONS */ /* TODO: ERROR OPTIONS */ /* NETWORK OPTIONS */ case CURLOPT_URL: { VALUE url = val; CURB_OBJECT_HSETTER(ruby_curl_easy, url); } break; case CURLOPT_CUSTOMREQUEST: curl_easy_setopt(rbce->curl, CURLOPT_CUSTOMREQUEST, NIL_P(val) ? NULL : StringValueCStr(val)); break; case CURLOPT_HTTP_VERSION: { long http_version = NIL_P(val) ? CURL_HTTP_VERSION_NONE : NUM2LONG(val); rbce->http_version = http_version; curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, http_version); } break; case CURLOPT_PROXY: { VALUE proxy_url = val; CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_url); } break; case CURLOPT_INTERFACE: { VALUE interface_hm = val; CURB_OBJECT_HSETTER(ruby_curl_easy, interface_hm); } break; case CURLOPT_HEADER: case CURLOPT_NOPROGRESS: case CURLOPT_NOSIGNAL: #ifdef HAVE_CURLOPT_PATH_AS_IS case CURLOPT_PATH_AS_IS: #endif #ifdef HAVE_CURLOPT_PIPEWAIT case CURLOPT_PIPEWAIT: #endif case CURLOPT_HTTPGET: case CURLOPT_NOBODY: { int type = rb_type(val); VALUE value; if (type == T_TRUE) { value = rb_int_new(1); } else if (type == T_FALSE) { value = rb_int_new(0); } else { value = rb_funcall(val, rb_intern("to_i"), 0); } curl_easy_setopt(rbce->curl, option, NUM2LONG(value)); } break; case CURLOPT_POST: { curl_easy_setopt(rbce->curl, CURLOPT_POST, rb_type(val) == T_TRUE); } break; case CURLOPT_MAXCONNECTS: { curl_easy_setopt(rbce->curl, CURLOPT_MAXCONNECTS, NUM2LONG(val)); } break; case CURLOPT_POSTFIELDS: { ruby_curl_easy_post_body_set_with_mode(self, val, 0); } break; case CURLOPT_USERPWD: { VALUE userpwd = val; CURB_OBJECT_HSETTER(ruby_curl_easy, userpwd); } break; case CURLOPT_PROXYUSERPWD: { VALUE proxypwd = val; CURB_OBJECT_HSETTER(ruby_curl_easy, proxypwd); } break; #ifdef HAVE_CURLOPT_NOPROXY case CURLOPT_NOPROXY: { VALUE noproxy = val; CURB_OBJECT_HSETTER(ruby_curl_easy, noproxy); } break; #endif case CURLOPT_COOKIE: { VALUE cookies = val; CURB_OBJECT_HSETTER(ruby_curl_easy, cookies); } break; case CURLOPT_COOKIEFILE: { VALUE cookiefile = val; CURB_OBJECT_HSETTER(ruby_curl_easy, cookiefile); } break; case CURLOPT_COOKIEJAR: { VALUE cookiejar = val; CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar); } break; #ifdef HAVE_CURLOPT_REQUEST_TARGET case CURLOPT_REQUEST_TARGET: { /* Forward request-target directly to libcurl as a string. */ curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val)); } break; #endif case CURLOPT_TCP_NODELAY: { curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val)); } break; /* FTP-specific toggles */ #ifdef HAVE_CURLOPT_DIRLISTONLY case CURLOPT_DIRLISTONLY: { int type = rb_type(val); VALUE value; if (type == T_TRUE) { value = rb_int_new(1); } else if (type == T_FALSE) { value = rb_int_new(0); } else { value = rb_funcall(val, rb_intern("to_i"), 0); } curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value)); } break; #endif #ifdef HAVE_CURLOPT_FTP_USE_EPSV case CURLOPT_FTP_USE_EPSV: { int type = rb_type(val); VALUE value; if (type == T_TRUE) { value = rb_int_new(1); } else if (type == T_FALSE) { value = rb_int_new(0); } else { value = rb_funcall(val, rb_intern("to_i"), 0); } curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value)); } break; #endif #ifdef HAVE_CURLOPT_FTP_USE_EPRT case CURLOPT_FTP_USE_EPRT: { int type = rb_type(val); VALUE value; if (type == T_TRUE) { value = rb_int_new(1); } else if (type == T_FALSE) { value = rb_int_new(0); } else { value = rb_funcall(val, rb_intern("to_i"), 0); } curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value)); } break; #endif #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP case CURLOPT_FTP_SKIP_PASV_IP: { int type = rb_type(val); VALUE value; if (type == T_TRUE) { value = rb_int_new(1); } else if (type == T_FALSE) { value = rb_int_new(0); } else { value = rb_funcall(val, rb_intern("to_i"), 0); } curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value)); } break; #endif case CURLOPT_RANGE: { curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val)); } break; case CURLOPT_RESUME_FROM: { curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, NUM2LONG(val)); } break; case CURLOPT_FAILONERROR: { curl_easy_setopt(rbce->curl, CURLOPT_FAILONERROR, NUM2LONG(val)); } break; case CURLOPT_SSL_CIPHER_LIST: { curl_easy_setopt(rbce->curl, CURLOPT_SSL_CIPHER_LIST, StringValueCStr(val)); } break; case CURLOPT_FORBID_REUSE: { curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, NUM2LONG(val)); } break; #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION case CURLOPT_GSSAPI_DELEGATION: { curl_easy_setopt(rbce->curl, CURLOPT_GSSAPI_DELEGATION, NUM2LONG(val)); } break; #endif #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH case CURLOPT_UNIX_SOCKET_PATH: { curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, StringValueCStr(val)); } break; #endif #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE case CURLOPT_MAX_SEND_SPEED_LARGE: { curl_easy_setopt(rbce->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) NUM2LL(val)); } break; #endif #ifdef HAVE_CURLOPT_MAX_RECV_SPEED_LARGE case CURLOPT_MAX_RECV_SPEED_LARGE: { curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val)); } break; #endif #ifdef HAVE_CURLOPT_MAXFILESIZE case CURLOPT_MAXFILESIZE: curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val)); break; #endif #ifdef HAVE_CURLOPT_TCP_KEEPALIVE case CURLOPT_TCP_KEEPALIVE: curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val)); break; case CURLOPT_TCP_KEEPIDLE: curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPIDLE, NUM2LONG(val)); break; case CURLOPT_TCP_KEEPINTVL: curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPINTVL, NUM2LONG(val)); break; #endif #ifdef HAVE_CURLOPT_HAPROXYPROTOCOL case CURLOPT_HAPROXYPROTOCOL: curl_easy_setopt(rbce->curl, CURLOPT_HAPROXYPROTOCOL, NUM2LONG(val)); break; #endif case CURLOPT_STDERR: // libcurl requires raw FILE pointer and this should be IO object in Ruby. // Tempfile or StringIO won't work. Check_Type(val, T_FILE); GetOpenFile(val, open_f_ptr); curl_easy_setopt(rbce->curl, CURLOPT_STDERR, rb_io_stdio_file(open_f_ptr)); /* Retain a Ruby reference to the IO to prevent GC/finalization * while libcurl still holds and writes to the underlying FILE*. */ rb_easy_set("stderr_io", val); break; case CURLOPT_PROTOCOLS: case CURLOPT_REDIR_PROTOCOLS: curl_easy_setopt(rbce->curl, option, NUM2LONG(val)); break; #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE case CURLOPT_SSL_SESSIONID_CACHE: curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val)); break; #endif #ifdef HAVE_CURLOPT_COOKIELIST case CURLOPT_COOKIELIST: { /* Forward to libcurl */ curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val)); /* Track whether the cookie engine should be enabled for requests. * According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine * when provided with a non-command string. Some environments may still require * an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests. * We do that in the perform setup when this flag is set. */ if (RB_TYPE_P(val, T_STRING)) { const char *s = StringValueCStr(val); if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) { rbce->cookielist_engine_enabled = 1; } } else { /* Non-string values are unexpected; be conservative and do not enable. */ } } break; #endif #ifdef HAVE_CURLOPT_PROXY_SSL_VERIFYHOST case CURLOPT_PROXY_SSL_VERIFYHOST: curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val)); break; #endif #ifdef HAVE_CURLOPT_RESOLVE case CURLOPT_RESOLVE: { struct curl_slist *list = NULL; ruby_curl_easy_clear_resolve_list(rbce); if (NIL_P(val)) { /* When nil is passed, we clear any previous resolve list */ list = NULL; } else if (TYPE(val) == T_ARRAY) { long i, len = RARRAY_LEN(val); for (i = 0; i < len; i++) { VALUE item = rb_ary_entry(val, i); struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item)); if (!new_list) { curl_slist_free_all(list); rb_raise(rb_eNoMemError, "Failed to append to resolve list"); } list = new_list; } } else { /* If a single string is passed, use it directly */ list = curl_slist_append(NULL, StringValueCStr(val)); if (!list) { rb_raise(rb_eNoMemError, "Failed to create resolve list"); } } /* Save the list pointer in the ruby_curl_easy structure for cleanup later */ rbce->curl_resolve = list; rb_hash_aset(rbce->opts, rb_easy_hkey("resolve"), val); curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, list); } break; #endif default: rb_raise(rb_eTypeError, "Curb unsupported option"); } return val; } /* * call-seq: * easy.getinfo(opt) => nil * * Iniital access to libcurl curl_easy_getinfo, remember getinfo doesn't return the same values as setopt * * @note This method is not implemented yet. * @param [Fixnum] code Constant +CURLINFO_*+ from libcurl * @return [nil] */ static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) { return Qnil; } /* * call-seq: * easy.inspect => "#" */ static VALUE ruby_curl_easy_inspect(VALUE self) { char buf[64]; ruby_curl_easy *rbce; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); /* if we don't have a url set... we'll crash... */ if( !rb_easy_nil("url") && rb_easy_type_check("url", T_STRING)) { VALUE url = rb_easy_get("url"); size_t len = 13+((RSTRING_LEN(url) > 50) ? 50 : RSTRING_LEN(url)); /* "#" */ memcpy(buf,"#"); } /* ================== ESCAPING FUNCS ==============*/ /* * call-seq: * easy.escape("some text") => "some%20text" * * Convert the given input string to a URL encoded string and return * the result. All input characters that are not a-z, A-Z or 0-9 are * converted to their "URL escaped" version (%NN where NN is a * two-digit hexadecimal number). */ static VALUE ruby_curl_easy_escape(VALUE self, VALUE svalue) { ruby_curl_easy *rbce; char *result; VALUE rresult; VALUE str = svalue; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); /* NOTE: make sure the value is a string, if not call to_s */ if( rb_type(str) != T_STRING ) { str = rb_funcall(str,rb_intern("to_s"),0); } #if (LIBCURL_VERSION_NUM >= 0x070f04) result = (char*)curl_easy_escape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str)); #else result = (char*)curl_escape(StringValuePtr(str), (int)RSTRING_LEN(str)); #endif rresult = rb_str_new2(result); curl_free(result); return rresult; } /* * call-seq: * easy.unescape("some%20text") => "some text" * * Convert the given URL encoded input string to a "plain string" and return * the result. All input characters that are URL encoded (%XX where XX is a * two-digit hexadecimal number) are converted to their binary versions. */ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) { ruby_curl_easy *rbce; int rlen; char *result; VALUE rresult; TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce); #if (LIBCURL_VERSION_NUM >= 0x070f04) result = (char*)curl_easy_unescape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str), &rlen); #else result = (char*)curl_unescape(StringValuePtr(str), (int)RSTRING_LEN(str)); rlen = strlen(result); #endif rresult = rb_str_new(result, rlen); curl_free(result); return rresult; } /* ================= CLASS METHODS ==================*/ /* * call-seq: * Curl::Easy.error(code) => [ErrCode, String] * * translate an internal libcurl error to ruby error class */ static VALUE ruby_curl_easy_error_message(VALUE klass, VALUE code) { return rb_curl_easy_error(NUM2INT(code)); } /* =================== INIT LIB =====================*/ // TODO: https://bugs.ruby-lang.org/issues/18007 void init_curb_easy() { idCall = rb_intern("call"); idJoin = rb_intern("join"); rbstrAmp = rb_str_new2("&"); rb_global_variable(&rbstrAmp); cCurlEasy = rb_define_class_under(mCurl, "Easy", rb_cObject); /* Class methods */ rb_define_alloc_func(cCurlEasy, ruby_curl_easy_allocate); rb_define_singleton_method(cCurlEasy, "error", ruby_curl_easy_error_message, 1); /* Initialize method */ rb_define_method(cCurlEasy, "initialize", ruby_curl_easy_initialize, -1); /* Attributes for config next perform */ rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0); rb_define_method(cCurlEasy, "proxy_url", ruby_curl_easy_proxy_url_get, 0); rb_define_method(cCurlEasy, "http_version=", ruby_curl_easy_http_version_set, 1); rb_define_method(cCurlEasy, "http_version", ruby_curl_easy_http_version_get, 0); rb_define_method(cCurlEasy, "proxy_headers=", ruby_curl_easy_proxy_headers_set, 1); rb_define_method(cCurlEasy, "proxy_headers", ruby_curl_easy_proxy_headers_get, 0); rb_define_method(cCurlEasy, "headers=", ruby_curl_easy_headers_set, 1); rb_define_method(cCurlEasy, "headers", ruby_curl_easy_headers_get, 0); rb_define_method(cCurlEasy, "interface", ruby_curl_easy_interface_get, 0); rb_define_method(cCurlEasy, "userpwd", ruby_curl_easy_userpwd_get, 0); rb_define_method(cCurlEasy, "proxypwd", ruby_curl_easy_proxypwd_get, 0); rb_define_method(cCurlEasy, "cookies", ruby_curl_easy_cookies_get, 0); rb_define_method(cCurlEasy, "cookiefile", ruby_curl_easy_cookiefile_get, 0); rb_define_method(cCurlEasy, "cookiejar", ruby_curl_easy_cookiejar_get, 0); rb_define_method(cCurlEasy, "cert=", ruby_curl_easy_cert_set, 1); rb_define_method(cCurlEasy, "cert", ruby_curl_easy_cert_get, 0); rb_define_method(cCurlEasy, "cert_key=", ruby_curl_easy_cert_key_set, 1); rb_define_method(cCurlEasy, "cert_key", ruby_curl_easy_cert_key_get, 0); rb_define_method(cCurlEasy, "cacert=", ruby_curl_easy_cacert_set, 1); rb_define_method(cCurlEasy, "cacert", ruby_curl_easy_cacert_get, 0); rb_define_method(cCurlEasy, "certpassword=", ruby_curl_easy_certpassword_set, 1); rb_define_method(cCurlEasy, "certtype=", ruby_curl_easy_certtype_set, 1); rb_define_method(cCurlEasy, "certtype", ruby_curl_easy_certtype_get, 0); rb_define_method(cCurlEasy, "encoding=", ruby_curl_easy_encoding_set, 1); rb_define_method(cCurlEasy, "encoding", ruby_curl_easy_encoding_get, 0); rb_define_method(cCurlEasy, "useragent=", ruby_curl_easy_useragent_set, 1); rb_define_method(cCurlEasy, "useragent", ruby_curl_easy_useragent_get, 0); rb_define_method(cCurlEasy, "post_body=", ruby_curl_easy_post_body_set, 1); rb_define_method(cCurlEasy, "post_body", ruby_curl_easy_post_body_get, 0); rb_define_method(cCurlEasy, "put_data=", ruby_curl_easy_put_data_set, 1); rb_define_method(cCurlEasy, "ftp_commands=", ruby_curl_easy_ftp_commands_set, 1); rb_define_method(cCurlEasy, "ftp_commands", ruby_curl_easy_ftp_commands_get, 0); rb_define_method(cCurlEasy, "resolve=", ruby_curl_easy_resolve_set, 1); rb_define_method(cCurlEasy, "resolve", ruby_curl_easy_resolve_get, 0); rb_define_method(cCurlEasy, "local_port=", ruby_curl_easy_local_port_set, 1); rb_define_method(cCurlEasy, "local_port", ruby_curl_easy_local_port_get, 0); rb_define_method(cCurlEasy, "local_port_range=", ruby_curl_easy_local_port_range_set, 1); rb_define_method(cCurlEasy, "local_port_range", ruby_curl_easy_local_port_range_get, 0); rb_define_method(cCurlEasy, "proxy_port=", ruby_curl_easy_proxy_port_set, 1); rb_define_method(cCurlEasy, "proxy_port", ruby_curl_easy_proxy_port_get, 0); rb_define_method(cCurlEasy, "proxy_type=", ruby_curl_easy_proxy_type_set, 1); rb_define_method(cCurlEasy, "proxy_type", ruby_curl_easy_proxy_type_get, 0); rb_define_method(cCurlEasy, "http_auth_types=", ruby_curl_easy_http_auth_types_set, -1); rb_define_method(cCurlEasy, "http_auth_types", ruby_curl_easy_http_auth_types_get, 0); rb_define_method(cCurlEasy, "proxy_auth_types=", ruby_curl_easy_proxy_auth_types_set, 1); rb_define_method(cCurlEasy, "proxy_auth_types", ruby_curl_easy_proxy_auth_types_get, 0); rb_define_method(cCurlEasy, "max_redirects=", ruby_curl_easy_max_redirects_set, 1); rb_define_method(cCurlEasy, "max_redirects", ruby_curl_easy_max_redirects_get, 0); rb_define_method(cCurlEasy, "timeout=", ruby_curl_easy_timeout_set, 1); rb_define_method(cCurlEasy, "timeout", ruby_curl_easy_timeout_get, 0); rb_define_method(cCurlEasy, "timeout_ms=", ruby_curl_easy_timeout_ms_set, 1); rb_define_method(cCurlEasy, "timeout_ms", ruby_curl_easy_timeout_ms_get, 0); rb_define_method(cCurlEasy, "connect_timeout=", ruby_curl_easy_connect_timeout_set, 1); rb_define_method(cCurlEasy, "connect_timeout", ruby_curl_easy_connect_timeout_get, 0); rb_define_method(cCurlEasy, "connect_timeout_ms=", ruby_curl_easy_connect_timeout_ms_set, 1); rb_define_method(cCurlEasy, "connect_timeout_ms", ruby_curl_easy_connect_timeout_ms_get, 0); rb_define_method(cCurlEasy, "dns_cache_timeout=", ruby_curl_easy_dns_cache_timeout_set, 1); rb_define_method(cCurlEasy, "dns_cache_timeout", ruby_curl_easy_dns_cache_timeout_get, 0); rb_define_method(cCurlEasy, "ftp_response_timeout=", ruby_curl_easy_ftp_response_timeout_set, 1); rb_define_method(cCurlEasy, "ftp_response_timeout", ruby_curl_easy_ftp_response_timeout_get, 0); rb_define_method(cCurlEasy, "low_speed_limit=", ruby_curl_easy_low_speed_limit_set, 1); rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0); rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1); rb_define_method(cCurlEasy, "low_speed_time", ruby_curl_easy_low_speed_time_get, 0); rb_define_method(cCurlEasy, "max_send_speed_large=", ruby_curl_easy_max_send_speed_large_set, 1); rb_define_method(cCurlEasy, "max_send_speed_large", ruby_curl_easy_max_send_speed_large_get, 0); rb_define_method(cCurlEasy, "max_recv_speed_large=", ruby_curl_easy_max_recv_speed_large_set, 1); rb_define_method(cCurlEasy, "max_recv_speed_large", ruby_curl_easy_max_recv_speed_large_get, 0); rb_define_method(cCurlEasy, "ssl_version=", ruby_curl_easy_ssl_version_set, 1); rb_define_method(cCurlEasy, "ssl_version", ruby_curl_easy_ssl_version_get, 0); rb_define_method(cCurlEasy, "use_ssl=", ruby_curl_easy_use_ssl_set, 1); rb_define_method(cCurlEasy, "use_ssl", ruby_curl_easy_use_ssl_get, 0); rb_define_method(cCurlEasy, "ftp_filemethod=", ruby_curl_easy_ftp_filemethod_set, 1); rb_define_method(cCurlEasy, "ftp_filemethod", ruby_curl_easy_ftp_filemethod_get, 0); rb_define_method(cCurlEasy, "username=", ruby_curl_easy_username_set, 1); rb_define_method(cCurlEasy, "username", ruby_curl_easy_username_get, 0); rb_define_method(cCurlEasy, "password=", ruby_curl_easy_password_set, 1); rb_define_method(cCurlEasy, "password", ruby_curl_easy_password_get, 0); rb_define_method(cCurlEasy, "proxy_tunnel=", ruby_curl_easy_proxy_tunnel_set, 1); rb_define_method(cCurlEasy, "proxy_tunnel?", ruby_curl_easy_proxy_tunnel_q, 0); rb_define_method(cCurlEasy, "fetch_file_time=", ruby_curl_easy_fetch_file_time_set, 1); rb_define_method(cCurlEasy, "fetch_file_time?", ruby_curl_easy_fetch_file_time_q, 0); rb_define_method(cCurlEasy, "ssl_verify_peer=", ruby_curl_easy_ssl_verify_peer_set, 1); rb_define_method(cCurlEasy, "ssl_verify_peer?", ruby_curl_easy_ssl_verify_peer_q, 0); rb_define_method(cCurlEasy, "ssl_verify_host_integer=", ruby_curl_easy_ssl_verify_host_set, 1); rb_define_method(cCurlEasy, "ssl_verify_host", ruby_curl_easy_ssl_verify_host_get, 0); rb_define_method(cCurlEasy, "header_in_body=", ruby_curl_easy_header_in_body_set, 1); rb_define_method(cCurlEasy, "header_in_body?", ruby_curl_easy_header_in_body_q, 0); rb_define_method(cCurlEasy, "use_netrc=", ruby_curl_easy_use_netrc_set, 1); rb_define_method(cCurlEasy, "use_netrc?", ruby_curl_easy_use_netrc_q, 0); rb_define_method(cCurlEasy, "follow_location?", ruby_curl_easy_follow_location_q, 0); rb_define_method(cCurlEasy, "autoreferer=", ruby_curl_easy_autoreferer_set, 1); rb_define_method(cCurlEasy, "unrestricted_auth=", ruby_curl_easy_unrestricted_auth_set, 1); rb_define_method(cCurlEasy, "unrestricted_auth?", ruby_curl_easy_unrestricted_auth_q, 0); rb_define_method(cCurlEasy, "verbose=", ruby_curl_easy_verbose_set, 1); rb_define_method(cCurlEasy, "verbose?", ruby_curl_easy_verbose_q, 0); rb_define_method(cCurlEasy, "multipart_form_post=", ruby_curl_easy_multipart_form_post_set, 1); rb_define_method(cCurlEasy, "multipart_form_post?", ruby_curl_easy_multipart_form_post_q, 0); rb_define_method(cCurlEasy, "enable_cookies=", ruby_curl_easy_enable_cookies_set, 1); rb_define_method(cCurlEasy, "enable_cookies?", ruby_curl_easy_enable_cookies_q, 0); rb_define_method(cCurlEasy, "ignore_content_length=", ruby_curl_easy_ignore_content_length_set, 1); rb_define_method(cCurlEasy, "ignore_content_length?", ruby_curl_easy_ignore_content_length_q, 0); rb_define_method(cCurlEasy, "resolve_mode", ruby_curl_easy_resolve_mode, 0); rb_define_method(cCurlEasy, "resolve_mode=", ruby_curl_easy_resolve_mode_set, 1); rb_define_method(cCurlEasy, "on_body", ruby_curl_easy_on_body_set, -1); rb_define_method(cCurlEasy, "on_header", ruby_curl_easy_on_header_set, -1); rb_define_method(cCurlEasy, "on_progress", ruby_curl_easy_on_progress_set, -1); rb_define_method(cCurlEasy, "on_debug", ruby_curl_easy_on_debug_set, -1); rb_define_method(cCurlEasy, "on_success", ruby_curl_easy_on_success_set, -1); rb_define_method(cCurlEasy, "on_failure", ruby_curl_easy_on_failure_set, -1); rb_define_method(cCurlEasy, "on_missing", ruby_curl_easy_on_missing_set, -1); rb_define_method(cCurlEasy, "on_redirect", ruby_curl_easy_on_redirect_set, -1); rb_define_method(cCurlEasy, "on_complete", ruby_curl_easy_on_complete_set, -1); rb_define_method(cCurlEasy, "http", ruby_curl_easy_perform_verb, 1); rb_define_method(cCurlEasy, "http_post", ruby_curl_easy_perform_post, -1); rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, -1); rb_define_method(cCurlEasy, "http_patch", ruby_curl_easy_perform_patch, -1); /* Post-perform info methods */ rb_define_method(cCurlEasy, "body_str", ruby_curl_easy_body_str_get, 0); rb_define_method(cCurlEasy, "header_str", ruby_curl_easy_header_str_get, 0); rb_define_method(cCurlEasy, "last_effective_url", ruby_curl_easy_last_effective_url_get, 0); rb_define_method(cCurlEasy, "response_code", ruby_curl_easy_response_code_get, 0); rb_define_method(cCurlEasy, "code", ruby_curl_easy_response_code_get, 0); #if defined(HAVE_CURLINFO_PRIMARY_IP) rb_define_method(cCurlEasy, "primary_ip", ruby_curl_easy_primary_ip_get, 0); #endif rb_define_method(cCurlEasy, "http_connect_code", ruby_curl_easy_http_connect_code_get, 0); rb_define_method(cCurlEasy, "file_time", ruby_curl_easy_file_time_get, 0); rb_define_method(cCurlEasy, "total_time", ruby_curl_easy_total_time_get, 0); rb_define_method(cCurlEasy, "name_lookup_time", ruby_curl_easy_name_lookup_time_get, 0); rb_define_method(cCurlEasy, "connect_time", ruby_curl_easy_connect_time_get, 0); #if defined(HAVE_CURLINFO_APPCONNECT_TIME) rb_define_method(cCurlEasy, "app_connect_time", ruby_curl_easy_app_connect_time_get, 0); #endif rb_define_method(cCurlEasy, "pre_transfer_time", ruby_curl_easy_pre_transfer_time_get, 0); rb_define_method(cCurlEasy, "start_transfer_time", ruby_curl_easy_start_transfer_time_get, 0); rb_define_method(cCurlEasy, "redirect_time", ruby_curl_easy_redirect_time_get, 0); rb_define_method(cCurlEasy, "redirect_count", ruby_curl_easy_redirect_count_get, 0); rb_define_method(cCurlEasy, "redirect_url", ruby_curl_easy_redirect_url_get, 0); rb_define_method(cCurlEasy, "downloaded_bytes", ruby_curl_easy_downloaded_bytes_get, 0); rb_define_method(cCurlEasy, "uploaded_bytes", ruby_curl_easy_uploaded_bytes_get, 0); rb_define_method(cCurlEasy, "download_speed", ruby_curl_easy_download_speed_get, 0); rb_define_method(cCurlEasy, "upload_speed", ruby_curl_easy_upload_speed_get, 0); rb_define_method(cCurlEasy, "header_size", ruby_curl_easy_header_size_get, 0); rb_define_method(cCurlEasy, "request_size", ruby_curl_easy_request_size_get, 0); rb_define_method(cCurlEasy, "ssl_verify_result", ruby_curl_easy_ssl_verify_result_get, 0); rb_define_method(cCurlEasy, "downloaded_content_length", ruby_curl_easy_downloaded_content_length_get, 0); rb_define_method(cCurlEasy, "uploaded_content_length", ruby_curl_easy_uploaded_content_length_get, 0); rb_define_method(cCurlEasy, "content_type", ruby_curl_easy_content_type_get, 0); rb_define_method(cCurlEasy, "os_errno", ruby_curl_easy_os_errno_get, 0); rb_define_method(cCurlEasy, "num_connects", ruby_curl_easy_num_connects_get, 0); rb_define_method(cCurlEasy, "cookielist", ruby_curl_easy_cookielist_get, 0); rb_define_method(cCurlEasy, "ftp_entry_path", ruby_curl_easy_ftp_entry_path_get, 0); rb_define_method(cCurlEasy, "close", ruby_curl_easy_close, 0); rb_define_method(cCurlEasy, "reset", ruby_curl_easy_reset, 0); /* Curl utils */ rb_define_method(cCurlEasy, "escape", ruby_curl_easy_escape, 1); rb_define_method(cCurlEasy, "unescape", ruby_curl_easy_unescape, 1); /* Runtime support */ rb_define_method(cCurlEasy, "clone", ruby_curl_easy_clone, 0); rb_define_alias(cCurlEasy, "dup", "clone"); rb_define_method(cCurlEasy, "inspect", ruby_curl_easy_inspect, 0); rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0); rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1); rb_define_private_method(cCurlEasy, "_take_callback_error", ruby_curl_easy_take_callback_error, 0); rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0); rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0); rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2); rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1); } curb-1.3.5/ext/curb_macros.h0000644000004100000410000002267715203731642015774 0ustar www-datawww-data/* Curb - helper macros for ruby integration * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb_macros.h 13 2006-11-23 23:54:25Z roscopeco $ */ #ifndef __CURB_MACROS_H #define __CURB_MACROS_H #define rb_easy_sym(sym) ID2SYM(rb_intern(sym)) #define rb_easy_hkey(key) ID2SYM(rb_intern(key)) #define rb_easy_set(key,val) rb_hash_aset(rbce->opts, rb_easy_hkey(key) , val) #define rb_easy_get(key) rb_hash_aref(rbce->opts, rb_easy_hkey(key)) #define rb_easy_del(key) rb_hash_delete(rbce->opts, rb_easy_hkey(key)) #define rb_easy_nil(key) (rb_hash_aref(rbce->opts, rb_easy_hkey(key)) == Qnil) #define rb_easy_type_check(key,type) (rb_type(rb_hash_aref(rbce->opts, rb_easy_hkey(key))) == type) // TODO: rb_sym_to_s may not be defined? #define rb_easy_get_str(key) \ RSTRING_PTR((rb_easy_type_check(key,T_STRING) ? rb_easy_get(key) : rb_str_to_str(rb_easy_get(key)))) /* getter/setter macros for various things */ /* setter for anything that stores a ruby VALUE in the struct */ #define CURB_OBJECT_SETTER(type, attr) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ ptr->attr = attr; \ \ return attr; /* getter for anything that stores a ruby VALUE */ #define CURB_OBJECT_GETTER(type, attr) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ return ptr->attr; /* setter for anything that stores a ruby VALUE in the struct opts hash */ #define CURB_OBJECT_HSETTER(type, attr) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ rb_hash_aset(ptr->opts, rb_easy_hkey(#attr), attr); \ \ return attr; /* getter for anything that stores a ruby VALUE in the struct opts hash */ #define CURB_OBJECT_HGETTER(type, attr) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ return rb_hash_aref(ptr->opts, rb_easy_hkey(#attr)); /* setter for bool flags */ #define CURB_BOOLEAN_SETTER(type, attr) \ type *ptr; \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ \ if (attr == Qnil || attr == Qfalse) { \ ptr->attr = 0; \ } else { \ ptr->attr = 1; \ } \ \ return attr; /* getter for bool flags */ #define CURB_BOOLEAN_GETTER(type, attr) \ type *ptr; \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ \ return((ptr->attr) ? Qtrue : Qfalse); /* special setter for on_event handlers that take a block */ #define CURB_HANDLER_PROC_SETTER(type, handler) \ type *ptr; \ VALUE oldproc; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ \ oldproc = ptr->handler; \ rb_scan_args(argc, argv, "0&", &ptr->handler); \ \ return oldproc; \ /* special setter for on_event handlers that take a block, same as above but stores int he opts hash */ #define CURB_HANDLER_PROC_HSETTER(type, handler) \ type *ptr; \ VALUE oldproc, newproc; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ \ oldproc = rb_hash_aref(ptr->opts, rb_easy_hkey(#handler)); \ rb_scan_args(argc, argv, "0&", &newproc); \ \ rb_hash_aset(ptr->opts, rb_easy_hkey(#handler), newproc); \ \ return oldproc; /* setter for numerics that are kept in c longs */ #define CURB_IMMED_SETTER(type, attr, nilval) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ if (attr == Qnil) { \ ptr->attr = nilval; \ } else { \ ptr->attr = NUM2LONG(attr); \ } \ \ return attr; \ /* setter for numerics that are kept in c longs */ #define CURB_IMMED_GETTER(type, attr, nilval) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ if (ptr->attr == nilval) { \ return Qnil; \ } else { \ return LONG2NUM(ptr->attr); \ } /* special setter for port / port ranges */ #define CURB_IMMED_PORT_SETTER(type, attr, msg) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ if (attr == Qnil) { \ ptr->attr = 0; \ } else { \ int port = NUM2INT(attr); \ \ if ((port) && ((port & 0xFFFF) == port)) { \ ptr->attr = port; \ } else { \ rb_raise(rb_eArgError, "Invalid " msg " %d (expected between 1 and 65535)", port); \ } \ } \ \ return attr; \ /* special getter for port / port ranges */ #define CURB_IMMED_PORT_GETTER(type, attr) \ type *ptr; \ \ TypedData_Get_Struct(self, type, &type##_data_type, ptr); \ if (ptr->attr == 0) { \ return Qnil; \ } else { \ return INT2NUM(ptr->attr); \ } #define CURB_DEFINE(name) \ rb_define_const(mCurl, #name, LONG2NUM(name)) /* copy and raise exception */ #define CURB_CHECK_RB_CALLBACK_RAISE(did_raise) \ VALUE exception = rb_hash_aref(did_raise, rb_easy_hkey("error")); \ if (FIX2INT(rb_hash_size(did_raise)) > 0 && exception != Qnil) { \ rb_hash_clear(did_raise); \ VALUE message = rb_funcall(exception, rb_intern("message"), 0); \ VALUE aborted_exception = rb_exc_new_str(eCurlErrAbortedByCallback, message); \ VALUE backtrace = rb_funcall(exception, rb_intern("backtrace"), 0); \ rb_funcall(aborted_exception, rb_intern("set_backtrace"), 1, backtrace); \ rb_exc_raise(aborted_exception); \ } #endif curb-1.3.5/ext/curb.c0000644000004100000410000011400215203731642014403 0ustar www-datawww-data/* Curb - Libcurl(3) bindings for Ruby. * Copyright (c)2006 Ross Bamford. * Licensed under the Ruby License. See LICENSE for details. * * $Id: curb.c 35 2006-12-23 15:22:19Z roscopeco $ */ #include "curb.h" #include "curb_upload.h" VALUE mCurl; /* ================== VER QUERY FUNCS ==============*/ /* * call-seq: * Curl.ipv6? => true or false * * Returns true if the installed libcurl supports IPv6. */ static VALUE ruby_curl_ipv6_q(VALUE mod) { curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_IPV6) ? Qtrue : Qfalse); } /* * call-seq: * Curl.kerberos4? => true or false * * Returns true if the installed libcurl supports Kerberos4 authentication * with FTP connections. */ static VALUE ruby_curl_kerberos4_q(VALUE mod) { curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_KERBEROS4) ? Qtrue : Qfalse); } /* * call-seq: * Curl.ssl? => true or false * * Returns true if the installed libcurl supports SSL connections. * For libcurl versions < 7.10, always returns false. */ static VALUE ruby_curl_ssl_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_SSL curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_SSL) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.libz? => true or false * * Returns true if the installed libcurl supports HTTP deflate * using libz. For libcurl versions < 7.10, always returns false. */ static VALUE ruby_curl_libz_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_LIBZ curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_LIBZ) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.ntlm? => true or false * * Returns true if the installed libcurl supports HTTP NTLM. * For libcurl versions < 7.10.6, always returns false. */ static VALUE ruby_curl_ntlm_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_NTLM curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_NTLM) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.gssnegotiate? => true or false * * Returns true if the installed libcurl supports HTTP GSS-Negotiate. * For libcurl versions < 7.10.6, always returns false. */ static VALUE ruby_curl_gssnegotiate_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_GSSNEGOTIATE curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_GSSNEGOTIATE) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.debug? => true or false * * Returns true if the installed libcurl was built with extra debug * capabilities built-in. For libcurl versions < 7.10.6, always returns * false. */ static VALUE ruby_curl_debug_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_DEBUG curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_DEBUG) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.asyncdns? => true or false * * Returns true if the installed libcurl was built with support for * asynchronous name lookups, which allows more exact timeouts (even * on Windows) and less blocking when using the multi interface. * For libcurl versions < 7.10.7, always returns false. */ static VALUE ruby_curl_asyncdns_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_ASYNCHDNS curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_ASYNCHDNS) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.spnego? => true or false * * Returns true if the installed libcurl was built with support for SPNEGO * authentication (Simple and Protected GSS-API Negotiation Mechanism, defined * in RFC 2478). For libcurl versions < 7.10.8, always returns false. */ static VALUE ruby_curl_spnego_q(VALUE mod) { curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); #ifdef HAVE_CURL_VERSION_SPNEGO if (ver->features & CURL_VERSION_SPNEGO) return Qtrue; #endif #ifdef HAVE_CURL_VERSION_GSSNEGOTIATE if (ver->features & CURL_VERSION_GSSNEGOTIATE) return Qtrue; #endif return Qfalse; } /* * call-seq: * Curl.largefile? => true or false * * Returns true if the installed libcurl was built with support for large * files. For libcurl versions < 7.11.1, always returns false. */ static VALUE ruby_curl_largefile_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_LARGEFILE curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_LARGEFILE) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.idn? => true or false * * Returns true if the installed libcurl was built with support for IDNA, * domain names with international letters. For libcurl versions < 7.12.0, * always returns false. */ static VALUE ruby_curl_idn_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_IDN curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_IDN) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.sspi? => true or false * * Returns true if the installed libcurl was built with support for SSPI. * This is only available on Windows and makes libcurl use Windows-provided * functions for NTLM authentication. It also allows libcurl to use the current * user and the current user's password without the app having to pass them on. * For libcurl versions < 7.13.2, always returns false. */ static VALUE ruby_curl_sspi_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_SSPI curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_SSPI) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.conv? => true or false * * Returns true if the installed libcurl was built with support for character * conversions. For libcurl versions < 7.15.4, always returns false. */ static VALUE ruby_curl_conv_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_CONV curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_CONV) ? Qtrue : Qfalse); #else return Qfalse; #endif } /* * call-seq: * Curl.http2? => true or false * * Returns true if the installed libcurl was built with support for HTTP2. * For libcurl versions < 7.33.0, always returns false. */ static VALUE ruby_curl_http2_q(VALUE mod) { #ifdef HAVE_CURL_VERSION_HTTP2 curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW); return((ver->features & CURL_VERSION_HTTP2) ? Qtrue : Qfalse); #else return Qfalse; #endif } static void finalize_curb_core(VALUE data) { curl_global_cleanup(); } void Init_curb_core() { curl_version_info_data *ver; VALUE curlver, curllongver, curlvernum; curl_global_init(CURL_GLOBAL_ALL); rb_set_end_proc(finalize_curb_core, Qnil); ver = curl_version_info(CURLVERSION_NOW); mCurl = rb_define_module("Curl"); curlver = rb_str_new2(ver->version); curllongver = rb_str_new2(curl_version()); curlvernum = LONG2NUM(LIBCURL_VERSION_NUM); rb_define_const(mCurl, "CURB_VERSION", rb_str_new2(CURB_VERSION)); rb_define_const(mCurl, "VERSION", curlver); rb_define_const(mCurl, "CURL_VERSION", curlver); rb_define_const(mCurl, "VERNUM", curlvernum); rb_define_const(mCurl, "CURL_VERNUM", curlvernum); rb_define_const(mCurl, "LONG_VERSION", curllongver); rb_define_const(mCurl, "CURL_LONG_VERSION", curllongver); /* Passed to on_debug handler to indicate that the data is informational text. */ rb_define_const(mCurl, "CURLINFO_TEXT", LONG2NUM(CURLINFO_TEXT)); /* Passed to on_debug handler to indicate that the data is header (or header-like) data received from the peer. */ rb_define_const(mCurl, "CURLINFO_HEADER_IN", LONG2NUM(CURLINFO_HEADER_IN)); /* Passed to on_debug handler to indicate that the data is header (or header-like) data sent to the peer. */ rb_define_const(mCurl, "CURLINFO_HEADER_OUT", LONG2NUM(CURLINFO_HEADER_OUT)); /* Passed to on_debug handler to indicate that the data is protocol data received from the peer. */ rb_define_const(mCurl, "CURLINFO_DATA_IN", LONG2NUM(CURLINFO_DATA_IN)); /* Passed to on_debug handler to indicate that the data is protocol data sent to the peer. */ rb_define_const(mCurl, "CURLINFO_DATA_OUT", LONG2NUM(CURLINFO_DATA_OUT)); #ifdef HAVE_CURLFTPMETHOD_MULTICWD rb_define_const(mCurl, "CURL_MULTICWD", LONG2NUM(CURLFTPMETHOD_MULTICWD)); #endif #ifdef HAVE_CURLFTPMETHOD_NOCWD rb_define_const(mCurl, "CURL_NOCWD", LONG2NUM(CURLFTPMETHOD_NOCWD)); #endif #ifdef HAVE_CURLFTPMETHOD_SINGLECWD rb_define_const(mCurl, "CURL_SINGLECWD", LONG2NUM(CURLFTPMETHOD_SINGLECWD)); #endif /* When passed to Curl::Easy#proxy_type , indicates that the proxy is an HTTP proxy. (libcurl >= 7.10) */ #ifdef HAVE_CURLPROXY_HTTP rb_define_const(mCurl, "CURLPROXY_HTTP", LONG2NUM(CURLPROXY_HTTP)); #else rb_define_const(mCurl, "CURLPROXY_HTTP", LONG2NUM(-1)); #endif #ifdef CURL_VERSION_SSL rb_define_const(mCurl, "CURL_SSLVERSION_DEFAULT", LONG2NUM(CURL_SSLVERSION_DEFAULT)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_DEFAULT", LONG2NUM(CURL_SSLVERSION_MAX_DEFAULT)); rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1", LONG2NUM(CURL_SSLVERSION_TLSv1)); rb_define_const(mCurl, "CURL_SSLVERSION_SSLv2", LONG2NUM(CURL_SSLVERSION_SSLv2)); rb_define_const(mCurl, "CURL_SSLVERSION_SSLv3", LONG2NUM(CURL_SSLVERSION_SSLv3)); #ifdef HAVE_CURL_SSLVERSION_TLSV1_0 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_0", LONG2NUM(CURL_SSLVERSION_TLSv1_0)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_0", LONG2NUM(CURL_SSLVERSION_MAX_TLSv1_0)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSV1_1 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_1", LONG2NUM(CURL_SSLVERSION_TLSv1_1)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_1", LONG2NUM(CURL_SSLVERSION_MAX_TLSv1_1)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSV1_2 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_2", LONG2NUM(CURL_SSLVERSION_TLSv1_2)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_2", LONG2NUM(CURL_SSLVERSION_MAX_TLSv1_2)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSV1_3 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_3", LONG2NUM(CURL_SSLVERSION_TLSv1_3)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_3", LONG2NUM(CURL_SSLVERSION_MAX_TLSv1_3)); #endif rb_define_const(mCurl, "CURL_USESSL_CONTROL", LONG2NUM(CURB_FTPSSL_CONTROL)); rb_define_const(mCurl, "CURL_USESSL_NONE", LONG2NUM(CURB_FTPSSL_NONE)); rb_define_const(mCurl, "CURL_USESSL_TRY", LONG2NUM(CURB_FTPSSL_TRY)); rb_define_const(mCurl, "CURL_USESSL_ALL", LONG2NUM(CURB_FTPSSL_ALL)); #else rb_define_const(mCurl, "CURL_SSLVERSION_DEFAULT", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_SSLv2", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_SSLv3", LONG2NUM(-1)); #ifdef HAVE_CURL_SSLVERSION_TLSv1_0 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_0", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_0", LONG2NUM(-1)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_1 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_1", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_1", LONG2NUM(-1)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_2 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_2", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_2", LONG2NUM(-1)); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_3 rb_define_const(mCurl, "CURL_SSLVERSION_TLSv1_3", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_SSLVERSION_MAX_TLSv1_3", LONG2NUM(-1)); #endif rb_define_const(mCurl, "CURL_USESSL_CONTROL", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_USESSL_NONE", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_USESSL_TRY", LONG2NUM(-1)); rb_define_const(mCurl, "CURL_USESSL_ALL", LONG2NUM(-1)); #endif /* When passed to Curl::Easy#proxy_type , indicates that the proxy is a SOCKS4 proxy. (libcurl >= 7.15.2) */ #ifdef HAVE_CURLPROXY_SOCKS4 rb_define_const(mCurl, "CURLPROXY_SOCKS4", LONG2NUM(CURLPROXY_SOCKS4)); #else rb_define_const(mCurl, "CURLPROXY_SOCKS4", LONG2NUM(-2)); #endif /* When passed to Curl::Easy#proxy_type , indicates that the proxy is a SOCKS4A proxy. (libcurl >= 7.18.0) */ #ifdef HAVE_CURLPROXY_SOCKS4A rb_define_const(mCurl, "CURLPROXY_SOCKS4A", LONG2NUM(CURLPROXY_SOCKS4A)); #else rb_define_const(mCurl, "CURLPROXY_SOCKS4A", LONG2NUM(-2)); #endif /* When passed to Curl::Easy#proxy_type , indicates that the proxy is a SOCKS5 proxy. (libcurl >= 7.10) */ #ifdef HAVE_CURLPROXY_SOCKS5 rb_define_const(mCurl, "CURLPROXY_SOCKS5", LONG2NUM(CURLPROXY_SOCKS5)); #else rb_define_const(mCurl, "CURLPROXY_SOCKS5", LONG2NUM(-2)); #endif /* When passed to Curl::Easy#proxy_type , indicates that the proxy is a SOCKS5 proxy (and that the proxy should resolve the hostname). (libcurl >= 7.17.2) */ #ifdef HAVE_CURLPROXY_SOCKS5_HOSTNAME rb_define_const(mCurl, "CURLPROXY_SOCKS5_HOSTNAME", LONG2NUM(CURLPROXY_SOCKS5_HOSTNAME)); #else rb_define_const(mCurl, "CURLPROXY_SOCKS5_HOSTNAME", LONG2NUM(-2)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, directs libcurl to use Basic authentication. */ #ifdef HAVE_CURLAUTH_BASIC rb_define_const(mCurl, "CURLAUTH_BASIC", LONG2NUM(CURLAUTH_BASIC)); #else rb_define_const(mCurl, "CURLAUTH_BASIC", LONG2NUM(0)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, directs libcurl to use Digest authentication. */ #ifdef HAVE_CURLAUTH_DIGEST rb_define_const(mCurl, "CURLAUTH_DIGEST", LONG2NUM(CURLAUTH_DIGEST)); #else rb_define_const(mCurl, "CURLAUTH_DIGEST", LONG2NUM(0)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, directs libcurl to use GSS Negotiate authentication. Requires a suitable GSS-API library. */ #ifdef HAVE_CURLAUTH_GSSNEGOTIATE rb_define_const(mCurl, "CURLAUTH_GSSNEGOTIATE", LONG2NUM(CURLAUTH_GSSNEGOTIATE)); #else rb_define_const(mCurl, "CURLAUTH_GSSNEGOTIATE", LONG2NUM(0)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, directs libcurl to use HTTP NTLM authentication. Requires MS Windows or OpenSSL support. */ #ifdef HAVE_CURLAUTH_NTLM rb_define_const(mCurl, "CURLAUTH_NTLM", LONG2NUM(CURLAUTH_NTLM)); #else rb_define_const(mCurl, "CURLAUTH_NTLM", LONG2NUM(0)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, allows libcurl to select any suitable authentication method except basic. */ #ifdef HAVE_CURLAUTH_ANYSAFE rb_define_const(mCurl, "CURLAUTH_ANYSAFE", LONG2NUM(CURLAUTH_ANYSAFE)); #else rb_define_const(mCurl, "CURLAUTH_ANYSAFE", LONG2NUM(0)); #endif /* When passed to Curl::Easy#http_auth_types or Curl::Easy#proxy_auth_types, allows libcurl to select any suitable authentication method. */ #ifdef HAVE_CURLAUTH_ANY rb_define_const(mCurl, "CURLAUTH_ANY", LONG2NUM(CURLAUTH_ANY)); #else rb_define_const(mCurl, "CURLAUTH_ANY", LONG2NUM(0)); #endif CURB_DEFINE(CURLOPT_VERBOSE); CURB_DEFINE(CURLOPT_HEADER); CURB_DEFINE(CURLOPT_NOPROGRESS); CURB_DEFINE(CURLOPT_NOSIGNAL); #ifdef HAVE_CURLOPT_PATH_AS_IS CURB_DEFINE(CURLOPT_PATH_AS_IS); #endif CURB_DEFINE(CURLOPT_WRITEFUNCTION); CURB_DEFINE(CURLOPT_WRITEDATA); CURB_DEFINE(CURLOPT_READFUNCTION); CURB_DEFINE(CURLOPT_READDATA); /* CURLOPT_IOCTLFUNCTION/DATA deprecated since 7.18.0, use SEEKFUNCTION/DATA */ #ifdef HAVE_CURLOPT_IOCTLFUNCTION CURB_DEFINE(CURLOPT_IOCTLFUNCTION); #endif #ifdef HAVE_CURLOPT_IOCTLDATA CURB_DEFINE(CURLOPT_IOCTLDATA); #endif #ifdef HAVE_CURLOPT_SEEKFUNCTION CURB_DEFINE(CURLOPT_SEEKFUNCTION); #endif #ifdef HAVE_CURLOPT_SEEKDATA CURB_DEFINE(CURLOPT_SEEKDATA); #endif #ifdef HAVE_CURLOPT_SOCKOPTFUNCTION CURB_DEFINE(CURLOPT_SOCKOPTFUNCTION); #endif #ifdef HAVE_CURLOPT_SOCKOPTDATA CURB_DEFINE(CURLOPT_SOCKOPTDATA); #endif #ifdef HAVE_CURLOPT_OPENSOCKETFUNCTION CURB_DEFINE(CURLOPT_OPENSOCKETFUNCTION); #endif #ifdef HAVE_CURLOPT_OPENSOCKETDATA CURB_DEFINE(CURLOPT_OPENSOCKETDATA); #endif /* CURLOPT_PROGRESSFUNCTION deprecated since 7.32.0, use XFERINFOFUNCTION */ #ifdef HAVE_CURLOPT_PROGRESSFUNCTION CURB_DEFINE(CURLOPT_PROGRESSFUNCTION); #endif #ifdef HAVE_CURLOPT_PROGRESSDATA CURB_DEFINE(CURLOPT_PROGRESSDATA); #endif #ifdef HAVE_CURLOPT_XFERINFOFUNCTION CURB_DEFINE(CURLOPT_XFERINFOFUNCTION); #endif #ifdef HAVE_CURLOPT_XFERINFODATA CURB_DEFINE(CURLOPT_XFERINFODATA); #endif CURB_DEFINE(CURLOPT_HEADERFUNCTION); CURB_DEFINE(CURLOPT_WRITEHEADER); CURB_DEFINE(CURLOPT_DEBUGFUNCTION); CURB_DEFINE(CURLOPT_DEBUGDATA); CURB_DEFINE(CURLOPT_SSL_CTX_FUNCTION); CURB_DEFINE(CURLOPT_SSL_CTX_DATA); /* CURLOPT_CONV_* deprecated since 7.82.0, serve no purpose anymore */ #ifdef HAVE_CURLOPT_CONV_TO_NETWORK_FUNCTION CURB_DEFINE(CURLOPT_CONV_TO_NETWORK_FUNCTION); #endif #ifdef HAVE_CURLOPT_CONV_FROM_NETWORK_FUNCTION CURB_DEFINE(CURLOPT_CONV_FROM_NETWORK_FUNCTION); #endif #ifdef HAVE_CURLOPT_CONV_FROM_UTF8_FUNCTION CURB_DEFINE(CURLOPT_CONV_FROM_UTF8_FUNCTION); #endif #ifdef HAVE_CURLOPT_INTERLEAVEFUNCTION CURB_DEFINE(CURLOPT_INTERLEAVEFUNCTION); #endif #ifdef HAVE_CURLOPT_INTERLEAVEDATA CURB_DEFINE(CURLOPT_INTERLEAVEDATA); #endif #ifdef HAVE_CURLOPT_CHUNK_BGN_FUNCTION CURB_DEFINE(CURLOPT_CHUNK_BGN_FUNCTION); #endif #ifdef HAVE_CURLOPT_CHUNK_END_FUNCTION CURB_DEFINE(CURLOPT_CHUNK_END_FUNCTION); #endif #ifdef HAVE_CURLOPT_CHUNK_DATA CURB_DEFINE(CURLOPT_CHUNK_DATA); #endif #ifdef HAVE_CURLOPT_FNMATCH_FUNCTION CURB_DEFINE(CURLOPT_FNMATCH_FUNCTION); #endif #ifdef HAVE_CURLOPT_FNMATCH_DATA CURB_DEFINE(CURLOPT_FNMATCH_DATA); #endif #ifdef HAVE_CURLOPT_ERRORBUFFER CURB_DEFINE(CURLOPT_ERRORBUFFER); #endif #ifdef HAVE_CURLOPT_STDERR CURB_DEFINE(CURLOPT_STDERR); #endif #ifdef HAVE_CURLOPT_FAILONERROR CURB_DEFINE(CURLOPT_FAILONERROR); #endif CURB_DEFINE(CURLOPT_URL); /* CURLOPT_PROTOCOLS deprecated since 7.85.0, use CURLOPT_PROTOCOLS_STR */ #ifdef HAVE_CURLOPT_PROTOCOLS CURB_DEFINE(CURLOPT_PROTOCOLS); #endif #ifdef HAVE_CURLOPT_PROTOCOLS_STR CURB_DEFINE(CURLOPT_PROTOCOLS_STR); #endif /* CURLOPT_REDIR_PROTOCOLS deprecated since 7.85.0, use CURLOPT_REDIR_PROTOCOLS_STR */ #ifdef HAVE_CURLOPT_REDIR_PROTOCOLS CURB_DEFINE(CURLOPT_REDIR_PROTOCOLS); #endif #ifdef HAVE_CURLOPT_REDIR_PROTOCOLS_STR CURB_DEFINE(CURLOPT_REDIR_PROTOCOLS_STR); #endif CURB_DEFINE(CURLOPT_PROXY); CURB_DEFINE(CURLOPT_PROXYPORT); #ifdef HAVE_CURLOPT_PROXYTYPE CURB_DEFINE(CURLOPT_PROXYTYPE); #endif #ifdef HAVE_CURLOPT_NOPROXY CURB_DEFINE(CURLOPT_NOPROXY); #endif CURB_DEFINE(CURLOPT_HTTPPROXYTUNNEL); /* CURLOPT_SOCKS5_GSSAPI_SERVICE deprecated since 7.49.0, use CURLOPT_PROXY_SERVICE_NAME */ #ifdef HAVE_CURLOPT_SOCKS5_GSSAPI_SERVICE CURB_DEFINE(CURLOPT_SOCKS5_GSSAPI_SERVICE); #endif #ifdef HAVE_CURLOPT_PROXY_SERVICE_NAME CURB_DEFINE(CURLOPT_PROXY_SERVICE_NAME); #endif #ifdef HAVE_CURLOPT_SOCKS5_GSSAPI_NEC CURB_DEFINE(CURLOPT_SOCKS5_GSSAPI_NEC); #endif CURB_DEFINE(CURLOPT_INTERFACE); #ifdef HAVE_CURLOPT_LOCALPORT CURB_DEFINE(CURLOPT_LOCALPORT); #endif CURB_DEFINE(CURLOPT_DNS_CACHE_TIMEOUT); /* CURLOPT_DNS_USE_GLOBAL_CACHE deprecated since 7.11.1, does nothing since 7.62.0 */ #ifdef HAVE_CURLOPT_DNS_USE_GLOBAL_CACHE CURB_DEFINE(CURLOPT_DNS_USE_GLOBAL_CACHE); #endif CURB_DEFINE(CURLOPT_BUFFERSIZE); CURB_DEFINE(CURLOPT_PORT); CURB_DEFINE(CURLOPT_TCP_NODELAY); #ifdef HAVE_CURLOPT_ADDRESS_SCOPE CURB_DEFINE(CURLOPT_ADDRESS_SCOPE); #endif CURB_DEFINE(CURLOPT_NETRC); CURB_DEFINE(CURL_NETRC_OPTIONAL); CURB_DEFINE(CURL_NETRC_IGNORED); CURB_DEFINE(CURL_NETRC_REQUIRED); #ifdef HAVE_CURLOPT_NETRC_FILE CURB_DEFINE(CURLOPT_NETRC_FILE); #endif CURB_DEFINE(CURLOPT_USERPWD); CURB_DEFINE(CURLOPT_PROXYUSERPWD); #ifdef HAVE_CURLOPT_USERNAME CURB_DEFINE(CURLOPT_USERNAME); #endif #ifdef HAVE_CURLOPT_PASSWORD CURB_DEFINE(CURLOPT_PASSWORD); #endif #ifdef HAVE_CURLOPT_PROXYUSERNAME CURB_DEFINE(CURLOPT_PASSWORD); #endif #ifdef HAVE_CURLOPT_PROXYPASSWORD CURB_DEFINE(CURLOPT_PASSWORD); #endif #ifdef HAVE_CURLOPT_HTTPAUTH CURB_DEFINE(CURLOPT_HTTPAUTH); #endif #ifdef HAVE_CURLAUTH_DIGEST_IE CURB_DEFINE(CURLAUTH_DIGEST_IE); #endif #ifdef HAVE_CURLAUTH_ONLY CURB_DEFINE(CURLAUTH_ONLY); #endif #ifdef HAVE_CURLOPT_TLSAUTH_TYPE CURB_DEFINE(CURLOPT_TLSAUTH_TYPE); #endif #ifdef HAVE_CURLOPT_TLSAUTH_SRP CURB_DEFINE(CURLOPT_TLSAUTH_SRP); #endif #ifdef HAVE_CURLOPT_TLSAUTH_USERNAME CURB_DEFINE(CURLOPT_TLSAUTH_USERNAME); #endif #ifdef HAVE_CURLOPT_TLSAUTH_PASSWORD CURB_DEFINE(CURLOPT_TLSAUTH_PASSWORD); #endif #ifdef HAVE_CURLOPT_PROXYAUTH CURB_DEFINE(CURLOPT_PROXYAUTH); #endif #ifdef HAVE_CURLOPT_AUTOREFERER CURB_DEFINE(CURLOPT_AUTOREFERER); #endif #ifdef HAVE_CURLOPT_ENCODING CURB_DEFINE(CURLOPT_ENCODING); #endif #ifdef HAVE_CURLOPT_FOLLOWLOCATION CURB_DEFINE(CURLOPT_FOLLOWLOCATION); #endif #ifdef HAVE_CURLOPT_UNRESTRICTED_AUTH CURB_DEFINE(CURLOPT_UNRESTRICTED_AUTH); #endif #ifdef HAVE_CURLOPT_MAXREDIRS CURB_DEFINE(CURLOPT_MAXREDIRS); #endif #ifdef HAVE_CURLOPT_POSTREDIR CURB_DEFINE(CURLOPT_POSTREDIR); #endif /* CURLOPT_PUT deprecated since 7.12.1, use CURLOPT_UPLOAD */ #ifdef HAVE_CURLOPT_PUT CURB_DEFINE(CURLOPT_PUT); #endif #ifdef HAVE_CURLOPT_POST CURB_DEFINE(CURLOPT_POST); #endif CURB_DEFINE(CURLOPT_POSTFIELDS); CURB_DEFINE(CURLOPT_POSTFIELDSIZE); #ifdef HAVE_CURLOPT_POSTFIELDSIZE_LARGE CURB_DEFINE(CURLOPT_POSTFIELDSIZE_LARGE); #endif #ifdef HAVE_CURLOPT_COPYPOSTFIELDS CURB_DEFINE(CURLOPT_COPYPOSTFIELDS); #endif /* CURLOPT_HTTPPOST deprecated since 7.56.0, use CURLOPT_MIMEPOST */ #ifdef HAVE_CURLOPT_HTTPPOST CURB_DEFINE(CURLOPT_HTTPPOST); #endif #ifdef HAVE_CURLOPT_MIMEPOST CURB_DEFINE(CURLOPT_MIMEPOST); #endif CURB_DEFINE(CURLOPT_REFERER); CURB_DEFINE(CURLOPT_USERAGENT); CURB_DEFINE(CURLOPT_HTTPHEADER); #ifdef HAVE_CURLOPT_PROXYHEADER CURB_DEFINE(CURLOPT_PROXYHEADER); #endif #ifdef HAVE_CURLOPT_REQUEST_TARGET /* Allows overriding the Request-URI target used in the request line. * Useful for absolute-form requests or special targets like "*". */ CURB_DEFINE(CURLOPT_REQUEST_TARGET); #endif #ifdef HAVE_CURLOPT_HTTP200ALIASES CURB_DEFINE(CURLOPT_HTTP200ALIASES); #endif CURB_DEFINE(CURLOPT_COOKIE); CURB_DEFINE(CURLOPT_COOKIEFILE); CURB_DEFINE(CURLOPT_COOKIEJAR); #ifdef HAVE_CURLOPT_COOKIESESSION CURB_DEFINE(CURLOPT_COOKIESESSION); #endif #ifdef HAVE_CURLOPT_COOKIELIST CURB_DEFINE(CURLOPT_COOKIELIST); #endif #ifdef HAVE_CURLOPT_HTTPGET CURB_DEFINE(CURLOPT_HTTPGET); #endif CURB_DEFINE(CURLOPT_HTTP_VERSION); CURB_DEFINE(CURL_HTTP_VERSION_NONE); CURB_DEFINE(CURL_HTTP_VERSION_1_0); CURB_DEFINE(CURL_HTTP_VERSION_1_1); #if LIBCURL_VERSION_NUM >= 0x072100 /* 7.33.0 */ CURB_DEFINE(CURL_HTTP_VERSION_2_0); #endif #if LIBCURL_VERSION_NUM >= 0x072f00 /* 7.47.0 */ CURB_DEFINE(CURL_HTTP_VERSION_2TLS); #endif #ifdef HAVE_CURLOPT_IGNORE_CONTENT_LENGTH CURB_DEFINE(CURLOPT_IGNORE_CONTENT_LENGTH); #endif #ifdef HAVE_CURLOPT_HTTP_CONTENT_DECODING CURB_DEFINE(CURLOPT_HTTP_CONTENT_DECODING); #endif #ifdef HAVE_CURLOPT_HTTP_TRANSFER_DECODING CURB_DEFINE(CURLOPT_HTTP_TRANSFER_DECODING); #endif #ifdef HAVE_CURLOPT_MAIL_FROM CURB_DEFINE(CURLOPT_MAIL_FROM); #endif #ifdef HAVE_CURLOPT_MAIL_RCPT CURB_DEFINE(CURLOPT_MAIL_RCPT); #endif #ifdef HAVE_CURLOPT_TFTP_BLKSIZE CURB_DEFINE(CURLOPT_TFTP_BLKSIZE); #endif #ifdef HAVE_CURLOPT_FTPPORT CURB_DEFINE(CURLOPT_FTPPORT); #endif #ifdef HAVE_CURLOPT_QUOTE CURB_DEFINE(CURLOPT_QUOTE); #endif #ifdef HAVE_CURLOPT_POSTQUOTE CURB_DEFINE(CURLOPT_POSTQUOTE); #endif #ifdef HAVE_CURLOPT_PREQUOTE CURB_DEFINE(CURLOPT_PREQUOTE); #endif #ifdef HAVE_CURLOPT_DIRLISTONLY CURB_DEFINE(CURLOPT_DIRLISTONLY); #endif #ifdef HAVE_CURLOPT_APPEND CURB_DEFINE(CURLOPT_APPEND); #endif #ifdef HAVE_CURLOPT_FTP_USE_EPRT CURB_DEFINE(CURLOPT_FTP_USE_EPRT); #endif #ifdef HAVE_CURLOPT_FTP_USE_EPSV CURB_DEFINE(CURLOPT_FTP_USE_EPSV); #endif #ifdef HAVE_CURLOPT_FTP_USE_PRET CURB_DEFINE(CURLOPT_FTP_USE_PRET); #endif #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS CURB_DEFINE(CURLOPT_FTP_CREATE_MISSING_DIRS); #endif #ifdef HAVE_CURLOPT_FTP_RESPONSE_TIMEOUT CURB_DEFINE(CURLOPT_FTP_RESPONSE_TIMEOUT); #endif #ifdef HAVE_CURLOPT_FTP_ALTERNATIVE_TO_USER CURB_DEFINE(CURLOPT_FTP_ALTERNATIVE_TO_USER); #endif #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP CURB_DEFINE(CURLOPT_FTP_SKIP_PASV_IP); #endif #ifdef HAVE_CURLOPT_FTPSSLAUTH CURB_DEFINE(CURLOPT_FTPSSLAUTH); #endif #ifdef HAVE_CURLFTPAUTH_DEFAULT CURB_DEFINE(CURLFTPAUTH_DEFAULT); #endif #ifdef HAVE_CURLFTPAUTH_SSL CURB_DEFINE(CURLFTPAUTH_SSL); #endif #ifdef HAVE_CURLFTPAUTH_TLS CURB_DEFINE(CURLFTPAUTH_TLS); #endif #ifdef HAVE_CURLOPT_FTP_SSL_CCC CURB_DEFINE(CURLOPT_FTP_SSL_CCC); #endif #ifdef HAVE_CURLFTPSSL_CCC_NONE CURB_DEFINE(CURLFTPSSL_CCC_NONE); #endif #ifdef HAVE_CURLFTPSSL_CCC_PASSIVE CURB_DEFINE(CURLFTPSSL_CCC_PASSIVE); #endif #ifdef HAVE_CURLFTPSSL_CCC_ACTIVE CURB_DEFINE(CURLFTPSSL_CCC_ACTIVE); #endif #ifdef HAVE_CURLOPT_FTP_ACCOUNT CURB_DEFINE(CURLOPT_FTP_ACCOUNT); #endif #ifdef HAVE_CURLOPT_FTP_FILEMETHOD CURB_DEFINE(CURLOPT_FTP_FILEMETHOD); #endif #ifdef HAVE_CURLFTPMETHOD_MULTICWD CURB_DEFINE(CURLFTPMETHOD_MULTICWD); #endif #ifdef HAVE_CURLFTPMETHOD_NOCWD CURB_DEFINE(CURLFTPMETHOD_NOCWD); #endif #ifdef HAVE_CURLFTPMETHOD_SINGLECWD CURB_DEFINE(CURLFTPMETHOD_SINGLECWD); #endif #ifdef HAVE_CURLOPT_RTSP_REQUEST CURB_DEFINE(CURLOPT_RTSP_REQUEST); #endif #ifdef HAVE_CURL_RTSPREQ_OPTIONS CURB_DEFINE(CURL_RTSPREQ_OPTIONS); #endif #ifdef HAVE_CURL_RTSPREQ_DESCRIBE CURB_DEFINE(CURL_RTSPREQ_DESCRIBE); #endif #ifdef HAVE_CURL_RTSPREQ_ANNOUNCE CURB_DEFINE(CURL_RTSPREQ_ANNOUNCE); #endif #ifdef HAVE_CURL_RTSPREQ_SETUP CURB_DEFINE(CURL_RTSPREQ_SETUP); #endif #ifdef HAVE_CURL_RTSPREQ_PLAY CURB_DEFINE(CURL_RTSPREQ_PLAY); #endif #ifdef HAVE_CURL_RTSPREQ_PAUSE CURB_DEFINE(CURL_RTSPREQ_PAUSE); #endif #ifdef HAVE_CURL_RTSPREQ_TEARDOWN CURB_DEFINE(CURL_RTSPREQ_TEARDOWN); #endif #ifdef HAVE_CURL_RTSPREQ_GET_PARAMETER CURB_DEFINE(CURL_RTSPREQ_GET_PARAMETER); #endif #ifdef HAVE_CURL_RTSPREQ_SET_PARAMETER CURB_DEFINE(CURL_RTSPREQ_SET_PARAMETER); #endif #ifdef HAVE_CURL_RTSPREQ_RECORD CURB_DEFINE(CURL_RTSPREQ_RECORD); #endif #ifdef HAVE_CURL_RTSPREQ_RECEIVE CURB_DEFINE(CURL_RTSPREQ_RECEIVE); #endif #ifdef HAVE_CURLOPT_RTSP_SESSION_ID CURB_DEFINE(CURLOPT_RTSP_SESSION_ID); #endif #ifdef HAVE_CURLOPT_RTSP_STREAM_URI CURB_DEFINE(CURLOPT_RTSP_STREAM_URI); #endif #ifdef HAVE_CURLOPT_RTSP_TRANSPORT CURB_DEFINE(CURLOPT_RTSP_TRANSPORT); #endif #ifdef HAVE_CURLOPT_RTSP_HEADER CURB_DEFINE(CURLOPT_RTSP_HEADER); #endif #ifdef HAVE_CURLOPT_RTSP_CLIENT_CSEQ CURB_DEFINE(CURLOPT_RTSP_CLIENT_CSEQ); #endif #ifdef HAVE_CURLOPT_RTSP_SERVER_CSEQ CURB_DEFINE(CURLOPT_RTSP_SERVER_CSEQ); #endif CURB_DEFINE(CURLOPT_TRANSFERTEXT); #ifdef HAVE_CURLOPT_PROXY_TRANSFER_MODE CURB_DEFINE(CURLOPT_PROXY_TRANSFER_MODE); #endif #ifdef HAVE_CURLOPT_CRLF CURB_DEFINE(CURLOPT_CRLF); #endif #ifdef HAVE_CURLOPT_RANGE CURB_DEFINE(CURLOPT_RANGE); #endif #ifdef HAVE_CURLOPT_RESUME_FROM CURB_DEFINE(CURLOPT_RESUME_FROM); #endif #ifdef HAVE_CURLOPT_RESUME_FROM_LARGE CURB_DEFINE(CURLOPT_RESUME_FROM_LARGE); #endif #ifdef HAVE_CURLOPT_CUSTOMREQUEST CURB_DEFINE(CURLOPT_CUSTOMREQUEST); #endif #ifdef HAVE_CURLOPT_FILETIME CURB_DEFINE(CURLOPT_FILETIME); #endif #ifdef HAVE_CURLOPT_NOBODY CURB_DEFINE(CURLOPT_NOBODY); #endif #ifdef HAVE_CURLOPT_INFILESIZE CURB_DEFINE(CURLOPT_INFILESIZE); #endif #ifdef HAVE_CURLOPT_INFILESIZE_LARGE CURB_DEFINE(CURLOPT_INFILESIZE_LARGE); #endif #ifdef HAVE_CURLOPT_UPLOAD CURB_DEFINE(CURLOPT_UPLOAD); #endif #ifdef HAVE_CURLOPT_MAXFILESIZE CURB_DEFINE(CURLOPT_MAXFILESIZE); #endif #ifdef HAVE_CURLOPT_MAXFILESIZE_LARGE CURB_DEFINE(CURLOPT_MAXFILESIZE_LARGE); #endif #ifdef HAVE_CURLOPT_TIMECONDITION CURB_DEFINE(CURLOPT_TIMECONDITION); #endif #ifdef HAVE_CURLOPT_TIMEVALUE CURB_DEFINE(CURLOPT_TIMEVALUE); #endif #ifdef HAVE_CURLOPT_TIMEOUT CURB_DEFINE(CURLOPT_TIMEOUT); #endif #ifdef HAVE_CURLOPT_TIMEOUT_MS CURB_DEFINE(CURLOPT_TIMEOUT_MS); #endif #ifdef HAVE_CURLOPT_LOW_SPEED_LIMIT CURB_DEFINE(CURLOPT_LOW_SPEED_LIMIT); #endif #ifdef HAVE_CURLOPT_LOW_SPEED_TIME CURB_DEFINE(CURLOPT_LOW_SPEED_TIME); #endif #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE CURB_DEFINE(CURLOPT_MAX_SEND_SPEED_LARGE); #endif #ifdef HAVE_CURLOPT_MAX_RECV_SPEED_LARGE CURB_DEFINE(CURLOPT_MAX_RECV_SPEED_LARGE); #endif #ifdef HAVE_CURLOPT_MAXCONNECTS CURB_DEFINE(CURLOPT_MAXCONNECTS); #endif #ifdef HAVE_CURLOPT_CLOSEPOLICY CURB_DEFINE(CURLOPT_CLOSEPOLICY); #endif #ifdef HAVE_CURLOPT_FRESH_CONNECT CURB_DEFINE(CURLOPT_FRESH_CONNECT); #endif #ifdef HAVE_CURLOPT_FORBID_REUSE CURB_DEFINE(CURLOPT_FORBID_REUSE); #endif #ifdef HAVE_CURLOPT_CONNECTTIMEOUT CURB_DEFINE(CURLOPT_CONNECTTIMEOUT); #endif #ifdef HAVE_CURLOPT_CONNECTTIMEOUT_MS CURB_DEFINE(CURLOPT_CONNECTTIMEOUT_MS); #endif #ifdef HAVE_CURLOPT_IPRESOLVE CURB_DEFINE(CURLOPT_IPRESOLVE); #endif #ifdef HAVE_CURL_IPRESOLVE_WHATEVER CURB_DEFINE(CURL_IPRESOLVE_WHATEVER); #endif #ifdef HAVE_CURL_IPRESOLVE_V4 CURB_DEFINE(CURL_IPRESOLVE_V4); #endif #ifdef HAVE_CURL_IPRESOLVE_V6 CURB_DEFINE(CURL_IPRESOLVE_V6); #endif #ifdef HAVE_CURLOPT_CONNECT_ONLY CURB_DEFINE(CURLOPT_CONNECT_ONLY); #endif #ifdef HAVE_CURLOPT_USE_SSL CURB_DEFINE(CURLOPT_USE_SSL); #endif #ifdef HAVE_CURLUSESSL_NONE CURB_DEFINE(CURLUSESSL_NONE); #endif #ifdef HAVE_CURLUSESSL_TRY CURB_DEFINE(CURLUSESSL_TRY); #endif #ifdef HAVE_CURLUSESSL_CONTROL CURB_DEFINE(CURLUSESSL_CONTROL); #endif #ifdef HAVE_CURLUSESSL_ALL CURB_DEFINE(CURLUSESSL_ALL); #endif #ifdef HAVE_CURLOPT_RESOLVE CURB_DEFINE(CURLOPT_RESOLVE); #endif #ifdef HAVE_CURLOPT_SSLCERT CURB_DEFINE(CURLOPT_SSLCERT); #endif #ifdef HAVE_CURLOPT_SSLCERTTYPE CURB_DEFINE(CURLOPT_SSLCERTTYPE); #endif #ifdef HAVE_CURLOPT_SSLKEY CURB_DEFINE(CURLOPT_SSLKEY); #endif #ifdef HAVE_CURLOPT_SSLKEYTYPE CURB_DEFINE(CURLOPT_SSLKEYTYPE); #endif #ifdef HAVE_CURLOPT_KEYPASSWD CURB_DEFINE(CURLOPT_KEYPASSWD); #endif #ifdef HAVE_CURLOPT_SSLENGINE CURB_DEFINE(CURLOPT_SSLENGINE); #endif #ifdef HAVE_CURLOPT_SSLENGINE_DEFAULT CURB_DEFINE(CURLOPT_SSLENGINE_DEFAULT); #endif #ifdef HAVE_CURLOPT_SSLVERSION CURB_DEFINE(CURLOPT_SSLVERSION); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1 CURB_DEFINE(CURL_SSLVERSION_TLSv1); #endif #ifdef HAVE_CURL_SSLVERSION_SSLv2 CURB_DEFINE(CURL_SSLVERSION_SSLv2); #endif #ifdef HAVE_CURL_SSLVERSION_SSLv3 CURB_DEFINE(CURL_SSLVERSION_SSLv3); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_0 CURB_DEFINE(CURL_SSLVERSION_TLSv1_0); CURB_DEFINE(CURL_SSLVERSION_MAX_TLSv1_0); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_1 CURB_DEFINE(CURL_SSLVERSION_TLSv1_1); CURB_DEFINE(CURL_SSLVERSION_MAX_TLSv1_1); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_2 CURB_DEFINE(CURL_SSLVERSION_TLSv1_2); CURB_DEFINE(CURL_SSLVERSION_MAX_TLSv1_2); #endif #ifdef HAVE_CURL_SSLVERSION_TLSv1_3 CURB_DEFINE(CURL_SSLVERSION_TLSv1_3); CURB_DEFINE(CURL_SSLVERSION_MAX_TLSv1_3); #endif #ifdef HAVE_CURLOPT_SSL_VERIFYPEER CURB_DEFINE(CURLOPT_SSL_VERIFYPEER); #endif #ifdef HAVE_CURLOPT_CAINFO CURB_DEFINE(CURLOPT_CAINFO); #endif #ifdef HAVE_CURLOPT_ISSUERCERT CURB_DEFINE(CURLOPT_ISSUERCERT); #endif #ifdef HAVE_CURLOPT_CAPATH CURB_DEFINE(CURLOPT_CAPATH); #endif #ifdef HAVE_CURLOPT_CRLFILE CURB_DEFINE(CURLOPT_CRLFILE); #endif #ifdef HAVE_CURLOPT_SSL_VERIFYHOST CURB_DEFINE(CURLOPT_SSL_VERIFYHOST); #endif #ifdef HAVE_CURLOPT_CERTINFO CURB_DEFINE(CURLOPT_CERTINFO); #endif /* CURLOPT_RANDOM_FILE deprecated since 7.84.0, serves no purpose */ #ifdef HAVE_CURLOPT_RANDOM_FILE CURB_DEFINE(CURLOPT_RANDOM_FILE); #endif /* CURLOPT_EGDSOCKET deprecated since 7.84.0, serves no purpose */ #ifdef HAVE_CURLOPT_EGDSOCKET CURB_DEFINE(CURLOPT_EGDSOCKET); #endif #ifdef HAVE_CURLOPT_SSL_CIPHER_LIST CURB_DEFINE(CURLOPT_SSL_CIPHER_LIST); #endif #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE CURB_DEFINE(CURLOPT_SSL_SESSIONID_CACHE); #endif #ifdef HAVE_CURLOPT_KRBLEVEL CURB_DEFINE(CURLOPT_KRBLEVEL); #endif #ifdef HAVE_CURLOPT_SSH_AUTH_TYPES CURB_DEFINE(CURLOPT_SSH_AUTH_TYPES); #endif #ifdef HAVE_CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 CURB_DEFINE(CURLOPT_SSH_HOST_PUBLIC_KEY_MD5); #endif #ifdef HAVE_CURLOPT_SSH_PUBLIC_KEYFILE CURB_DEFINE(CURLOPT_SSH_PUBLIC_KEYFILE); #endif #ifdef HAVE_CURLOPT_SSH_PRIVATE_KEYFILE CURB_DEFINE(CURLOPT_SSH_PRIVATE_KEYFILE); #endif #ifdef HAVE_CURLOPT_SSH_KNOWNHOSTS CURB_DEFINE(CURLOPT_SSH_KNOWNHOSTS); #endif #ifdef HAVE_CURLOPT_SSH_KEYFUNCTION CURB_DEFINE(CURLOPT_SSH_KEYFUNCTION); #endif #ifdef HAVE_CURLKHSTAT_FINE_ADD_TO_FILE CURB_DEFINE(CURLKHSTAT_FINE_ADD_TO_FILE); #endif #ifdef HAVE_CURLKHSTAT_FINE CURB_DEFINE(CURLKHSTAT_FINE); #endif #ifdef HAVE_CURLKHSTAT_REJECT CURB_DEFINE(CURLKHSTAT_REJECT); #endif #ifdef HAVE_CURLKHSTAT_DEFER CURB_DEFINE(CURLKHSTAT_DEFER); #endif #ifdef HAVE_CURLOPT_SSH_KEYDATA CURB_DEFINE(CURLOPT_SSH_KEYDATA); #endif #ifdef HAVE_CURLOPT_PRIVATE CURB_DEFINE(CURLOPT_PRIVATE); #endif #ifdef HAVE_CURLOPT_SHARE CURB_DEFINE(CURLOPT_SHARE); #endif #ifdef HAVE_CURLOPT_NEW_FILE_PERMS CURB_DEFINE(CURLOPT_NEW_FILE_PERMS); #endif #ifdef HAVE_CURLOPT_NEW_DIRECTORY_PERMS CURB_DEFINE(CURLOPT_NEW_DIRECTORY_PERMS); #endif #ifdef HAVE_CURLOPT_TELNETOPTIONS CURB_DEFINE(CURLOPT_TELNETOPTIONS); #endif #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION CURB_DEFINE(CURLOPT_GSSAPI_DELEGATION); #endif #ifdef HAVE_CURLGSSAPI_DELEGATION_FLAG CURB_DEFINE(CURLGSSAPI_DELEGATION_FLAG); #endif #ifdef HAVE_CURLGSSAPI_DELEGATION_POLICY_FLAG CURB_DEFINE(CURLGSSAPI_DELEGATION_POLICY_FLAG); #endif #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH CURB_DEFINE(CURLOPT_UNIX_SOCKET_PATH); #endif #ifdef HAVE_CURLOPT_PIPEWAIT CURB_DEFINE(CURLOPT_PIPEWAIT); #endif #ifdef HAVE_CURLOPT_TCP_KEEPALIVE CURB_DEFINE(CURLOPT_TCP_KEEPALIVE); CURB_DEFINE(CURLOPT_TCP_KEEPIDLE); CURB_DEFINE(CURLOPT_TCP_KEEPINTVL); #endif #ifdef HAVE_CURLOPT_HAPROXYPROTOCOL CURB_DEFINE(CURLOPT_HAPROXYPROTOCOL); #endif #ifdef HAVE_CURLOPT_PROXY_SSL_VERIFYHOST CURB_DEFINE(CURLOPT_PROXY_SSL_VERIFYHOST); #endif #ifdef HAVE_CURLPROTO_RTMPTE CURB_DEFINE(CURLPROTO_RTMPTE); #endif #ifdef HAVE_CURLPROTO_RTMPTS CURB_DEFINE(CURLPROTO_RTMPTS); #endif #ifdef HAVE_CURLPROTO_SMBS CURB_DEFINE(CURLPROTO_SMBS); #endif #ifdef HAVE_CURLPROTO_LDAP CURB_DEFINE(CURLPROTO_LDAP); #endif #ifdef HAVE_CURLPROTO_FTP CURB_DEFINE(CURLPROTO_FTP); #endif #ifdef HAVE_CURLPROTO_SMTPS CURB_DEFINE(CURLPROTO_SMTPS); #endif #ifdef HAVE_CURLPROTO_HTTP CURB_DEFINE(CURLPROTO_HTTP); #endif #ifdef HAVE_CURLPROTO_SMTP CURB_DEFINE(CURLPROTO_SMTP); #endif #ifdef HAVE_CURLPROTO_TFTP CURB_DEFINE(CURLPROTO_TFTP); #endif #ifdef HAVE_CURLPROTO_LDAPS CURB_DEFINE(CURLPROTO_LDAPS); #endif #ifdef HAVE_CURLPROTO_IMAPS CURB_DEFINE(CURLPROTO_IMAPS); #endif #ifdef HAVE_CURLPROTO_SCP CURB_DEFINE(CURLPROTO_SCP); #endif #ifdef HAVE_CURLPROTO_SFTP CURB_DEFINE(CURLPROTO_SFTP); #endif #ifdef HAVE_CURLPROTO_TELNET CURB_DEFINE(CURLPROTO_TELNET); #endif #ifdef HAVE_CURLPROTO_FILE CURB_DEFINE(CURLPROTO_FILE); #endif #ifdef HAVE_CURLPROTO_FTPS CURB_DEFINE(CURLPROTO_FTPS); #endif #ifdef HAVE_CURLPROTO_HTTPS CURB_DEFINE(CURLPROTO_HTTPS); #endif #ifdef HAVE_CURLPROTO_IMAP CURB_DEFINE(CURLPROTO_IMAP); #endif #ifdef HAVE_CURLPROTO_POP3 CURB_DEFINE(CURLPROTO_POP3); #endif #ifdef HAVE_CURLPROTO_GOPHER CURB_DEFINE(CURLPROTO_GOPHER); #endif #ifdef HAVE_CURLPROTO_DICT CURB_DEFINE(CURLPROTO_DICT); #endif #ifdef HAVE_CURLPROTO_SMB CURB_DEFINE(CURLPROTO_SMB); #endif #ifdef HAVE_CURLPROTO_RTMP CURB_DEFINE(CURLPROTO_RTMP); #endif #ifdef HAVE_CURLPROTO_ALL CURB_DEFINE(CURLPROTO_ALL); #endif #ifdef HAVE_CURLPROTO_RTMPE CURB_DEFINE(CURLPROTO_RTMPE); #endif #ifdef HAVE_CURLPROTO_RTMPS CURB_DEFINE(CURLPROTO_RTMPS); #endif #ifdef HAVE_CURLPROTO_RTMPT CURB_DEFINE(CURLPROTO_RTMPT); #endif #ifdef HAVE_CURLPROTO_POP3S CURB_DEFINE(CURLPROTO_POP3S); #endif #ifdef HAVE_CURLPROTO_RTSP CURB_DEFINE(CURLPROTO_RTSP); #endif #if LIBCURL_VERSION_NUM >= 0x072B00 /* 7.43.0 */ CURB_DEFINE(CURLPIPE_NOTHING); CURB_DEFINE(CURLPIPE_HTTP1); CURB_DEFINE(CURLPIPE_MULTIPLEX); rb_define_const(mCurl, "PIPE_NOTHING", LONG2NUM(CURLPIPE_NOTHING)); rb_define_const(mCurl, "PIPE_HTTP1", LONG2NUM(CURLPIPE_HTTP1)); rb_define_const(mCurl, "PIPE_MULTIPLEX", LONG2NUM(CURLPIPE_MULTIPLEX)); #endif #if LIBCURL_VERSION_NUM >= 0x072100 /* 7.33.0 */ rb_define_const(mCurl, "HTTP_2_0", LONG2NUM(CURL_HTTP_VERSION_2_0)); #endif #ifdef CURL_HTTP_VERSION_2TLS rb_define_const(mCurl, "HTTP_2TLS", LONG2NUM(CURL_HTTP_VERSION_2TLS)); #endif #ifdef CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE rb_define_const(mCurl, "HTTP_2_PRIOR_KNOWLEDGE", LONG2NUM(CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)); #endif rb_define_const(mCurl, "HTTP_1_1", LONG2NUM(CURL_HTTP_VERSION_1_1)); rb_define_const(mCurl, "HTTP_1_0", LONG2NUM(CURL_HTTP_VERSION_1_0)); rb_define_const(mCurl, "HTTP_NONE", LONG2NUM(CURL_HTTP_VERSION_NONE)); rb_define_singleton_method(mCurl, "ipv6?", ruby_curl_ipv6_q, 0); rb_define_singleton_method(mCurl, "kerberos4?", ruby_curl_kerberos4_q, 0); rb_define_singleton_method(mCurl, "ssl?", ruby_curl_ssl_q, 0); rb_define_singleton_method(mCurl, "libz?", ruby_curl_libz_q, 0); rb_define_singleton_method(mCurl, "ntlm?", ruby_curl_ntlm_q, 0); rb_define_singleton_method(mCurl, "gssnegotiate?", ruby_curl_gssnegotiate_q, 0); rb_define_singleton_method(mCurl, "debug?", ruby_curl_debug_q, 0); rb_define_singleton_method(mCurl, "asyncdns?", ruby_curl_asyncdns_q, 0); rb_define_singleton_method(mCurl, "spnego?", ruby_curl_spnego_q, 0); rb_define_singleton_method(mCurl, "largefile?", ruby_curl_largefile_q, 0); rb_define_singleton_method(mCurl, "idn?", ruby_curl_idn_q, 0); rb_define_singleton_method(mCurl, "sspi?", ruby_curl_sspi_q, 0); rb_define_singleton_method(mCurl, "conv?", ruby_curl_conv_q, 0); rb_define_singleton_method(mCurl, "http2?", ruby_curl_http2_q, 0); init_curb_errors(); init_curb_easy(); init_curb_postfield(); init_curb_multi(); init_curb_upload(); } curb-1.3.5/ext/extconf.rb0000644000004100000410000005353015203731642015307 0ustar www-datawww-datarequire 'mkmf' require 'tmpdir' begin require 'etc' rescue LoadError # Etc may not be available on all Ruby builds (very rare). Fallback later. end dir_config('curl') if find_executable('curl-config') $CFLAGS << " #{`curl-config --cflags`.strip} -g" if ENV['STATIC_BUILD'] $LIBS << " #{`curl-config --static-libs`.strip}" else $LIBS << " #{`curl-config --libs`.strip}" end ca_bundle_path=`curl-config --ca`.strip.gsub(/^"([^"]+)"$/,'\1') if !ca_bundle_path.nil? and ca_bundle_path != '' $defs.push( %{-D HAVE_CURL_CONFIG_CA} ) $defs.push( %{-D CURL_CONFIG_CA='#{ca_bundle_path.inspect}'} ) end elsif !have_library('curl') or !have_header('curl/curl.h') fail <<-EOM Can't find libcurl or curl/curl.h Make sure development libs (ie libcurl4-openssl-dev) are installed on the system. Try passing --with-curl-dir or --with-curl-lib and --with-curl-include options to extconf. EOM end # Check arch flags # TODO: detect mismatched arch types when libcurl mac ports is mixed with native mac ruby or vice versa #archs = $CFLAGS.scan(/-arch\s(.*?)\s/).first # get the first arch flag #if archs and archs.size >= 1 # # need to reduce the number of archs... # # guess the first one is correct... at least the first one is probably the ruby installed arch... # # this could lead to compiled binaries that crash at runtime... # $CFLAGS.gsub!(/-arch\s(.*?)\s/,' ') # $CFLAGS << " -arch #{archs.first}" # puts "Selected arch: #{archs.first}" #end def define(s, v = 1) $defs.push(format("-D HAVE_%s=%d", s.to_s.upcase, v)) end # Optional parallelization support for constant checks only. # Enable automatically when job hints are present (JOBS, BUNDLE_JOBS, MAKEFLAGS -jN), # or explicitly via EXTCONF_JOBS/EXTCONF_PARALLEL. def parse_jobs_from_makeflags(flags) return nil if flags.nil? || flags.empty? tokens = flags.to_s.split(/\s+/) jobs = nil tokens.each_with_index do |tok, i| case tok when /\A-j(\d+)\z/ jobs = $1.to_i when '-j' nxt = tokens[i + 1] jobs = nxt.to_i if nxt && nxt =~ /\A\d+\z/ when /\A--jobs(?:=(\d+)|\s+(\d+))\z/ jobs = ($1 || $2).to_i when /\Aj(\d+)\z/ # sometimes make condenses flags jobs = $1.to_i end break if jobs && jobs > 0 end jobs end def detect_job_hints # Priority: explicit extconf hint, then common envs used by bundler/rubygems [ ENV['EXTCONF_JOBS'], ENV['JOBS'], ENV['BUNDLE_JOBS'], parse_jobs_from_makeflags(ENV['MAKEFLAGS']) ].each do |v| n = v.to_i if v return n if n && n > 0 end nil end explicit_parallel = ENV.key?('EXTCONF_PARALLEL') && ENV['EXTCONF_PARALLEL'] == '1' job_hints = detect_job_hints DEFAULT_PARALLEL_JOBS = begin n = Etc.respond_to?(:nprocessors) ? Etc.nprocessors.to_i : 1 n = 1 if n <= 0 # Use half the CPUs by default (rounded up), but at least 1 jobs = [(n.to_f / 2).ceil, 1].max jobs rescue 1 end PARALLEL_JOBS = begin if job_hints && job_hints > 0 job_hints else # If no hints provided, default to half the CPUs. DEFAULT_PARALLEL_JOBS end end # Only enable if the platform supports fork. On Windows this stays sequential. PARALLEL_CONSTANT_CHECKS = PARALLEL_JOBS > 1 && Process.respond_to?(:fork) $queued_constants = [] def try_constant_compile(sname) src = %{ #include int main() { int test = (int)#{sname}; (void)test; return 0; } } try_compile(src, "#{$CFLAGS} #{$LIBS}") end def have_constant(name) # Queue constants for optional parallel probing. Falls back to sequential. if PARALLEL_CONSTANT_CHECKS $queued_constants << name true else sname = name.is_a?(Symbol) ? name.to_s : name.upcase checking_for name do if try_constant_compile(sname) define name true else false end end end end def flush_constant_checks return if $queued_constants.empty? constants = $queued_constants.uniq $queued_constants.clear # On platforms without fork (e.g., Windows), run sequentially. unless PARALLEL_CONSTANT_CHECKS constants.each { |name| have_constant(name) } return end # Run constant probes in isolated child processes to avoid mkmf # scratch file and logfile contention. results = {} pending = constants.dup running = {} max_jobs = PARALLEL_JOBS spawn_probe = lambda do |const_name| r, w = IO.pipe pid = fork do begin r.close Dir.mktmpdir('curb-mkmf-') do |dir| Dir.chdir(dir) do begin # Avoid clobbering the main mkmf.log Logging::logfile = File.open(File::NULL, 'w') rescue File.open(File.join(dir, 'mkmf.log'), 'w') rescue # best-effort end sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase ok = try_constant_compile(sname) w.write([const_name, ok ? 1 : 0].join("\t")) end end rescue # Treat as failure if anything unexpected happens in child begin w.write([const_name, 0].join("\t")) rescue end ensure begin w.close rescue nil end # Ensure the child exits without running at_exit handlers exit! 0 end end w.close running[pid] = r end # Start initial batch while running.size < max_jobs && !pending.empty? spawn_probe.call(pending.shift) end # Collect results and keep spawning until done until running.empty? pid = Process.wait io = running.delete(pid) if io begin msg = io.read.to_s name_str, ok_str = msg.split("\t", 2) if name_str # Map back to the original object if symbol-like original = constants.find { |n| n.to_s == name_str } results[original || name_str] = ok_str.to_i == 1 end ensure begin io.close rescue nil end end end # Fill next task slot spawn_probe.call(pending.shift) unless pending.empty? end # Apply results to $defs and output summary via checking_for results.each do |const_name, ok| sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase checking_for const_name do if ok define const_name true else false end end end end have_constant "curlopt_tcp_keepalive" have_constant "curlopt_tcp_keepidle" have_constant "curlopt_tcp_keepintvl" have_constant "curlinfo_appconnect_time" have_constant "curlinfo_redirect_time" have_constant "curlinfo_response_code" have_constant "curlinfo_filetime" have_constant "curlinfo_redirect_count" have_constant "curlinfo_os_errno" have_constant "curlinfo_num_connects" have_constant "curlinfo_cookielist" have_constant "curlinfo_ftp_entry_path" have_constant "curl_version_ssl" have_constant "curl_version_libz" have_constant "curl_version_ntlm" have_constant "curl_version_gssnegotiate" have_constant "curl_version_debug" have_constant "curl_version_asynchdns" have_constant "curl_version_spnego" have_constant "curl_version_largefile" have_constant "curl_version_idn" have_constant "curl_version_sspi" have_constant "curl_version_conv" have_constant "curl_version_http2" have_constant "curlproxy_http" have_constant "curlproxy_socks4" have_constant "curlproxy_socks4a" have_constant "curlproxy_socks5" have_constant "curlproxy_socks5_hostname" have_constant "curlauth_basic" have_constant "curlauth_digest" have_constant "curlauth_gssnegotiate" have_constant "curlauth_ntlm" have_constant "curlauth_anysafe" have_constant "curlauth_any" have_constant "curle_tftp_notfound" have_constant "curle_tftp_perm" have_constant "curle_tftp_diskfull" have_constant "curle_tftp_illegal" have_constant "curle_tftp_unknownid" have_constant "curle_tftp_exists" have_constant "curle_tftp_nosuchuser" # older versions of libcurl 7.12 have_constant "curle_send_fail_rewind" have_constant "curle_ssl_engine_initfailed" have_constant "curle_login_denied" # older than 7.10.0 have_constant "curlopt_nosignal" # older than 7.16.0 have_constant "curlmopt_pipelining" # older than 7.16.3 have_constant "curlmopt_maxconnects" have_constant "curlopt_seekfunction" have_constant "curlopt_seekdata" have_constant "curlopt_sockoptfunction" have_constant "curlopt_sockoptdata" have_constant "curlopt_opensocketfunction" have_constant "curlopt_opensocketdata" # Deprecated constants (still check for them for backward compat) have_constant "curlopt_ioctlfunction" have_constant "curlopt_ioctldata" have_constant "curlopt_progressfunction" have_constant "curlopt_progressdata" have_constant "curlopt_conv_to_network_function" have_constant "curlopt_conv_from_network_function" have_constant "curlopt_conv_from_utf8_function" # Replacements for deprecated constants # CURLOPT_PROGRESSFUNCTION -> CURLOPT_XFERINFOFUNCTION (since 7.32.0) have_constant "curlopt_xferinfofunction" have_constant "curlopt_xferinfodata" # CURLOPT_PROTOCOLS -> CURLOPT_PROTOCOLS_STR (since 7.85.0) have_constant "curlopt_protocols_str" have_constant "curlopt_redir_protocols_str" # CURLOPT_SOCKS5_GSSAPI_SERVICE -> CURLOPT_PROXY_SERVICE_NAME (since 7.49.0) have_constant "curlopt_proxy_service_name" # CURLOPT_HTTPPOST -> CURLOPT_MIMEPOST (since 7.56.0) have_constant "curlopt_mimepost" # CURLINFO_* -> CURLINFO_*_T (since 7.55.0) have_constant "curlinfo_size_upload_t" have_constant "curlinfo_size_download_t" have_constant "curlinfo_speed_upload_t" have_constant "curlinfo_speed_download_t" have_constant "curlinfo_content_length_download_t" have_constant "curlinfo_content_length_upload_t" # additional consts have_constant "curle_conv_failed" have_constant "curle_conv_reqd" have_constant "curle_ssl_cacert_badfile" have_constant "curle_remote_file_not_found" have_constant "curle_ssh" have_constant "curle_ssl_shutdown_failed" have_constant "curle_again" have_constant "curle_ssl_crl_badfile" have_constant "curle_ssl_issuer_error" # added in 7.18.2 have_constant "curlinfo_redirect_url" # username/password added in 7.19.1 have_constant "curlopt_username" have_constant "curlopt_password" have_constant "curlinfo_primary_ip" # ie quirk added in 7.19.3 have_constant "curlauth_digest_ie" # added in 7.15.1 have_constant "curlftpmethod_multicwd" have_constant "curlftpmethod_nocwd" have_constant "curlftpmethod_singlecwd" # centos 4.5 build of libcurl have_constant "curlm_bad_socket" have_constant "curlm_unknown_option" have_func("curl_multi_timeout") have_func("curl_multi_fdset") have_func("curl_multi_perform") have_constant "curlopt_haproxyprotocol" # constants have_constant "curlopt_interleavefunction" have_constant "curlopt_interleavedata" have_constant "curlopt_chunk_bgn_function" have_constant "curlopt_chunk_end_function" have_constant "curlopt_chunk_data" have_constant "curlopt_fnmatch_function" have_constant "curlopt_fnmatch_data" have_constant "curlopt_errorbuffer" have_constant "curlopt_stderr" have_constant "curlopt_failonerror" have_constant "curlopt_url" have_constant "curlopt_protocols" have_constant "curlopt_redir_protocols" have_constant "curlopt_proxy" have_constant "curlopt_proxyport" have_constant "curlopt_proxytype" have_constant "curlopt_noproxy" have_constant "curlopt_httpproxytunnel" have_constant "curlopt_socks5_gssapi_service" have_constant "curlopt_socks5_gssapi_nec" have_constant "curlopt_interface" have_constant "curlopt_localport" have_constant "curlopt_dns_cache_timeout" have_constant "curlopt_dns_use_global_cache" have_constant "curlopt_buffersize" have_constant "curlopt_port" have_constant "curlopt_tcp_nodelay" have_constant "curlopt_address_scope" have_constant "curlopt_netrc" have_constant "curl_netrc_optional" have_constant "curl_netrc_ignored" have_constant "curl_netrc_required" have_constant "curlopt_netrc_file" have_constant "curlopt_userpwd" have_constant "curlopt_proxyuserpwd" have_constant "curlopt_username" have_constant "curlopt_password" have_constant "curlopt_password" have_constant "curlopt_password" have_constant "curlopt_httpauth" have_constant "curlauth_digest_ie" have_constant "curlauth_only" have_constant "curlopt_tlsauth_type" have_constant "curlopt_tlsauth_srp" have_constant "curlopt_tlsauth_username" have_constant "curlopt_tlsauth_password" have_constant "curlopt_proxyauth" have_constant "curlopt_autoreferer" have_constant "curlopt_encoding" have_constant "curlopt_followlocation" have_constant "curlopt_unrestricted_auth" have_constant "curlopt_maxredirs" have_constant "curlopt_postredir" have_constant "curlopt_put" have_constant "curlopt_post" have_constant "curlopt_postfields" have_constant "curlopt_postfieldsize" have_constant "curlopt_postfieldsize_large" have_constant "curlopt_copypostfields" have_constant "curlopt_httppost" have_constant "curlopt_referer" have_constant "curlopt_useragent" have_constant "curlopt_httpheader" have_constant "curlopt_proxyheader" have_constant "curlopt_http200aliases" have_constant "curlopt_cookie" have_constant "curlopt_cookiefile" have_constant "curlopt_cookiejar" have_constant "curlopt_cookiesession" have_constant "curlopt_cookielist" have_constant "curlopt_httpget" have_constant "curlopt_http_version" have_constant "curl_http_version_none" have_constant "curl_http_version_1_0" have_constant "curl_http_version_1_1" have_constant "curlopt_ignore_content_length" have_constant "curlopt_http_content_decoding" have_constant "curlopt_http_transfer_decoding" have_constant "curlopt_mail_from" have_constant "curlopt_mail_rcpt" have_constant "curlopt_tftp_blksize" have_constant "curlopt_ftpport" have_constant "curlopt_quote" have_constant "curlopt_postquote" have_constant "curlopt_prequote" have_constant "curlopt_dirlistonly" have_constant "curlopt_append" have_constant "curlopt_ftp_use_eprt" have_constant "curlopt_ftp_use_epsv" have_constant "curlopt_ftp_use_pret" have_constant "curlopt_ftp_create_missing_dirs" have_constant "curlopt_ftp_response_timeout" have_constant "curlopt_ftp_alternative_to_user" have_constant "curlopt_ftp_skip_pasv_ip" have_constant "curlopt_ftpsslauth" have_constant "curlftpauth_default" have_constant "curlftpauth_ssl" have_constant "curlftpauth_tls" have_constant "curlopt_ftp_ssl_ccc" have_constant "curlftpssl_ccc_none" have_constant "curlftpssl_ccc_passive" have_constant "curlftpssl_ccc_active" have_constant "curlopt_ftp_account" have_constant "curlopt_ftp_filemethod" have_constant "curlftpmethod_multicwd" have_constant "curlftpmethod_nocwd" have_constant "curlftpmethod_singlecwd" have_constant "curlopt_rtsp_request" have_constant "curl_rtspreq_options" have_constant "curl_rtspreq_describe" have_constant "curl_rtspreq_announce" have_constant "curl_rtspreq_setup" have_constant "curl_rtspreq_play" have_constant "curl_rtspreq_pause" have_constant "curl_rtspreq_teardown" have_constant "curl_rtspreq_get_parameter" have_constant "curl_rtspreq_set_parameter" have_constant "curl_rtspreq_record" have_constant "curl_rtspreq_receive" have_constant "curlopt_rtsp_session_id" have_constant "curlopt_rtsp_stream_uri" have_constant "curlopt_rtsp_transport" have_constant "curlopt_rtsp_header" have_constant "curlopt_rtsp_client_cseq" have_constant "curlopt_rtsp_server_cseq" have_constant "curlopt_transfertext" have_constant "curlopt_proxy_transfer_mode" have_constant "curlopt_crlf" have_constant "curlopt_range" have_constant "curlopt_resume_from" have_constant "curlopt_resume_from_large" have_constant "curlopt_customrequest" have_constant "curlopt_filetime" have_constant "curlopt_nobody" have_constant "curlopt_infilesize" have_constant "curlopt_infilesize_large" have_constant "curlopt_upload" have_constant "curlopt_maxfilesize" have_constant "curlopt_maxfilesize_large" have_constant "curlopt_timecondition" have_constant "curlopt_timevalue" have_constant "curlopt_timeout" have_constant "curlopt_timeout_ms" have_constant "curlopt_low_speed_limit" have_constant "curlopt_low_speed_time" have_constant "curlopt_max_send_speed_large" have_constant "curlopt_max_recv_speed_large" have_constant "curlopt_maxconnects" have_constant "curlopt_closepolicy" have_constant "curlopt_fresh_connect" have_constant "curlopt_forbid_reuse" have_constant "curlopt_connecttimeout" have_constant "curlopt_connecttimeout_ms" have_constant "curlopt_ipresolve" have_constant "curl_ipresolve_whatever" have_constant "curl_ipresolve_v4" have_constant "curl_ipresolve_v6" have_constant "curlopt_connect_only" have_constant "curlopt_use_ssl" have_constant "curlusessl_none" have_constant "curlusessl_try" have_constant "curlusessl_control" have_constant "curlusessl_all" have_constant "curlopt_resolve" have_constant "curlopt_request_target" have_constant "curlopt_sslcert" have_constant "curlopt_sslcerttype" have_constant "curlopt_sslkey" have_constant "curlopt_sslkeytype" have_constant "curlopt_keypasswd" have_constant "curlopt_sslengine" have_constant "curlopt_sslengine_default" have_constant "curlopt_sslversion" have_constant "curl_sslversion_default" have_constant :CURL_SSLVERSION_TLSv1 have_constant :CURL_SSLVERSION_SSLv2 have_constant :CURL_SSLVERSION_SSLv3 # Added in 7.30.0 have_constant "curlmopt_max_host_connections" # Added in 7.34.0 have_constant :CURL_SSLVERSION_TLSv1_0 have_constant :CURL_SSLVERSION_TLSv1_1 have_constant :CURL_SSLVERSION_TLSv1_2 # Added in 7.52.0 have_constant :CURL_SSLVERSION_TLSv1_3 have_constant "curlopt_ssl_verifypeer" have_constant "curlopt_cainfo" have_constant "curlopt_issuercert" have_constant "curlopt_capath" have_constant "curlopt_crlfile" have_constant "curlopt_ssl_verifyhost" have_constant "curlopt_certinfo" have_constant "curlopt_random_file" have_constant "curlopt_egdsocket" have_constant "curlopt_ssl_cipher_list" have_constant "curlopt_ssl_sessionid_cache" have_constant "curlopt_krblevel" have_constant "curlopt_ssh_auth_types" have_constant "curlopt_ssh_host_public_key_md5" have_constant "curlopt_ssh_public_keyfile" have_constant "curlopt_ssh_private_keyfile" have_constant "curlopt_ssh_knownhosts" have_constant "curlopt_ssh_keyfunction" have_constant "curlkhstat_fine_add_to_file" have_constant "curlkhstat_fine" have_constant "curlkhstat_reject" have_constant "curlkhstat_defer" have_constant "curlopt_ssh_keydata" have_constant "curlopt_private" have_constant "curlopt_share" have_constant "curlopt_new_file_perms" have_constant "curlopt_new_directory_perms" have_constant "curlopt_telnetoptions" # was obsoleted in August 2007 for 7.17.0, reused in April 2011 for 7.21.5 have_constant "curle_not_built_in" have_constant "curle_obsolete" # removed in 7.24 ? have_constant "curle_ftp_pret_failed" have_constant "curle_rtsp_cseq_error" have_constant "curle_rtsp_session_error" have_constant "curle_ftp_bad_file_list" have_constant "curle_chunk_failed" have_constant "curle_no_connection_available" have_constant "curle_ssl_pinnedpubkeynotmatch" have_constant "curle_ssl_invalidcertstatus" have_constant "curle_http2_stream" # gssapi/spnego delegation related constants have_constant "curlopt_gssapi_delegation" have_constant "curlgssapi_delegation_policy_flag" have_constant "curlgssapi_delegation_flag" have_constant "CURLM_ADDED_ALREADY" # added in 7.40.0 have_constant "curlopt_unix_socket_path" # added in 7.42.0 have_constant "curlopt_path_as_is" # added in 7.43.0 have_constant "curlopt_pipewait" have_constant "curlopt_proxy_ssl_verifyhost" # protocol constants have_constant "curlproto_all" have_constant "curlproto_dict" have_constant "curlproto_file" have_constant "curlproto_ftp" have_constant "curlproto_ftps" have_constant "curlproto_gopher" have_constant "curlproto_http" have_constant "curlproto_https" have_constant "curlproto_imap" have_constant "curlproto_imaps" have_constant "curlproto_ldap" have_constant "curlproto_ldaps" have_constant "curlproto_pop3" have_constant "curlproto_pop3s" have_constant "curlproto_rtmp" have_constant "curlproto_rtmpe" have_constant "curlproto_rtmps" have_constant "curlproto_rtmpt" have_constant "curlproto_rtmpte" have_constant "curlproto_rtmpts" have_constant "curlproto_rtsp" have_constant "curlproto_scp" have_constant "curlproto_sftp" have_constant "curlproto_smb" have_constant "curlproto_smbs" have_constant "curlproto_smtp" have_constant "curlproto_smtps" have_constant "curlproto_telnet" have_constant "curlproto_tftp" if try_compile('int main() { return 0; }','-Wall') $CFLAGS << ' -Wall' end # Clang-specific warning suppressions (not recognized by GCC) # These are used to suppress warnings in Ruby header macros %w[-Wno-self-assign -Wno-parentheses-equality -Wno-constant-logical-operand].each do |flag| if try_compile('int main() { return 0; }', flag) $CFLAGS << " #{flag}" end end # do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c def test_for(name, const, src) checking_for name do if try_compile(src,"#{$CFLAGS} #{$LIBS}") define const true else false end end end test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{ #include int main() { CURL *easy = curl_easy_init(); curl_easy_escape(easy,"hello",5); return 0; } }) have_func('rb_thread_blocking_region') have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') have_header('ruby/io.h') # Ruby 4.x exports rb_thread_fd_select without declaring it in ruby/io.h. have_func('rb_thread_fd_select') have_func('rb_wait_for_single_fd', 'ruby/io.h') have_header('ruby/fiber/scheduler.h') have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h') have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h') have_func('rb_fiber_scheduler_io_select', 'ruby/fiber/scheduler.h') have_func('rb_io_stdio_file') have_func('curl_multi_wait') have_func('curl_multi_socket_action') have_func('curl_multi_assign') have_func('curl_multi_wakeup') have_func('curl_multi_poll') have_func('curl_multi_socket') have_func('curl_multi_timer_callback') have_constant 'curlmopt_socketfunction' have_constant 'curlmopt_timerfunction' have_func('curl_easy_duphandle') # Optional: enable verbose socket-action debug logging. # Set CURB_SOCKET_DEBUG=1 in the environment before running extconf to enable. if ENV['CURB_SOCKET_DEBUG'] == '1' $defs << '-DCURB_SOCKET_DEBUG=1' end # Run any queued constant checks (in parallel if enabled) before header generation. flush_constant_checks create_header('curb_config.h') create_makefile('curb_core') curb-1.3.5/README.md0000644000004100000410000003740715203731642014000 0ustar www-datawww-data# Curb - Libcurl bindings for Ruby [![CI](https://github.com/taf2/curb/actions/workflows/ci.yml/badge.svg)](https://github.com/taf2/curb/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/taf2/curb/branch/master/graph/badge.svg)](https://codecov.io/gh/taf2/curb) [![Gem Version](https://badge.fury.io/rb/curb.svg)](https://badge.fury.io/rb/curb) * [CI Build Status](https://github.com/taf2/curb/actions/workflows/ci.yml) * [rubydoc rdoc](http://www.rubydoc.info/github/taf2/curb/) * [github project](http://github.com/taf2/curb/tree/master) Curb (probably CUrl-RuBy or something) provides Ruby-language bindings for the libcurl(3), a fully-featured client-side URL transfer library. cURL and libcurl live at [https://curl.se/libcurl/](https://curl.se/libcurl/) . Curb is a work-in-progress, and currently only supports libcurl's `easy` and `multi` modes. A big advantage to Curb over all other known ruby http libraries is it's ability to handle timeouts without the use of threads. ## License Curb is copyright (c) 2006 Ross Bamford, and released under the terms of the Ruby license. See the LICENSE file for the gory details. ## Easy mode GET request ``` res = Curl.get("https://www.google.com/") {|http| http.timeout = 10 # raise exception if request/response not handled within 10 seconds } puts res.code puts res.head puts res.body ``` POST request ``` res = Curl.post("https://your-server.com/endpoint", {post: "this"}.to_json) {|http| http.headers["Content-Type"] = "application/json" } puts res.code puts res.head puts res.body ``` ## FTP Support require 'curb' ### Basic FTP Download ```ruby puts "=== FTP Download Example ===" ftp = Curl::Easy.new('ftp://ftp.example.com/remote/file.txt') ftp.username = 'user' ftp.password = 'password' ftp.perform puts ftp.body ``` ### FTP Upload ```ruby puts "\n=== FTP Upload Example ===" upload = Curl::Easy.new('ftp://ftp.example.com/remote/upload.txt') upload.username = 'user' upload.password = 'password' upload.upload = true upload.put_data = File.read('local_file.txt') upload.perform ``` ### List Directory Contents ```ruby puts "\n=== FTP Directory Listing Example ===" list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/') list.username = 'user' list.password = 'password' list.set(:dirlistonly, 1) list.perform puts list.body ``` ### FTP over HTTP proxy tunnel (NLST/LIST) When listing directories through an HTTP proxy with `proxy_tunnel` (CONNECT), let libcurl manage the passive data connection. Do not send `PASV`/`EPSV` or `NLST` via `easy.ftp_commands` — QUOTE commands run on the control connection and libcurl will not open the data connection, resulting in 425 errors. To get NLST-like output safely: ```ruby list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/') list.username = 'user' list.password = 'password' list.proxy_url = 'http://proxy.example.com:80' list.proxy_tunnel = true # Ask libcurl to perform a listing (names only) list.set(:dirlistonly, 1) # If the proxy or server has trouble with EPSV/EPRT, you can adjust: # list.set(:ftp_use_epsv, 0) # disable EPSV # list.set(:ftp_use_eprt, 0) # disable EPRT (stick to IPv4 PASV) # list.set(:ftp_skip_pasv_ip, 1) # ignore PASV host, reuse control host list.perform puts list.body ``` If you need a full `LIST` output instead of just names, omit `dirlistonly` and parse the server response accordingly. The key is to let libcurl initiate the data connection (PASV/EPSV) instead of trying to drive it via `ftp_commands`. #### Full LIST directory listing To retrieve the full `LIST` output (permissions, owner, size, timestamp, name), simply do not set `dirlistonly`: ```ruby list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/') list.username = 'user' list.password = 'password' # Explicitly ensure names+metadata (LIST) rather than NLST # list.set(:dirlistonly, 0) # optional; default is LIST for directory URLs list.perform puts list.body # multi-line LIST output ``` Through an HTTP proxy tunnel, the same considerations apply as the NLST example above — just omit `dirlistonly` and keep the optional EPSV/EPRT/PASV tweaks if needed: ```ruby list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/') list.username = 'user' list.password = 'password' list.proxy_url = 'http://proxy.example.com:80' list.proxy_tunnel = true # Optional tweaks if the proxy/server combination struggles # list.set(:ftp_use_epsv, 0) # list.set(:ftp_use_eprt, 0) # list.set(:ftp_skip_pasv_ip, 1) list.perform puts list.body ``` ### Advanced FTP Usage with Various Options ``` puts "\n=== Advanced FTP Example ===" advanced = Curl::Easy.new do |curl| curl.url = 'ftp://ftp.example.com/remote/file.txt' curl.username = 'user' curl.password = 'password' # FTP Options curl.ftp_response_timeout = 30 curl.ftp_create_missing_dirs = true # Create directories if they don't exist curl.ftp_filemethod = Curl::CURL_MULTICWD # Use multicwd method for traversing paths # SSL/TLS Options for FTPS curl.use_ssl = Curl::CURLUSESSL_ALL # Use SSL/TLS for control and data curl.ssl_verify_peer = true curl.ssl_verify_host = true curl.cacert = "/path/to/cacert.pem" # Progress callback curl.on_progress do |dl_total, dl_now, ul_total, ul_now| puts "Download: #{dl_now}/#{dl_total} Upload: #{ul_now}/#{ul_total}" true # must return true to continue end # Debug output curl.verbose = true curl.on_debug do |type, data| puts "#{type}: #{data}" true end end advanced.perform ``` ### Parallel FTP Downloads ``` puts "\n=== Parallel FTP Downloads Example ===" urls = [ 'ftp://ftp.example.com/file1.txt', 'ftp://ftp.example.com/file2.txt', 'ftp://ftp.example.com/file3.txt' ] ``` ### Common options for all connections ``` options = { :username => 'user', :password => 'password', :timeout => 30, :on_success => proc { |easy| puts "Successfully downloaded: #{easy.url}" }, :on_failure => proc { |easy, code| puts "Failed to download: #{easy.url} (#{code})" } } Curl::Multi.download(urls, options) do |curl, file_path| puts "Completed downloading to: #{file_path}" end ``` ## You will need * A working Ruby installation (`2.0.0+` will work but `2.1+` preferred) (it's possible it still works with 1.8.7 but you'd have to tell me if not...) * A working libcurl development installation (Ideally one of the versions listed in the compatibility chart below that maps to your `curb` version) * A sane build environment (e.g. gcc, make) ## Version Compatibility chart A **non-exhaustive** set of compatibility versions of the libcurl library with this gem are as follows. (Note that these are only the ones that have been tested and reported to work across a variety of platforms / rubies) | Gem Version | Release Date | libcurl versions | | ----------- | -------------- | ----------------- | | 1.0.8 | Feb 10, 2025 | 7.58 – 8.12.1 | | 1.0.7 | Feb 09, 2025 | 7.58 – 8.12.1 | | 1.0.6 | Aug 23, 2024 | 7.58 – 8.12.1 | | 1.0.5 | Jan 2023 | 7.58 – 8.12.1 | | 1.0.4 | Jan 2023 | 7.58 – 8.12.1 | | 1.0.3* | Dec 2022 | 7.58 – 8.12.1 | | 1.0.2* | Dec 2022 | 7.58 – 8.12.1 | | 1.0.1 | Apr 2022 | 7.58 – 8.12.1 | | 1.0.0 | Jan 2022 | 7.58 – 8.12.1 | | 0.9.8 | Jan 2019 | 7.58 – 7.81 | | 0.9.7 | Nov 2018 | 7.56 – 7.60 | | 0.9.6 | May 2018 | 7.51 – 7.59 | | 0.9.5 | May 2018 | 7.51 – 7.59 | | 0.9.4 | Aug 2017 | 7.41 – 7.58 | | 0.9.3 | Apr 2016 | 7.26 – 7.58 | ```*avoid using these version are known to have issues with segmentation faults``` ## Installation... ... will usually be as simple as: $ gem install curb On Windows, make sure you're using the [DevKit](http://rubyinstaller.org/downloads/) and the [development version of libcurl](http://curl.se/gknw.net/7.39.0/dist-w32/curl-7.39.0-devel-mingw32.zip). Unzip, then run this in your command line (alter paths to your curl location, but remember to use forward slashes): gem install curb --platform=ruby -- --with-curl-lib=C:/curl-7.39.0-devel-mingw32/lib --with-curl-include=C:/curl-7.39.0-devel-mingw32/include Note that with Windows moving from one method of compiling to another as of Ruby `2.4` (DevKit -> MYSYS2), the usage of Ruby `2.4+` with this gem on windows is unlikely to work. It is advised to use the latest version of Ruby 2.3 available [HERE](https://dl.bintray.com/oneclick/rubyinstaller/rubyinstaller-2.3.3.exe) Or, if you downloaded the archive: $ rake compile && rake install If you have a weird setup, you might need extconf options. In this case, pass them like so: $ rake compile EXTCONF_OPTS='--with-curl-dir=/path/to/libcurl --prefix=/what/ever' && rake install Curb is tested only on GNU/Linux x86 and Mac OSX - YMMV on other platforms. If you do use another platform and experience problems, or if you can expand on the above instructions, please report the issue at http://github.com/taf2/curb/issues On Ubuntu, the dependencies can be satisfied by installing the following packages: 18.04 and onwards $ sudo apt-get install libcurl4 libcurl3-gnutls libcurl4-openssl-dev < 18.04 $ sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev On RedHat: $ sudo yum install ruby-devel libcurl-devel openssl-devel Curb has fairly extensive RDoc comments in the source. You can build the documentation with: $ rake doc ## Usage & examples Curb provides two classes: * `Curl::Easy` - simple API, for day-to-day tasks. * `Curl::Multi` - more advanced API, for operating on multiple URLs simultaneously. To use either, you will need to require the curb gem: ```ruby require 'curb' ``` ### Super simple API (less typing) ```ruby http = Curl.get("http://www.google.com/") puts http.body http = Curl.post("http://www.google.com/", {:foo => "bar"}) puts http.body http = Curl.get("http://www.google.com/") do |http| http.headers['Cookie'] = 'foo=1;bar=2' end puts http.body ``` ### Simple fetch via HTTP: ```ruby c = Curl::Easy.perform("http://www.google.co.uk") puts c.body ``` Same thing, more manual: ```ruby c = Curl::Easy.new("http://www.google.co.uk") c.perform puts c.body ``` ### Additional config: ```ruby http = Curl::Easy.perform("http://www.google.co.uk") do |curl| curl.headers["User-Agent"] = "myapp-0.0" curl.verbose = true end ``` Same thing, more manual: ```ruby c = Curl::Easy.new("http://www.google.co.uk") do |curl| curl.headers["User-Agent"] = "myapp-0.0" curl.verbose = true end c.perform ``` ### HTTP basic authentication: ```ruby c = Curl::Easy.new("http://github.com/") c.http_auth_types = :basic c.username = 'foo' c.password = 'bar' c.perform ``` ### HTTP "insecure" SSL connections (like curl -k, --insecure) to avoid Curl::Err::SSLCACertificateError: ```ruby c = Curl::Easy.new("https://github.com/") c.ssl_verify_peer = false c.perform ``` ### Supplying custom handlers: ```ruby c = Curl::Easy.new("http://www.google.co.uk") c.on_body { |data| print(data) } c.on_header { |data| print(data) } c.perform ``` ### Reusing Curls: ```ruby c = Curl::Easy.new ["http://www.google.co.uk", "http://www.ruby-lang.org/"].map do |url| c.url = url c.perform c.body end ``` ### HTTP POST form: Note: Instance methods like `easy.http_post(...)` do not accept a URL argument. Set the URL first (for example, `Curl::Easy.new(url)` or `easy.url = url`) and then call `easy.http_post(...)`. If you want to pass the URL directly to the call, use the class/module helpers such as `Curl::Easy.http_post(url, ...)` or `Curl.post(url, ...)`. ```ruby c = Curl::Easy.http_post("http://my.rails.box/thing/create", Curl::PostField.content('thing[name]', 'box'), Curl::PostField.content('thing[type]', 'storage')) ``` ### HTTP POST file upload: ```ruby c = Curl::Easy.new("http://my.rails.box/files/upload") c.multipart_form_post = true c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb')) ### Custom request target Some advanced scenarios need a request-target that differs from the URL host/path (for example, absolute-form targets or special values like `*`). If your libcurl supports `CURLOPT_REQUEST_TARGET` (libcurl ≥ 7.55), you can override it: ```ruby c = Curl::Easy.new("http://127.0.0.1:9129/methods") c.request_target = "http://localhost:9129/methods" # absolute-form target c.headers = { 'Host' => 'example.com' } # override Host header if needed c.perform ``` For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned. ``` ### Using HTTP/2 ```ruby c = Curl::Easy.new("https://http2.akamai.com") c.set(:HTTP_VERSION, Curl::HTTP_2_0) c.perform puts (c.body.include? "You are using HTTP/2 right now!") ? "HTTP/2" : "HTTP/1.x" ``` ### Multi Interface (Basic HTTP GET): ```ruby # make multiple GET requests easy_options = {:follow_location => true} # Use Curl::CURLPIPE_MULTIPLEX for HTTP/2 multiplexing multi_options = {:pipeline => Curl::CURLPIPE_HTTP1} Curl::Multi.get(['url1','url2','url3','url4','url5'], easy_options, multi_options) do|easy| # do something interesting with the easy response puts easy.last_effective_url end ``` ### Multi Interface (Basic HTTP POST): ```ruby # make multiple POST requests easy_options = {:follow_location => true, :multipart_form_post => true} multi_options = {:pipeline => Curl::CURLPIPE_HTTP1} url_fields = [ { :url => 'url1', :post_fields => {'f1' => 'v1'} }, { :url => 'url2', :post_fields => {'f1' => 'v1'} }, { :url => 'url3', :post_fields => {'f1' => 'v1'} } ] Curl::Multi.post(url_fields, easy_options, multi_options) do|easy| # do something interesting with the easy response puts easy.last_effective_url end ``` ### Multi Interface (Advanced): ```ruby responses = {} requests = ["http://www.google.co.uk/", "http://www.ruby-lang.org/"] m = Curl::Multi.new # add a few easy handles requests.each do |url| responses[url] = "" c = Curl::Easy.new(url) do|curl| curl.follow_location = true curl.on_body{|data| responses[url] << data; data.size } curl.on_success {|easy| puts "success, add more easy handles" } end m.add(c) end m.perform do puts "idling... can do some work here" end requests.each do|url| puts responses[url] end ``` ### Easy Callbacks * `on_success` is called when the response code is 2xx * `on_redirect` is called when the response code is 3xx * `on_missing` is called when the response code is 4xx * `on_failure` is called when the response code is 5xx * `on_complete` is called in all cases. ### Cookies - Manual cookies: Set the outgoing `Cookie` header via `easy.cookies = "name=value; other=val"`. This only affects the request header and does not modify libcurl's internal cookie engine. - Cookie engine: Enable with `easy.enable_cookies = true`. Optionally set `easy.cookiefile` (to load) and/or `easy.cookiejar` (to persist). Cookies received via `Set-Cookie` go into this engine. - Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format). - Modify engine cookies: use `easy.cookielist = ...` or `easy.set(:cookielist, ...)` with either a `Set-Cookie` style string, Netscape cookie lines, or special commands: `"ALL"` (clear), `"SESS"` (remove session cookies), `"FLUSH"` (write to jar), `"RELOAD"` (reload from file). - Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions. Examples: ```ruby easy = Curl::Easy.new("https://example.com") # Use the cookie engine and persist cookies easy.enable_cookies = true easy.cookiejar = "/tmp/cookies.txt" easy.perform # Later: inspect and tweak engine cookies p easy.cookielist easy.cookielist = 'ALL' # clear stored cookies # Send custom Cookie header for a single request easy.cookies = "flag=1; session_override=abc" easy.perform easy.cookies = '' # clear manual Cookie header ```