ethon-0.9.0/0000755000004100000410000000000012734673625012704 5ustar www-datawww-dataethon-0.9.0/Rakefile0000644000004100000410000000142112734673625014347 0ustar www-datawww-datarequire "bundler" Bundler.setup require "rake" require "rspec/core/rake_task" $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require "ethon/version" task :gem => :build task :build do system "gem build ethon.gemspec" end task :install => :build do system "gem install ethon-#{Ethon::VERSION}.gem" end task :release => :build do system "git tag -a v#{Ethon::VERSION} -m 'Tagging #{Ethon::VERSION}'" system "git push --tags" system "gem push ethon-#{Ethon::VERSION}.gem" end RSpec::Core::RakeTask.new(:spec) do |t| t.verbose = false t.ruby_opts = "-W -I./spec -rspec_helper" end desc "Start up the test servers" task :start do require_relative 'spec/support/boot' begin Boot.start_servers(:rake) rescue Exception end end task :default => :spec ethon-0.9.0/Gemfile0000644000004100000410000000063112734673625014177 0ustar www-datawww-datasource "https://rubygems.org" gemspec if Gem.ruby_version < Gem::Version.new("1.9.3") gem "rake", "< 11" else gem "rake" end group :development, :test do gem "rspec", "~> 3.4" gem "sinatra" gem "json" gem "mime-types", "~> 1.18" unless ENV["CI"] gem "guard-rspec", "~> 0.7" gem "rb-fsevent", "~> 0.9.1" end end group :perf do gem "benchmark-ips" gem "patron" gem "curb" end ethon-0.9.0/.rspec0000644000004100000410000000004512734673625014020 0ustar www-datawww-data--tty --color --format documentation ethon-0.9.0/spec/0000755000004100000410000000000012734673625013636 5ustar www-datawww-dataethon-0.9.0/spec/ethon/0000755000004100000410000000000012734673625014753 5ustar www-datawww-dataethon-0.9.0/spec/ethon/libc_spec.rb0000644000004100000410000000052412734673625017224 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Libc do describe "#getdtablesize", :if => !Ethon::Curl.windows? do it "returns an integer" do expect(Ethon::Libc.getdtablesize).to be_a(Integer) end it "returns bigger zero", :if => !Ethon::Curl.windows? do expect(Ethon::Libc.getdtablesize).to_not be_zero end end end ethon-0.9.0/spec/ethon/easy_spec.rb0000644000004100000410000001173312734673625017260 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy do let(:easy) { Ethon::Easy.new } describe ".new" do it "inits curl" do expect(Ethon::Curl).to receive(:init) easy end context "when options are empty" do it "sets only callbacks" do expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks) expect(Ethon::Easy).to receive(:set_option).never easy end end context "when options not empty" do context "when followlocation is set" do let(:options) { { :followlocation => true } } let(:easy) { Ethon::Easy.new(options) } it "sets followlocation" do expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks) expect(Ethon::Curl).to receive(:set_option).with(:followlocation, true, anything) easy end end end end describe "#set_attributes" do context "when options are empty" do it "sets only callbacks" do expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks) expect(Ethon::Easy).to receive(:set_option).never easy end end context "when options aren't empty" do context "when valid key" do it "sets" do expect(easy).to receive(:verbose=).with(true) easy.set_attributes({:verbose => true}) end end context "when invalid key" do it "raises invalid option error" do expect{ easy.set_attributes({:fubar => 1}) }.to raise_error(Ethon::Errors::InvalidOption) end end end end describe "#reset" do before { easy.url = "www.example.com" } it "resets url" do easy.reset expect(easy.url).to be_nil end it "resets escape?" do easy.escape = false easy.reset expect(easy.escape?).to be_truthy end it "resets hash" do easy.reset expect(easy.instance_variable_get(:@hash)).to be_nil end it "resets easy handle" do expect(Ethon::Curl).to receive(:easy_reset) easy.reset end it "resets on_complete" do easy.on_complete { p 1 } easy.reset expect(easy.on_complete).to be_empty end it "resets on_headers" do easy.on_headers { p 1 } easy.reset expect(easy.on_headers).to be_empty end it "resets on_body" do easy.on_body { p 1 } easy.reset expect(easy.on_body).to be_empty end end describe "#dup" do let!(:easy) do easy = Ethon::Easy.new easy.url = "http://localhost:3001/" easy.on_complete { 'on_complete' } easy.on_headers { 'on_headers' } easy.response_body = 'test_body' easy.response_headers = 'test_headers' easy end let!(:e) { easy.dup } it "sets a new handle" do expect(e.handle).not_to eq(easy.handle) end it "preserves url" do expect(e.url).to eq(easy.url) end it "preserves on_complete callback" do expect(e.on_complete).to be(easy.on_complete) end it "preserves on_headers callback" do expect(e.on_headers).to be(easy.on_headers) end it 'preserves body_write_callback of original handle' do expect { easy.perform }.to change { easy.response_body } expect { easy.perform }.not_to change { e.response_body } end it 'sets new body_write_callback of duplicated handle' do expect { e.perform }.to change { e.response_body } expect { e.perform }.not_to change { easy.response_body } end it 'preserves headers_write_callback of original handle' do expect { easy.perform }.to change { easy.response_headers } expect { easy.perform }.not_to change { e.response_headers } end it 'sets new headers_write_callback of duplicated handle' do expect { e.perform }.to change { e.response_headers } expect { e.perform }.not_to change { easy.response_headers } end it "resets response_body" do expect(e.response_body).to be_empty end it "resets response_headers" do expect(e.response_headers).to be_empty end it "sets response_body for duplicated Easy" do e.perform expect(e.response_body).not_to be_empty end it "sets response_headers for duplicated Easy" do e.perform expect(e.response_headers).not_to be_empty end it "preserves response_body for original Easy" do e.perform expect(easy.response_body).to eq('test_body') end it "preserves response_headers for original Easy" do e.perform expect(easy.response_headers).to eq('test_headers') end end describe "#mirror" do it "returns a Mirror" do expect(easy.mirror).to be_a(Ethon::Easy::Mirror) end it "builds from easy" do expect(Ethon::Easy::Mirror).to receive(:from_easy).with(easy) easy.mirror end end describe "#log_inspect" do [ :url, :response_code, :return_code, :total_time ].each do |name| it "contains #{name}" do expect(easy.log_inspect).to match name.to_s end end end end ethon-0.9.0/spec/ethon/loggable_spec.rb0000644000004100000410000000050712734673625020070 0ustar www-datawww-datarequire "spec_helper" describe Ethon::Loggable do describe "#logger=" do let(:logger) do Logger.new($stdout).tap do |log| log.level = Logger::INFO end end before do Ethon.logger = logger end it "sets the logger" do expect(Ethon.logger).to eq(logger) end end end ethon-0.9.0/spec/ethon/easy/0000755000004100000410000000000012734673625015714 5ustar www-datawww-dataethon-0.9.0/spec/ethon/easy/mirror_spec.rb0000644000004100000410000000227112734673625020567 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Mirror do let(:options) { nil } let(:mirror) { described_class.new(options) } describe "::INFORMATIONS_TO_LOG" do [ :return_code, :response_code, :response_body, :response_headers, :total_time, :starttransfer_time, :appconnect_time, :pretransfer_time, :connect_time, :namelookup_time, :redirect_time, :effective_url, :primary_ip, :redirect_count, :debug_info ].each do |name| it "contains #{name}" do expect(described_class::INFORMATIONS_TO_MIRROR).to include(name) end end end describe "#to_hash" do let(:options) { {:return_code => 1} } it "returns mirror as hash" do expect(mirror.to_hash).to eq(options) end end describe "#log_informations" do let(:options) { {:return_code => 1} } it "returns hash" do expect(mirror.log_informations).to be_a(Hash) end it "only calls methods that exist" do described_class::INFORMATIONS_TO_LOG.each do |method_name| expect(mirror.respond_to? method_name).to eql(true) end end it "includes return code" do expect(mirror.log_informations).to include(options) end end end ethon-0.9.0/spec/ethon/easy/features_spec.rb0000644000004100000410000000104212734673625021066 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Informations do describe "#supports_asynch_dns?" do it "returns boolean" do expect([true, false].include? Ethon::Easy.supports_asynch_dns?).to be_truthy end end describe "#supports_zlib?" do it "returns boolean" do expect([true, false].include? Ethon::Easy.supports_zlib?).to be_truthy end end describe "#supports_timeout_ms?" do it "returns boolean" do expect([true, false].include? Ethon::Easy.supports_timeout_ms?).to be_truthy end end end ethon-0.9.0/spec/ethon/easy/response_callbacks_spec.rb0000644000004100000410000000444112734673625023113 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::ResponseCallbacks do let(:easy) { Ethon::Easy.new } [:on_complete, :on_headers, :on_body].each do |callback_type| describe "##{callback_type}" do it "responds" do expect(easy).to respond_to("#{callback_type}") end context "when no block given" do it "returns @#{callback_type}" do expect(easy.send("#{callback_type}")).to eq([]) end end context "when block given" do it "stores" do easy.send(callback_type) { p 1 } expect(easy.instance_variable_get("@#{callback_type}").size).to eq(1) end end context "when multiple blocks given" do it "stores" do easy.send(callback_type) { p 1 } easy.send(callback_type) { p 2 } expect(easy.instance_variable_get("@#{callback_type}").size).to eq(2) end end end end describe "#complete" do before do easy.on_complete {|r| String.new(r.url) } end it "executes blocks and passes self" do expect(String).to receive(:new).with(easy.url) easy.complete end context "when @on_complete nil" do it "doesn't raise" do easy.instance_variable_set(:@on_complete, nil) expect{ easy.complete }.to_not raise_error end end end describe "#headers" do before do easy.on_headers {|r| String.new(r.url) } end it "executes blocks and passes self" do expect(String).to receive(:new).with(easy.url) easy.headers end context "when @on_headers nil" do it "doesn't raise" do easy.instance_variable_set(:@on_headers, nil) expect{ easy.headers }.to_not raise_error end end end describe "#body" do before do @chunk = nil @r = nil easy.on_body { |chunk, r| @chunk = chunk ; @r = r } end it "executes blocks and passes self" do easy.body("the chunk") expect(@r).to be(easy) end it "executes blocks and passes chunk" do easy.body("the chunk") expect(@chunk).to eq("the chunk") end context "when @on_body nil" do it "doesn't raise" do easy.instance_variable_set(:@on_body, nil) expect{ easy.body("the chunk") }.to_not raise_error end end end end ethon-0.9.0/spec/ethon/easy/header_spec.rb0000644000004100000410000000371212734673625020506 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Header do let(:easy) { Ethon::Easy.new } describe "#headers=" do let(:headers) { { 'User-Agent' => 'Ethon' } } it "sets header" do expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks) expect(Ethon::Curl).to receive(:set_option) easy.headers = headers end context "when requesting" do before do easy.headers = headers easy.url = "http://localhost:3001" easy.perform end it "sends" do expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"') end context "when header value contains null byte" do let(:headers) { { 'User-Agent' => "Ethon\0" } } it "escapes" do expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon\\\\0"') end end context "when header value has leading whitespace" do let(:headers) { { 'User-Agent' => " Ethon" } } it "removes" do expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"') end end context "when header value has traiing whitespace" do let(:headers) { { 'User-Agent' => "Ethon " } } it "removes" do expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"') end end end end describe "#compose_header" do it "has space in between" do expect(easy.compose_header('a', 'b')).to eq('a: b') end context "when value is a symbol" do it "works" do expect{ easy.compose_header('a', :b) }.to_not raise_error end end end describe "#header_list" do context "when no set_headers" do it "returns nil" do expect(easy.header_list).to eq(nil) end end context "when set_headers" do it "returns pointer to header list" do easy.headers = {'User-Agent' => 'Custom'} expect(easy.header_list).to be_a(FFI::Pointer) end end end end ethon-0.9.0/spec/ethon/easy/operations_spec.rb0000644000004100000410000001520612734673625021442 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Operations do let(:easy) { Ethon::Easy.new } describe "#handle" do it "returns a pointer" do expect(easy.handle).to be_a(FFI::Pointer) end end describe "#perform" do let(:url) { nil } let(:timeout) { nil } let(:connect_timeout) { nil } let(:follow_location) { nil } let(:max_redirs) { nil } let(:user_pwd) { nil } let(:http_auth) { nil } let(:headers) { nil } let(:protocols) { nil } let(:redir_protocols) { nil } let(:username) { nil } let(:password) { nil } before do Ethon.logger.level = Logger::DEBUG easy.url = url easy.timeout = timeout easy.connecttimeout = connect_timeout easy.followlocation = follow_location easy.maxredirs = max_redirs easy.httpauth = http_auth easy.headers = headers easy.protocols = protocols easy.redir_protocols = redir_protocols if user_pwd easy.userpwd = user_pwd else easy.username = username easy.password = password end easy.perform end it "calls Curl.easy_perform" do expect(Ethon::Curl).to receive(:easy_perform) easy.perform end it "calls Curl.easy_cleanup" do expect_any_instance_of(FFI::AutoPointer).to receive(:free) easy.cleanup end it "logs" do expect(Ethon.logger).to receive(:debug) easy.perform end it "doesn't log after completing because completing could reset" do easy.on_complete{ expect(Ethon.logger).to receive(:debug).never } easy.perform end context "when url" do let(:url) { "http://localhost:3001/" } it "returns ok" do expect(easy.return_code).to eq(:ok) end it "sets response body" do expect(easy.response_body).to be end it "sets response headers" do expect(easy.response_headers).to be end context "when request timed out" do let(:url) { "http://localhost:3001/?delay=1" } let(:timeout) { 1 } it "returns operation_timedout" do expect(easy.return_code).to eq(:operation_timedout) end end context "when connection timed out" do let(:url) { "http://localhost:3009" } let(:connect_timeout) { 1 } it "returns couldnt_connect" do expect(easy.return_code).to eq(:couldnt_connect) end end context "when no follow location" do let(:url) { "http://localhost:3001/redirect" } let(:follow_location) { false } it "doesn't follow" do expect(easy.response_code).to eq(302) end end context "when follow location" do let(:url) { "http://localhost:3001/redirect" } let(:follow_location) { true } it "follows" do expect(easy.response_code).to eq(200) end context "when infinite redirect loop" do let(:url) { "http://localhost:3001/bad_redirect" } let(:max_redirs) { 5 } context "when max redirect set" do it "follows only x times" do expect(easy.response_code).to eq(302) end end end end context "when user agent" do let(:headers) { { 'User-Agent' => 'Ethon' } } it "sets" do expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"') end end end context "when auth url" do before { easy.url = url } context "when basic auth" do let(:url) { "http://localhost:3001/auth_basic/username/password" } context "when no user_pwd" do it "returns 401" do expect(easy.response_code).to eq(401) end end context "when invalid user_pwd" do let(:user_pwd) { "invalid:invalid" } it "returns 401" do expect(easy.response_code).to eq(401) end end context "when valid user_pwd" do let(:user_pwd) { "username:password" } it "returns 200" do expect(easy.response_code).to eq(200) end end context "when user and password" do let(:username) { "username" } let(:password) { "password" } it "returns 200" do expect(easy.response_code).to eq(200) end end end context "when ntlm" do let(:url) { "http://localhost:3001/auth_ntlm" } let(:http_auth) { :ntlm } context "when no user_pwd" do it "returns 401" do expect(easy.response_code).to eq(401) end end context "when user_pwd" do let(:user_pwd) { "username:password" } it "returns 200" do expect(easy.response_code).to eq(200) end end end end context "when protocols" do context "when asking for a allowed url" do let(:url) { "http://localhost:3001" } let(:protocols) { :http } it "returns ok" do expect(easy.return_code).to be(:ok) end end context "when asking for a not allowed url" do let(:url) { "http://localhost:3001" } let(:protocols) { :https } it "returns unsupported_protocol" do expect(easy.return_code).to be(:unsupported_protocol) end end end context "when multiple protocols" do context "when asking for a allowed url" do let(:protocols) { [:http, :https] } context "when http" do let(:url) { "http://localhost:3001" } it "returns ok for http" do expect(easy.return_code).to be(:ok) end end context "when https" do let(:url) { "https://localhost:3001" } it "returns ssl_connect_error for https" do expect(easy.return_code).to be(:ssl_connect_error) end end end context "when asking for a not allowed url" do let(:url) { "ssh://localhost" } let(:protocols) { [:https, :http] } it "returns unsupported_protocol" do expect(easy.return_code).to be(:unsupported_protocol) end end end context "when redir_protocols" do context "when redirecting to a not allowed url" do let(:url) { "http://localhost:3001/redirect" } let(:follow_location) { true } let(:redir_protocols) { :https } it "returns unsupported_protocol" do expect(easy.return_code).to be(:unsupported_protocol) end end end context "when no url" do it "returns url_malformat" do expect(easy.perform).to eq(:url_malformat) end end end end ethon-0.9.0/spec/ethon/easy/options_spec.rb0000644000004100000410000001035712734673625020754 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Options do let(:easy) { Ethon::Easy.new } [ :accept_encoding, :cainfo, :capath, :connecttimeout, :connecttimeout_ms, :cookie, :cookiejar, :cookiefile, :copypostfields, :customrequest, :dns_cache_timeout, :followlocation, :forbid_reuse, :http_version, :httpauth, :httpget, :httppost, :infilesize, :interface, :keypasswd, :maxredirs, :nobody, :nosignal, :postfieldsize, :postredir, :protocols, :proxy, :proxyauth, :proxyport, :proxytype, :proxyuserpwd, :readdata, :readfunction, :redir_protocols, :ssl_verifyhost, :ssl_verifypeer, :sslcert, :sslcerttype, :sslkey, :sslkeytype, :sslversion, :timeout, :timeout_ms, :unrestricted_auth, :upload, :url, :useragent, :userpwd, :verbose ].each do |name| describe "#{name}=" do it "responds_to" do expect(easy).to respond_to("#{name}=") end it "sets option" do expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks) expect(Ethon::Curl).to receive(:set_option).with(name, anything, anything) value = case name when :http_version :httpv1_0 when :httpauth :basic when :protocols, :redir_protocols :http when :postredir :post_301 when :proxytype :http when :sslversion :default when :httppost FFI::Pointer::NULL else 1 end easy.method("#{name}=").call(value) end end end describe '#escape?' do context 'by default' do it 'returns true' do expect(easy.escape?).to be_truthy end end context 'when #escape=nil' do it 'returns true' do easy.escape = nil expect(easy.escape?).to be_truthy end end context 'when #escape=true' do it 'returns true' do easy.escape = true expect(easy.escape?).to be_truthy end end context 'when #escape=false' do it 'returns true' do easy.escape = false expect(easy.escape?).to be_falsey end end end describe "#httppost=" do it "raises unless given a FFI::Pointer" do expect{ easy.httppost = 1 }.to raise_error(Ethon::Errors::InvalidValue) end end context "when requesting" do let(:url) { "localhost:3001" } let(:timeout) { nil } let(:timeout_ms) { nil } let(:connecttimeout) { nil } let(:connecttimeout_ms) { nil } let(:userpwd) { nil } before do easy.url = url easy.timeout = timeout easy.timeout_ms = timeout_ms easy.connecttimeout = connecttimeout easy.connecttimeout_ms = connecttimeout_ms easy.userpwd = userpwd easy.perform end context "when userpwd" do context "when contains /" do let(:url) { "localhost:3001/auth_basic/test/te%2Fst" } let(:userpwd) { "test:te/st" } it "works" do expect(easy.response_code).to eq(200) end end end context "when timeout" do let(:timeout) { 1 } context "when request takes longer" do let(:url) { "localhost:3001?delay=2" } it "times out" do expect(easy.return_code).to eq(:operation_timedout) end end end context "when connecttimeout" do let(:connecttimeout) { 1 } context "when cannot connect" do let(:url) { "localhost:3002" } it "times out" do expect(easy.return_code).to eq(:couldnt_connect) end end end if Ethon::Easy.supports_timeout_ms? context "when timeout_ms" do let(:timeout_ms) { 100 } context "when request takes longer" do let(:url) { "localhost:3001?delay=1" } it "times out" do expect(easy.return_code).to eq(:operation_timedout) end end end context "when connecttimeout_ms" do let(:connecttimeout_ms) { 100 } context "when cannot connect" do let(:url) { "localhost:3002" } it "times out" do # this can either lead to a timeout or couldnt connect depending on which happens first expect([:couldnt_connect, :operation_timedout]).to include(easy.return_code) end end end end end end ethon-0.9.0/spec/ethon/easy/queryable_spec.rb0000644000004100000410000001407712734673625021255 0ustar www-datawww-data# encoding: utf-8 require 'spec_helper' describe Ethon::Easy::Queryable do let(:hash) { {} } let!(:easy) { Ethon::Easy.new } let(:params) { Ethon::Easy::Params.new(easy, hash) } describe "#to_s" do context "when query_pairs empty" do before { params.instance_variable_set(:@query_pairs, []) } it "returns empty string" do expect(params.to_s).to eq("") end end context "when query_pairs not empty" do context "when escape" do before do params.escape = true end { '!' => '%21', '*' => '%2A', "'" => '%27', '(' => '%28', ')' => '%29', ';' => '%3B', ':' => '%3A', '@' => '%40', '&' => '%26', '=' => '%3D', '+' => '%2B', '$' => '%24', ',' => '%2C', '/' => '%2F', '?' => '%3F', '#' => '%23', '[' => '%5B', ']' => '%5D', '<' => '%3C', '>' => '%3E', '"' => '%22', '{' => '%7B', '}' => '%7D', '|' => '%7C', '\\' => '%5C', '`' => '%60', '^' => '%5E', '%' => '%25', ' ' => '%20', "\0" => '%00', 'まつもと' => '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8', }.each do |value, percent| it "turns #{value.inspect} into #{percent}" do params.instance_variable_set(:@query_pairs, [[:a, value]]) expect(params.to_s).to eq("a=#{percent}") end end { '.' => '%2E', '-' => '%2D', '_' => '%5F', '~' => '%7E', }.each do |value, percent| it "leaves #{value.inspect} instead of turning into #{percent}" do params.instance_variable_set(:@query_pairs, [[:a, value]]) expect(params.to_s).to eq("a=#{value}") end end end context "when no escape" do before { params.instance_variable_set(:@query_pairs, [[:a, 1], [:b, 2]]) } it "returns concatenated query string" do expect(params.to_s).to eq("a=1&b=2") end end end context "when query_pairs contains a string" do before { params.instance_variable_set(:@query_pairs, ["{a: 1}"]) } it "returns correct string" do expect(params.to_s).to eq("{a: 1}") end end end describe "#build_query_pairs" do let(:pairs) { params.method(:build_query_pairs).call(hash) } context "when params is empty" do it "returns empty array" do expect(pairs).to eq([]) end end context "when params is string" do let(:hash) { "{a: 1}" } it "wraps it in an array" do expect(pairs).to eq([hash]) end end context "when params is simple hash" do let(:hash) { {:a => 1, :b => 2} } it "transforms" do expect(pairs).to include([:a, 1]) expect(pairs).to include([:b, 2]) end end context "when params is a nested hash" do let(:hash) { {:a => 1, :b => {:c => 2}} } it "transforms" do expect(pairs).to include([:a, 1]) expect(pairs).to include(["b[c]", 2]) end end context "when params contains an array" do let(:hash) { {:a => 1, :b => [2, 3]} } context "by default" do it "transforms" do expect(pairs).to include([:a, 1]) expect(pairs).to include(["b[0]", 2]) expect(pairs).to include(["b[1]", 3]) end end context "when params_encoding is :rack" do before { params.params_encoding = :rack } it "transforms without indexes" do expect(pairs).to include([:a, 1]) expect(pairs).to include(["b[]", 2]) expect(pairs).to include(["b[]", 3]) end end end context "when params contains something nested in an array" do context "when string" do let(:hash) { {:a => {:b => ["hello", "world"]}} } it "transforms" do expect(pairs).to eq([["a[b][0]", "hello"], ["a[b][1]", "world"]]) end end context "when hash" do let(:hash) { {:a => {:b => [{:c =>1}, {:d => 2}]}} } it "transforms" do expect(pairs).to eq([["a[b][0][c]", 1], ["a[b][1][d]", 2]]) end end context "when file" do let(:file) { File.open("spec/spec_helper.rb") } let(:file_info) { params.method(:file_info).call(file) } let(:hash) { {:a => {:b => [file]}} } let(:mime_type) { file_info[1] } it "transforms" do expect(pairs).to eq([["a[b][0]", file_info]]) end context "when MIME" do context "when mime type" do it "sets mime type to text" do expect(mime_type).to eq("application/x-ruby") end end context "when no mime type" do let(:file) { Tempfile.new("fubar") } it "sets mime type to default application/octet-stream" do expect(mime_type).to eq("application/octet-stream") end end end context "when no MIME" do before { hide_const("MIME") } it "sets mime type to default application/octet-stream" do expect(mime_type).to eq("application/octet-stream") end end end end context "when params contains file" do let(:file) { Tempfile.new("fubar") } let(:file_info) { params.method(:file_info).call(file) } let(:hash) { {:a => 1, :b => file} } it "transforms" do expect(pairs).to include([:a, 1]) expect(pairs).to include([:b, file_info]) end end context "when params key contains a null byte" do let(:hash) { {:a => "1\0" } } it "preserves" do expect(pairs).to eq([[:a, "1\0"]]) end end context "when params value contains a null byte" do let(:hash) { {"a\0" => 1 } } it "preserves" do expect(pairs).to eq([["a\0", 1]]) end end end describe "#empty?" do context "when params empty" do it "returns true" do expect(params.empty?).to be_truthy end end context "when params not empty" do let(:hash) { {:a => 1} } it "returns false" do expect(params.empty?).to be_falsey end end end end ethon-0.9.0/spec/ethon/easy/informations_spec.rb0000644000004100000410000000356312734673625021772 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Informations do let(:easy) { Ethon::Easy.new } before do easy.url = "http://localhost:3001" easy.perform end describe "#httpauth_avail" do it "returns" do expect(easy.httpauth_avail).to be end end describe "#total_time" do it "returns float" do expect(easy.total_time).to be_a(Float) end end describe "#starttransfer_time" do it "returns float" do expect(easy.starttransfer_time).to be_a(Float) end end describe "#appconnect_time" do it "returns float" do expect(easy.appconnect_time).to be_a(Float) end end describe "#pretransfer_time" do it "returns float" do expect(easy.pretransfer_time).to be_a(Float) end end describe "#connect_time" do it "returns float" do expect(easy.connect_time).to be_a(Float) end end describe "#namelookup_time" do it "returns float" do expect(easy.namelookup_time).to be_a(Float) end end describe "#redirect_time" do it "returns float" do expect(easy.redirect_time).to be_a(Float) end end describe "#effective_url" do it "returns url" do expect(easy.effective_url).to match(/^http:\/\/localhost:3001\/?/) end end describe "#primary_ip" do it "returns localhost" do expect(easy.primary_ip).to match(/::1|127\.0\.0\.1/) end end describe "#response_code" do it "returns 200" do expect(easy.response_code).to eq(200) end end describe "#redirect_count" do it "returns 0" do expect(easy.redirect_count).to eq(0) end end describe "#request_size" do it "returns 53" do expect(easy.request_size).to eq(53) end end describe "#supports_zlib?" do it "returns true" do expect(Kernel).to receive(:warn) expect(easy.supports_zlib?).to be_truthy end end end ethon-0.9.0/spec/ethon/easy/http_spec.rb0000644000004100000410000000407612734673625020241 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http do let(:easy) { Ethon::Easy.new } describe "#http_request" do let(:url) { "http://localhost:3001/" } let(:action_name) { :get } let(:options) { {} } let(:get) { double(:setup) } let(:get_class) { Ethon::Easy::Http::Get } it "instanciates action" do expect(get).to receive(:setup) expect(get_class).to receive(:new).and_return(get) easy.http_request(url, action_name, options) end context "when requesting" do [ :get, :post, :put, :delete, :head, :patch, :options ].map do |action| it "returns ok" do easy.http_request(url, action, options) easy.perform expect(easy.return_code).to be(:ok) end unless action == :head it "makes a #{action.to_s.upcase} request" do easy.http_request(url, action, options) easy.perform expect(easy.response_body).to include("\"REQUEST_METHOD\":\"#{action.to_s.upcase}\"") end it "streams the response body from the #{action.to_s.upcase} request" do bytes_read = 0 easy.on_body { |chunk, response| bytes_read += chunk.bytesize } easy.http_request(url, action, options) easy.perform content_length = ((easy.response_headers =~ /Content-Length: (\d+)/) && $1.to_i) expect(bytes_read).to eq(content_length) expect(easy.response_body).to eq("") end it "notifies when headers are ready" do headers = [] easy.on_headers { |r| headers << r.response_headers } easy.http_request(url, action, options) easy.perform expect(headers).to eq([easy.response_headers]) expect(headers.first).to match(/Content-Length: (\d+)/) end end end it "makes requests with custom HTTP verbs" do easy.http_request(url, :purge, options) easy.perform expect(easy.response_body).to include(%{"REQUEST_METHOD":"PURGE"}) end end end end ethon-0.9.0/spec/ethon/easy/util_spec.rb0000644000004100000410000000107012734673625020226 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Util do class Dummy include Ethon::Easy::Util end let(:klass) { Dummy.new } describe "escape_zero_byte" do context "when value has no zero byte" do let(:value) { "hello world" } it "returns same value" do expect(klass.escape_zero_byte(value)).to be(value) end end context "when value has zero byte" do let(:value) { "hello \0world" } it "returns escaped" do expect(klass.escape_zero_byte(value)).to eq("hello \\0world") end end end end ethon-0.9.0/spec/ethon/easy/debug_info_spec.rb0000644000004100000410000000245512734673625021362 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::DebugInfo do let(:easy) { Ethon::Easy.new } before do easy.url = "http://localhost:3001/" easy.perform end describe "#debug_info" do context "when verbose is not set to true" do it "does not save any debug info after a request" do expect(easy.debug_info.to_a.length).to eq(0) expect(easy.debug_info.to_h.values.flatten.length).to eq(0) end end context "when verbose is set to true" do before do easy.verbose = true easy.perform end after do easy.reset end it "saves debug info after a request" do expect(easy.debug_info.to_a.length).to be > 0 end it "saves request headers" do expect(easy.debug_info.header_out.join).to include('GET / HTTP/1.1') end it "saves response headers" do expect(easy.debug_info.header_in.length).to be > 0 expect(easy.response_headers).to include(easy.debug_info.header_in.join) end it "saves incoming data" do expect(easy.debug_info.data_in.length).to be > 0 expect(easy.response_body).to include(easy.debug_info.data_in.join) end it "saves debug text" do expect(easy.debug_info.text.length).to be > 0 end end end end ethon-0.9.0/spec/ethon/easy/http/0000755000004100000410000000000012734673625016673 5ustar www-datawww-dataethon-0.9.0/spec/ethon/easy/http/put_spec.rb0000644000004100000410000001027212734673625021044 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Put do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:options) { Hash.new } let(:put) { described_class.new(url, options.merge({:params => params, :body => form})) } describe "#setup" do context "when nothing" do it "sets url" do put.setup(easy) expect(easy.url).to eq(url) end it "sets upload" do expect(easy).to receive(:upload=).with(true) put.setup(easy) end it "sets infilesize" do expect(easy).to receive(:infilesize=).with(0) put.setup(easy) end context "when requesting" do it "makes a put request" do put.setup(easy) easy.perform expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"') end end end context "when params" do let(:params) { {:a => "1&"} } it "attaches escaped to url" do put.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end context "with arrays" do let(:params) { {:a => %w( foo bar )} } context "by default" do it "encodes them with indexes" do put.setup(easy) expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar") end end context "when params_encoding is :rack" do let(:options) { {:params_encoding => :rack} } it "encodes them without indexes" do put.setup(easy) expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar") end end end it "sets upload" do expect(easy).to receive(:upload=).with(true) put.setup(easy) end it "sets infilesize" do expect(easy).to receive(:infilesize=).with(0) put.setup(easy) end context "when requesting" do before do put.setup(easy) easy.perform end it "makes a put request" do expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"') end end end context "when body" do let(:form) { {:a => "1&b=2"} } it "sets infilesize" do expect(easy).to receive(:infilesize=).with(11) put.setup(easy) end it "sets readfunction" do expect(easy).to receive(:readfunction) put.setup(easy) end it "sets upload" do expect(easy).to receive(:upload=).with(true) put.setup(easy) end context "when requesting" do context "sending string body" do before do easy.headers = { 'Expect' => '' } put.setup(easy) easy.perform end it "makes a put request" do expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"') end it "submits a body" do expect(easy.response_body).to include('"body":"a=1%26b%3D2"') end end context "when injecting a file as body" do let(:file) { File.open(__FILE__) } let(:easy) do e = Ethon::Easy.new(:url => url, :upload => true) e.set_read_callback(file) e.infilesize = File.size(file.path) e end before do easy.headers = { 'Expect' => '' } easy.perform end it "submits file" do expect(easy.response_body).to include("injecting") end end end context "when arrays" do let(:form) { {:a => %w( foo bar )} } before do put.setup(easy) easy.perform end context "by default" do it "submits an indexed, escaped representation" do expect(easy.response_body).to include('"body":"a%5B0%5D=foo&a%5B1%5D=bar"') end end context "when params_encoding is :rack" do let(:options) { {:params_encoding => :rack} } it "submits an non-indexed, escaped representation" do expect(easy.response_body).to include('"body":"a%5B%5D=foo&a%5B%5D=bar"') end end end end context "when params and body" end end ethon-0.9.0/spec/ethon/easy/http/post_spec.rb0000644000004100000410000002053612734673625021225 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Post do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:options) { Hash.new } let(:post) { described_class.new(url, options.merge({:params => params, :body => form})) } describe "#setup" do context "when nothing" do it "sets url" do post.setup(easy) expect(easy.url).to eq(url) end it "sets postfield_size" do expect(easy).to receive(:postfieldsize=).with(0) post.setup(easy) end it "sets copy_postfields" do expect(easy).to receive(:copypostfields=).with("") post.setup(easy) end it "makes a post request" do post.setup(easy) easy.perform expect(easy.response_body).to include('"REQUEST_METHOD":"POST"') end end context "when params" do let(:params) { {:a => "1&"} } it "attaches escaped to url" do post.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end context "with arrays" do let(:params) { {:a => %w( foo bar )} } context "by default" do it "encodes them with indexes" do post.setup(easy) expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar") end end context "when params_encoding is :rack" do let(:options) { {:params_encoding => :rack} } it "encodes them without indexes" do post.setup(easy) expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar") end end end context "with :escape" do context 'missing' do it "escapes values" do post.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end end context 'nil' do let(:options) { {:escape => nil} } it "escapes values" do post.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end end context 'true' do let(:options) { {:escape => true} } it "escapes values" do post.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end end context 'false' do let(:options) { {:escape => false} } it "sends raw values" do post.setup(easy) expect(easy.url).to eq("#{url}?a=1&") end end end it "sets postfieldsize" do expect(easy).to receive(:postfieldsize=).with(0) post.setup(easy) end it "sets copypostfields" do expect(easy).to receive(:copypostfields=).with("") post.setup(easy) end context "when requesting" do let(:postredir) { nil } before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.postredir = postredir easy.followlocation = true easy.perform end it "is a post" do expect(easy.response_body).to include('"REQUEST_METHOD":"POST"') end it "uses application/x-www-form-urlencoded content type" do expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded"') end it "requests parameterized url" do expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?a=1%26"') end context "when redirection" do let(:url) { "localhost:3001/redirect" } context "when no postredirs" do it "is a get" do expect(easy.response_body).to include('"REQUEST_METHOD":"GET"') end end unless ENV['TRAVIS'] context "when postredirs" do let(:postredir) { :post_all } it "is a post" do expect(easy.response_body).to include('"REQUEST_METHOD":"POST"') end end end end end end context "when body" do context "when multipart" do let(:form) { {:a => File.open(__FILE__, 'r')} } it "sets httppost" do expect(easy).to receive(:httppost=) post.setup(easy) end context "when requesting" do before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a post" do expect(easy.response_body).to include('"REQUEST_METHOD":"POST"') end it "uses multipart/form-data content type" do expect(easy.response_body).to include('"CONTENT_TYPE":"multipart/form-data') end it "submits a body" do expect(easy.response_body).to match('"body":".+"') end it "submits the data" do expect(easy.response_body).to include('"filename":"post_spec.rb"') end end end context "when not multipart" do let(:form) { {:a => "1&b=2"} } let(:encoded) { "a=1%26b%3D2" } it "sets escaped copypostfields" do expect(easy).to receive(:copypostfields=).with(encoded) post.setup(easy) end it "sets postfieldsize" do expect(easy).to receive(:postfieldsize=).with(encoded.bytesize) post.setup(easy) end context "when requesting" do before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a post" do expect(easy.response_body).to include('"REQUEST_METHOD":"POST"') end it "uses multipart/form-data content type" do expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded') end it "submits a body" do expect(easy.response_body).to match('"body":"a=1%26b%3D2"') end it "submits the data" do expect(easy.response_body).to include('"rack.request.form_hash":{"a":"1&b=2"}') end end end context "when string" do let(:form) { "{a: 1}" } context "when requesting" do before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "sends string" do expect(easy.response_body).to include('"body":"{a: 1}"') end end end context "when binary with null bytes" do let(:form) { [1, 0, 1].pack('c*') } context "when requesting" do before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "sends binary data" do expect(easy.response_body).to include('"body":"\\u0001\\u0000\\u0001"') end end end context "when arrays" do let(:form) { {:a => %w( foo bar )} } context "by default" do it "sets copypostfields with indexed, escaped representation" do expect(easy).to receive(:copypostfields=).with('a%5B0%5D=foo&a%5B1%5D=bar') post.setup(easy) end end context "when params_encoding is :rack" do let(:options) { {:params_encoding => :rack} } it "sets copypostfields with non-indexed, escaped representation" do expect(easy).to receive(:copypostfields=).with('a%5B%5D=foo&a%5B%5D=bar') post.setup(easy) end end end end context "when params and body" do let(:form) { {:a => "1"} } let(:params) { {:b => "2"} } context "when requesting" do before do easy.headers = { 'Expect' => '' } post.setup(easy) easy.perform end it "url contains params" do expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?b=2"') end it "body contains form" do expect(easy.response_body).to include('"body":"a=1"') end end end end end ethon-0.9.0/spec/ethon/easy/http/get_spec.rb0000644000004100000410000000605012734673625021012 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Get do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:options) { {} } let(:get) { described_class.new(url, {:params => params, :body => form}.merge(options)) } describe "#setup" do it "sets url" do get.setup(easy) expect(easy.url).to eq(url) end context "when body" do let(:form) { { :a => 1 } } it "sets customrequest" do expect(easy).to receive(:customrequest=).with("GET") get.setup(easy) end end context "when no body" do it "doesn't set customrequest" do expect(easy).to receive(:customrequest=).never get.setup(easy) end end context "when requesting" do before do get.setup(easy) easy.perform end context "when url already contains params" do let(:url) { "http://localhost:3001/?query=here" } let(:params) { {:a => "1&b=2"} } it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a get request" do expect(easy.response_body).to include('"REQUEST_METHOD":"GET"') end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2") end end context "when params and no body" do let(:params) { {:a => "1&b=2"} } it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a get request" do expect(easy.response_body).to include('"REQUEST_METHOD":"GET"') end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2") end end context "when params and body" do let(:params) { {:a => "1&b=2"} } let(:form) { {:b => "2"} } it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a get request" do expect(easy.response_body).to include('"REQUEST_METHOD":"GET"') end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2") end end context "with :escape" do let(:params) { {:a => "1&b=2"} } context 'missing' do it "escapes values" do expect(easy.url).to eq("#{url}?a=1%26b%3D2") end end context 'nil' do let(:options) { {:escape => nil} } it "escapes values" do expect(easy.url).to eq("#{url}?a=1%26b%3D2") end end context 'true' do let(:options) { {:escape => true} } it "escapes values" do expect(easy.url).to eq("#{url}?a=1%26b%3D2") end end context 'false' do let(:options) { {:escape => false} } it "sends raw values" do expect(easy.url).to eq("#{url}?a=1&b=2") end end end end end end ethon-0.9.0/spec/ethon/easy/http/options_spec.rb0000644000004100000410000000237012734673625021727 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Options do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:options) { described_class.new(url, {:params => params, :body => form}) } describe "#setup" do it "sets customrequest" do expect(easy).to receive(:customrequest=).with("OPTIONS") options.setup(easy) end it "sets url" do options.setup(easy) expect(easy.url).to eq(url) end context "when requesting" do let(:params) { {:a => "1&b=2"} } before do options.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a options request" do expect(easy.response_body).to include('"REQUEST_METHOD":"OPTIONS"') end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2") end context "when url already contains params" do let(:url) { "http://localhost:3001/?query=here" } it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2") end end end end end ethon-0.9.0/spec/ethon/easy/http/patch_spec.rb0000644000004100000410000000235112734673625021332 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Patch do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:patch) { described_class.new(url, {:params => params, :body => form}) } describe "#setup" do it "sets customrequest" do expect(easy).to receive(:customrequest=).with("PATCH") patch.setup(easy) end it "sets url" do patch.setup(easy) expect(easy.url).to eq(url) end context "when requesting" do let(:params) { {:a => "1&b=2"} } before do patch.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a patch request" do expect(easy.response_body).to include('"REQUEST_METHOD":"PATCH"') end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2") end context "when url already contains params" do let(:url) { "http://localhost:3001/?query=here" } it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2") end end end end end ethon-0.9.0/spec/ethon/easy/http/delete_spec.rb0000644000004100000410000000074412734673625021501 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Delete do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:delete) { described_class.new(url, {:params => params, :body => form}) } context "when requesting" do before do delete.setup(easy) easy.perform end it "makes a delete request" do expect(easy.response_body).to include('"REQUEST_METHOD":"DELETE"') end end end ethon-0.9.0/spec/ethon/easy/http/custom_spec.rb0000644000004100000410000001126612734673625021552 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Custom do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:custom) { described_class.new("PURGE", url, {:params => params, :body => form}) } describe "#setup" do context "when nothing" do it "sets url" do custom.setup(easy) expect(easy.url).to eq(url) end it "makes a custom request" do custom.setup(easy) easy.perform expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"') end end context "when params" do let(:params) { {:a => "1&"} } it "attaches escaped to url" do custom.setup(easy) expect(easy.url).to eq("#{url}?a=1%26") end context "when requesting" do before do easy.headers = { 'Expect' => '' } custom.setup(easy) easy.perform end it "is a custom verb" do expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"') end it "does not use application/x-www-form-urlencoded content type" do expect(easy.response_body).to_not include('"CONTENT_TYPE":"application/x-www-form-urlencoded"') end it "requests parameterized url" do expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?a=1%26"') end end end context "when body" do context "when multipart" do let(:form) { {:a => File.open(__FILE__, 'r')} } it "sets httppost" do expect(easy).to receive(:httppost=) custom.setup(easy) end context "when requesting" do before do easy.headers = { 'Expect' => '' } custom.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a custom verb" do expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"') end it "uses multipart/form-data content type" do expect(easy.response_body).to include('"CONTENT_TYPE":"multipart/form-data') end it "submits a body" do expect(easy.response_body).to match('"body":".+"') end it "submits the data" do expect(easy.response_body).to include('"filename":"custom_spec.rb"') end end end context "when not multipart" do let(:form) { {:a => "1&b=2"} } let(:encoded) { "a=1%26b%3D2" } it "sets escaped copypostfields" do expect(easy).to receive(:copypostfields=).with(encoded) custom.setup(easy) end it "sets postfieldsize" do expect(easy).to receive(:postfieldsize=).with(encoded.bytesize) custom.setup(easy) end context "when requesting" do before do easy.headers = { 'Expect' => '' } custom.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "is a custom verb" do expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"') end it "uses multipart/form-data content type" do expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded') end it "submits a body" do expect(easy.response_body).to match('"body":"a=1%26b%3D2"') end it "submits the data" do expect(easy.response_body).to include('"rack.request.form_hash":{"a":"1&b=2"}') end end end context "when string" do let(:form) { "{a: 1}" } context "when requesting" do before do easy.headers = { 'Expect' => '' } custom.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "sends string" do expect(easy.response_body).to include('"body":"{a: 1}"') end end end end context "when params and body" do let(:form) { {:a => "1"} } let(:params) { {:b => "2"} } context "when requesting" do before do easy.headers = { 'Expect' => '' } custom.setup(easy) easy.perform end it "url contains params" do expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?b=2"') end it "body contains form" do expect(easy.response_body).to include('"body":"a=1"') end end end end end ethon-0.9.0/spec/ethon/easy/http/head_spec.rb0000644000004100000410000000347612734673625021145 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Http::Head do let(:easy) { Ethon::Easy.new } let(:url) { "http://localhost:3001/" } let(:params) { nil } let(:form) { nil } let(:head) { described_class.new(url, {:params => params, :body => form}) } describe "#setup" do context "when nothing" do it "sets nobody" do expect(easy).to receive(:nobody=).with(true) head.setup(easy) end it "sets url" do head.setup(easy) expect(easy.url).to eq(url) end end context "when params" do let(:params) { {:a => "1&b=2"} } it "sets nobody" do expect(easy).to receive(:nobody=).with(true) head.setup(easy) end it "attaches escaped to url" do head.setup(easy) expect(easy.url).to eq("#{url}?a=1%26b%3D2") end context "when requesting" do before do head.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end it "has no body" do expect(easy.response_body).to be_empty end it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2") end context "when url already contains params" do let(:url) { "http://localhost:3001/?query=here" } it "requests parameterized url" do expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2") end end end end context "when body" do let(:form) { {:a => 1} } context "when requesting" do before do head.setup(easy) easy.perform end it "returns ok" do expect(easy.return_code).to eq(:ok) end end end end end ethon-0.9.0/spec/ethon/easy/form_spec.rb0000644000004100000410000000342012734673625020215 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Form do let(:hash) { {} } let!(:easy) { Ethon::Easy.new } let(:form) { Ethon::Easy::Form.new(easy, hash) } describe ".new" do it "assigns attribute to @params" do expect(form.instance_variable_get(:@params)).to eq(hash) end end describe "#first" do it "returns a pointer" do expect(form.first).to be_a(FFI::Pointer) end end describe "#last" do it "returns a pointer" do expect(form.first).to be_a(FFI::Pointer) end end describe "#multipart?" do before { form.instance_variable_set(:@query_pairs, pairs) } context "when query_pairs contains string values" do let(:pairs) { [['a', '1'], ['b', '2']] } it "returns false" do expect(form.multipart?).to be_falsey end end context "when query_pairs contains file" do let(:pairs) { [['a', '1'], ['b', ['path', 'encoding', 'abs_path']]] } it "returns true" do expect(form.multipart?).to be_truthy end end end describe "#materialize" do before { form.instance_variable_set(:@query_pairs, pairs) } context "when query_pairs contains string values" do let(:pairs) { [['a', '1']] } it "adds params to form" do expect(Ethon::Curl).to receive(:formadd) form.materialize end end context "when query_pairs contains nil" do let(:pairs) { [['a', nil]] } it "adds params to form" do expect(Ethon::Curl).to receive(:formadd) form.materialize end end context "when query_pairs contains file" do let(:pairs) { [['a', ["file", "type", "path/file"]]] } it "adds file to form" do expect(Ethon::Curl).to receive(:formadd) form.materialize end end end end ethon-0.9.0/spec/ethon/easy/callbacks_spec.rb0000644000004100000410000000262512734673625021177 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Easy::Callbacks do let!(:easy) { Ethon::Easy.new } describe "#set_callbacks" do before do expect(Ethon::Curl).to receive(:set_option).exactly(3).times end it "sets write- and headerfunction" do easy.set_callbacks end it "resets @response_body" do easy.set_callbacks expect(easy.instance_variable_get(:@response_body)).to eq("") end it "resets @response_headers" do easy.set_callbacks expect(easy.instance_variable_get(:@response_headers)).to eq("") end it "resets @debug_info" do easy.set_callbacks expect(easy.instance_variable_get(:@debug_info).to_a).to eq([]) end end describe "#body_write_callback" do let(:body_write_callback) { easy.instance_variable_get(:@body_write_callback) } let(:stream) { double(:read_string => "") } context "when body returns not :abort" do it "returns number bigger than 0" do expect(body_write_callback.call(stream, 1, 1, nil) > 0).to be(true) end end context "when body returns :abort" do before do easy.on_body.clear easy.on_body { :abort } end let(:body_write_callback) { easy.instance_variable_get(:@body_write_callback) } it "returns -1 to indicate abort to libcurl" do expect(body_write_callback.call(stream, 1, 1, nil)).to eq(-1) end end end end ethon-0.9.0/spec/ethon/multi_spec.rb0000644000004100000410000000075412734673625017452 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Multi do describe ".new" do it "inits curl" do expect(Ethon::Curl).to receive(:init) Ethon::Multi.new end context "when options not empty" do context "when pipelining is set" do let(:options) { { :pipelining => true } } it "sets pipelining" do expect_any_instance_of(Ethon::Multi).to receive(:pipelining=).with(true) Ethon::Multi.new(options) end end end end end ethon-0.9.0/spec/ethon/curl_spec.rb0000644000004100000410000000174312734673625017264 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Curl do describe ".init" do before { Ethon::Curl.send(:class_variable_set, :@@initialized, false) } context "when global_init fails" do it "raises global init error" do expect(Ethon::Curl).to receive(:global_init).and_return(1) expect{ Ethon::Curl.init }.to raise_error(Ethon::Errors::GlobalInit) end end context "when global_init works" do before { expect(Ethon::Curl).to receive(:global_init).and_return(0) } it "doesn't raises global init error" do expect{ Ethon::Curl.init }.to_not raise_error end it "logs" do expect(Ethon.logger).to receive(:debug) Ethon::Curl.init end end context "when global_cleanup is called" do before { expect(Ethon::Curl).to receive(:global_cleanup) } it "logs" do expect(Ethon.logger).to receive(:debug).twice Ethon::Curl.init Ethon::Curl.cleanup end end end end ethon-0.9.0/spec/ethon/multi/0000755000004100000410000000000012734673625016105 5ustar www-datawww-dataethon-0.9.0/spec/ethon/multi/operations_spec.rb0000644000004100000410000001656012734673625021637 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Multi::Operations do let(:multi) { Ethon::Multi.new } let(:easy) { Ethon::Easy.new } let(:pointer) { FFI::MemoryPointer.new(:int) } describe "#handle" do it "returns a pointer" do expect(multi.handle).to be_a(FFI::Pointer) end end describe "#running_count" do context "when hydra has no easy" do it "returns nil" do expect(multi.send(:running_count)).to be_nil end end context "when hydra has easy" do before do easy.url = "http://localhost:3001/" multi.add(easy) multi.send(:trigger, pointer) end it "returns 1" do expect(multi.send(:running_count)).to eq(1) end end context "when hydra has more easys" do let(:another_easy) { Ethon::Easy.new } before do easy.url = "http://localhost:3001/" another_easy.url = "http://localhost:3001/" multi.add(easy) multi.add(another_easy) multi.send(:trigger, pointer) end it "returns 2" do expect(multi.send(:running_count)).to eq(2) end end end describe "#get_timeout" do context "when code ok" do let(:timeout) { 1 } before do expect(Ethon::Curl).to receive(:multi_timeout).and_return(:ok) multi.instance_variable_set(:@timeout, double(:read_long => timeout)) end it "doesn't raise" do expect{ multi.send(:get_timeout) }.to_not raise_error end context "when timeout smaller zero" do let(:timeout) { -1 } it "returns 1" do expect(multi.send(:get_timeout)).to eq(1) end end context "when timeout bigger or equal zero" do let(:timeout) { 2 } it "returns timeout" do expect(multi.send(:get_timeout)).to eq(timeout) end end end context "when code not ok" do before { expect(Ethon::Curl).to receive(:multi_timeout).and_return(:not_ok) } it "raises MultiTimeout error" do expect{ multi.send(:get_timeout) }.to raise_error(Ethon::Errors::MultiTimeout) end end end describe "#set_fds" do let(:timeout) { 1 } let(:max_fd) { 1 } context "when code ok" do before { expect(Ethon::Curl).to receive(:multi_fdset).and_return(:ok) } it "doesn't raise" do expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error end context "when max_fd -1" do let(:max_fd) { -1 } before do multi.instance_variable_set(:@max_fd, double(:read_int => max_fd)) expect(multi).to receive(:sleep).with(0.001) end it "waits 100ms" do multi.method(:set_fds).call(timeout) end end context "when max_fd not -1" do context "when code smaller zero" do before { expect(Ethon::Curl).to receive(:select).and_return(-1) } it "raises Select error" do expect{ multi.method(:set_fds).call(timeout) }.to raise_error(Ethon::Errors::Select) end end context "when code bigger or equal zero" do before { expect(Ethon::Curl).to receive(:select).and_return(0) } it "doesn't raise" do expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error end end end end context "when code not ok" do before { expect(Ethon::Curl).to receive(:multi_fdset).and_return(:not_ok) } it "raises MultiFdset error" do expect{ multi.method(:set_fds).call(timeout) }.to raise_error(Ethon::Errors::MultiFdset) end end end describe "#perform" do context "when no easy handles" do it "returns nil" do expect(multi.perform).to be_nil end it "logs" do expect(Ethon.logger).to receive(:debug).twice multi.perform end end context "when easy handle" do before do easy.url = "http://localhost:3001/" multi.add(easy) end it "requests" do multi.perform end it "sets easy" do multi.perform expect(easy.response_code).to eq(200) end end context "when four easy handles" do let(:easies) do ary = [] 4.times do ary << another_easy = Ethon::Easy.new another_easy.url = "http://localhost:3001/" end ary end before do easies.each { |e| multi.add(e) } multi.perform end it "sets response codes" do expect(easies.all?{ |e| e.response_code == 200 }).to be_truthy end end end describe "#ongoing?" do context "when easy_handles" do before { multi.easy_handles << 1 } context "when running_count not greater 0" do before { multi.instance_variable_set(:@running_count, 0) } it "returns true" do expect(multi.method(:ongoing?).call).to be_truthy end end context "when running_count greater 0" do before { multi.instance_variable_set(:@running_count, 1) } it "returns true" do expect(multi.method(:ongoing?).call).to be_truthy end end end context "when no easy_handles" do context "when running_count not greater 0" do before { multi.instance_variable_set(:@running_count, 0) } it "returns false" do expect(multi.method(:ongoing?).call).to be_falsey end end context "when running_count greater 0" do before { multi.instance_variable_set(:@running_count, 1) } it "returns true" do expect(multi.method(:ongoing?).call).to be_truthy end end end end describe "#init_vars" do it "sets @timeout" do expect(multi.instance_variable_get(:@timeout)).to be_a(FFI::MemoryPointer) end it "sets @timeval" do expect(multi.instance_variable_get(:@timeval)).to be_a(Ethon::Curl::Timeval) end it "sets @fd_read" do expect(multi.instance_variable_get(:@fd_read)).to be_a(Ethon::Curl::FDSet) end it "sets @fd_write" do expect(multi.instance_variable_get(:@fd_write)).to be_a(Ethon::Curl::FDSet) end it "sets @fd_excep" do expect(multi.instance_variable_get(:@fd_excep)).to be_a(Ethon::Curl::FDSet) end it "sets @max_fd" do expect(multi.instance_variable_get(:@max_fd)).to be_a(FFI::MemoryPointer) end end describe "#reset_fds" do after { multi.method(:reset_fds).call } it "resets @fd_read" do expect(multi.instance_variable_get(:@fd_read)).to receive(:clear) end it "resets @fd_write" do expect(multi.instance_variable_get(:@fd_write)).to receive(:clear) end it "resets @fd_excep" do expect(multi.instance_variable_get(:@fd_excep)).to receive(:clear) end end describe "#check" do it { skip("untested") } end describe "#run" do it { skip("untested") } end describe "#trigger" do it "calls multi perform" do expect(Ethon::Curl).to receive(:multi_perform) multi.send(:trigger, pointer) end it "sets running count" do multi.instance_variable_set(:@running_count, nil) multi.send(:trigger, pointer) expect(multi.instance_variable_get(:@running_count)).to_not be_nil end it "returns multi perform code" do expect(Ethon::Curl).to receive(:multi_perform).and_return(:ok) expect(multi.send(:trigger, pointer)).to eq(:ok) end end end ethon-0.9.0/spec/ethon/multi/options_spec.rb0000644000004100000410000000320412734673625021136 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Multi::Options do let(:multi) { Ethon::Multi.new } [ :maxconnects, :pipelining, :socketdata, :socketfunction, :timerdata, :timerfunction, :max_total_connections ].each do |name| describe "#{name}=" do it "responds_to" do expect(multi).to respond_to("#{name}=") end it "sets option" do expect(Ethon::Curl).to receive(:set_option).with(name, anything, anything, anything) multi.method("#{name}=").call(1) end end end describe "#value_for" do context "when option in bool" do context "when value true" do let(:value) { true } it "returns 1" do expect(multi.method(:value_for).call(value, :bool)).to eq(1) end end context "when value false" do let(:value) { false } it "returns 0" do expect(multi.method(:value_for).call(value, :bool)).to eq(0) end end end context "when value in int" do let(:value) { "2" } it "returns value casted to int" do expect(multi.method(:value_for).call(value, :int)).to eq(2) end end context "when value in unspecific_options" do context "when value a string" do let(:value) { "www.example.\0com" } it "returns zero byte escaped string" do expect(multi.method(:value_for).call(value, nil)).to eq("www.example.\\0com") end end context "when value not a string" do let(:value) { 1 } it "returns value" do expect(multi.method(:value_for).call(value, nil)).to eq(1) end end end end end ethon-0.9.0/spec/ethon/multi/stack_spec.rb0000644000004100000410000000414512734673625020555 0ustar www-datawww-datarequire 'spec_helper' describe Ethon::Multi::Stack do let(:multi) { Ethon::Multi.new } let(:easy) { Ethon::Easy.new } describe "#add" do context "when easy already added" do before { multi.add(easy) } it "returns nil" do expect(multi.add(easy)).to be_nil end end context "when easy new" do it "adds easy to multi" do expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok) multi.add(easy) end it "adds easy to easy_handles" do multi.add(easy) expect(multi.easy_handles).to include(easy) end end context "when multi_add_handle fails" do it "raises multi add error" do expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:bad_easy_handle) expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd) end end context "when multi cleaned up before" do it "raises multi add error" do Ethon::Curl.multi_cleanup(multi.handle) expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd) end end end describe "#delete" do context "when easy in easy_handles" do before { multi.add(easy) } it "deletes easy from multi" do expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:ok) multi.delete(easy) end it "deletes easy from easy_handles" do multi.delete(easy) expect(multi.easy_handles).to_not include(easy) end end context "when easy is not in easy_handles" do it "does nothing" do expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok) multi.add(easy) end it "adds easy to easy_handles" do multi.add(easy) expect(multi.easy_handles).to include(easy) end end context "when multi_remove_handle fails" do before { multi.add(easy) } it "raises multi remove error" do expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:bad_easy_handle) expect{ multi.delete(easy) }.to raise_error(Ethon::Errors::MultiRemove) end end end end ethon-0.9.0/spec/spec_helper.rb0000644000004100000410000000111112734673625016446 0ustar www-datawww-data$LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require 'bundler' Bundler.setup require "ethon" require 'rspec' if defined? require_relative require_relative 'support/localhost_server' require_relative 'support/server' else require 'support/localhost_server' require 'support/server' end # Ethon.logger = Logger.new($stdout).tap do |log| # log.level = Logger::DEBUG # end RSpec.configure do |config| # config.order = :rand config.before(:suite) do LocalhostServer.new(TESTSERVER.new, 3001) end end ethon-0.9.0/spec/support/0000755000004100000410000000000012734673625015352 5ustar www-datawww-dataethon-0.9.0/spec/support/localhost_server.rb0000644000004100000410000000435712734673625021266 0ustar www-datawww-datarequire 'rack' require 'rack/handler/webrick' require 'net/http' # The code for this is inspired by Capybara's server: # http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb class LocalhostServer READY_MESSAGE = "Server ready" class Identify def initialize(app) @app = app end def call(env) if env["PATH_INFO"] == "/__identify__" [200, {}, [LocalhostServer::READY_MESSAGE]] else @app.call(env) end end end attr_reader :port def initialize(rack_app, port = nil) @port = port || find_available_port @rack_app = rack_app concurrently { boot } wait_until(10, "Boot failed.") { booted? } end private def find_available_port server = TCPServer.new('127.0.0.1', 0) server.addr[1] ensure server.close if server end def boot # Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters. options = { :Port => port } options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER'] Rack::Handler::WEBrick.run(Identify.new(@rack_app), options) end def booted? res = ::Net::HTTP.get_response("localhost", '/__identify__', port) if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection) return res.body == READY_MESSAGE end rescue Errno::ECONNREFUSED, Errno::EBADF return false end def concurrently if should_use_subprocess? pid = Process.fork do trap(:INT) { ::Rack::Handler::WEBrick.shutdown } yield exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet. end at_exit do Process.kill('INT', pid) begin Process.wait(pid) rescue Errno::ECHILD # ignore this error...I think it means the child process has already exited. end end else Thread.new { yield } end end def should_use_subprocess? # !ENV['THREADED'] false end def wait_until(timeout, error_message, &block) start_time = Time.now while true return if yield raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout sleep(0.05) end end end ethon-0.9.0/spec/support/server.rb0000644000004100000410000000532612734673625017213 0ustar www-datawww-data#!/usr/bin/env ruby require 'json' require 'zlib' require 'sinatra/base' TESTSERVER = Sinatra.new do set :logging, nil fail_count = 0 post '/file' do { 'content-type' => params[:file][:type], 'filename' => params[:file][:filename], 'content' => params[:file][:tempfile].read, 'request-content-type' => request.env['CONTENT_TYPE'] }.to_json end get '/multiple-headers' do [200, { 'Set-Cookie' => %w[ foo bar ], 'Content-Type' => 'text/plain' }, ['']] end get '/fail/:number' do if fail_count >= params[:number].to_i "ok" else fail_count += 1 error 500, "oh noes!" end end get '/fail_forever' do error 500, "oh noes!" end get '/redirect' do redirect '/' end post '/redirect' do redirect '/' end get '/bad_redirect' do redirect '/bad_redirect' end get '/auth_basic/:username/:password' do @auth ||= Rack::Auth::Basic::Request.new(request.env) # Check that we've got a basic auth, and that it's credentials match the ones # provided in the request if @auth.provided? && @auth.basic? && @auth.credentials == [ params[:username], params[:password] ] # auth is valid - confirm it true else # invalid auth - request the authentication response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth") throw(:halt, [401, "Not authorized\n"]) end end get '/auth_ntlm' do # we're just checking for the existence if NTLM auth header here. It's validation # is too troublesome and really doesn't bother is much, it's up to libcurl to make # it valid response['WWW-Authenticate'] = 'NTLM' is_ntlm_auth = /^NTLM/ =~ request.env['HTTP_AUTHORIZATION'] true if is_ntlm_auth throw(:halt, [401, "Not authorized\n"]) if !is_ntlm_auth end get '/gzipped' do req_env = request.env.to_json z = Zlib::Deflate.new gzipped_env = z.deflate(req_env, Zlib::FINISH) z.close response['Content-Encoding'] = 'gzip' gzipped_env end get '/**' do sleep params["delay"].to_i if params.has_key?("delay") request.env.merge!(:body => request.body.read).to_json end head '/**' do sleep params["delay"].to_i if params.has_key?("delay") end put '/**' do request.env.merge!(:body => request.body.read).to_json end post '/**' do request.env.merge!(:body => request.body.read).to_json end delete '/**' do request.env.merge!(:body => request.body.read).to_json end patch '/**' do request.env.merge!(:body => request.body.read).to_json end options '/**' do request.env.merge!(:body => request.body.read).to_json end route 'PURGE', '/**' do request.env.merge!(:body => request.body.read).to_json end end ethon-0.9.0/.travis.yml0000644000004100000410000000050212734673625015012 0ustar www-datawww-datalanguage: ruby cache: bundler sudo: false bundler_args: --without perf script: bundle exec rake rvm: - 1.8.7 - 1.9.2 - 1.9.3 - 2.0.0-p648 - 2.1.8 - 2.2.4 - 2.3.0 - ruby-head - ree - jruby-18mode - jruby-19mode - jruby-head - rbx-2 matrix: allow_failures: - rvm: ree - rvm: ruby-head ethon-0.9.0/lib/0000755000004100000410000000000012734673625013452 5ustar www-datawww-dataethon-0.9.0/lib/ethon/0000755000004100000410000000000012734673625014567 5ustar www-datawww-dataethon-0.9.0/lib/ethon/curl.rb0000644000004100000410000000664112734673625016070 0ustar www-datawww-datarequire 'ethon/curls/codes' require 'ethon/curls/options' require 'ethon/curls/infos' require 'ethon/curls/form_options' require 'ethon/curls/messages' require 'ethon/curls/functions' module Ethon # FFI Wrapper module for Curl. Holds constants and required initializers. # # @api private module Curl extend ::FFI::Library extend Ethon::Curls::Codes extend Ethon::Curls::Options extend Ethon::Curls::Infos extend Ethon::Curls::FormOptions extend Ethon::Curls::Messages # :nodoc: def self.windows? Libc.windows? end require 'ethon/curls/constants' require 'ethon/curls/settings' require 'ethon/curls/classes' extend Ethon::Curls::Functions @blocking = true @@initialized = false @@curl_mutex = Mutex.new class << self # This function sets up the program environment that libcurl needs. # Think of it as an extension of the library loader. # # This function must be called at least once within a program (a program is all the # code that shares a memory space) before the program calls any other function in libcurl. # The environment it sets up is constant for the life of the program and is the same for # every program, so multiple calls have the same effect as one call. # # The flags option is a bit pattern that tells libcurl exactly what features to init, # as described below. Set the desired bits by ORing the values together. In normal # operation, you must specify CURL_GLOBAL_ALL. Don't use any other value unless # you are familiar with it and mean to control internal operations of libcurl. # # This function is not thread safe. You must not call it when any other thread in # the program (i.e. a thread sharing the same memory) is running. This doesn't just # mean no other thread that is using libcurl. Because curl_global_init() calls # functions of other libraries that are similarly thread unsafe, it could conflict with # any other thread that uses these other libraries. # # @raise [ Ethon::Errors::GlobalInit ] If Curl.global_init fails. def init @@curl_mutex.synchronize { if not @@initialized raise Errors::GlobalInit.new if Curl.global_init(GLOBAL_ALL) != 0 @@initialized = true Ethon.logger.debug("ETHON: Libcurl initialized") if Ethon.logger end } end # This function releases resources acquired by curl_global_init. # You should call curl_global_cleanup once for each call you make to # curl_global_init, after you are done using libcurl. # This function is not thread safe. You must not call it when any other thread in the # program (i.e. a thread sharing the same memory) is running. This doesn't just # mean no other thread that is using libcurl. Because curl_global_cleanup calls functions of other # libraries that are similarly thread unsafe, it could conflict with # any other thread that uses these other libraries. # See the description in libcurl of global environment requirements # for details of how to use this function. def cleanup @@curl_mutex.synchronize { if @@initialized Curl.global_cleanup() @@initialized = false Ethon.logger.debug("ETHON: Libcurl cleanup") if Ethon.logger end } end end end end ethon-0.9.0/lib/ethon/errors.rb0000644000004100000410000000063512734673625016434 0ustar www-datawww-datarequire 'ethon/errors/ethon_error' require 'ethon/errors/global_init' require 'ethon/errors/multi_timeout' require 'ethon/errors/multi_fdset' require 'ethon/errors/multi_add' require 'ethon/errors/multi_remove' require 'ethon/errors/select' require 'ethon/errors/invalid_option' require 'ethon/errors/invalid_value' module Ethon # This namespace contains all errors raised by ethon. module Errors end end ethon-0.9.0/lib/ethon/libc.rb0000644000004100000410000000050412734673625016024 0ustar www-datawww-datamodule Ethon # FFI Wrapper module for Libc. # # @api private module Libc extend FFI::Library ffi_lib 'c' # :nodoc: def self.windows? Gem.win_platform? end unless windows? attach_function :getdtablesize, [], :int attach_function :free, [:pointer], :void end end end ethon-0.9.0/lib/ethon/loggable.rb0000644000004100000410000000240612734673625016672 0ustar www-datawww-data# encoding: utf-8 module Ethon # Contains logging behaviour. module Loggable # Get the logger. # # @note Will try to grab Rails' logger first before creating a new logger # with stdout. # # @example Get the logger. # Loggable.logger # # @return [ Logger ] The logger. def logger return @logger if defined?(@logger) @logger = rails_logger || default_logger end # Set the logger. # # @example Set the logger. # Loggable.logger = Logger.new($stdout) # # @param [ Logger ] logger The logger to set. # # @return [ Logger ] The new logger. def logger=(logger) @logger = logger end private # Gets the default Ethon logger - stdout. # # @example Get the default logger. # Loggable.default_logger # # @return [ Logger ] The default logger. def default_logger logger = Logger.new($stdout) logger.level = Logger::INFO logger end # Get the Rails logger if it's defined. # # @example Get Rails' logger. # Loggable.rails_logger # # @return [ Logger ] The Rails logger. def rails_logger defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger end end extend Loggable end ethon-0.9.0/lib/ethon/multi.rb0000644000004100000410000001065312734673625016253 0ustar www-datawww-datarequire 'ethon/easy/util' require 'ethon/multi/stack' require 'ethon/multi/operations' require 'ethon/multi/options' module Ethon # This class represents libcurl multi. class Multi include Ethon::Multi::Stack include Ethon::Multi::Operations include Ethon::Multi::Options # Create a new multi. Initialize curl in case # it didn't happen before. # # @example Create a new Multi. # Multi.new # # @param [ Hash ] options The options. # # @option options :socketdata [String] # Pass a pointer to whatever you want passed to the # curl_socket_callback's forth argument, the userp pointer. This is not # used by libcurl but only passed-thru as-is. Set the callback pointer # with CURLMOPT_SOCKETFUNCTION. # @option options :pipelining [Boolean] # Pass a long set to 1 to enable 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) # @option options :timerfunction [Proc] # Pass a pointer to a function matching the curl_multi_timer_callback # prototype. This function will then be called when the timeout value # changes. The timeout value is at what latest time the application # should call one of the "performing" functions of the multi interface # (curl_multi_socket_action(3) and curl_multi_perform(3)) - to allow # libcurl to keep timeouts and retries etc to work. A timeout value of # -1 means that there is no timeout at all, and 0 means that the # timeout is already reached. Libcurl attempts to limit calling this # only when the fixed future timeout time actually changes. See also # CURLMOPT_TIMERDATA. This callback can be used instead of, or in # addition to, curl_multi_timeout(3). (Added in 7.16.0) # @option options :timerdata [String] # Pass a pointer to whatever you want passed to the # curl_multi_timer_callback's third argument, the userp pointer. This # is not used by libcurl but only passed-thru as-is. Set the callback # pointer with CURLMOPT_TIMERFUNCTION. (Added in 7.16.0) # @option options :maxconnects [Integer] # Pass a long. The set number will be used as the maximum amount of # simultaneously open connections that libcurl may cache. Default is # 10, and libcurl will enlarge the size for each added easy handle to # make it fit 4 times the number of added easy handles. # By setting this option, you can prevent the cache size from growing # beyond the limit set by you. # When the cache is full, curl closes the oldest one in the cache to # prevent the number of open connections from increasing. # This option is for the multi handle's use only, when using the easy # interface you should instead use the CURLOPT_MAXCONNECTS option. # (Added in 7.16.3) # @option options :max_total_connections [Integer] # Pass a long. The set number will be used as the maximum amount of # simultaneously open connections in total. For each new session, # libcurl will open a new connection up to the limit set by # CURLMOPT_MAX_TOTAL_CONNECTIONS. When the limit is reached, the # sessions will be pending until there are available connections. # If CURLMOPT_PIPELINING is 1, libcurl will try to pipeline if the host # is capable of it. # The default value is 0, which means that there is no limit. However, # for backwards compatibility, setting it to 0 when CURLMOPT_PIPELINING # is 1 will not be treated as unlimited. Instead it will open only 1 # connection and try to pipeline on it. # (Added in 7.30.0) # # @return [ Multi ] The new multi. def initialize(options = {}) Curl.init set_attributes(options) init_vars end # Set given options. # # @example Set options. # multi.set_attributes(options) # # @raise InvalidOption # # @see initialize # # @api private def set_attributes(options) options.each_pair do |key, value| unless respond_to?("#{key}=") raise Errors::InvalidOption.new(key) end method("#{key}=").call(value) end end end end ethon-0.9.0/lib/ethon/errors/0000755000004100000410000000000012734673625016103 5ustar www-datawww-dataethon-0.9.0/lib/ethon/errors/global_init.rb0000644000004100000410000000031512734673625020712 0ustar www-datawww-datamodule Ethon module Errors # Raises when global_init failed. class GlobalInit < EthonError def initialize super("An error occured initializing curl.") end end end end ethon-0.9.0/lib/ethon/errors/multi_fdset.rb0000644000004100000410000000033212734673625020745 0ustar www-datawww-datamodule Ethon module Errors # Raises when multi_fdset failed. class MultiFdset < EthonError def initialize(code) super("An error occured getting the fdset: #{code}") end end end end ethon-0.9.0/lib/ethon/errors/invalid_option.rb0000644000004100000410000000032512734673625021446 0ustar www-datawww-datamodule Ethon module Errors # Raises when option is invalid. class InvalidOption < EthonError def initialize(option) super("The option: #{option} is invalid.") end end end end ethon-0.9.0/lib/ethon/errors/multi_remove.rb0000644000004100000410000000041012734673625021132 0ustar www-datawww-datamodule Ethon module Errors # Raises when multi_remove_handle failed. class MultiRemove < EthonError def initialize(code, easy) super("An error occured removing the easy handle: #{easy} from the multi: #{code}") end end end end ethon-0.9.0/lib/ethon/errors/multi_add.rb0000644000004100000410000000037612734673625020400 0ustar www-datawww-datamodule Ethon module Errors # Raises when multi_add_handle failed. class MultiAdd < EthonError def initialize(code, easy) super("An error occured adding the easy handle: #{easy} to the multi: #{code}") end end end end ethon-0.9.0/lib/ethon/errors/multi_timeout.rb0000644000004100000410000000047012734673625021331 0ustar www-datawww-datamodule Ethon module Errors # Raised when multi_timeout failed. class MultiTimeout < EthonError def initialize(code) super("An error occured getting the timeout: #{code}") # "An error occured getting the timeout: #{code}: #{Curl.multi_strerror(code)}" end end end end ethon-0.9.0/lib/ethon/errors/select.rb0000644000004100000410000000031412734673625017705 0ustar www-datawww-datamodule Ethon module Errors # Raised when select failed. class Select < EthonError def initialize(errno) super("An error occured on select: #{errno}") end end end end ethon-0.9.0/lib/ethon/errors/invalid_value.rb0000644000004100000410000000035712734673625021257 0ustar www-datawww-datamodule Ethon module Errors # Raises when option is invalid. class InvalidValue < EthonError def initialize(option, value) super("The value: #{value} is invalid for option: #{option}.") end end end end ethon-0.9.0/lib/ethon/errors/ethon_error.rb0000644000004100000410000000021412734673625020753 0ustar www-datawww-datamodule Ethon module Errors # Default Ethon error class for all custom errors. class EthonError < StandardError end end end ethon-0.9.0/lib/ethon/version.rb0000644000004100000410000000007112734673625016577 0ustar www-datawww-datamodule Ethon # Ethon version. VERSION = '0.9.0' end ethon-0.9.0/lib/ethon/easy/0000755000004100000410000000000012734673625015530 5ustar www-datawww-dataethon-0.9.0/lib/ethon/easy/response_callbacks.rb0000644000004100000410000000505312734673625021715 0ustar www-datawww-datamodule Ethon class Easy # This module contains the logic for the response callbacks. # The on_complete callback is the only one at the moment. # # You can set multiple callbacks, which are then executed # in the same order. # # easy.on_complete { p 1 } # easy.on_complete { p 2 } # easy.complete # #=> 1 # #=> 2 # # You can clear the callbacks: # # easy.on_complete { p 1 } # easy.on_complete { p 2 } # easy.on_complete.clear # easy.on_complete # #=> [] module ResponseCallbacks # Set on_headers callback. # # @example Set on_headers. # request.on_headers { p "yay" } # # @param [ Block ] block The block to execute. def on_headers(&block) @on_headers ||= [] @on_headers << block if block_given? @on_headers end # Execute on_headers callbacks. # # @example Execute on_headers. # request.headers def headers if defined?(@on_headers) and not @on_headers.nil? @on_headers.each{ |callback| callback.call(self) } end end # Set on_complete callback. # # @example Set on_complete. # request.on_complete { p "yay" } # # @param [ Block ] block The block to execute. def on_complete(&block) @on_complete ||= [] @on_complete << block if block_given? @on_complete end # Execute on_complete callbacks. # # @example Execute on_completes. # request.complete def complete if defined?(@on_complete) and not @on_complete.nil? @on_complete.each{ |callback| callback.call(self) } end end # Set on_body callback. # # @example Set on_body. # request.on_body { |chunk| p "yay" } # # @param [ Block ] block The block to execute. def on_body(&block) @on_body ||= [] @on_body << block if block_given? @on_body end # Execute on_body callbacks. # # @example Execute on_body. # request.body("This data came from HTTP.") # # @return [ Object ] If there are no on_body callbacks, returns the symbol :unyielded. def body(chunk) if defined?(@on_body) and not @on_body.nil? result = nil @on_body.each do |callback| result = callback.call(chunk, self) break if result == :abort end result else :unyielded end end end end end ethon-0.9.0/lib/ethon/easy/form.rb0000644000004100000410000000630712734673625017026 0ustar www-datawww-datarequire 'ethon/easy/util' require 'ethon/easy/queryable' module Ethon class Easy # This class represents a form and is used to send a payload in the # request body via POST/PUT. # It handles multipart forms, too. # # @api private class Form include Ethon::Easy::Util include Ethon::Easy::Queryable # Return a new Form. # # @example Return a new Form. # Form.new({}) # # @param [ Hash ] params The parameter with which to initialize the form. # # @return [ Form ] A new Form. def initialize(easy, params) @easy = easy @params = params || {} end # Return a pointer to the first form element in libcurl. # # @example Return the first form element. # form.first # # @return [ FFI::Pointer ] The first element. def first @first ||= FFI::MemoryPointer.new(:pointer) end # Return a pointer to the last form element in libcurl. # # @example Return the last form element. # form.last # # @return [ FFI::Pointer ] The last element. def last @last ||= FFI::MemoryPointer.new(:pointer) end # Return if form is multipart. The form is multipart # when it contains a file. # # @example Return if form is multipart. # form.multipart? # # @return [ Boolean ] True if form is multipart, else false. def multipart? query_pairs.any?{|pair| pair.respond_to?(:last) && pair.last.is_a?(Array)} end # Add form elements to libcurl. # # @example Add form to libcurl. # form.materialize def materialize query_pairs.each { |pair| form_add(pair.first.to_s, pair.last) } end private def form_add(name, content) case content when Array Curl.formadd(first, last, :form_option, :copyname, :pointer, name, :form_option, :namelength, :long, name.bytesize, :form_option, :file, :string, content[2], :form_option, :filename, :string, content[0], :form_option, :contenttype, :string, content[1], :form_option, :end ) else Curl.formadd(first, last, :form_option, :copyname, :pointer, name, :form_option, :namelength, :long, name.bytesize, :form_option, :copycontents, :pointer, content, :form_option, :contentslength, :long, content ? content.bytesize : 0, :form_option, :end ) end setup_garbage_collection end def setup_garbage_collection # first is a pointer to a pointer. Since it's a MemoryPointer it will # auto clean itself up, but we need to clean up the object it points # to. So this results in (pseudo-c): # form_data_cleanup_handler = *first # curl_form_free(form_data_cleanup_handler) @form_data_cleanup_handler ||= FFI::AutoPointer.new(@first.get_pointer(0), Curl.method(:formfree)) end end end end ethon-0.9.0/lib/ethon/easy/debug_info.rb0000644000004100000410000000163712734673625020165 0ustar www-datawww-datamodule Ethon class Easy # This class is used to store and retreive debug information, # which is only saved when verbose is set to true. # # @api private class DebugInfo MESSAGE_TYPES = Ethon::Curl::DebugInfoType.to_h.keys class Message attr_reader :type, :message def initialize(type, message) @type = type @message = message end end def initialize @messages = [] end def add(type, message) @messages << Message.new(type, message) end def messages_for(type) @messages.select {|m| m.type == type }.map(&:message) end MESSAGE_TYPES.each do |type| eval %Q|def #{type}; messages_for(:#{type}); end| end def to_a @messages.map(&:message) end def to_h Hash[MESSAGE_TYPES.map {|k| [k, send(k)] }] end end end end ethon-0.9.0/lib/ethon/easy/options.rb0000644000004100000410000000166712734673625017562 0ustar www-datawww-datamodule Ethon class Easy # This module contains the logic and knowledge about the # available options on easy. module Options attr_reader :url def url=(value) @url = value Curl.set_option(:url, value, handle) end def escape=( b ) @escape = b end def escape? return true if @escape @escape.nil? ? true : false end Curl.easy_options(nil).each do |opt, props| method_name = "#{opt}=".freeze unless method_defined? method_name define_method(method_name) do |value| Curl.set_option(opt, value, handle) value end end next if props[:type] != :callback || method_defined?(opt) define_method(opt) do |&block| @procs ||= {} @procs[opt.to_sym] = block Curl.set_option(opt, block, handle) nil end end end end end ethon-0.9.0/lib/ethon/easy/queryable.rb0000644000004100000410000000731612734673625020055 0ustar www-datawww-datamodule Ethon class Easy # This module contains logic about building # query parameters for url or form. module Queryable # :nodoc: def self.included(base) base.send(:attr_accessor, :escape) base.send(:attr_accessor, :params_encoding) end # Return wether there are elements in params or not. # # @example Return if params is empty. # form.empty? # # @return [ Boolean ] True if params is empty, else false. def empty? @params.empty? end # Return the string representation of params. # # @example Return string representation. # params.to_s # # @return [ String ] The string representation. def to_s @to_s ||= query_pairs.map{ |pair| return pair if pair.is_a?(String) if escape && @easy pair.map{ |e| @easy.escape(e.to_s) }.join("=") else pair.join("=") end }.join('&') end # Return the query pairs. # # @example Return the query pairs. # params.query_pairs # # @return [ Array ] The query pairs. def query_pairs @query_pairs ||= build_query_pairs(@params) end # Return query pairs build from a hash. # # @example Build query pairs. # action.build_query_pairs({a: 1, b: 2}) # #=> [[:a, 1], [:b, 2]] # # @param [ Hash ] hash The hash to go through. # # @return [ Array ] The array of query pairs. def build_query_pairs(hash) return [hash] if hash.is_a?(String) pairs = [] recursively_generate_pairs(hash, nil, pairs) pairs end # Return file info for a file. # # @example Return file info. # action.file_info(File.open('fubar', 'r')) # # @param [ File ] file The file to handle. # # @return [ Array ] Array of informations. def file_info(file) filename = File.basename(file.path) [ filename, mime_type(filename), File.expand_path(file.path) ] end private def mime_type(filename) if defined?(MIME) && t = MIME::Types.type_for(filename).first t.to_s else 'application/octet-stream' end end def recursively_generate_pairs(h, prefix, pairs) case h when Hash encode_hash_pairs(h, prefix, pairs) when Array if params_encoding == :rack encode_rack_array_pairs(h, prefix, pairs) elsif params_encoding == :multi encode_multi_array_pairs(h, prefix, pairs) else encode_indexed_array_pairs(h, prefix, pairs) end end end def encode_hash_pairs(h, prefix, pairs) h.each_pair do |k,v| key = prefix.nil? ? k : "#{prefix}[#{k}]" pairs_for(v, key, pairs) end end def encode_indexed_array_pairs(h, prefix, pairs) h.each_with_index do |v, i| key = "#{prefix}[#{i}]" pairs_for(v, key, pairs) end end def encode_rack_array_pairs(h, prefix, pairs) h.each do |v| key = "#{prefix}[]" pairs_for(v, key, pairs) end end def encode_multi_array_pairs(h, prefix, pairs) h.each_with_index do |v, i| key = prefix pairs_for(v, key, pairs) end end def pairs_for(v, key, pairs) case v when Hash, Array recursively_generate_pairs(v, key, pairs) when File, Tempfile pairs << [key, file_info(v)] else pairs << [key, v] end end end end end ethon-0.9.0/lib/ethon/easy/header.rb0000644000004100000410000000312412734673625017305 0ustar www-datawww-datamodule Ethon class Easy # This module contains the logic around adding headers to libcurl. # # @api private module Header # Return headers, return empty hash if none. # # @example Return the headers. # easy.headers # # @return [ Hash ] The headers. def headers @headers ||= {} end # Set the headers. # # @example Set the headers. # easy.headers = {'User-Agent' => 'ethon'} # # @param [ Hash ] headers The headers. def headers=(headers) headers ||= {} header_list = nil headers.each do |k, v| header_list = Curl.slist_append(header_list, compose_header(k,v)) end Curl.set_option(:httpheader, header_list, handle) @header_list = header_list && FFI::AutoPointer.new(header_list, Curl.method(:slist_free_all)) end # Return header_list. # # @example Return header_list. # easy.header_list # # @return [ FFI::Pointer ] The header list. def header_list @header_list end # Compose libcurl header string from key and value. # Also replaces null bytes, because libcurl will complain # otherwise. # # @example Compose header. # easy.compose_header('User-Agent', 'Ethon') # # @param [ String ] key The header name. # @param [ String ] value The header value. # # @return [ String ] The composed header. def compose_header(key, value) Util.escape_zero_byte("#{key}: #{value}") end end end end ethon-0.9.0/lib/ethon/easy/informations.rb0000644000004100000410000000717012734673625020572 0ustar www-datawww-datamodule Ethon class Easy # This module contains the methods to return informations # from the easy handle. See http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html # for more information. module Informations # Holds available informations and their type, which is needed to # request the informations from libcurl. AVAILABLE_INFORMATIONS = { # Return the available HTTP auth methods. :httpauth_avail => :long, # Return the total time in seconds for the previous # transfer, including name resolution, TCP connection, etc. :total_time => :double, # Return the time, in seconds, it took from the start # until the first byte was received by libcurl. This # includes pre-transfer time and also the time the # server needs to calculate the result. :starttransfer_time => :double, # Return 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 pre-transfer time can be delayed # due to waits in line for the pipeline and more. :appconnect_time => :double, # Return the time, in seconds, it took from the start # until the file transfer was just about to begin. This # includes all pre-transfer commands and negotiations # that are specific to the particular protocol(s) involved. # It does not involve the sending of the protocol- # specific request that triggers a transfer. :pretransfer_time => :double, # Return the time, in seconds, it took from the start # until the connect to the remote host (or proxy) was completed. :connect_time => :double, # Return the time, in seconds, it took from the # start until the name resolution was completed. :namelookup_time => :double, # Return the time, in seconds, it took for all redirection steps # include name lookup, connect, pretransfer and transfer before the # final transaction was started. time_redirect shows the complete # execution time for multiple redirections. (Added in 7.12.3) :redirect_time => :double, # Return the last used effective url. :effective_url => :string, # Return the string holding the IP address of the most recent # connection done with this curl handle. This string # may be IPv6 if that's enabled. :primary_ip => :string, # Return the last received HTTP, FTP or SMTP response code. # The value 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. :response_code => :long, :request_size => :long, # Return the total number of redirections that were # actually followed. :redirect_count => :long } AVAILABLE_INFORMATIONS.each do |name, type| eval %Q|def #{name}; Curl.send(:get_info_#{type}, :#{name}, handle); end| end # Returns true if this curl version supports zlib. # # @example Return wether zlib is supported. # easy.supports_zlib? # # @return [ Boolean ] True if supported, else false. # @deprecated Please use the static version instead def supports_zlib? Kernel.warn("Ethon: Easy#supports_zlib? is deprecated and will be removed, please use Easy#.") Easy.supports_zlib? end end end end ethon-0.9.0/lib/ethon/easy/operations.rb0000644000004100000410000000312212734673625020236 0ustar www-datawww-datamodule Ethon class Easy # This module contains the logic to prepare and perform # an easy. module Operations # Returns a pointer to the curl easy handle. # # @example Return the handle. # easy.handle # # @return [ FFI::Pointer ] A pointer to the curl easy handle. def handle @handle ||= FFI::AutoPointer.new(Curl.easy_init, Curl.method(:easy_cleanup)) end # Sets a pointer to the curl easy handle. # @param [ ::FFI::Pointer ] Easy handle that will be assigned. def handle=(h) @handle = h end # Perform the easy request. # # @example Perform the request. # easy.perform # # @return [ Integer ] The return code. def perform @return_code = Curl.easy_perform(handle) if Ethon.logger.level.zero? Ethon.logger.debug { "ETHON: performed #{log_inspect}" } end complete @return_code end # Clean up the easy. # # @example Perform clean up. # easy.cleanup # # @return the result of the free which is nil def cleanup handle.free end # Prepare the easy. Options, headers and callbacks # were set. # # @example Prepare easy. # easy.prepare # # @deprecated It is no longer necessary to call prepare. def prepare Ethon.logger.warn( "ETHON: It is no longer necessary to call "+ "Easy#prepare. It's going to be removed "+ "in future versions." ) end end end end ethon-0.9.0/lib/ethon/easy/http.rb0000644000004100000410000000411712734673625017037 0ustar www-datawww-datarequire 'ethon/easy/http/actionable' require 'ethon/easy/http/post' require 'ethon/easy/http/get' require 'ethon/easy/http/head' require 'ethon/easy/http/put' require 'ethon/easy/http/delete' require 'ethon/easy/http/patch' require 'ethon/easy/http/options' require 'ethon/easy/http/custom' module Ethon class Easy # This module contains logic about making valid HTTP requests. module Http # Set specified options in order to make a HTTP request. # Look at {Ethon::Easy::Options Options} to see what you can # provide in the options hash. # # @example Set options for HTTP request. # easy.http_request("www.google.com", :get, {}) # # @param [ String ] url The url. # @param [ String ] action_name The HTTP action name. # @param [ Hash ] options The options hash. # # @option options :params [ Hash ] Params hash which # is attached to the url. # @option options :body [ Hash ] Body hash which # becomes the request body. It is a PUT body for # PUT requests and a POST for everything else. # @option options :headers [ Hash ] Request headers. # # @return [ void ] # # @see Ethon::Easy::Options def http_request(url, action_name, options = {}) fabricate(url, action_name, options).setup(self) end private # Return the corresponding action class. # # @example Return the action. # Action.fabricate(:get) # Action.fabricate(:smash) # # @param [ String ] url The url. # @param [ String ] action_name The HTTP action name. # @param [ Hash ] options The option hash. # # @return [ Easy::Ethon::Actionable ] The request instance. def fabricate(url, action_name, options) constant_name = action_name.to_s.capitalize if Ethon::Easy::Http.const_defined?(constant_name) Ethon::Easy::Http.const_get(constant_name).new(url, options) else Ethon::Easy::Http::Custom.new(constant_name.upcase, url, options) end end end end end ethon-0.9.0/lib/ethon/easy/callbacks.rb0000644000004100000410000000727012734673625020002 0ustar www-datawww-datamodule Ethon class Easy # This module contains all the logic around the callbacks, # which are needed to interact with libcurl. # # @api private module Callbacks # :nodoc: def self.included(base) base.send(:attr_accessor, *[:response_body, :response_headers, :debug_info]) end # Set writefunction and headerfunction callback. # They are called by libcurl in order to provide the header and # the body from the request. # # @example Set callbacks. # easy.set_callbacks def set_callbacks Curl.set_option(:writefunction, body_write_callback, handle) Curl.set_option(:headerfunction, header_write_callback, handle) Curl.set_option(:debugfunction, debug_callback, handle) @response_body = "" @response_headers = "" @headers_called = false @debug_info = Ethon::Easy::DebugInfo.new end # Returns the body write callback. # # @example Return the callback. # easy.body_write_callback # # @return [ Proc ] The callback. def body_write_callback @body_write_callback ||= proc {|stream, size, num, object| unless @headers_called @headers_called = true headers end result = body(chunk = stream.read_string(size * num)) @response_body << chunk if result == :unyielded result != :abort ? size*num : -1 } end # Returns the header write callback. # # @example Return the callback. # easy.header_write_callback # # @return [ Proc ] The callback. def header_write_callback @header_write_callback ||= proc {|stream, size, num, object| @response_headers << stream.read_string(size * num) size * num } end # Returns the debug callback. This callback is currently used # write the raw http request headers. # # @example Return the callback. # easy.body_write_callback # # @return [ Proc ] The callback. def debug_callback @debug_callback ||= proc {|handle, type, data, size, udata| message = data.read_string(size) @debug_info.add type, message print message unless [:data_in, :data_out].include?(type) 0 } end # Set the read callback. This callback is used by libcurl to # read data when performing a PUT request. # # @example Set the callback. # easy.set_read_callback("a=1") # # @param [ String ] body The body. def set_read_callback(body) @request_body_read = 0 readfunction do |stream, size, num, object| size = size * num body_size = if body.respond_to?(:bytesize) body.bytesize elsif body.respond_to?(:size) body.size elsif body.is_a?(File) File.size(body.path) end left = body_size - @request_body_read size = left if size > left if size > 0 chunk = if body.respond_to?(:byteslice) body.byteslice(@request_body_read, size) elsif body.respond_to?(:read) body.read(size) else body[@request_body_read, size] end stream.write_string( chunk, size ) @request_body_read += size end size end end # Returns the body read callback. # # @example Return the callback. # easy.read_callback # # @return [ Proc ] The callback. def read_callback @read_callback end end end end ethon-0.9.0/lib/ethon/easy/features.rb0000644000004100000410000000144512734673625017677 0ustar www-datawww-datamodule Ethon class Easy # This module contains class methods for feature checks module Features # Returns true if this curl version supports zlib. # # @example Return wether zlib is supported. # Ethon::Easy.supports_zlib? # # @return [ Boolean ] True if supported, else false. def supports_zlib? !!(Curl.version_info[:features] & Curl::VERSION_LIBZ) end # Returns true if this curl version supports AsynchDNS. # # @example # Ethon::Easy.supports_asynch_dns? # # @return [ Boolean ] True if supported, else false. def supports_asynch_dns? !!(Curl.version_info[:features] & Curl::VERSION_ASYNCHDNS) end alias :supports_timeout_ms? :supports_asynch_dns? end end end ethon-0.9.0/lib/ethon/easy/http/0000755000004100000410000000000012734673625016507 5ustar www-datawww-dataethon-0.9.0/lib/ethon/easy/http/custom.rb0000644000004100000410000000117012734673625020345 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making requests for custom HTTP verbs. class Custom include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable def initialize(verb, url, options) @verb = verb super(url, options) end # Setup easy to make a request. # # @example Setup. # custom.set_params(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.customrequest = @verb end end end end end ethon-0.9.0/lib/ethon/easy/http/putable.rb0000644000004100000410000000113512734673625020470 0ustar www-datawww-datamodule Ethon class Easy module Http # This module contains logic about setting up a PUT body. module Putable # Set things up when form is provided. # Deals with multipart forms. # # @example Setup. # put.set_form(easy) # # @param [ Easy ] easy The easy to setup. def set_form(easy) easy.upload = true form.escape = true form.params_encoding = params_encoding easy.infilesize = form.to_s.bytesize easy.set_read_callback(form.to_s) end end end end end ethon-0.9.0/lib/ethon/easy/http/post.rb0000644000004100000410000000107712734673625020026 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making POST requests. class Post include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a POST request. # # @example Setup. # post.setup(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super if form.empty? easy.postfieldsize = 0 easy.copypostfields = "" end end end end end end ethon-0.9.0/lib/ethon/easy/http/options.rb0000644000004100000410000000100612734673625020524 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making OPTIONS requests. class Options include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a OPTIONS request. # # @example Setup. # options.setup(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.customrequest = "OPTIONS" end end end end end ethon-0.9.0/lib/ethon/easy/http/actionable.rb0000644000004100000410000000756212734673625021147 0ustar www-datawww-datarequire 'ethon/easy/http/putable' require 'ethon/easy/http/postable' module Ethon class Easy module Http # This module represents a Http Action and is a factory # for more real actions like GET, HEAD, POST and PUT. module Actionable QUERY_OPTIONS = [ :params, :body, :params_encoding ] # Create a new action. # # @example Create a new action. # Action.new("www.example.com", {}) # # @param [ String ] url The url. # @param [ Hash ] options The options. # # @return [ Action ] A new action. def initialize(url, options) @url = url @options, @query_options = parse_options(options) end # Return the url. # # @example Return url. # action.url # # @return [ String ] The url. def url @url end # Return the options hash. # # @example Return options. # action.options # # @return [ Hash ] The options. def options @options end # Returns the query options hash. # # @example Return query options. # action.query_options # # @return [ Hash ] The query options. def query_options @query_options end # Return the params. # # @example Return params. # action.params # # @return [ Params ] The params. def params @params ||= Params.new(@easy, query_options.fetch(:params, nil)) end # Return the form. # # @example Return form. # action.form # # @return [ Form ] The form. def form @form ||= Form.new(@easy, query_options.fetch(:body, nil)) end # Get the requested array encoding. By default it's # :typhoeus, but it can also be set to :rack. # # @example Get encoding from options # action.params_encoding # def params_encoding @params_encoding ||= query_options.fetch(:params_encoding, :typhoeus) end # Setup everything necessary for a proper request. # # @example setup. # action.setup(easy) # # @param [ easy ] easy the easy to setup. def setup(easy) @easy = easy # Order is important, @easy will be used to provide access to options # relevant to the following operations (like whether or not to escape # values). easy.set_attributes(options) set_form(easy) unless form.empty? if params.empty? easy.url = url else set_params(easy) end end # Setup request with params. # # @example Setup nothing. # action.set_params(easy) # # @param [ Easy ] easy The easy to setup. def set_params(easy) params.escape = easy.escape? params.params_encoding = params_encoding base_url, base_params = url.split('?') base_url << '?' base_url << base_params.to_s base_url << '&' if base_params base_url << params.to_s easy.url = base_url end # Setup request with form. # # @example Setup nothing. # action.set_form(easy) # # @param [ Easy ] easy The easy to setup. def set_form(easy) end private def parse_options(options) query_options = {} options = options.dup QUERY_OPTIONS.each do |query_option| if options.key?(query_option) query_options[query_option] = options.delete(query_option) end end return options, query_options end end end end end ethon-0.9.0/lib/ethon/easy/http/head.rb0000644000004100000410000000076212734673625017742 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making HEAD requests. class Head include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a HEAD request. # # @example Setup. # get.set_params(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.nobody = true end end end end end ethon-0.9.0/lib/ethon/easy/http/get.rb0000644000004100000410000000101212734673625017605 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making GET requests. class Get include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a GET request. # # @example Setup. # get.set_params(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.customrequest = "GET" unless form.empty? end end end end end ethon-0.9.0/lib/ethon/easy/http/delete.rb0000644000004100000410000000102012734673625020267 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making DELETE requests. class Delete include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a DELETE request. # # @example Setup customrequest. # delete.setup(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.customrequest = "DELETE" end end end end end ethon-0.9.0/lib/ethon/easy/http/put.rb0000644000004100000410000000106212734673625017643 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making PUT requests. class Put include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Putable # Setup easy to make a PUT request. # # @example Setup. # put.setup(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super if form.empty? easy.upload = true easy.infilesize = 0 end end end end end end ethon-0.9.0/lib/ethon/easy/http/patch.rb0000644000004100000410000000077412734673625020143 0ustar www-datawww-datamodule Ethon class Easy module Http # This class knows everything about making PATCH requests. class Patch include Ethon::Easy::Http::Actionable include Ethon::Easy::Http::Postable # Setup easy to make a PATCH request. # # @example Setup. # patch.setup(easy) # # @param [ Easy ] easy The easy to setup. def setup(easy) super easy.customrequest = "PATCH" end end end end end ethon-0.9.0/lib/ethon/easy/http/postable.rb0000644000004100000410000000144312734673625020647 0ustar www-datawww-datamodule Ethon class Easy module Http # This module contains logic for setting up a [multipart] POST body. module Postable # Set things up when form is provided. # Deals with multipart forms. # # @example Setup. # post.set_form(easy) # # @param [ Easy ] easy The easy to setup. def set_form(easy) easy.url ||= url form.params_encoding = params_encoding if form.multipart? form.escape = false form.materialize easy.httppost = form.first.read_pointer else form.escape = easy.escape? easy.postfieldsize = form.to_s.bytesize easy.copypostfields = form.to_s end end end end end end ethon-0.9.0/lib/ethon/easy/util.rb0000644000004100000410000000114412734673625017032 0ustar www-datawww-datamodule Ethon class Easy # :nodoc: # This module contains small helpers. # # @api private module Util # Escapes zero bytes in strings. # # @example Escape zero bytes. # Util.escape_zero_byte("1\0") # #=> "1\\0" # # @param [ Object ] value The value to escape. # # @return [ String, Object ] Escaped String if # zero byte found, original object if not. def escape_zero_byte(value) return value unless value.to_s.include?(0.chr) value.to_s.gsub(0.chr, '\\\0') end extend self end end end ethon-0.9.0/lib/ethon/easy/mirror.rb0000644000004100000410000000154312734673625017372 0ustar www-datawww-datamodule Ethon class Easy class Mirror attr_reader :options alias_method :to_hash, :options INFORMATIONS_TO_MIRROR = Informations::AVAILABLE_INFORMATIONS.keys + [:return_code, :response_headers, :response_body, :debug_info] INFORMATIONS_TO_LOG = [:effective_url, :response_code, :return_code, :total_time] def self.from_easy(easy) options = {} INFORMATIONS_TO_MIRROR.each do |info| options[info] = easy.send(info) end new(options) end def initialize(options = {}) @options = options end def log_informations Hash[*INFORMATIONS_TO_LOG.map do |info| [info, options[info]] end.flatten] end INFORMATIONS_TO_MIRROR.each do |info| eval %Q|def #{info}; options[#{info}]; end| end end end end ethon-0.9.0/lib/ethon/easy/params.rb0000644000004100000410000000107412734673625017342 0ustar www-datawww-datarequire 'ethon/easy/util' require 'ethon/easy/queryable' module Ethon class Easy # This class represents HTTP request parameters. # # @api private class Params include Ethon::Easy::Util include Ethon::Easy::Queryable # Create a new Params. # # @example Create a new Params. # Params.new({}) # # @param [ Hash ] params The params to use. # # @return [ Params ] A new Params. def initialize(easy, params) @easy = easy @params = params || {} end end end end ethon-0.9.0/lib/ethon/curls/0000755000004100000410000000000012734673625015717 5ustar www-datawww-dataethon-0.9.0/lib/ethon/curls/infos.rb0000644000004100000410000001371712734673625017373 0ustar www-datawww-datamodule Ethon module Curls # This module contains logic for the available informations # on an easy, eg.: connect_time. module Infos # Return info types. # # @example Return info types. # Ethon::Curl.info_types # # @return [ Hash ] The info types. def info_types { :string =>0x100000, :long => 0x200000, :double =>0x300000, :slist => 0x400000 } end # http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTDEBUGFUNCTION # https://github.com/bagder/curl/blob/master/include/curl/curl.h#L378 # # @example Return debug info types. # Ethon::Curl.debug_info_types # # @return [ Hash ] The info types available to curl_debug_callback. def debug_info_types [ :text, 0, :header_in, :header_out, :data_in, :data_out, :ssl_data_in, :ssl_data_out ] end # Return Info details, refer # https://github.com/bagder/curl/blob/master/src/tool_writeout.c#L66 for details # # @example Return infos. # Ethon::Curl.infos # # @return [ Hash ] The infos. def infos { :effective_url => info_types[:string] + 1, :response_code => info_types[:long] + 2, :total_time => info_types[:double] + 3, :namelookup_time => info_types[:double] + 4, :connect_time => info_types[:double] + 5, :pretransfer_time => info_types[:double] + 6, :size_upload => info_types[:double] + 7, :size_download => info_types[:double] + 8, :speed_download => info_types[:double] + 9, :speed_upload => info_types[:double] + 10, :header_size => info_types[:long] + 11, :request_size => info_types[:long] + 12, :ssl_verifyresult => info_types[:long] + 13, :filetime => info_types[:long] + 14, :content_length_download =>info_types[:double] + 15, :content_length_upload => info_types[:double] + 16, :starttransfer_time => info_types[:double] + 17, :content_type => info_types[:string] + 18, :redirect_time => info_types[:double] + 19, :redirect_count => info_types[:long] + 20, :private => info_types[:string] + 21, :http_connectcode => info_types[:long] + 22, :httpauth_avail => info_types[:long] + 23, :proxyauth_avail => info_types[:long] + 24, :os_errno => info_types[:long] + 25, :num_connects => info_types[:long] + 26, :ssl_engines => info_types[:slist] + 27, :cookielist => info_types[:slist] + 28, :lastsocket => info_types[:long] + 29, :ftp_entry_path => info_types[:string] + 30, :redirect_url => info_types[:string] + 31, :primary_ip => info_types[:string] + 32, :appconnect_time => info_types[:double] + 33, :certinfo => info_types[:slist] + 34, :condition_unmet => info_types[:long] + 35, :rtsp_session_id => info_types[:string] + 36, :rtsp_client_cseq => info_types[:long] + 37, :rtsp_server_cseq => info_types[:long] + 38, :rtsp_cseq_recv => info_types[:long] + 39, :primary_port => info_types[:long] + 40, :local_ip => info_types[:string] + 41, :local_port => info_types[:long] + 42, :last =>42 } end # Return info as string. # # @example Return info. # Curl.get_info_string(:primary_ip, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ String ] The info. def get_info_string(option, handle) if easy_getinfo(handle, option, string_ptr) == :ok ptr=string_ptr.read_pointer ptr.null? ? nil : ptr.read_string end end # Return info as integer. # # @example Return info. # Curl.get_info_long(:response_code, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ Integer ] The info. def get_info_long(option, handle) if easy_getinfo(handle, option, long_ptr) == :ok long_ptr.read_long end end # Return info as float # # @example Return info. # Curl.get_info_double(:response_code, easy) # # @param [ Symbol ] option The option name. # @param [ ::FFI::Pointer ] handle The easy handle. # # @return [ Float ] The info. def get_info_double(option, handle) if easy_getinfo(handle, option, double_ptr) == :ok double_ptr.read_double end end # Return a string pointer. # # @example Return a string pointer. # Curl.string_ptr # # @return [ ::FFI::Pointer ] The string pointer. def string_ptr @string_ptr ||= ::FFI::MemoryPointer.new(:pointer) end # Return a long pointer. # # @example Return a long pointer. # Curl.long_ptr # # @return [ ::FFI::Pointer ] The long pointer. def long_ptr @long_ptr ||= ::FFI::MemoryPointer.new(:long) end # Return a double pointer. # # @example Return a double pointer. # Curl.double_ptr # # @return [ ::FFI::Pointer ] The double pointer. def double_ptr @double_ptr ||= ::FFI::MemoryPointer.new(:double) end end end end ethon-0.9.0/lib/ethon/curls/form_options.rb0000644000004100000410000000141412734673625020762 0ustar www-datawww-datamodule Ethon module Curls # This module contains the available options for forms. module FormOptions # Form options, used by FormAdd for temporary storage, refer # https://github.com/bagder/curl/blob/master/lib/formdata.h#L51 for details def form_options [ :none, :copyname, :ptrname, :namelength, :copycontents, :ptrcontents, :contentslength, :filecontent, :array, :obsolete, :file, :buffer, :bufferptr, :bufferlength, :contenttype, :contentheader, :filename, :end, :obsolete2, :stream, :last ] end end end end ethon-0.9.0/lib/ethon/curls/options.rb0000644000004100000410000005114712734673625017747 0ustar www-datawww-datamodule Ethon module Curls # This module contains logic for setting options on # easy or multi interface. module Options OPTION_STRINGS = { :easy => 'easy_options', :multi => 'multi_options' }.freeze FOPTION_STRINGS = { :easy => 'EASY_OPTIONS', :multi => 'MULTI_OPTIONS' }.freeze FTYPES = [:long, :string, :ffipointer, :callback, :debug_callback, :off_t] FUNCS = Hash[*[:easy, :multi].zip([:easy, :multi].map { |t| Hash[*FTYPES.zip(FTYPES.map { |ft| "#{t}_setopt_#{ft}" }).flatten] }).flatten] # Sets appropriate option for easy, depending on value type. def set_option(option, value, handle, type = :easy) type = type.to_sym unless type.is_a?(Symbol) raise NameError, "Ethon::Curls::Options unknown type #{type}." unless respond_to?(OPTION_STRINGS[type]) opthash=send(OPTION_STRINGS[type], nil) raise Errors::InvalidOption.new(option) unless opthash.include?(option) case opthash[option][:type] when :none return if value.nil? value=1 func=:long when :int return if value.nil? func=:long value=value.to_i when :bool return if value.nil? func=:long value=(value&&value!=0) ? 1 : 0 when :time return if value.nil? func=:long value=value.to_i when :enum return if value.nil? func=:long value = case value when Symbol opthash[option][:opts][value] when String opthash[option][:opts][value.to_sym] end value = value.to_i when :bitmask return if value.nil? func=:long value = case value when Symbol opthash[option][:opts][value] when Array value.inject(0) { |res,v| res|opthash[option][:opts][v] } end value = value.to_i when :string func=:string value=value.to_s unless value.nil? when :string_as_pointer func = :ffipointer s = '' s = value.to_s unless value.nil? value = FFI::MemoryPointer.new(:char, s.bytesize) value.put_bytes(0, s) when :string_escape_null func=:string value=Util.escape_zero_byte(value) unless value.nil? when :ffipointer func=:ffipointer raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? FFI::Pointer when :curl_slist func=:ffipointer raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? FFI::Pointer when :buffer raise NotImplementedError, "Ethon::Curls::Options option #{option} buffer type not implemented." when :dontuse_object raise NotImplementedError, "Ethon::Curls::Options option #{option} type not implemented." when :cbdata raise NotImplementedError, "Ethon::Curls::Options option #{option} callback data type not implemented. Use Ruby closures." when :callback func=:callback raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc when :debug_callback func=:debug_callback raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc when :off_t return if value.nil? func=:off_t value=value.to_i end if func==:long or func==:off_t then bits=FFI.type_size(:long)*8 if func==:long bits=FFI.type_size(:int64)*8 if func==:off_t tv=((value<0) ? value.abs-1 : value) raise Errors::InvalidValue.new(option,value) unless tv<(1< 0, :objectpoint => 10000, :functionpoint => 20000, :off_t => 30000 } OPTION_TYPE_MAP = { :none => :long, :int => :long, :bool => :long, :time => :long, :enum => :long, # Two ways to specify values (as opts parameter): # * Array of symbols, these will number sequentially # starting at 0. Skip elements with nil. (see :netrc) # * Hash of :symbol => enum_value (See :proxytype) :bitmask => :long, # Three ways to specify values (as opts parameter): # * Hash of :symbol => bitmask_value or Array. # An Array can be an array of already defined # Symbols, which represents a bitwise or of those # symbols. (See :httpauth) # * Array of symbols, these will number the bits # sequentially (i.e. 0, 1, 2, 4, etc.). Skip # elements with nil. The last element can be a # Hash, which will be interpreted as above. # (See :protocols) # :all defaults to all bits set :string => :objectpoint, :string_escape_null => :objectpoint, :string_as_pointer => :objectpoint, :ffipointer => :objectpoint, # FFI::Pointer :curl_slist => :objectpoint, :buffer => :objectpoint, # A memory buffer of size defined in the options :dontuse_object => :objectpoint, # An object we don't support (e.g. FILE*) :cbdata => :objectpoint, :callback => :functionpoint, :debug_callback => :functionpoint, :off_t => :off_t, } def self.option(ftype,name,type,num,opts=nil) case type when :enum if opts.is_a? Array then opts=Hash[opts.each_with_index.to_a] elsif not opts.is_a? Hash then raise TypeError, "Ethon::Curls::Options #{ftype} #{name} Expected opts to be an Array or a Hash." end when :bitmask if opts.is_a? Array then if opts.last.is_a? Hash then hopts=opts.pop else hopts={} end opts.each_with_index do |v,i| next if v.nil? if i==0 then hopts[v]=0 else hopts[v]=1<<(i-1) end end opts=hopts elsif not opts.is_a? Hash then raise TypeError, "Ethon::Curls::Options #{ftype} #{name} Expected opts to be an Array or a Hash." end opts[:all]=-1 unless opts.include? :all opts.each do |k,v| if v.is_a? Array then opts[k]=v.map { |b| opts[b] }.inject :| end end when :buffer raise TypeError, "Ethon::Curls::Options #{ftype} #{name} Expected opts to be an Array or a Hash." unless opts.is_a? Integer else raise ArgumentError, "Ethon::Curls::Options #{ftype} #{name} Expected no opts." unless opts.nil? end opthash=const_get(FOPTION_STRINGS[ftype]) opthash[name] = { :type => type, :opt => OPTION_TYPE_BASE[OPTION_TYPE_MAP[type]] + num, :opts => opts } end def self.option_alias(ftype,name,*aliases) opthash=const_get(FOPTION_STRINGS[ftype]) aliases.each { |a| opthash[a]=opthash[name] } end def self.option_type(type) cname = FOPTION_STRINGS[type] const_set(cname, {}) define_method(OPTION_STRINGS[type]) do |rt| return Ethon::Curls::Options.const_get(cname).map { |k, v| [k, v[:opt]] } if rt == :enum Ethon::Curls::Options.const_get(cname) end end # Curl multi options, refer # Defined @ https://github.com/bagder/curl/blob/master/include/curl/multi.h # Documentation @ http://curl.haxx.se/libcurl/c/curl_multi_setopt.html option_type :multi option :multi, :socketfunction, :callback, 1 option :multi, :socketdata, :cbdata, 2 option :multi, :pipelining, :bool, 3 option :multi, :timerfunction, :callback, 4 option :multi, :timerdata, :cbdata, 5 option :multi, :maxconnects, :int, 6 option :multi, :max_host_connections, :int, 7 option :multi, :max_pipeline_length, :int, 8 option :multi, :content_length_penalty_size, :off_t, 9 option :multi, :chunk_length_penalty_size, :off_t, 10 option :multi, :pipelining_site_bl, :dontuse_object, 11 option :multi, :pipelining_server_bl, :dontuse_object, 12 option :multi, :max_total_connections, :int, 3 # Curl easy options # Defined @ https://github.com/bagder/curl/blob/master/include/curl/curl.h # Documentation @ http://curl.haxx.se/libcurl/c/curl_easy_setopt.html ## BEHAVIOR OPTIONS option_type :easy option :easy, :verbose, :bool, 41 option :easy, :header, :bool, 42 option :easy, :noprogress, :bool, 43 option :easy, :nosignal, :bool, 99 option :easy, :wildcardmatch, :bool, 197 ## CALLBACK OPTIONS option :easy, :writefunction, :callback, 11 option :easy, :file, :cbdata, 1 option_alias :easy, :file, :writedata option :easy, :readfunction, :callback, 12 option :easy, :infile, :cbdata, 9 option_alias :easy, :infile, :readdata option :easy, :ioctlfunction, :callback, 130 option :easy, :ioctldata, :cbdata, 131 option :easy, :seekfunction, :callback, 167 option :easy, :seekdata, :cbdata, 168 option :easy, :sockoptfunction, :callback, 148 option :easy, :sockoptdata, :cbdata, 149 option :easy, :opensocketfunction, :callback, 163 option :easy, :opensocketdata, :cbdata, 164 option :easy, :closesocketfunction, :callback, 208 option :easy, :closesocketdata, :cbdata, 209 option :easy, :progressfunction, :callback, 56 option :easy, :progressdata, :cbdata, 57 option :easy, :headerfunction, :callback, 79 option :easy, :writeheader, :cbdata, 29 option_alias :easy, :writeheader, :headerdata option :easy, :debugfunction, :debug_callback, 94 option :easy, :debugdata, :cbdata, 95 option :easy, :ssl_ctx_function, :callback, 108 option :easy, :ssl_ctx_data, :cbdata, 109 option :easy, :conv_to_network_function, :callback, 143 option :easy, :conv_from_network_function, :callback, 142 option :easy, :conv_from_utf8_function, :callback, 144 option :easy, :interleavefunction, :callback, 196 option :easy, :interleavedata, :cbdata, 195 option :easy, :chunk_bgn_function, :callback, 198 option :easy, :chunk_end_function, :callback, 199 option :easy, :chunk_data, :cbdata, 201 option :easy, :fnmatch_function, :callback, 200 option :easy, :fnmatch_data, :cbdata, 202 ## ERROR OPTIONS option :easy, :errorbuffer, :buffer, 10, 256 option :easy, :stderr, :dontuse_object, 37 option :easy, :failonerror, :bool, 45 ## NETWORK OPTIONS option :easy, :url, :string, 2 option :easy, :protocols, :bitmask, 181, [nil, :http, :https, :ftp, :ftps, :scp, :sftp, :telnet, :ldap, :ldaps, :dict, :file, :tftp, :imap, :imaps, :pop3, :pop3s, :smtp, :smtps, :rtsp, :rtmp, :rtmpt, :rtmpe, :rtmpte, :rtmps, :rtmpts, :gopher] option :easy, :redir_protocols, :bitmask, 182, [nil, :http, :https, :ftp, :ftps, :scp, :sftp, :telnet, :ldap, :ldaps, :dict, :file, :tftp, :imap, :imaps, :pop3, :pop3s, :smtp, :smtps, :rtsp, :rtmp, :rtmpt, :rtmpe, :rtmpte, :rtmps, :rtmpts, :gopher] option :easy, :proxy, :string, 4 option :easy, :proxyport, :int, 59 option :easy, :proxytype, :enum, 101, [:http, :http_1_0, nil, nil, :socks4, :socks5, :socks4a, :socks5_hostname] option :easy, :noproxy, :string, 177 option :easy, :httpproxytunnel, :bool, 61 option :easy, :socks5_gssapi_service, :string, 179 option :easy, :socks5_gssapi_nec, :bool, 180 option :easy, :interface, :string, 62 option :easy, :localport, :int, 139 option :easy, :localportrange, :int, 140 option :easy, :dns_cache_timeout, :int, 92 option :easy, :dns_use_global_cache, :bool, 91 # Obsolete option :easy, :buffersize, :int, 98 option :easy, :port, :int, 3 option :easy, :tcp_nodelay, :bool, 121 option :easy, :address_scope, :int, 171 option :easy, :tcp_keepalive, :bool, 213 option :easy, :tcp_keepidle, :int, 214 option :easy, :tcp_keepintvl, :int, 215 ## NAMES and PASSWORDS OPTIONS (Authentication) option :easy, :netrc, :enum, 51, [:ignored, :optional, :required] option :easy, :netrc_file, :string, 118 option :easy, :userpwd, :string, 5 option :easy, :proxyuserpwd, :string, 6 option :easy, :username, :string, 173 option :easy, :password, :string, 174 option :easy, :proxyusername, :string, 175 option :easy, :proxypassword, :string, 176 option :easy, :httpauth, :bitmask, 107, [:none, :basic, :digest, :gssnegotiate, :ntlm, :digest_ie, :ntlm_wb, {:only => 1<<31, :any => ~0x10, :anysafe => ~0x11, :auto => 0x1f}] option :easy, :tlsauth_type, :enum, 206, [:none, :srp] option :easy, :tlsauth_username, :string, 204 option :easy, :tlsauth_password, :string, 205 option :easy, :proxyauth, :bitmask, 111, [:none, :basic, :digest, :gssnegotiate, :ntlm, :digest_ie, :ntlm_wb, {:only => 1<<31, :any => ~0x10, :anysafe => ~0x11, :auto => 0x1f}] option :easy, :sasl_ir, :bool, 218 ## HTTP OPTIONS option :easy, :autoreferer, :bool, 58 option :easy, :accept_encoding, :string, 102 option_alias :easy, :accept_encoding, :encoding option :easy, :transfer_encoding, :bool, 207 option :easy, :followlocation, :bool, 52 option :easy, :unrestricted_auth, :bool, 105 option :easy, :maxredirs, :int, 68 option :easy, :postredir, :bitmask, 161, [:get_all, :post_301, :post_302, :post_303, {:post_all => [:post_301, :post_302, :post_303]}] option_alias :easy, :postredir, :post301 option :easy, :put, :bool, 54 option :easy, :post, :bool, 47 option :easy, :postfields, :string, 15 option :easy, :postfieldsize, :int, 60 option :easy, :postfieldsize_large, :off_t, 120 option :easy, :copypostfields, :string_as_pointer, 165 option :easy, :httppost, :ffipointer, 24 option :easy, :referer, :string, 16 option :easy, :useragent, :string, 18 option :easy, :httpheader, :curl_slist, 23 option :easy, :http200aliases, :curl_slist, 104 option :easy, :cookie, :string, 22 option :easy, :cookiefile, :string, 31 option :easy, :cookiejar, :string, 82 option :easy, :cookiesession, :bool, 96 option :easy, :cookielist, :string, 135 option :easy, :httpget, :bool, 80 option :easy, :http_version, :enum, 84, [:none, :httpv1_0, :httpv1_1] option :easy, :ignore_content_length, :bool, 136 option :easy, :http_content_decoding, :bool, 158 option :easy, :http_transfer_decoding, :bool, 157 ## SMTP OPTIONS option :easy, :mail_from, :string, 186 option :easy, :mail_rcpt, :curl_slist, 187 option :easy, :mail_auth, :string, 217 ## TFTP OPTIONS option :easy, :tftp_blksize, :int, 178 ## FTP OPTIONS option :easy, :ftpport, :string, 17 option :easy, :quote, :curl_slist, 28 option :easy, :postquote, :curl_slist, 39 option :easy, :prequote, :curl_slist, 93 option :easy, :dirlistonly, :bool, 48 option_alias :easy, :dirlistonly, :ftplistonly option :easy, :append, :bool, 50 option_alias :easy, :append, :ftpappend option :easy, :ftp_use_eprt, :bool, 106 option :easy, :ftp_use_epsv, :bool, 85 option :easy, :ftp_use_pret, :bool, 188 option :easy, :ftp_create_missing_dirs, :bool, 110 option :easy, :ftp_response_timeout, :int, 112 option_alias :easy, :ftp_response_timeout, :server_response_timeout option :easy, :ftp_alternative_to_user, :string, 147 option :easy, :ftp_skip_pasv_ip, :bool, 137 option :easy, :ftpsslauth, :enum, 129, [:default, :ssl, :tls] option :easy, :ftp_ssl_ccc, :enum, 154, [:none, :passive, :active] option :easy, :ftp_account, :string, 134 option :easy, :ftp_filemethod, :enum, 138, [:default, :multicwd, :nocwd, :singlecwd] ## RTSP OPTIONS option :easy, :rtsp_request, :enum, 189, [:none, :options, :describe, :announce, :setup, :play, :pause, :teardown, :get_parameter, :set_parameter, :record, :receive] option :easy, :rtsp_session_id, :string, 190 option :easy, :rtsp_stream_uri, :string, 191 option :easy, :rtsp_transport, :string, 192 option_alias :easy, :httpheader, :rtspheader option :easy, :rtsp_client_cseq, :int, 193 option :easy, :rtsp_server_cseq, :int, 194 ## PROTOCOL OPTIONS option :easy, :transfertext, :bool, 53 option :easy, :proxy_transfer_mode, :bool, 166 option :easy, :crlf, :bool, 27 option :easy, :range, :string, 7 option :easy, :resume_from, :int, 21 option :easy, :resume_from_large, :off_t, 116 option :easy, :customrequest, :string, 36 option :easy, :filetime, :bool, 69 option :easy, :nobody, :bool, 44 option :easy, :infilesize, :int, 14 option :easy, :infilesize_large, :off_t, 115 option :easy, :upload, :bool, 46 option :easy, :maxfilesize, :int, 114 option :easy, :maxfilesize_large, :off_t, 117 option :easy, :timecondition, :enum, 33, [:none, :ifmodsince, :ifunmodsince, :lastmod] option :easy, :timevalue, :time, 34 ## CONNECTION OPTIONS option :easy, :timeout, :int, 13 option :easy, :timeout_ms, :int, 155 option :easy, :low_speed_limit, :int, 19 option :easy, :low_speed_time, :int, 20 option :easy, :max_send_speed_large, :off_t, 145 option :easy, :max_recv_speed_large, :off_t, 146 option :easy, :maxconnects, :int, 71 option :easy, :fresh_connect, :bool, 74 option :easy, :forbid_reuse, :bool, 75 option :easy, :connecttimeout, :int, 78 option :easy, :connecttimeout_ms, :int, 156 option :easy, :ipresolve, :enum, 113, [:whatever, :v4, :v6] option :easy, :connect_only, :bool, 141 option :easy, :use_ssl, :enum, 119, [:none, :try, :control, :all] option_alias :easy, :use_ssl, :ftp_ssl option :easy, :resolve, :curl_slist, 203 option :easy, :dns_servers, :string, 211 option :easy, :accepttimeout_ms, :int, 212 ## SSL and SECURITY OPTIONS option :easy, :sslcert, :string, 25 option :easy, :sslcerttype, :string, 86 option :easy, :sslkey, :string, 87 option :easy, :sslkeytype, :string, 88 option :easy, :keypasswd, :string, 26 option_alias :easy, :keypasswd, :sslcertpasswd option_alias :easy, :keypasswd, :sslkeypasswd option :easy, :sslengine, :string, 89 option :easy, :sslengine_default, :none, 90 option :easy, :sslversion, :enum, 32, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2] option :easy, :ssl_verifypeer, :bool, 64 option :easy, :cainfo, :string, 65 option :easy, :issuercert, :string, 170 option :easy, :capath, :string, 97 option :easy, :crlfile, :string, 169 option :easy, :ssl_verifyhost, :int, 81 option :easy, :certinfo, :bool, 172 option :easy, :random_file, :string, 76 option :easy, :egdsocket, :string, 77 option :easy, :ssl_cipher_list, :string, 83 option :easy, :ssl_sessionid_cache, :bool, 150 option :easy, :ssl_options, :bitmask, 216, [nil, :allow_beast] option :easy, :krblevel, :string, 63 option_alias :easy, :krblevel, :krb4level option :easy, :gssapi_delegation, :bitmask, 210, [:none, :policy_flag, :flag] ## SSH OPTIONS option :easy, :ssh_auth_types, :bitmask, 151, [:none, :publickey, :password, :host, :keyboard, :agent, {:any => [:all], :default => [:any]}] option :easy, :ssh_host_public_key_md5, :string, 162 option :easy, :ssh_public_keyfile, :string, 152 option :easy, :ssh_private_keyfile, :string, 153 option :easy, :ssh_knownhosts, :string, 183 option :easy, :ssh_keyfunction, :callback, 184 option :easy, :khstat, :enum, -1, [:fine_add_to_file, :fine, :reject, :defer] # Kludge to make this enum available... Access via CurL::EASY_OPTIONS[:khstat][:opt] option :easy, :ssh_keydata, :cbdata, 185 ## OTHER OPTIONS option :easy, :private, :cbdata, 103 option :easy, :share, :dontuse_object, 100 option :easy, :new_file_perms, :int, 159 option :easy, :new_directory_perms, :int, 160 ## TELNET OPTIONS option :easy, :telnetoptions, :curl_slist, 70 end end end ethon-0.9.0/lib/ethon/curls/functions.rb0000644000004100000410000001246612734673625020265 0ustar www-datawww-datamodule Ethon module Curls # This module contains the functions to be attached in order to work with # libcurl. module Functions # :nodoc: def self.extended(base) base.attach_function :global_init, :curl_global_init, [:long], :int base.attach_function :global_cleanup, :curl_global_cleanup, [], :void base.attach_function :free, :curl_free, [:pointer], :void base.attach_function :easy_init, :curl_easy_init, [], :pointer base.attach_function :easy_cleanup, :curl_easy_cleanup, [:pointer], :void base.attach_function :easy_getinfo, :curl_easy_getinfo, [:pointer, :info, :pointer], :easy_code base.attach_function :easy_setopt_ffipointer, :curl_easy_setopt, [:pointer, :easy_option, :pointer], :easy_code base.attach_function :easy_setopt_string, :curl_easy_setopt, [:pointer, :easy_option, :string], :easy_code base.attach_function :easy_setopt_long, :curl_easy_setopt, [:pointer, :easy_option, :long], :easy_code base.attach_function :easy_setopt_callback, :curl_easy_setopt, [:pointer, :easy_option, :callback], :easy_code base.attach_function :easy_setopt_debug_callback, :curl_easy_setopt, [:pointer, :easy_option, :debug_callback], :easy_code base.attach_function :easy_setopt_off_t, :curl_easy_setopt, [:pointer, :easy_option, :int64], :easy_code base.instance_variable_set(:@blocking, true) base.attach_function :easy_perform, :curl_easy_perform, [:pointer], :easy_code base.attach_function :easy_strerror, :curl_easy_strerror, [:easy_code], :string base.attach_function :easy_escape, :curl_easy_escape, [:pointer, :pointer, :int], :pointer base.attach_function :easy_reset, :curl_easy_reset, [:pointer], :void base.attach_function :easy_duphandle, :curl_easy_duphandle, [:pointer], :pointer base.attach_function :formadd, :curl_formadd, [:pointer, :pointer, :varargs], :int base.attach_function :formfree, :curl_formfree, [:pointer], :void base.attach_function :multi_init, :curl_multi_init, [], :pointer base.attach_function :multi_cleanup, :curl_multi_cleanup, [:pointer], :void base.attach_function :multi_add_handle, :curl_multi_add_handle, [:pointer, :pointer], :multi_code base.attach_function :multi_remove_handle, :curl_multi_remove_handle, [:pointer, :pointer], :multi_code base.attach_function :multi_info_read, :curl_multi_info_read, [:pointer, :pointer], Curl::Msg.ptr base.attach_function :multi_perform, :curl_multi_perform, [:pointer, :pointer], :multi_code base.attach_function :multi_timeout, :curl_multi_timeout, [:pointer, :pointer], :multi_code base.attach_function :multi_fdset, :curl_multi_fdset, [:pointer, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, :pointer], :multi_code base.attach_function :multi_strerror, :curl_multi_strerror, [:int], :string base.attach_function :multi_setopt_ffipointer, :curl_multi_setopt, [:pointer, :multi_option, :pointer], :multi_code base.attach_function :multi_setopt_string, :curl_multi_setopt, [:pointer, :multi_option, :string], :multi_code base.attach_function :multi_setopt_long, :curl_multi_setopt, [:pointer, :multi_option, :long], :multi_code base.attach_function :multi_setopt_callback, :curl_multi_setopt, [:pointer, :multi_option, :callback], :multi_code base.attach_function :multi_setopt_off_t, :curl_multi_setopt, [:pointer, :multi_option, :int64], :multi_code base.attach_function :version, :curl_version, [], :string base.attach_function :version_info, :curl_version_info, [], Curl::VersionInfoData.ptr base.attach_function :slist_append, :curl_slist_append, [:pointer, :string], :pointer base.attach_function :slist_free_all, :curl_slist_free_all, [:pointer], :void base.instance_variable_set(:@blocking, true) if Curl.windows? base.ffi_lib 'ws2_32' else base.ffi_lib ::FFI::Library::LIBC end base.attach_function :select, [:int, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::Timeval.ptr], :int end end end end ethon-0.9.0/lib/ethon/curls/constants.rb0000644000004100000410000000450512734673625020264 0ustar www-datawww-datamodule Ethon module Curl # :nodoc: VERSION_NOW = 3 # Flag. Initialize SSL. GLOBAL_SSL = 0x01 # Flag. Initialize win32 socket libraries. GLOBAL_WIN32 = 0x02 # Flag. Initialize everything possible. GLOBAL_ALL = (GLOBAL_SSL | GLOBAL_WIN32) # Flag. Initialize everything by default. GLOBAL_DEFAULT = GLOBAL_ALL # :nodoc: EasyCode = enum(:easy_code, easy_codes) # :nodoc: MultiCode = enum(:multi_code, multi_codes) # :nodoc: EasyOption = enum(:easy_option, easy_options(:enum).to_a.flatten) # :nodoc: MultiOption = enum(:multi_option, multi_options(:enum).to_a.flatten) # Used by curl_debug_callback when setting CURLOPT_DEBUGFUNCTION # https://github.com/bagder/curl/blob/master/include/curl/curl.h#L378 for details DebugInfoType = enum(:debug_info_type, debug_info_types) # :nodoc: InfoType = enum(info_types.to_a.flatten) # Info details, refer # https://github.com/bagder/curl/blob/master/src/tool_writeout.c#L66 for details Info = enum(:info, infos.to_a.flatten) # Form options, used by FormAdd for temporary storage, refer # https://github.com/bagder/curl/blob/master/lib/formdata.h#L51 for details FormOption = enum(:form_option, form_options) # :nodoc: MsgCode = enum(:msg_code, msg_codes) VERSION_IPV6 = (1<<0) # IPv6-enabled VERSION_KERBEROS4 = (1<<1) # kerberos auth is supported VERSION_SSL = (1<<2) # SSL options are present VERSION_LIBZ = (1<<3) # libz features are present VERSION_NTLM = (1<<4) # NTLM auth is supported VERSION_GSSNEGOTIATE = (1<<5) # Negotiate auth supp VERSION_DEBUG = (1<<6) # built with debug capabilities VERSION_ASYNCHDNS = (1<<7) # asynchronous dns resolves VERSION_SPNEGO = (1<<8) # SPNEGO auth is supported VERSION_LARGEFILE = (1<<9) # supports files bigger than 2GB VERSION_IDN = (1<<10) # International Domain Names support VERSION_SSPI = (1<<11) # SSPI is supported VERSION_CONV = (1<<12) # character conversions supported VERSION_CURLDEBUG = (1<<13) # debug memory tracking supported VERSION_TLSAUTH_SRP = (1<<14) # TLS-SRP auth is supported VERSION_NTLM_WB = (1<<15) # NTLM delegating to winbind helper VERSION_HTTP2 = (1<<16) # HTTP2 support built VERSION_GSSAPI = (1<<17) # GSS-API is supported end end ethon-0.9.0/lib/ethon/curls/classes.rb0000644000004100000410000000241612734673625017704 0ustar www-datawww-datamodule Ethon module Curl # :nodoc: class MsgData < ::FFI::Union layout :whatever, :pointer, :code, :easy_code end # :nodoc: class Msg < ::FFI::Struct layout :code, :msg_code, :easy_handle, :pointer, :data, MsgData end class VersionInfoData < ::FFI::Struct layout :curl_version, :uint8, :version, :string, :version_num, :int, :host, :string, :features, :int, :ssl_version, :string, :ssl_version_num, :long, :libz_version, :string, :protocols, :pointer end # :nodoc: class FDSet < ::FFI::Struct if Curl.windows? layout :fd_count, :uint, # TODO: Make it future proof by dynamically grabbing FD_SETSIZE. :fd_array, [:uint, 2048] def clear; self[:fd_count] = 0; end else # FD Set size. FD_SETSIZE = ::Ethon::Libc.getdtablesize layout :fds_bits, [:long, FD_SETSIZE / ::FFI::Type::LONG.size] # :nodoc: def clear; super; end end end # :nodoc: class Timeval < ::FFI::Struct if Curl.windows? layout :sec, :long, :usec, :long else layout :sec, :time_t, :usec, :suseconds_t end end end end ethon-0.9.0/lib/ethon/curls/codes.rb0000644000004100000410000000622212734673625017343 0ustar www-datawww-datamodule Ethon module Curls # :nodoc: # This module contains all easy and # multi return codes. module Codes # Libcurl error codes, refer # https://github.com/bagder/curl/blob/master/include/curl/curl.h for details def easy_codes [ :ok, :unsupported_protocol, :failed_init, :url_malformat, :not_built_in, :couldnt_resolve_proxy, :couldnt_resolve_host, :couldnt_connect, :ftp_weird_server_reply, :remote_access_denied, :ftp_accept_failed, :ftp_weird_pass_reply, :ftp_accept_timeout, :ftp_weird_pasv_reply, :ftp_weird_227_format, :ftp_cant_get_host, :obsolete16, :ftp_couldnt_set_type, :partial_file, :ftp_couldnt_retr_file, :obsolete20, :quote_error, :http_returned_error, :write_error, :obsolete24, :upload_failed, :read_error, :out_of_memory, :operation_timedout, :obsolete29, :ftp_port_failed, :ftp_couldnt_use_rest, :obsolete32, :range_error, :http_post_error, :ssl_connect_error, :bad_download_resume, :file_couldnt_read_file, :ldap_cannot_bind, :ldap_search_failed, :obsolete40, :function_not_found, :aborted_by_callback, :bad_function_argument, :obsolete44, :interface_failed, :obsolete46, :too_many_redirects , :unknown_option, :telnet_option_syntax , :obsolete50, :peer_failed_verification, :got_nothing, :ssl_engine_notfound, :ssl_engine_setfailed, :send_error, :recv_error, :obsolete57, :ssl_certproblem, :ssl_cipher, :ssl_cacert, :bad_content_encoding, :ldap_invalid_url, :filesize_exceeded, :use_ssl_failed, :send_fail_rewind, :ssl_engine_initfailed, :login_denied, :tftp_notfound, :tftp_perm, :remote_disk_full, :tftp_illegal, :tftp_unknownid, :remote_file_exists, :tftp_nosuchuser, :conv_failed, :conv_reqd, :ssl_cacert_badfile, :remote_file_not_found, :ssh, :ssl_shutdown_failed, :again, :ssl_crl_badfile, :ssl_issuer_error, :ftp_pret_failed, :rtsp_cseq_error, :rtsp_session_error, :ftp_bad_file_list, :chunk_failed, :last ] end # Curl-Multi socket error codes, refer # https://github.com/bagder/curl/blob/master/include/curl/multi.h for details def multi_codes [ :call_multi_perform, -1, :ok, :bad_handle, :bad_easy_handle, :out_of_memory, :internal_error, :bad_socket, :unknown_option, :last ] end end end end ethon-0.9.0/lib/ethon/curls/messages.rb0000644000004100000410000000052712734673625020057 0ustar www-datawww-datamodule Ethon module Curls # This module contains available message codes. module Messages # Return message codes. # # @example Return message codes. # Ethon::Curl.msg_codes # # @return [ Array ] The messages codes. def msg_codes [:none, :done, :last] end end end end ethon-0.9.0/lib/ethon/curls/settings.rb0000644000004100000410000000042312734673625020103 0ustar www-datawww-datamodule Ethon module Curl callback :callback, [:pointer, :size_t, :size_t, :pointer], :size_t callback :debug_callback, [:pointer, :debug_info_type, :pointer, :size_t, :pointer], :int ffi_lib_flags :now, :global ffi_lib ['libcurl', 'libcurl.so.4'] end end ethon-0.9.0/lib/ethon/easy.rb0000644000004100000410000003372512734673625016067 0ustar www-datawww-datarequire 'ethon/easy/informations' require 'ethon/easy/features' require 'ethon/easy/callbacks' require 'ethon/easy/options' require 'ethon/easy/header' require 'ethon/easy/util' require 'ethon/easy/params' require 'ethon/easy/form' require 'ethon/easy/http' require 'ethon/easy/operations' require 'ethon/easy/response_callbacks' require 'ethon/easy/debug_info' require 'ethon/easy/mirror' module Ethon # This is the class representing the libcurl easy interface # See http://curl.haxx.se/libcurl/c/libcurl-easy.html for more informations. # # @example You can access the libcurl easy interface through this class, every request is based on it. The simplest setup looks like that: # # e = Ethon::Easy.new(url: "www.example.com") # e.perform # #=> :ok # # @example You can the reuse this Easy for the next request: # # e.reset # reset easy handle # e.url = "www.google.com" # e.followlocation = true # e.perform # #=> :ok # # @see initialize class Easy include Ethon::Easy::Informations include Ethon::Easy::Callbacks include Ethon::Easy::Options include Ethon::Easy::Header include Ethon::Easy::Http include Ethon::Easy::Operations include Ethon::Easy::ResponseCallbacks extend Ethon::Easy::Features # Returns the curl return code. # # @return [ Symbol ] The return code. # * :ok: All fine. Proceed as usual. # * :unsupported_protocol: The URL you passed to libcurl used a # protocol that this libcurl does not support. The support # might be a compile-time option that you didn't use, it can # be a misspelled protocol string or just a protocol # libcurl has no code for. # * :failed_init: Very early initialization code failed. This # is likely to be an internal error or problem, or a # resource problem where something fundamental couldn't # get done at init time. # * :url_malformat: The URL was not properly formatted. # * :not_built_in: A requested feature, protocol or option # was not found built-in in this libcurl due to a build-time # decision. This means that a feature or option was not enabled # or explicitly disabled when libcurl was built and in # order to get it to function you have to get a rebuilt libcurl. # * :couldnt_resolve_proxy: Couldn't resolve proxy. The given # proxy host could not be resolved. # * :couldnt_resolve_host: Couldn't resolve host. The given remote # host was not resolved. # * :couldnt_connect: Failed to connect() to host or proxy. # * :ftp_weird_server_reply: After connecting to a FTP server, # libcurl expects to get a certain reply back. This error # code implies that it got a strange or bad reply. The given # remote server is probably not an OK FTP server. # * :remote_access_denied: We were denied access to the resource # given in the URL. For FTP, this occurs while trying to # change to the remote directory. # * :ftp_accept_failed: While waiting for the server to connect # back when an active FTP session is used, an error code was # sent over the control connection or similar. # * :ftp_weird_pass_reply: After having sent the FTP password to # the server, libcurl expects a proper reply. This error code # indicates that an unexpected code was returned. # * :ftp_accept_timeout: During an active FTP session while # waiting for the server to connect, the CURLOPT_ACCEPTTIMOUT_MS # (or the internal default) timeout expired. # * :ftp_weird_pasv_reply: libcurl failed to get a sensible result # back from the server as a response to either a PASV or a # EPSV command. The server is flawed. # * :ftp_weird_227_format: FTP servers return a 227-line as a response # to a PASV command. If libcurl fails to parse that line, # this return code is passed back. # * :ftp_cant_get_host: An internal failure to lookup the host used # for the new connection. # * :ftp_couldnt_set_type: Received an error when trying to set # the transfer mode to binary or ASCII. # * :partial_file: A file transfer was shorter or larger than # expected. This happens when the server first reports an expected # transfer size, and then delivers data that doesn't match the # previously given size. # * :ftp_couldnt_retr_file: This was either a weird reply to a # 'RETR' command or a zero byte transfer complete. # * :quote_error: When sending custom "QUOTE" commands to the # remote server, one of the commands returned an error code that # was 400 or higher (for FTP) or otherwise indicated unsuccessful # completion of the command. # * :http_returned_error: This is returned if CURLOPT_FAILONERROR is # set TRUE and the HTTP server returns an error code that is >= 400. # * :write_error: An error occurred when writing received data to a # local file, or an error was returned to libcurl from a write callback. # * :upload_failed: Failed starting the upload. For FTP, the server # typically denied the STOR command. The error buffer usually # contains the server's explanation for this. # * :read_error: There was a problem reading a local file or an error # returned by the read callback. # * :out_of_memory: A memory allocation request failed. This is serious # badness and things are severely screwed up if this ever occurs. # * :operation_timedout: Operation timeout. The specified time-out # period was reached according to the conditions. # * :ftp_port_failed: The FTP PORT command returned error. This mostly # happens when you haven't specified a good enough address for # libcurl to use. See CURLOPT_FTPPORT. # * :ftp_couldnt_use_rest: The FTP REST command returned error. This # should never happen if the server is sane. # * :range_error: The server does not support or accept range requests. # * :http_post_error: This is an odd error that mainly occurs due to # internal confusion. # * :ssl_connect_error: A problem occurred somewhere in the SSL/TLS # handshake. You really want the error buffer and read the message # there as it pinpoints the problem slightly more. Could be # certificates (file formats, paths, permissions), passwords, and others. # * :bad_download_resume: The download could not be resumed because # the specified offset was out of the file boundary. # * :file_couldnt_read_file: A file given with FILE:// couldn't be # opened. Most likely because the file path doesn't identify an # existing file. Did you check file permissions? # * :ldap_cannot_bind: LDAP cannot bind. LDAP bind operation failed. # * :ldap_search_failed: LDAP search failed. # * :function_not_found: Function not found. A required zlib function was not found. # * :aborted_by_callback: Aborted by callback. A callback returned # "abort" to libcurl. # * :bad_function_argument: Internal error. A function was called with # a bad parameter. # * :interface_failed: Interface error. A specified outgoing interface # could not be used. Set which interface to use for outgoing # connections' source IP address with CURLOPT_INTERFACE. # * :too_many_redirects: Too many redirects. When following redirects, # libcurl hit the maximum amount. Set your limit with CURLOPT_MAXREDIRS. # * :unknown_option: An option passed to libcurl is not recognized/known. # Refer to the appropriate documentation. This is most likely a # problem in the program that uses libcurl. The error buffer might # contain more specific information about which exact option it concerns. # * :telnet_option_syntax: A telnet option string was Illegally formatted. # * :peer_failed_verification: The remote server's SSL certificate or # SSH md5 fingerprint was deemed not OK. # * :got_nothing: Nothing was returned from the server, and under the # circumstances, getting nothing is considered an error. # * :ssl_engine_notfound: The specified crypto engine wasn't found. # * :ssl_engine_setfailed: Failed setting the selected SSL crypto engine as default! # * :send_error: Failed sending network data. # * :recv_error: Failure with receiving network data. # * :ssl_certproblem: problem with the local client certificate. # * :ssl_cipher: Couldn't use specified cipher. # * :ssl_cacert: Peer certificate cannot be authenticated with known CA certificates. # * :bad_content_encoding: Unrecognized transfer encoding. # * :ldap_invalid_url: Invalid LDAP URL. # * :filesize_exceeded: Maximum file size exceeded. # * :use_ssl_failed: Requested FTP SSL level failed. # * :send_fail_rewind: When doing a send operation curl had to rewind the data to # retransmit, but the rewinding operation failed. # * :ssl_engine_initfailed: Initiating the SSL Engine failed. # * :login_denied: The remote server denied curl to login # * :tftp_notfound: File not found on TFTP server. # * :tftp_perm: Permission problem on TFTP server. # * :remote_disk_full: Out of disk space on the server. # * :tftp_illegal: Illegal TFTP operation. # * :tftp_unknownid: Unknown TFTP transfer ID. # * :remote_file_exists: File already exists and will not be overwritten. # * :tftp_nosuchuser: This error should never be returned by a properly # functioning TFTP server. # * :conv_failed: Character conversion failed. # * :conv_reqd: Caller must register conversion callbacks. # * :ssl_cacert_badfile: Problem with reading the SSL CA cert (path? access rights?): # * :remote_file_not_found: The resource referenced in the URL does not exist. # * :ssh: An unspecified error occurred during the SSH session. # * :ssl_shutdown_failed: Failed to shut down the SSL connection. # * :again: Socket is not ready for send/recv wait till it's ready and try again. # This return code is only returned from curl_easy_recv(3) and curl_easy_send(3) # * :ssl_crl_badfile: Failed to load CRL file # * :ssl_issuer_error: Issuer check failed # * :ftp_pret_failed: The FTP server does not understand the PRET command at # all or does not support the given argument. Be careful when # using CURLOPT_CUSTOMREQUEST, a custom LIST command will be sent with PRET CMD # before PASV as well. # * :rtsp_cseq_error: Mismatch of RTSP CSeq numbers. # * :rtsp_session_error: Mismatch of RTSP Session Identifiers. # * :ftp_bad_file_list: Unable to parse FTP file list (during FTP wildcard downloading). # * :chunk_failed: Chunk callback reported error. # * :obsolete: These error codes will never be returned. They were used in an old # libcurl version and are currently unused. # # @see http://curl.haxx.se/libcurl/c/libcurl-errors.html attr_accessor :return_code # Initialize a new Easy. # It initializes curl, if not already done and applies the provided options. # Look into {Ethon::Easy::Options Options} to see what you can provide in the # options hash. # # @example Create a new Easy. # Easy.new(url: "www.google.de") # # @param [ Hash ] options The options to set. # @option options :headers [ Hash ] Request headers. # # @return [ Easy ] A new Easy. # # @see Ethon::Easy::Options # @see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html def initialize(options = {}) Curl.init set_attributes(options) set_callbacks end # Set given options. # # @example Set options. # easy.set_attributes(options) # # @param [ Hash ] options The options. # # @raise InvalidOption # # @see initialize def set_attributes(options) options.each_pair do |key, value| method = "#{key}=" unless respond_to?(method) raise Errors::InvalidOption.new(key) end send(method, value) end end # Reset easy. This means resetting all options and instance variables. # Also the easy handle is resetted. # # @example Reset. # easy.reset def reset @url = nil @escape = nil @hash = nil @on_complete = nil @on_headers = nil @on_body = nil @procs = nil @mirror = nil Curl.easy_reset(handle) set_callbacks end # Clones libcurl session handle. This means that all options that is set in # the current handle will be set on duplicated handle. def dup e = super e.handle = Curl.easy_duphandle(handle) e.instance_variable_set(:@body_write_callback, nil) e.instance_variable_set(:@header_write_callback, nil) e.instance_variable_set(:@debug_callback, nil) e.set_callbacks e end # Url escapes the value. # # @example Url escape. # easy.escape(value) # # @param [ String ] value The value to escape. # # @return [ String ] The escaped value. # # @api private def escape(value) string_pointer = Curl.easy_escape(handle, value, value.bytesize) returned_string = string_pointer.read_string Curl.free(string_pointer) returned_string end # Returns the informations available through libcurl as # a hash. # # @return [ Hash ] The informations hash. def to_hash Kernel.warn("Ethon: Easy#to_hash is deprecated and will be removed, please use #mirror.") mirror.to_hash end def mirror @mirror ||= Mirror.from_easy(self) end # Return pretty log out. # # @example Return log out. # easy.log_inspect # # @return [ String ] The log out. def log_inspect "EASY #{mirror.log_informations.map{|k, v| "#{k}=#{v}"}.flatten.join(' ')}" end end end ethon-0.9.0/lib/ethon/multi/0000755000004100000410000000000012734673625015721 5ustar www-datawww-dataethon-0.9.0/lib/ethon/multi/stack.rb0000644000004100000410000000232712734673625017357 0ustar www-datawww-datamodule Ethon class Multi # This module provides the multi stack behaviour. module Stack # Return easy handles. # # @example Return easy handles. # multi.easy_handles # # @return [ Array ] The easy handles. def easy_handles @easy_handles ||= [] end # Add an easy to the stack. # # @example Add easy. # multi.add(easy) # # @param [ Easy ] easy The easy to add. # # @raise [ Ethon::Errors::MultiAdd ] If adding an easy failed. def add(easy) return nil if easy_handles.include?(easy) code = Curl.multi_add_handle(handle, easy.handle) raise Errors::MultiAdd.new(code, easy) unless code == :ok easy_handles << easy end # Delete an easy from stack. # # @example Delete easy from stack. # # @param [ Easy ] easy The easy to delete. # # @raise [ Ethon::Errors::MultiRemove ] If removing an easy failed. def delete(easy) if easy_handles.delete(easy) code = Curl.multi_remove_handle(handle, easy.handle) raise Errors::MultiRemove.new(code, handle) unless code == :ok end end end end end ethon-0.9.0/lib/ethon/multi/options.rb0000644000004100000410000000606712734673625017752 0ustar www-datawww-datamodule Ethon class Multi # This module contains the logic and knowledge about the # available options on multi. module Options # Sets max_total_connections option. # # @example Set max_total_connections option. # easy.max_total_conections = $value # # @param [ String ] value The value to set. # # @return [ void ] def max_total_connections=(value) Curl.set_option(:max_total_connections, value_for(value, :int), handle, :multi) end # Sets maxconnects option. # # @example Set maxconnects option. # easy.maxconnects = $value # # @param [ String ] value The value to set. # # @return [ void ] def maxconnects=(value) Curl.set_option(:maxconnects, value_for(value, :int), handle, :multi) end # Sets pipelining option. # # @example Set pipelining option. # easy.pipelining = $value # # @param [ String ] value The value to set. # # @return [ void ] def pipelining=(value) Curl.set_option(:pipelining, value_for(value, :bool), handle, :multi) end # Sets socketdata option. # # @example Set socketdata option. # easy.socketdata = $value # # @param [ String ] value The value to set. # # @return [ void ] def socketdata=(value) Curl.set_option(:socketdata, value_for(value, :string), handle, :multi) end # Sets socketfunction option. # # @example Set socketfunction option. # easy.socketfunction = $value # # @param [ String ] value The value to set. # # @return [ void ] def socketfunction=(value) Curl.set_option(:socketfunction, value_for(value, :string), handle, :multi) end # Sets timerdata option. # # @example Set timerdata option. # easy.timerdata = $value # # @param [ String ] value The value to set. # # @return [ void ] def timerdata=(value) Curl.set_option(:timerdata, value_for(value, :string), handle, :multi) end # Sets timerfunction option. # # @example Set timerfunction option. # easy.timerfunction = $value # # @param [ String ] value The value to set. # # @return [ void ] def timerfunction=(value) Curl.set_option(:timerfunction, value_for(value, :string), handle, :multi) end private # Return the value to set to multi handle. It is converted with the help # of bool_options, enum_options and int_options. # # @example Return casted the value. # multi.value_for(:verbose) # # @return [ Object ] The casted value. def value_for(value, type, option = nil) return nil if value.nil? if type == :bool value ? 1 : 0 elsif type == :int value.to_i elsif value.is_a?(String) Ethon::Easy::Util.escape_zero_byte(value) else value end end end end end ethon-0.9.0/lib/ethon/multi/operations.rb0000644000004100000410000001176312734673625020441 0ustar www-datawww-datamodule Ethon class Multi # :nodoc # This module contains logic to run a multi. module Operations STARTED_MULTI = "ETHON: started MULTI" PERFORMED_MULTI = "ETHON: performed MULTI" # Return the multi handle. Inititialize multi handle, # in case it didn't happened already. # # @example Return multi handle. # multi.handle # # @return [ FFI::Pointer ] The multi handle. def handle @handle ||= FFI::AutoPointer.new(Curl.multi_init, Curl.method(:multi_cleanup)) end # Initialize variables. # # @example Initialize variables. # multi.init_vars # # @return [ void ] def init_vars @timeout = ::FFI::MemoryPointer.new(:long) @timeval = Curl::Timeval.new @fd_read = Curl::FDSet.new @fd_write = Curl::FDSet.new @fd_excep = Curl::FDSet.new @max_fd = ::FFI::MemoryPointer.new(:int) end # Perform multi. # # @return [ nil ] # # @example Perform multi. # multi.perform def perform Ethon.logger.debug(STARTED_MULTI) while ongoing? run timeout = get_timeout next if timeout == 0 reset_fds set_fds(timeout) end Ethon.logger.debug(PERFORMED_MULTI) nil end # Prepare multi. # # @return [ nil ] # # @example Prepare multi. # multi.prepare # # @deprecated It is no longer necessary to call prepare. def prepare Ethon.logger.warn( "ETHON: It is no longer necessay to call "+ "Multi#prepare. Its going to be removed "+ "in future versions." ) end private # Return wether the multi still requests or not. # # @example Return if ongoing. # multi.ongoing? # # @return [ Boolean ] True if ongoing, else false. def ongoing? easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0) end # Get timeout. # # @example Get timeout. # multi.get_timeout # # @return [ Integer ] The timeout. # # @raise [ Ethon::Errors::MultiTimeout ] If getting the timeout fails. def get_timeout code = Curl.multi_timeout(handle, @timeout) raise Errors::MultiTimeout.new(code) unless code == :ok timeout = @timeout.read_long timeout = 1 if timeout < 0 timeout end # Reset file describtors. # # @example Reset fds. # multi.reset_fds # # @return [ void ] def reset_fds @fd_read.clear @fd_write.clear @fd_excep.clear end # Set fds. # # @example Set fds. # multi.set_fds # # @return [ void ] # # @raise [ Ethon::Errors::MultiFdset ] If setting the file descriptors fails. # @raise [ Ethon::Errors::Select ] If select fails. def set_fds(timeout) code = Curl.multi_fdset(handle, @fd_read, @fd_write, @fd_excep, @max_fd) raise Errors::MultiFdset.new(code) unless code == :ok max_fd = @max_fd.read_int if max_fd == -1 sleep(0.001) else @timeval[:sec] = timeout / 1000 @timeval[:usec] = (timeout * 1000) % 1000000 loop do code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval) break unless code < 0 && ::FFI.errno == Errno::EINTR::Errno end raise Errors::Select.new(::FFI.errno) if code < 0 end end # Check. # # @example Check. # multi.check # # @return [ void ] def check msgs_left = ::FFI::MemoryPointer.new(:int) while true msg = Curl.multi_info_read(handle, msgs_left) break if msg.null? next if msg[:code] != :done easy = easy_handles.find{ |e| e.handle == msg[:easy_handle] } easy.return_code = msg[:data][:code] Ethon.logger.debug { "ETHON: performed #{easy.log_inspect}" } delete(easy) easy.complete end end # Run. # # @example Run # multi.run # # @return [ void ] def run running_count_pointer = FFI::MemoryPointer.new(:int) begin code = trigger(running_count_pointer) end while code == :call_multi_perform check end # Trigger. # # @example Trigger. # multi.trigger # # @return [ Symbol ] The Curl.multi_perform return code. def trigger(running_count_pointer) code = Curl.multi_perform(handle, running_count_pointer) @running_count = running_count_pointer.read_int code end # Return number of running requests. # # @example Return count. # multi.running_count # # @return [ Integer ] Number running requests. def running_count @running_count ||= nil end end end end ethon-0.9.0/lib/ethon.rb0000644000004100000410000000145512734673625015121 0ustar www-datawww-datarequire 'logger' require 'ffi' require 'thread' begin require 'mime/types/columnar' rescue LoadError begin require 'mime/types' rescue LoadError end end require 'tempfile' require 'ethon/libc' require 'ethon/curl' require 'ethon/easy' require 'ethon/errors' require 'ethon/loggable' require 'ethon/multi' require 'ethon/version' # Ethon is a very simple libcurl. # It provides direct access to libcurl functionality # as well as some helpers for doing http requests. # # Ethon was extracted from Typhoeus. If you want to # see how others use Ethon look at the Typhoeus code. # # @see https://www.github.com/typhoeus/typhoeus Typhoeus # # @note Please update to the latest libcurl version in order # to benefit from all features and bugfixes. # http://curl.haxx.se/download.html module Ethon end ethon-0.9.0/ethon.gemspec0000644000004100000410000000131312734673625015364 0ustar www-datawww-data# encoding: utf-8 lib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) require 'ethon/version' Gem::Specification.new do |s| s.name = "ethon" s.version = Ethon::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Hans Hasselberg"] s.email = ["me@hans.io"] s.homepage = "https://github.com/typhoeus/ethon" s.summary = "Libcurl wrapper." s.description = "Very lightweight libcurl wrapper." s.required_rubygems_version = ">= 1.3.6" s.license = 'MIT' s.add_dependency('ffi', ['>= 1.3.0']) s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- spec/*`.split("\n") s.require_path = 'lib' end ethon-0.9.0/.gitignore0000644000004100000410000000007212734673625014673 0ustar www-datawww-data*.gem .bundle Gemfile.lock .DS_Store .yardoc doc coverage ethon-0.9.0/LICENSE0000644000004100000410000000205012734673625013706 0ustar www-datawww-dataCopyright (c) 2012-2016 Hans Hasselberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ethon-0.9.0/CHANGELOG.md0000644000004100000410000002103612734673625014517 0ustar www-datawww-data# Changelog ## Master [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.9.0...master) ## 0.9.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.8.1...v0.9.0) ## 0.8.1 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.8.0...v0.8.1) * Support optional escaping of params. ([Tasos Laskos](https://github.com/zapotek) * `Easy::Mirror`: Reduced object allocations and method calls during info handling. ([Tasos Laskos](https://github.com/zapotek) ## 0.8.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.3...v0.7.4) * `Easy::Mirror`: Reduced object allocations and method calls during info handling. ([Tasos Laskos](https://github.com/zapotek) ## 0.7.4 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.3...v0.7.4) * Support different array encodings for params. ([Marcello Barnaba](https://github.com/ifad), [\#104](https://github.com/typhoeus/ethon/pull/104)) * Programtic access to version infos. ([Jonas Wagner](https://github.com/jwagner), [\#90](https://github.com/typhoeus/ethon/pull/90)) ## 0.7.3 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.2...v0.7.3) * `Ethon::Curl::FDSet` * Set `:fd_array` size to the current MS Windows `FD_SETSIZE` (2048). ([Tasos Laskos](https://github.com/zapotek) * Added `redirect_time` value to available informations and `Easy::Mirror`. ([Adrien Jarthon](https://github.com/jarthod) ## 0.7.2 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.1...v0.7.2) * FFI data-types updated to be more correct. ## 0.7.1 * MS Windows determination delegated to `Gem.windows?` for better accuracy. * FFI data-types updated to work on MS Windows. ## 0.7.0 Not backwards compatible changes: * `mime-types` are no longer a dependency. The gem will be still used if available to determine the mime type of a file which is uploaded. That means you have to have take care of the gem installation yourself. [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.3...v0.7.0) ## 0.6.3 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.2...v0.6.3) ## 0.6.2 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.1...v0.6.2) ## 0.6.1 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.6.0...v0.6.1) The changelog entries are coming soon! ## 0.6.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.12...v0.6.0) The changelog entries are coming soon! Bugfixes: * URL-encode nullbytes in parameters instead of escaping them to `\\0`. ([Tasos Laskos](https://github.com/zapotek) ## 0.5.12 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.11...v0.5.12) Enhancements: * Performance optimizations. ([Kyle Oppenheim](https://github.com/koppenheim) and [Richie Vos](https://github.com/richievos), [\#48](https://github.com/typhoeus/ethon/pull/48)) * Reuse memory pointer. ([Richie Vos](https://github.com/richievos), [\#49](https://github.com/typhoeus/ethon/pull/49)) Bugfixes: * Fix windows install. ([Derik Olsson](https://github.com/derikolsson), [\#47](https://github.com/typhoeus/ethon/pull/47)) * Handle urls that already contain query params. ([Turner King](https://github.com/turnerking ), [\#45](https://github.com/typhoeus/ethon/pull/45)) ## 0.5.11 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.10...v0.5.11) Enhancements: * Add support for postredirs, unrestricted_auth. * Add support for cookie, cookiejar, cookiefile. ([erwanlr](https://github.com/erwanlr), [\#46](https://github.com/typhoeus/ethon/pull/46)) * Relax ffi requirements. ([voxik](https://github.com/voxik), [\#40](https://github.com/typhoeus/ethon/pull/40)) * Various documentation improvements. ([Craig Little](https://github.com/craiglittle)) Bugfixes: * Fix the memory leaks. ([Richie Vos](https://github.com/richievos), [\#45](https://github.com/typhoeus/ethon/pull/45)) ## 0.5.10 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.9...v0.5.10) Enhancements: * Allow custom requests. ([Nathan Sutton](https://github.com/nate), [\#36](https://github.com/typhoeus/ethon/pull/36)) * Use updated version of FFI. Bugfixes: * Fix windows install issue. ([brainsucker](https://github.com/brainsucker), [\#38](https://github.com/typhoeus/ethon/pull/38)) ## 0.5.9 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.8...v0.5.9) Enhancements: * Allow to set multiple protocols. ## 0.5.8 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.7...v0.5.8) Enhancements: * Add support for protocols and redir_protocols( [libcurl SASL buffer overflow vulnerability](http://curl.haxx.se/docs/adv_20130206.html)). * Add max_send_speed_large and max_recv_speed_large([Paul Schuegraf](https://github.com/pschuegr), [\#33](https://github.com/typhoeus/ethon/pull/33)) ## 0.5.7 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.6...v0.5.7) Enhancements: * Use new version of ffi. ## 0.5.6 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.4...v0.5.6) Bugfixes: * Easy#reset resets on_complete callbacks. ## 0.5.4 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.3...v0.5.4) Enhancements: * Use Libc#getdtablesize to get the FDSet size. * New libcurl option accept_encoding. * Documentation updates. ## 0.5.3 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.2...v0.5.3) Enhancements: * Deprecate Easy#prepare. It is no longer necessary. * Unroll metaprogramming for easy and multi options. * More specs. Bugfixes: * Correct size for FDSets * Add proxytypes to enums. ## 0.5.2 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.1...v0.5.2) Enhancements: * New libcurl option keypasswd. Bugfixes: * Correct request logging when using multi interface. * Remove invalid libcurl option sslcertpasswd. ## 0.5.1 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.0...v0.5.1) Bugfixes: * Mark Curl.select and Curl.easy_perform as blocking so that the GIL is released by ffi. ## 0.5.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.4.4...v0.5.0) Enhancements: * New libcurl option proxyuserpwd * Rename response_header to response_headers Bugfixes: * Mark Curl.select and Curl.easy_perform as blocking so that the GIL is released by ffi. ## 0.4.4 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.4.3...v0.4.4) Enhancements: * Prepare multi explicit like easy ## 0.4.3 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.4.2...v0.4.3) Enhancements: * Remove deprecated libcurl option put * More documentation * New libcurl option connecttimeout_ms and timeout_ms * Support multi options Bugfixes: * Handle nil values in query params ## 0.4.2 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.4.1...v0.4.2) Enhancements: * New libcurl option forbid_reuse * Use libcurls escape instead of CGI::escape ## 0.4.1 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.4.0...v0.4.1) Bugfixes: * Handle nested hash in an array in params correct ( [\#201](https://github.com/typhoeus/typhoeus/issues/201) ) ## 0.4.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.3.0...v0.4.0) Enhancements: * ruby 1.8.7 compatible * Ethon.logger * Deal with string param/body * More documentation Bugfixes: * Add multi_cleanup to curl ## 0.3.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.2.0...v0.3.0) Enhancements: * New libcurl option proxyport * Raise invalid value error when providing a wrong key for sslversion or httpauth Bugfixes: * Libcurl option sslversion is handled correct ## 0.2.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.1.0...v0.2.0) Enhancements: * GET requests are using custom requests only when there is a request body * Easy#on_complete takes multiple callbacks * raise Errors::GlobalInit when libcurls global_init failed instead of runtime error * raise Errors::InvalidOption if option is invalid ## 0.1.0 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.0.2...v0.1.0) Enhancements: * Documentation ( [Alex P](https://github.com/ifesdjeen), [\#13](https://github.com/typhoeus/ethon/issues/13) ) * New libcurl option dns_cache_timeout ( [Chris Heald](https://github.com/cheald), [\#192](https://github.com/typhoeus/typhoeus/pull/192) ) Bugfixes: * Libcurl option ssl_verifyhost takes an integer. * Add space between header key and value. ## 0.0.2 [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.0.1...v0.0.2) Bugfixes: * Add libcurl.so.4 to ffi_lib in order to load correct lib on Debian. * Escape zero bytes. ## 0.0.1 Initial version ethon-0.9.0/README.md0000644000004100000410000000550612734673625014171 0ustar www-datawww-data# Ethon [![Build Status](https://secure.travis-ci.org/typhoeus/ethon.png?branch=master)](http://travis-ci.org/typhoeus/ethon) [![Gem Version](https://badge.fury.io/rb/ethon.png)](http://badge.fury.io/rb/ethon) In Greek mythology, Ethon, the son of Typhoeus and Echidna, is a gigantic eagle. So much for the history. In the modern world, Ethon is a very basic libcurl wrapper using ffi. * [Documentation](http://rubydoc.info/github/typhoeus/ethon/frames/Ethon) * [Website](http://typhoeus.github.com/) * [Mailing list](http://groups.google.com/group/typhoeus) ## Installation With bundler: gem "ethon" With rubygems: gem install ethon ## Usage Making the first request is simple: ```ruby easy = Ethon::Easy.new(url: "www.example.com") easy.perform #=> :ok ``` You have access to various options, such as following redirects: ```ruby easy = Ethon::Easy.new(url: "www.example.com", followlocation: true) easy.perform #=> :ok ``` Once you're done you can inspect the response code and body: ```ruby easy = Ethon::Easy.new(url: "www.example.com", followlocation: true) easy.perform easy.response_code #=> 200 easy.response_body #=> " :ok ``` ```ruby easy = Ethon::Easy.new easy.http_request("www.example.com", :post, { params: { a: 1 }, body: { b: 2 } }) easy.perform #=> :ok ``` This is really handy when making requests since you don't have to care about setting everything up correctly. ## LICENSE (The MIT License) Copyright © 2012-2016 [Hans Hasselberg](http://www.hans.io) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/typhoeus/ethon/trend.png)](https://bitdeli.com/free "Bitdeli Badge") ethon-0.9.0/Guardfile0000644000004100000410000000036112734673625014531 0ustar www-datawww-data# vim:set filetype=ruby: guard( "rspec", :all_after_pass => false, :cli => "--fail-fast --tty --format documentation --colour") do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |match| "spec/#{match[1]}_spec.rb" } end ethon-0.9.0/profile/0000755000004100000410000000000012734673625014344 5ustar www-datawww-dataethon-0.9.0/profile/memory_leaks.rb0000644000004100000410000000714112734673625017363 0ustar www-datawww-datarequire 'ethon' require 'ethon/easy' require_relative 'perf_spec_helper' require 'rspec/autorun' describe "low-level interactions with libcurl" do describe Ethon::Multi do memory_leak_test("init") do Ethon::Multi.new end memory_leak_test("handle") do Ethon::Multi.new.handle end end describe Ethon::Easy do memory_leak_test("init") do Ethon::Easy.new end memory_leak_test("handle") do Ethon::Easy.new.handle end memory_leak_test("headers") do Ethon::Easy.new.headers = { "a" => 1, "b" => 2, "c" => 3, "d" => 4} end memory_leak_test("escape") do Ethon::Easy.new.escape("the_sky&is_blue") end end describe Ethon::Easy::Form do memory_leak_test("init") do Ethon::Easy::Form.new(nil, {}) end memory_leak_test("first") do Ethon::Easy::Form.new(nil, {}).first end memory_leak_test("last") do Ethon::Easy::Form.new(nil, {}).last end memory_leak_test("materialized with some params") do form = Ethon::Easy::Form.new(nil, { "a" => "1" }) form.materialize end memory_leak_test("materialized with a file") do File.open(__FILE__, "r") do |file| form = Ethon::Easy::Form.new(nil, { "a" => file }) form.materialize end end end end describe "higher level operations" do memory_leak_test("a simple request") do Ethon::Easy.new(:url => "http://localhost:3001/", :forbid_reuse => true).perform end memory_leak_test("a request with headers") do Ethon::Easy.new(:url => "http://localhost:3001/", :headers => { "Content-Type" => "application/json", "Something" => "1", "Else" => "qwerty", "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"}, :forbid_reuse => true).perform end memory_leak_test("a request with headers and params") do easy = Ethon::Easy.new(:url => "http://localhost:3001/", :headers => { "Content-Type" => "application/json", "Something" => "1", "Else" => "qwerty", "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"}, :forbid_reuse => true) easy.http_request("http://localhost:3001/", :get, :params => { "param1" => "value1", "param2" => "value2", "param3" => "value3", "param4" => "value4"}) end memory_leak_test("a request with headers, params, and body") do easy = Ethon::Easy.new(:url => "http://localhost:3001/", :headers => { "Content-Type" => "application/json", "Something" => "1", "Else" => "qwerty", "Long-String" => "aassddffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz"}, :forbid_reuse => true) easy.http_request("http://localhost:3001/", :get, :params => { "param1" => "value1", "param2" => "value2", "param3" => "value3", "param4" => "value4"}, :body => { "body1" => "value1", "body2" => "value2", "body3" => "value3" }) end end ethon-0.9.0/profile/perf_spec_helper.rb0000644000004100000410000000150712734673625020201 0ustar www-datawww-data#### SETUP require 'bundler' Bundler.setup require 'rspec' require 'support/localhost_server' require 'support/server' require_relative 'support/memory_test_helpers' require 'logger' if ENV['VERBOSE'] Ethon.logger = Logger.new($stdout) Ethon.logger.level = Logger::DEBUG end RSpec.configure do |config| config.before(:suite) do LocalhostServer.new(TESTSERVER.new, 3001) end config.include(MemoryTestHelpers) config.extend(MemoryTestHelpers::TestMethods) end MemoryTestHelpers.setup MemoryTestHelpers.logger = Logger.new($stdout) MemoryTestHelpers.logger.level = Logger::INFO MemoryTestHelpers.logger.formatter = proc do |severity, datetime, progname, msg| "\t\t#{msg}\n" end if ENV['VERBOSE'] MemoryTestHelpers.logger.level = Logger::DEBUG end MemoryTestHelpers.iterations = ENV.fetch("ITERATIONS", 10_000).to_i ethon-0.9.0/profile/benchmarks.rb0000644000004100000410000000426312734673625017013 0ustar www-datawww-data# encoding: utf-8 require 'ethon' require 'open-uri' require 'patron' require 'curb' require 'net/http' require 'cgi' require 'benchmark/ips' require_relative '../spec/support/server' require_relative '../spec/support/localhost_server' LocalhostServer.new(TESTSERVER.new, 3000) LocalhostServer.new(TESTSERVER.new, 3001) LocalhostServer.new(TESTSERVER.new, 3002) url = 'http://localhost:3000/'.freeze uri = URI.parse('http://localhost:3000/').freeze ethon = Ethon::Easy.new(url: url) patron = Patron::Session.new patron_url = Patron::Session.new(base_url: url) curb = Curl::Easy.new(url) puts '[Creation]' Benchmark.ips do |x| x.report('String.new') { '' } x.report('Easy.new') { Ethon::Easy.new } end puts '[Escape]' Benchmark.ips do |x| x.report('CGI.escape') { CGI.escape("まつもと") } x.report('Easy.escape') { ethon.escape("まつもと") } end puts '[Requests]' Benchmark.ips do |x| x.report('net/http') { Net::HTTP.get_response(uri) } x.report('open-uri') { open url } x.report('patron') do patron.base_url = url patron.get('/') end x.report('patron reuse') { patron_url.get('/') } x.report('curb') do curb.url = url curb.perform end x.report('curb reuse') { curb.perform } x.report('Easy.perform') do ethon.url = url ethon.perform end x.report('Easy.perform reuse') { ethon.perform } end puts "[ 4 delayed Requests ]" Benchmark.ips do |x| x.report('net/http') do 3.times do |i| uri = URI.parse("http://localhost:300#{i}/?delay=1") Net::HTTP.get_response(uri) end end x.report("open-uri") do 3.times do |i| open("http://localhost:300#{i}/?delay=1") end end x.report("patron") do sess = Patron::Session.new 3.times do |i| sess.base_url = "http://localhost:300#{i}/?delay=1" sess.get("/") end end x.report("Easy.perform") do easy = Ethon::Easy.new 3.times do |i| easy.url = "http://localhost:300#{i}/?delay=1" easy.perform end end x.report("Multi.perform") do multi = Ethon::Multi.new 3.times do |i| easy = Ethon::Easy.new easy.url = "http://localhost:300#{i}/?delay=1" multi.add(easy) end multi.perform end end ethon-0.9.0/profile/support/0000755000004100000410000000000012734673625016060 5ustar www-datawww-dataethon-0.9.0/profile/support/os_memory_leak_tracker.rb0000644000004100000410000000215012734673625023123 0ustar www-datawww-dataclass OSMemoryLeakTracker attr_reader :current_run def initialize @previous_run = @current_run = 0 end def difference_between_runs(basis=@previous_run) @current_run - basis end def total_difference_between_runs difference_between_runs(@initial_count_run) end def capture_initial_memory_usage capture_memory_usage @initial_count_run = @current_run end def capture_memory_usage @previous_run = @current_run @current_run = rss_bytes end def dump_status(logger) delta = difference_between_runs logger.add(log_level(delta), sprintf("\tTotal memory usage (kb): %d (%+d)", current_run, delta)) end private # amount of memory the current process "is using", in RAM # (doesn't include any swap memory that it may be using, just that in actual RAM) # Code loosely based on https://github.com/rdp/os/blob/master/lib/os.rb # returns 0 on windows def rss_bytes if ENV['OS'] == 'Windows_NT' 0 else `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes end end def log_level(delta) delta > 0 ? Logger::WARN : Logger::DEBUG end endethon-0.9.0/profile/support/memory_test_helpers.rb0000644000004100000410000000417712734673625022507 0ustar www-datawww-datarequire_relative 'ruby_object_leak_tracker' require_relative 'os_memory_leak_tracker' module MemoryTestHelpers class << self attr_accessor :gc_proc, :iterations, :logger def setup if RUBY_PLATFORM == "java" # for leak detection JRuby.objectspace = true if defined?(JRuby) # for gc require 'java' java_import 'java.lang.System' self.gc_proc = proc { System.gc } else self.gc_proc = proc { GC.start } end end end module TestMethods def memory_leak_test(description, &block) context(description) do it "doesn't leak ruby objects" do object_leak_tracker = RubyObjectLeakTracker.new track_memory_usage(object_leak_tracker, &block) object_leak_tracker.total_difference_between_runs.should be <= 10 end it "doesn't leak OS memory (C interop check)" do os_memory_leak_tracker = OSMemoryLeakTracker.new track_memory_usage(os_memory_leak_tracker, &block) os_memory_leak_tracker.total_difference_between_runs.should be <= 10 end end end end def track_memory_usage(tracker) # Intentionally do all this setup before we do any testing logger = MemoryTestHelpers.logger iterations = MemoryTestHelpers.iterations checkpoint_frequency = (iterations / 10.0).to_i gc_frequency = 20 warmup_iterations = [(iterations / 3.0).to_i, 500].min logger.info "Performing #{warmup_iterations} warmup iterations" warmup_iterations.times do yield MemoryTestHelpers.gc_proc.call end tracker.capture_initial_memory_usage logger.info "Performing #{iterations} iterations (checkpoint every #{checkpoint_frequency})" iterations.times do |i| yield last_iteration = (i == iterations - 1) checkpoint = last_iteration || (i % checkpoint_frequency == 0) if checkpoint || (i % gc_frequency == 0) MemoryTestHelpers.gc_proc.call end if checkpoint logger.info "Iteration #{i} checkpoint" tracker.capture_memory_usage tracker.dump_status(logger) end end end end ethon-0.9.0/profile/support/ruby_object_leak_tracker.rb0000644000004100000410000000261312734673625023425 0ustar www-datawww-dataclass RubyObjectLeakTracker attr_reader :previous_count_hash, :current_count_hash def initialize @previous_count_hash = @current_count_hash = {} end def difference_between_runs(basis=@previous_count_hash) @difference_between_runs ||= Hash[@current_count_hash.map do |object_class, count| [object_class, count - (basis[object_class] || 0)] end] end def total_difference_between_runs difference_between_runs(@initial_count_hash).values.inject(0) { |sum, count| sum + count } end def capture_initial_memory_usage capture_memory_usage @initial_count_hash = @current_count_hash end def capture_memory_usage @difference_between_runs = nil @previous_count_hash = @current_count_hash class_to_count = Hash.new { |hash, key| hash[key] = 0 } ObjectSpace.each_object { |obj| class_to_count[obj.class] += 1 } sorted_class_to_count = class_to_count.sort_by { |k, v| -v } @current_count_hash = Hash[sorted_class_to_count] end def dump_status(logger) diff = difference_between_runs most_used_objects = current_count_hash.to_a.sort_by(&:last).reverse[0, 20] most_used_objects.each do |object_class, count| delta = diff[object_class] logger.add(log_level(delta), sprintf("\t%s: %d (%+d)", object_class, count, delta)) end end private def log_level(delta) delta > 0 ? Logger::WARN : Logger::DEBUG end end