pax_global_header00006660000000000000000000000064121350357620014516gustar00rootroot0000000000000052 comment=32a811577d640ac6282825bcb8cc12b7ffe8b404 ethon-0.5.12/000077500000000000000000000000001213503576200127205ustar00rootroot00000000000000ethon-0.5.12/.gitignore000066400000000000000000000000721213503576200147070ustar00rootroot00000000000000*.gem .bundle Gemfile.lock .DS_Store .yardoc doc coverage ethon-0.5.12/.rspec000066400000000000000000000000451213503576200140340ustar00rootroot00000000000000--tty --color --format documentation ethon-0.5.12/.travis.yml000066400000000000000000000002521213503576200150300ustar00rootroot00000000000000script: "bundle exec rake" bundler_args: --without perf rvm: - 1.8.7 - 1.9.2 - 1.9.3 - jruby-head - jruby-18mode - jruby-19mode - rbx-18mode - rbx-19mode ethon-0.5.12/CHANGELOG.md000066400000000000000000000141221213503576200145310ustar00rootroot00000000000000# Changelog ## Master [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.5.12...master) ## 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.5.12/Gemfile000066400000000000000000000005331213503576200142140ustar00rootroot00000000000000source "https://rubygems.org" gemspec gem "rake" group :development, :test do gem "rspec", "~> 2.11" gem "sinatra", git: "https://github.com/sinatra/sinatra.git" gem "json" unless ENV["CI"] gem "guard-rspec", "~> 0.7" gem 'rb-fsevent', '~> 0.9.1' end end group :perf do gem "patron", "~> 0.4" gem "curb", "~> 0.8.0" end ethon-0.5.12/Guardfile000066400000000000000000000003611213503576200145450ustar00rootroot00000000000000# 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.5.12/LICENSE000066400000000000000000000020431213503576200137240ustar00rootroot00000000000000Copyright (c) 2012 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.5.12/README.md000066400000000000000000000051621213503576200142030ustar00rootroot00000000000000# Ethon [![Build Status](https://secure.travis-ci.org/typhoeus/ethon.png?branch=master)](http://travis-ci.org/typhoeus/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-2013 [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. ethon-0.5.12/Rakefile000066400000000000000000000014211213503576200143630ustar00rootroot00000000000000require "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.5.12/ethon.gemspec000066400000000000000000000014061213503576200154030ustar00rootroot00000000000000# 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.rubyforge_project = '[none]' s.add_dependency('ffi', ['>= 1.3.0']) s.add_dependency('mime-types', ['~> 1.18']) s.files = Dir.glob("lib/**/*") + %w(CHANGELOG.md Gemfile LICENSE README.md Rakefile) s.require_path = 'lib' end ethon-0.5.12/lib/000077500000000000000000000000001213503576200134665ustar00rootroot00000000000000ethon-0.5.12/lib/ethon.rb000066400000000000000000000013151213503576200151300ustar00rootroot00000000000000require 'logger' require 'ffi' require 'thread' require 'mime/types' 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.5.12/lib/ethon/000077500000000000000000000000001213503576200146035ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/curl.rb000066400000000000000000000056021213503576200161000ustar00rootroot00000000000000require 'ethon/curls/codes' require 'ethon/curls/options' require 'ethon/curls/http_versions' require 'ethon/curls/infos' require 'ethon/curls/form_options' require 'ethon/curls/auth_types' require 'ethon/curls/postredir' require 'ethon/curls/protocols' require 'ethon/curls/proxy_types' require 'ethon/curls/ssl_versions' 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::HttpVersions extend Ethon::Curls::Infos extend Ethon::Curls::FormOptions extend Ethon::Curls::AuthTypes extend Ethon::Curls::ProxyTypes extend Ethon::Curls::SslVersions extend Ethon::Curls::Messages extend Ethon::Curls::Protocols extend Ethon::Curls::Postredir # :nodoc: def self.windows? !(RbConfig::CONFIG['host_os'] !~ /mingw|mswin|bccwin/) end require 'ethon/curls/constants' require 'ethon/curls/settings' require 'ethon/curls/classes' extend Ethon::Curls::Functions @blocking = true @@initialized = false @@init_mutex = Mutex.new class << self # This function sets up the program environment that libcurl needs. # Think of it as an extension of the library loader. # # This function must be called at least once within a program (a program is all the # code that shares a memory space) before the program calls any other function in libcurl. # The environment it sets up is constant for the life of the program and is the same for # every program, so multiple calls have the same effect as one call. # # The flags option is a bit pattern that tells libcurl exactly what features to init, # as described below. Set the desired bits by ORing the values together. In normal # operation, you must specify CURL_GLOBAL_ALL. Don't use any other value unless # you are familiar with it and mean to control internal operations of libcurl. # # This function is not thread safe. You must not call it when any other thread in # the program (i.e. a thread sharing the same memory) is running. This doesn't just # mean no other thread that is using libcurl. Because curl_global_init() calls # functions of other libraries that are similarly thread unsafe, it could conflict with # any other thread that uses these other libraries. # # @raise [ Ethon::Errors::GlobalInit ] If Curl.global_init fails. def init @@init_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 end end end ethon-0.5.12/lib/ethon/curls/000077500000000000000000000000001213503576200157335ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/curls/auth_types.rb000066400000000000000000000010321213503576200204410ustar00rootroot00000000000000module Ethon module Curls # :nodoc: # This module contain available auth types. module AuthTypes # Return available auth types. # # @example Return auth types. # Ethon::Curl.auth_types # # @return [ Hash ] The auth types. def auth_types { :basic => 0x01, :digest => 0x02, :gssnegotiate => 0x04, :ntlm => 0x08, :digest_ie => 0x10, :auto => 0x1f } end end end end ethon-0.5.12/lib/ethon/curls/classes.rb000066400000000000000000000014451213503576200177210ustar00rootroot00000000000000module 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 # :nodoc: class FDSet < ::FFI::Struct if Curl.windows? layout :fd_count, :u_int, :fd_array, [:u_int, 64] # 2048 FDs 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 layout :sec, :time_t, :usec, :suseconds_t end end end ethon-0.5.12/lib/ethon/curls/codes.rb000066400000000000000000000062221213503576200173570ustar00rootroot00000000000000module 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.5.12/lib/ethon/curls/constants.rb000066400000000000000000000030061213503576200202730ustar00rootroot00000000000000module 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.to_a.flatten) # :nodoc: MultiOption = enum(:multi_option, multi_options.to_a.flatten) # :nodoc: OptionType = enum(option_types.to_a.flatten) # :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: Auth = enum(auth_types.to_a.flatten) # :nodoc: Postredir = enum(postredir.to_a.flatten) # :nodoc: Protocols = enum(protocols.to_a.flatten) # :nodoc: Proxy = enum(proxy_types.to_a.flatten) # :nodoc: SSLVersion = enum(ssl_versions.to_a.flatten) # :nodoc: HTTPVersion = enum(http_versions.to_a.flatten) # :nodoc: MsgCode = enum(:msg_code, msg_codes) end end ethon-0.5.12/lib/ethon/curls/form_options.rb000066400000000000000000000014141213503576200207760ustar00rootroot00000000000000module 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.5.12/lib/ethon/curls/functions.rb000066400000000000000000000122471213503576200202760ustar00rootroot00000000000000module 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 :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, :curl_easy_setopt, [:pointer, :easy_option, :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_fixnum, :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_proc, :curl_easy_setopt, [:pointer, :easy_option, :callback], :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, [:int], :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 :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, :curl_multi_setopt, [:pointer, :multi_option, :pointer], :multi_code 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_fixnum, :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_proc, :curl_multi_setopt, [:pointer, :multi_option, :callback], :multi_code base.attach_function :version, :curl_version, [], :string 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.5.12/lib/ethon/curls/http_versions.rb000066400000000000000000000006441213503576200211730ustar00rootroot00000000000000module Ethon module Curls # This module contains the available proxy types. module HttpVersions # Return http versions. # # @example Return http versions. # Ethon::Curl.http_versions # # @return [ Hash ] The http_versions. def http_versions { :none => 0, :httpv1_0 => 1, :httpv1_1 => 2 } end end end end ethon-0.5.12/lib/ethon/curls/infos.rb000066400000000000000000000126171213503576200174050ustar00rootroot00000000000000module 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 # 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 string_ptr.read_pointer.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.5.12/lib/ethon/curls/messages.rb000066400000000000000000000005271213503576200200730ustar00rootroot00000000000000module 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.5.12/lib/ethon/curls/options.rb000066400000000000000000000411351213503576200177570ustar00rootroot00000000000000module Ethon module Curls # This module contains logic for setting options on # easy or multi interface. module Options # Sets appropriate option for easy, depending on value type. def set_option(option, value, handle, type = :easy) return unless value name = "#{type}_setopt_#{value.class.to_s.downcase.delete(':')}" send(name, handle, option, value) end # :nodoc: def option_types { :long => 0, :object_point => 10000, :function_point => 20000, :off_t => 30000 } end # Curl multi options, refer # https://github.com/bagder/curl/blob/master/include/curl/multi.h def multi_options { :socketfunction => option_types[:function_point] + 1, :socketdata => option_types[:object_point] + 2, :pipelining => option_types[:long] + 3, :timerfunction => option_types[:function_point] + 4, :timerdata => option_types[:object_point] + 5, :maxconnects => option_types[:long] + 6 } end # Curl easy options, refer # https://github.com/bagder/curl/blob/master/include/curl/curl.h def easy_options { :file => option_types[:object_point] + 1, :writedata => option_types[:object_point] + 1, :url => option_types[:object_point] + 2, :port => option_types[:long] + 3, :proxy => option_types[:object_point] + 4, :userpwd => option_types[:object_point] + 5, :proxyuserpwd => option_types[:object_point] + 6, :range => option_types[:object_point] + 7, :infile => option_types[:object_point] + 9, :readdata => option_types[:object_point] + 9, :errorbuffer => option_types[:object_point] + 10, :writefunction => option_types[:function_point] + 11, :readfunction => option_types[:function_point] + 12, :timeout => option_types[:long] + 13, :infilesize => option_types[:long] + 14, :postfields => option_types[:object_point] + 15, :referer => option_types[:object_point] + 16, :ftpport => option_types[:object_point] + 17, :useragent => option_types[:object_point] + 18, :low_speed_time => option_types[:long] + 20, :resume_from => option_types[:long] + 21, :cookie => option_types[:object_point] + 22, :httpheader => option_types[:object_point] + 23, :httppost => option_types[:object_point] + 24, :sslcert => option_types[:object_point] + 25, :keypasswd => option_types[:object_point] + 26, :crlf => option_types[:long] + 27, :quote => option_types[:object_point] + 28, :writeheader => option_types[:object_point] + 29, :headerdata => option_types[:object_point] + 29, :cookiefile => option_types[:object_point] + 31, :sslversion => option_types[:long] + 32, :timecondition => option_types[:long] + 33, :timevalue => option_types[:long] + 34, :customrequest => option_types[:object_point] + 36, :stderr => option_types[:object_point] + 37, :postquote => option_types[:object_point] + 39, :writeinfo => option_types[:object_point] + 40, :verbose => option_types[:long] + 41, :header => option_types[:long] + 42, :noprogress => option_types[:long] + 43, :nobody => option_types[:long] + 44, :failonerror => option_types[:long] + 45, :upload => option_types[:long] + 46, :post => option_types[:long] + 47, :ftplistonly => option_types[:long] + 48, :ftpappend => option_types[:long] + 50, :netrc => option_types[:long] + 51, :followlocation => option_types[:long] + 52, :transfertext => option_types[:long] + 53, :put => option_types[:long] + 54, :progressfunction => option_types[:function_point] + 56, :progressdata => option_types[:object_point] + 57, :autoreferer => option_types[:long] + 58, :proxyport => option_types[:long] + 59, :postfieldsize => option_types[:long] + 60, :httpproxytunnel => option_types[:long] + 61, :interface => option_types[:object_point] + 62, :ssl_verifypeer => option_types[:long] + 64, :cainfo => option_types[:object_point] + 65, :maxredirs => option_types[:long] + 68, :filetime => option_types[:long] + 69, :telnetoptions => option_types[:object_point] + 70, :maxconnects => option_types[:long] + 71, :closepolicy => option_types[:long] + 72, :fresh_connect => option_types[:long] + 74, :forbid_reuse => option_types[:long] + 75, :random_file => option_types[:object_point] + 76, :egdsocket => option_types[:object_point] + 77, :connecttimeout => option_types[:long] + 78, :headerfunction => option_types[:function_point] + 79, :httpget => option_types[:long] + 80, :ssl_verifyhost => option_types[:long] + 81, :cookiejar => option_types[:object_point] + 82, :ssl_cipher_list => option_types[:object_point] + 83, :http_version => option_types[:long] + 84, :ftp_use_epsv => option_types[:long] + 85, :sslcerttype => option_types[:object_point] + 86, :sslkey => option_types[:object_point] + 87, :sslkeytype => option_types[:object_point] + 88, :sslengine => option_types[:object_point] + 89, :sslengine_default => option_types[:long] + 90, :dns_use_global_cache => option_types[:long] + 91, :dns_cache_timeout => option_types[:long] + 92, :prequote => option_types[:object_point] + 93, :debugfunction => option_types[:function_point] + 94, :debugdata => option_types[:object_point] + 95, :cookiesession => option_types[:long] + 96, :capath => option_types[:object_point] + 97, :buffersize => option_types[:long] + 98, :nosignal => option_types[:long] + 99, :share => option_types[:object_point] + 100, :proxytype => option_types[:long] + 101, :accept_encoding => option_types[:object_point] + 102, :private => option_types[:object_point] + 103, :unrestricted_auth => option_types[:long] + 105, :ftp_use_eprt => option_types[:long] + 106, :httpauth => option_types[:long] + 107, :ssl_ctx_function => option_types[:function_point] + 108, :ssl_ctx_data => option_types[:object_point] + 109, :ftp_create_missing_dirs => option_types[:long] + 110, :proxyauth => option_types[:long] + 111, :ipresolve => option_types[:long] + 113, :maxfilesize => option_types[:long] + 114, :infilesize_large => option_types[:off_t] + 115, :resume_from_large => option_types[:off_t] + 116, :maxfilesize_large => option_types[:off_t] + 117, :netrc_file => option_types[:object_point] + 118, :ftp_ssl => option_types[:long] + 119, :postfieldsize_large => option_types[:off_t] + 120, :tcp_nodelay => option_types[:long] + 121, :ftpsslauth => option_types[:long] + 129, :ioctlfunction => option_types[:function_point] + 130, :ioctldata => option_types[:object_point] + 131, :ftp_account => option_types[:object_point] + 134, :cookielist => option_types[:object_point] + 135, :ignore_content_length => option_types[:long] + 136, :ftp_skip_pasv_ip => option_types[:long] + 137, :ftp_filemethod => option_types[:long] + 138, :localport => option_types[:long] + 139, :localportrange => option_types[:long] + 140, :connect_only => option_types[:long] + 141, :conv_from_network_function => option_types[:function_point] + 142, :conv_to_network_function => option_types[:function_point] + 143, :max_send_speed_large => option_types[:off_t] + 145, :max_recv_speed_large => option_types[:off_t] + 146, :ftp_alternative_to_user => option_types[:object_point] + 147, :sockoptfunction => option_types[:function_point] + 148, :sockoptdata => option_types[:object_point] + 149, :ssl_sessionid_cache => option_types[:long] + 150, :ssh_auth_types => option_types[:long] + 151, :ssh_public_keyfile => option_types[:object_point] + 152, :ssh_private_keyfile => option_types[:object_point] + 153, :ftp_ssl_ccc => option_types[:long] + 154, :timeout_ms => option_types[:long] + 155, :connecttimeout_ms => option_types[:long] + 156, :http_transfer_decoding => option_types[:long] + 157, :http_content_decoding => option_types[:long] + 158, :postredir => option_types[:long] + 161, :copypostfields => option_types[:object_point] + 165, :proxy_transfer_mode => option_types[:long] + 166, :seekfunction => option_types[:function_point] + 167, :seekdata => option_types[:object_point] + 168, :crlfile => option_types[:object_point] + 169, :issuercert => option_types[:object_point] + 170, :address_scope => option_types[:long] + 171, :certinfo => option_types[:long] + 172, :username => option_types[:object_point] + 173, :password => option_types[:object_point] + 174, :proxyusername => option_types[:object_point] + 175, :proxypassword => option_types[:object_point] + 176, :noproxy => option_types[:object_point] + 177, :tftp_blksize => option_types[:long] + 178, :socks5_gssapi_service => option_types[:object_point] + 179, :socks5_gssapi_nec => option_types[:long] + 180, :protocols => option_types[:long] + 181, :redir_protocols => option_types[:long] + 182, :ssh_knownhosts => option_types[:object_point] + 183, :ssh_keyfunction => option_types[:function_point] + 184, :ssh_keydata => option_types[:object_point] + 185, :mail_from => option_types[:object_point] + 186, :mail_rcpt => option_types[:object_point] + 187, :ftp_use_pret => option_types[:long] + 188, :rtsp_request => option_types[:long] + 189, :rtsp_session_id => option_types[:object_point] + 190, :rtsp_stream_uri => option_types[:object_point] + 191, :rtsp_transport => option_types[:object_point] + 192, :rtsp_client_cseq => option_types[:long] + 193, :rtsp_server_cseq => option_types[:long] + 194, :interleavedata => option_types[:object_point] + 195, :interleavefunction => option_types[:function_point] + 196, :wildcardmatch => option_types[:long] + 197, :chunk_bgn_function => option_types[:function_point] + 198, :chunk_end_function => option_types[:function_point] + 199, :fnmatch_function => option_types[:function_point] + 200, :chunk_data => option_types[:object_point] + 201, :fnmatch_data => option_types[:object_point] + 202, :resolve => option_types[:object_point] + 203, :tlsauth_username => option_types[:object_point] + 204, :tlsauth_password => option_types[:object_point] + 205, :tlsauth_type => option_types[:object_point] + 206, :transfer_encoding => option_types[:long] + 207, :closesocketfunction => option_types[:function_point] + 208, :closesocketdata => option_types[:object_point] + 209, :gssapi_delegation => option_types[:long] + 210, :dns_servers => option_types[:object_point] + 211, :accepttimeout_ms => option_types[:long] + 212, :tcp_keepalive => option_types[:long] + 213, :tcp_keepidle => option_types[:long] + 214, :tcp_keepintvl => option_types[:long] + 215, :ssl_options => option_types[:long] + 216, :mail_auth => option_types[:object_point] + 217 } end end end end ethon-0.5.12/lib/ethon/curls/postredir.rb000066400000000000000000000004051213503576200202720ustar00rootroot00000000000000module Ethon module Curls module Postredir def postredir { :get_all => 0x00, :post_301 => 0x01, :post_302 => 0x02, :post_303 => 0x04, :post_all => 0x07 } end end end end ethon-0.5.12/lib/ethon/curls/protocols.rb000066400000000000000000000017171213503576200203120ustar00rootroot00000000000000module Ethon module Curls module Protocols def protocols { :http => 0x00000001, :https => 0x00000002, :ftp => 0x00000004, :ftps => 0x00000008, :scp => 0x00000010, :sftp => 0x00000020, :telnet => 0x00000040, :ldap => 0x00000080, :ldaps => 0x00000100, :dict => 0x00001200, :file => 0x00001400, :tftp => 0x00001800, :imap => 0x00011000, :imaps => 0x00012000, :pop3 => 0x00014000, :pop3s => 0x00018000, :smtp => 0x00110000, :smtps => 0x00120000, :rtsp => 0x00140000, :rtmp => 0x00180000, :rtmpt => 0x02100000, :rtmpe => 0x02200000, :rtmpte => 0x02400000, :rtmps => 0x02800000, :rtmpts => 0x21000000, :gopher => 0x22000000, } end end end end ethon-0.5.12/lib/ethon/curls/proxy_types.rb000066400000000000000000000010141213503576200206610ustar00rootroot00000000000000module Ethon module Curls # This module contains the available proxy types. module ProxyTypes # Return proxy types. # # @example Return proxy types. # Ethon::Curl.proxy_types # # @return [ Hash ] The proxy_types. def proxy_types { :http => 0, :http_1_0 => 1, :socks4 => 4, :socks5 => 5, :socks4a => 6, :socks5_hostname =>7 } end end end end ethon-0.5.12/lib/ethon/curls/settings.rb000066400000000000000000000002651213503576200201230ustar00rootroot00000000000000module Ethon module Curl callback :callback, [:pointer, :size_t, :size_t, :pointer], :size_t ffi_lib_flags :now, :global ffi_lib ['libcurl', 'libcurl.so.4'] end end ethon-0.5.12/lib/ethon/curls/ssl_versions.rb000066400000000000000000000006531213503576200210150ustar00rootroot00000000000000module Ethon module Curls # This module contains the ssl version. module SslVersions # Return the ssl versions. # # @example Retur the ssl versions. # Ethon::Curl.ssl_versions # # @return [ Hash ] The versions. def ssl_versions { :default =>0, :tlsv1 => 1, :sslv2 => 2, :sslv3 => 3 } end end end end ethon-0.5.12/lib/ethon/easy.rb000066400000000000000000000331201213503576200160700ustar00rootroot00000000000000require 'ethon/easy/informations' 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' 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 # 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 @hash = nil @on_complete = nil Curl.easy_reset(handle) set_callbacks 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, 0) 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 return @hash if defined?(@hash) && @hash @hash = { :return_code => return_code, :response_headers => response_headers, :response_body => response_body } Easy::Informations::AVAILABLE_INFORMATIONS.keys.each do |info| @hash[info] = send(info) end @hash end # Return pretty log out. # # @example Return log out. # easy.log_inspect # # @return [ String ] The log out. def log_inspect hash = { :url => url, :response_code => response_code, :return_code => return_code, :total_time => total_time } "EASY #{hash.map{|k, v| "#{k}=#{v}"}.flatten.join(' ')}" end end end ethon-0.5.12/lib/ethon/easy/000077500000000000000000000000001213503576200155445ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/easy/callbacks.rb000066400000000000000000000050171213503576200200130ustar00rootroot00000000000000module 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]) 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) @response_body = "" @response_headers = "" 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| @response_body << stream.read_string(size * num) size * num } 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 # 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 @read_callback = proc {|stream, size, num, object| size = size * num left = body.bytesize - @request_body_read size = left if size > left if size > 0 stream.write_string( body.respond_to?(:byteslice) ? body.byteslice(@request_body_read, size) : body[@request_body_read, size], size ) @request_body_read += size end size } self.readfunction = read_callback 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.5.12/lib/ethon/easy/form.rb000066400000000000000000000063071213503576200170420ustar00rootroot00000000000000require '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.5.12/lib/ethon/easy/header.rb000066400000000000000000000031241213503576200173210ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http.rb000066400000000000000000000041171213503576200170530ustar00rootroot00000000000000require '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.5.12/lib/ethon/easy/http/000077500000000000000000000000001213503576200165235ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/easy/http/actionable.rb000066400000000000000000000050451213503576200211550ustar00rootroot00000000000000require '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 # 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 = options.dup 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 # Return the params. # # @example Return params. # action.params # # @return [ Params ] The params. def params @params ||= Params.new(@easy, options.delete(:params)) end # Return the form. # # @example Return form. # action.form # # @return [ Form ] The form. def form @form ||= Form.new(@easy, options.delete(:body)) 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 if params.empty? easy.url = url else set_params(easy) end set_form(easy) unless form.empty? easy.set_attributes(options) 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 = true base_url, base_params = url.split("?") base_params += "&" if base_params easy.url = "#{base_url}?#{base_params}#{params.to_s}" end # Setup request with form. # # @example Setup nothing. # action.set_form(easy) # # @param [ Easy ] easy The easy to setup. def set_form(easy) end end end end end ethon-0.5.12/lib/ethon/easy/http/custom.rb000066400000000000000000000011701213503576200203610ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/delete.rb000066400000000000000000000010201213503576200203030ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/get.rb000066400000000000000000000010121213503576200176210ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/head.rb000066400000000000000000000007621213503576200177560ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/options.rb000066400000000000000000000010061213503576200205400ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/patch.rb000066400000000000000000000007741213503576200201570ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/post.rb000066400000000000000000000010771213503576200200420ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/postable.rb000066400000000000000000000013521213503576200206620ustar00rootroot00000000000000module 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 if form.multipart? form.escape = false form.materialize easy.httppost = form.first.read_pointer else form.escape = true easy.postfieldsize = form.to_s.bytesize easy.copypostfields = form.to_s end end end end end end ethon-0.5.12/lib/ethon/easy/http/put.rb000066400000000000000000000010621213503576200176570ustar00rootroot00000000000000module 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.5.12/lib/ethon/easy/http/putable.rb000066400000000000000000000010541213503576200205040ustar00rootroot00000000000000module 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 easy.infilesize = form.to_s.bytesize easy.set_read_callback(form.to_s) end end end end end ethon-0.5.12/lib/ethon/easy/informations.rb000066400000000000000000000061571213503576200206120ustar00rootroot00000000000000module 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 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, # 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 this curl version supports zlib. # # @example Return wether zlib is supported. # easy.supports_zlib? # # @return [ Boolean ] True if supported, else false. def supports_zlib? !!(Curl.version.match(/zlib/)) end end end end ethon-0.5.12/lib/ethon/easy/operations.rb000066400000000000000000000022621213503576200202560ustar00rootroot00000000000000module 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 # Perform the easy request. # # @example Perform the request. # easy.perform # # @return [ Integer ] The return code. def perform @return_code = Curl.easy_perform(handle) complete Ethon.logger.debug { "ETHON: performed #{self.log_inspect}" } @return_code 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.5.12/lib/ethon/easy/options.rb000066400000000000000000001576371213503576200176070ustar00rootroot00000000000000module Ethon class Easy # This module contains the logic and knowledge about the # available options on easy. module Options attr_reader :url # Sets the contents of the Accept-Encoding: header sent in a HTTP # request, and enables decoding of a response when a # Content-Encoding: header is received. Three encodings are # supported: identity, which does nothing, deflate which requests # the server to compress its response using the zlib algorithm, # and gzip which requests the gzip algorithm. If a zero-length # string is set, then an Accept-Encoding: header containing all # supported encodings is sent. # This is a request, not an order; the server may or may not do it. # This option must be set (to any non-NULL value) or else any # unsolicited encoding done by the server is ignored. See the # special file lib/README.encoding for details. # (This option was called CURLOPT_ENCODING before 7.21.6) # # @example Set accept_encoding option. # easy.accept_encoding = "gzip" # # @param [ String ] value The value to set. # # @return [ void ] def accept_encoding=(value) Curl.set_option(:accept_encoding, value_for(value, :string), handle) end # Pass a string to a zero-terminated string naming a file holding one # or more certificates with which to verify the peer. This makes sense # only when used in combination with the CURLOPT_SSL_VERIFYPEER option. # If CURLOPT_SSL_VERIFYPEER is zero, CURLOPT_CAINFO need not even # indicate an accessible file. This option is by default set to the # system path where libcurl's cacert bundle is assumed to be stored, # as established at build time. When built against NSS, this is the # directory that the NSS certificate database resides in. # # @example Set cainfo option. # easy.cainfo = "/path/to/file" # # @param [ String ] value The value to set. # # @return [ void ] def cainfo=(value) Curl.set_option(:cainfo, value_for(value, :string), handle) end # Pass a string to a zero-terminated string naming a directory holding # multiple CA certificates with which to verify the peer. If libcurl is # built against OpenSSL, the certificate directory must be prepared using # the openssl c_rehash utility. This makes sense only when used in # combination with the CURLOPT_SSL_VERIFYPEER option. If # CURLOPT_SSL_VERIFYPEER is zero, CURLOPT_CAPATH need not even indicate # an accessible path. The CURLOPT_CAPATH function apparently does not # work in Windows due to some limitation in openssl. This option is # OpenSSL-specific and does nothing if libcurl is built to use GnuTLS. # NSS-powered libcurl provides the option only for backward # compatibility. # # @example Set capath option. # easy.capath = "/path/to/file" # # @param [ String ] value The value to set. # # @return [ void ] def capath=(value) Curl.set_option(:capath, value_for(value, :string), handle) end # Pass a long. It should contain the maximum time in seconds that you # allow the connection to the server to take. This only limits the # connection phase, once it has connected, this option is of no more # use. Set to zero to switch to the default built-in connection timeout # \- 300 seconds. See also the CURLOPT_TIMEOUT option. # In Unix-like systems, this might cause signals to be used unless # CURLOPT_NOSIGNAL is set. # # @example Set connecttimeout option. # easy.connecttimeout = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def connecttimeout=(value) Curl.set_option(:connecttimeout, value_for(value, :int), handle) end # Like CURLOPT_CONNECTTIMEOUT but takes the number of milliseconds # instead. If libcurl is built to use the standard system name # resolver, that portion of the connect will still use full-second # resolution for timeouts with a minimum timeout allowed of one second. # (Added in 7.16.2) # # @example Set connecttimeout_ms option. # easy.connecttimeout_ms = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def connecttimeout_ms=(value) Curl.set_option(:connecttimeout_ms, value_for(value, :int), handle) end # Sets the cookie value # # If you want to read/write the cookie from a file, # see cookiefile= and cookiejar= # # @example Set the cookie option # easy.cookie = "cookie-value" # # @param [ String ] value The cookie value # # @return [ void ] def cookie=(value) Curl.set_option(:cookie, value_for(value, :string), handle) end # Sets the cookie jar file # The file will only be used to write the cookie value # If you want to read the cookie from a file, see cookiefile= # # If the file does not exist, it will try to create it # # @example Set cookiejar option # easy.cookiejar = "/path/to/file" # # @param [ String ] file The path to the file # # @return [ void ] def cookiejar=(file) Curl.set_option(:cookiejar, value_for(file, :string), handle) end # Sets the cookie file # The file will only be used to read the cookie value # If you want to set the cookie in a file, see cookiejar= # # @example Set cookiefile option # easy.cookiefile = "/path/to/file" # # @param [ String ] file The path to the file # # @return [ void ] def cookiefile=(file) Curl.set_option(:cookiefile, value_for(file, :string), handle) end # Pass a string as parameter, which should be the full data to post in # a HTTP POST operation. It behaves as the CURLOPT_POSTFIELDS option, # but the original data are copied by the library, allowing the # application to overwrite the original data after setting this option. # Because data are copied, care must be taken when using this option in # conjunction with CURLOPT_POSTFIELDSIZE or # CURLOPT_POSTFIELDSIZE_LARGE: If the size has not been set prior to # CURLOPT_COPYPOSTFIELDS, the data are assumed to be a NUL-terminated # string; else the stored size informs the library about the data byte # count to copy. In any case, the size must not be changed after # CURLOPT_COPYPOSTFIELDS, unless another CURLOPT_POSTFIELDS or # CURLOPT_COPYPOSTFIELDS option is issued. (Added in 7.17.1) # # @example Set copypostfields option. # easy.copypostfields = "PATCH" # # @param [ String ] value The value to set. # # @return [ void ] def copypostfields=(value) Curl.set_option(:copypostfields, value_for(value, :string), handle) end # Pass a pointer to a zero-terminated string as parameter. It can be # used to specify the request instead of GET or HEAD when performing # HTTP based requests, instead of LIST and NLST when performing FTP # directory listings and instead of LIST and RETR when issuing POP3 # based commands. This is particularly useful, for example, for # performing a HTTP DELETE request or a POP3 DELE command. # Please don't perform this at will, on HTTP based requests, by making # sure your server supports the command you are sending first. # When you change the request method by setting CURLOPT_CUSTOMREQUEST # to something, you don't actually change how libcurl behaves or acts # in regards to the particular request method, it will only change the # actual string sent in the request. # For example: # With the HTTP protocol, when you tell libcurl to do a HEAD request, # but then specify a GET though a custom request libcurl will still act # as if it sent a HEAD. To switch to a proper HEAD use CURLOPT_NOBODY, # to switch to a proper POST use CURLOPT_POST or CURLOPT_POSTFIELDS and # to switch to a proper GET use CURLOPT_HTTPGET. # With the POP3 protocol when you tell libcurl to use a custom request # it will behave like a LIST or RETR command was sent where it expects # data to be returned by the server. As such CURLOPT_NOBODY should be # used when specifying commands such as DELE and NOOP for example. # Restore to the internal default by setting this to NULL. # Many people have wrongly used this option to replace the entire # request with their own, including multiple headers and POST contents. # While that might work in many cases, it will cause libcurl to send # invalid requests and it could possibly confuse the remote server # badly. Use CURLOPT_POST and CURLOPT_POSTFIELDS to set POST data. Use # CURLOPT_HTTPHEADER to replace or extend the set of headers sent by # libcurl. Use CURLOPT_HTTP_VERSION to change HTTP version. # (Support for POP3 added in 7.26.0) # # @example Set customrequest option. # easy.customrequest = "PATCH" # # @param [ String ] value The value to set. # # @return [ void ] def customrequest=(value) Curl.set_option(:customrequest, value_for(value, :string), handle) end # Pass a long, this sets the timeout in seconds. Name resolutions will be # kept in memory for this number of seconds. Set to zero to completely # disable caching, or set to -1 to make the cached entries remain # forever. By default, libcurl caches this info for 60 seconds. # The name resolve functions of various libc implementations don't # re-read name server information unless explicitly told so (for # example, by calling res_init(3)). This may cause libcurl to keep # using the older server even if DHCP has updated the server info, and # this may look like a DNS cache issue to the casual libcurl-app user. # # @example Set dns_cache_timeout option. # easy.dns_cache_timeout = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def dns_cache_timeout=(value) Curl.set_option(:dns_cache_timeout, value_for(value, :int), handle) end # A parameter set to 1 tells the library to follow any Location: header # that the server sends as part of a HTTP header. # This means that the library will re-send the same request on the new # location and follow new Location: headers all the way until no more # such headers are returned. CURLOPT_MAXREDIRS can be used to limit the # number of redirects libcurl will follow. # Since 7.19.4, libcurl can limit what protocols it will automatically # follow. The accepted protocols are set with CURLOPT_REDIR_PROTOCOLS # and it excludes the FILE protocol by default. # # @example Set followlocation option. # easy.followlocation = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def followlocation=(value) Curl.set_option(:followlocation, value_for(value, :bool), handle) end # Pass a long. Set to 1 to make the next transfer explicitly close the # connection when done. Normally, libcurl keeps all connections alive # when done with one transfer in case a succeeding one follows that can # re-use them. This option should be used with caution and only if you # understand what it does. Set to 0 to have libcurl keep the connection # open for possible later re-use (default behavior). # # @example Set forbid_reuse option. # easy.forbid_reuse = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def forbid_reuse=(value) Curl.set_option(:forbid_reuse, value_for(value, :bool), handle) end # Pass a long as parameter, which is set to a bitmask, to tell libcurl # which authentication method(s) you want it to use. The available bits # are listed below. If more than one bit is set, libcurl will first # query the site to see which authentication methods it supports and # then pick the best one you allow it to use. For some methods, this # will induce an extra network round-trip. Set the actual name and # password with the CURLOPT_USERPWD option or with the CURLOPT_USERNAME # and the CURLOPT_PASSWORD options. (Added in 7.10.6) # # @example Set httpauth option. # easy.httpauth = :basic # # @param [ $type_doc ] value The value to set. # # @return [ void ] def httpauth=(value) Curl.set_option(:httpauth, value_for(value, :enum, :httpauth), handle) end # Pass a long. If the long is 1, this forces the HTTP request to get # back to GET. Usable if a POST, HEAD, PUT, or a custom request has # been used previously using the same curl handle. # When setting CURLOPT_HTTPGET to 1, it will automatically set # CURLOPT_NOBODY to 0 (since 7.14.1). # # @example Set httpget option. # easy.httpget = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def httpget=(value) Curl.set_option(:httpget, value_for(value, :bool), handle) end # Tells libcurl you want a multipart/formdata HTTP POST to be made and # you instruct what data to pass on to the server. Pass a pointer to a # linked list of curl_httppost structs as parameter. The easiest way to # create such a list, is to use curl_formadd(3) as documented. The data # in this list must remain intact until you close this curl handle # again with curl_easy_cleanup(3). # Using POST with HTTP 1.1 implies the use of a "Expect: 100-continue" # header. You can disable this header with CURLOPT_HTTPHEADER as usual. # When setting CURLOPT_HTTPPOST, it will automatically set # CURLOPT_NOBODY to 0 (since 7.14.1). # # @example Set httppost option. # easy.httppost = value # # @param [ String ] value The value to set. # # @return [ void ] def httppost=(value) Curl.set_option(:httppost, value_for(value, :string), handle) end # Pass a long, set to one of the values described below. They force # libcurl to use the specific HTTP versions. This is not sensible # to do unless you have a good reason. # # Options: # # * :none: We don't care about what version the library uses. # libcurl will use whatever it thinks fit. # * :httpv1_0: Enforce HTTP 1.0 requests. # * :httpv1_1: Enforce HTTP 1.1 requests. # # @example Set http_version option. # easy.http_version = :httpv1_0 # # @param [ Symbol ] value The value to set. # # @return [ void ] def http_version=(value) Curl.set_option(:http_version, value_for(value, :enum, :http_version), handle) end # When uploading a file to a remote site, this option should be used to # tell libcurl what the expected size of the infile is. This value # should be passed as a long. See also CURLOPT_INFILESIZE_LARGE. # For uploading using SCP, this option or CURLOPT_INFILESIZE_LARGE is # mandatory. # When sending emails using SMTP, this command can be used to specify # the optional SIZE parameter for the MAIL FROM command. (Added in # 7.23.0) # This option does not limit how much data libcurl will actually send, # as that is controlled entirely by what the read callback returns. # # @example Set infilesize option. # easy.infilesize = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def infilesize=(value) Curl.set_option(:infilesize, value_for(value, :int), handle) end # Pass a string as parameter. This sets the interface name to use as # outgoing network interface. The name can be an interface name, an IP # address, or a host name. # Starting with 7.24.0: If the parameter starts with "if!" then it is # treated as only as interface name and no attempt will ever be named # to do treat it as an IP address or to do name resolution on it. If # the parameter starts with "host!" it is treated as either an IP # address or a hostname. Hostnames are resolved synchronously. Using # the if! format is highly recommended when using the multi interfaces # to avoid allowing the code to block. If "if!" is specified but the # parameter does not match an existing interface, # CURLE_INTERFACE_FAILED is returned. # # @example Set interface option. # easy.interface = "eth0" # # @param [ String ] value The value to set. # # @return [ void ] def interface=(value) Curl.set_option(:interface, value_for(value, :string), handle) end # Pass a pointer to a zero terminated string as parameter. It will be # used as the password required to use the CURLOPT_SSLKEY or # CURLOPT_SSH_PRIVATE_KEYFILE private key. You never needed a pass # phrase to load a certificate but you need one to load your private key. # (This option was known as CURLOPT_SSLKEYPASSWD up to 7.16.4 and # CURLOPT_SSLCERTPASSWD up to 7.9.2) # # @example Set keypasswd option. # easy.keypasswd = "password" # # @param [ String ] value The value to set. # # @return [ void ] def keypasswd=(value) Curl.set_option(:keypasswd, value_for(value, :string), handle) end # Pass a long. The set number will be the redirection limit. If that # many redirections have been followed, the next redirect will cause an # error (CURLE_TOO_MANY_REDIRECTS). This option only makes sense if the # CURLOPT_FOLLOWLOCATION is used at the same time. Added in 7.15.1: # Setting the limit to 0 will make libcurl refuse any redirect. Set it # to -1 for an infinite number of redirects (which is the default) # # @example Set maxredirs option. # easy.maxredirs = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def maxredirs=(value) Curl.set_option(:maxredirs, value_for(value, :int), handle) end # Pass a curl_off_t as parameter. If an upload exceeds this speed # (counted in bytes per second) on cumulative average during the # transfer, the transfer will pause to keep the average rate less than or # equal to the parameter value. Defaults to unlimited speed. (Added in # 7.15.5) # # @example Set max_send_speed_large option. # easy.max_send_speed_large = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def max_send_speed_large=(value) Curl.set_option(:max_send_speed_large, value_for(value, :int), handle) end # Pass a curl_off_t as parameter. If a download exceeds this speed # (counted in bytes per second) on cumulative average during the # transfer, the transfer will pause to keep the average rate less than or # equal to the parameter value. Defaults to unlimited speed. (Added in # 7.15.5) # # @example Set max_recv_speed_large option. # easy.max_recv_speed_large = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def max_recv_speed_large=(value) Curl.set_option(:max_recv_speed_large, value_for(value, :int), handle) end # A parameter set to 1 tells the library to not include the body-part # in the output. This is only relevant for protocols that have separate # header and body parts. On HTTP(S) servers, this will make libcurl do # a HEAD request. # To change request to GET, you should use CURLOPT_HTTPGET. Change # request to POST with CURLOPT_POST etc. # # @example Set nobody option. # easy.nobody = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def nobody=(value) Curl.set_option(:nobody, value_for(value, :bool), handle) end # Pass a long. If it is 1, libcurl will not use any functions that # install signal handlers or any functions that cause signals to be # sent to the process. This option is mainly here to allow # multi-threaded unix applications to still set/use all timeout options # etc, without risking getting signals. (Added in 7.10) # If this option is set and libcurl has been built with the standard # name resolver, timeouts will not occur while the name resolve takes # place. Consider building libcurl with c-ares support to enable # asynchronous DNS lookups, which enables nice timeouts for name # resolves without signals. # Setting CURLOPT_NOSIGNAL to 1 makes libcurl NOT ask the system to # ignore SIGPIPE signals, which otherwise are sent by the system when # trying to send data to a socket which is closed in the other end. # libcurl makes an effort to never cause such SIGPIPEs to trigger, but # some operating systems have no way to avoid them and even on those # that have there are some corner cases when they may still happen, # contrary to our desire. In addition, using CURLAUTH_NTLM_WB # authentication could cause a SIGCHLD signal to be raised. # # @example Set nosignal option. # easy.nosignal = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def nosignal=(value) Curl.set_option(:nosignal, value_for(value, :bool), handle) end # If you want to post data to the server without letting libcurl do a # strlen() to measure the data size, this option must be used. When # this option is used you can post fully binary data, which otherwise # is likely to fail. If this size is set to -1, the library will use # strlen() to get the size. # # @example Set postfieldsize option. # easy.postfieldsize = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def postfieldsize=(value) Curl.set_option(:postfieldsize, value_for(value, :int), handle) end # Pass a bitmask to control how libcurl acts on redirects after # POSTs that get a 301, 302 or 303 response back. A parameter # with bit 0 set (value CURL_REDIR_POST_301) tells the library # to respect RFC 2616/10.3.2 and not convert POST requests into # GET requests when following a 301 redirection. Setting bit 1 # (value CURL_REDIR_POST_302) makes libcurl maintain the request # method after a 302 redirect whilst setting bit 2 (value # CURL_REDIR_POST_303) makes libcurl maintain the request method # after a 303 redirect. The value CURL_REDIR_POST_ALL is a # convenience define that sets all three bits. # # The non-RFC behaviour is ubiquitous in web browsers, so the # library does the conversion by default to maintain # consistency. However, a server may require a POST to remain a # POST after such a redirection. This option is meaningful only # when setting CURLOPT_FOLLOWLOCATION. (Added in 7.17.1) (This # option was known as CURLOPT_POST301 up to 7.19.0 as it only # supported the 301 then) # # @example Set postredir option. # easy.postredir = :post_all # # @param [ Symbol ] value The value to set. # # @return [ void ] def postredir=(value) Curl.set_option(:postredir, value_for(value, :enum, :postredir), handle) end # Pass a long that holds a bitmask of CURLPROTO_* defines. If used, this # bitmask limits what protocols libcurl may use in the transfer. This # allows you to have a libcurl built to support a wide range of protocols # but still limit specific transfers to only be allowed to use a subset # of them. By default libcurl will accept all protocols it supports. See # also CURLOPT_REDIR_PROTOCOLS. (Added in 7.19.4) # # @example Set protocols option. # easy.protocols = :http # # @param [ Symbol ] value The value or array of values to set. # # @return [ void ] def protocols=(value) Curl.set_option(:protocols, value_for(value, :enum, :protocols), handle) end # Pass a long that holds a bitmask of CURLPROTO_* defines. If used, this # bitmask limits what protocols libcurl may use in a transfer that it # follows to in a redirect when CURLOPT_FOLLOWLOCATION is enabled. This # allows you to limit specific transfers to only be allowed to use a # subset of protocols in redirections. By default libcurl will allow all # protocols except for FILE and SCP. This is a difference compared to # pre-7.19.4 versions which unconditionally would follow to all protocols # supported. (Added in 7.19.4) # # @example Set redir_protocols option. # easy.redir_protocols = :http # # @param [ Symbol ] value The value or array of values to set. # # @return [ void ] def redir_protocols=(value) Curl.set_option(:redir_protocols, value_for(value, :enum, :redir_protocols), handle) end # Set HTTP proxy to use. The parameter should be a string to a zero # terminated string holding the host name or dotted IP address. To # specify port number in this string, append :[port] to the end of the # host name. The proxy string may be prefixed with [protocol]:// since # any such prefix will be ignored. The proxy's port number may # optionally be specified with the separate option. If not specified, # libcurl will default to using port 1080 for proxies. # CURLOPT_PROXYPORT. # When you tell the library to use a HTTP proxy, libcurl will # transparently convert operations to HTTP even if you specify an FTP # URL etc. This may have an impact on what other features of the # library you can use, such as CURLOPT_QUOTE and similar FTP specifics # that don't work unless you tunnel through the HTTP proxy. Such # tunneling is activated with CURLOPT_HTTPPROXYTUNNEL. # libcurl respects the environment variables http_proxy, ftp_proxy, # all_proxy etc, if any of those are set. The CURLOPT_PROXY option does # however override any possibly set environment variables. # Setting the proxy string to "" (an empty string) will explicitly # disable the use of a proxy, even if there is an environment variable # set for it. # Since 7.14.1, the proxy host string given in environment variables # can be specified the exact same way as the proxy can be set with # CURLOPT_PROXY, include protocol prefix (http://) and embedded user + # password. # Since 7.21.7, the proxy string may be specified with a protocol:// # prefix to specify alternative proxy protocols. Use socks4://, # socks4a://, socks5:// or socks5h:// (the last one to enable socks5 # and asking the proxy to do the resolving, also known as # CURLPROXY_SOCKS5_HOSTNAME type) to request the specific SOCKS version # to be used. No protocol specified, http:// and all others will be # treated as HTTP proxies. # # @example Set proxy option. # easy.proxy = "socks5://27.0.0.1:9050" # # @param [ String ] value The value to set. # # @return [ void ] def proxy=(value) Curl.set_option(:proxy, value_for(value, :string), handle) end # Pass a long as parameter, which is set to a bitmask, to tell libcurl # which authentication method(s) you want it to use for your proxy # authentication. If more than one bit is set, libcurl will first query # the site to see what authentication methods it supports and then pick # the best one you allow it to use. For some methods, this will induce # an extra network round-trip. Set the actual name and password with # the CURLOPT_PROXYUSERPWD option. The bitmask can be constructed by # or'ing together the bits listed above for the CURLOPT_HTTPAUTH # option. As of this writing, only Basic, Digest and NTLM work. (Added # in 7.10.7) # # @example Set proxyauth option. # easy.proxyauth = value # # @param [ String ] value The value to set. # # @return [ void ] def proxyauth=(value) Curl.set_option(:proxyauth, value_for(value, :string), handle) end # Pass a long with this option to set the proxy port to connect to # unless it is specified in the proxy string CURLOPT_PROXY. # # @example Set proxyport option. # easy.proxyport = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def proxyport=(value) Curl.set_option(:proxyport, value_for(value, :int), handle) end # Pass a long with this option to set type of the proxy. Available # options for this are CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 (added in # 7.19.4), CURLPROXY_SOCKS4 (added in 7.10), CURLPROXY_SOCKS5, # CURLPROXY_SOCKS4A (added in 7.18.0) and CURLPROXY_SOCKS5_HOSTNAME # (added in 7.18.0). The HTTP type is default. (Added in 7.10) # If you set CURLOPT_PROXYTYPE to CURLPROXY_HTTP_1_0, it will only # affect how libcurl speaks to a proxy when CONNECT is used. The HTTP # version used for "regular" HTTP requests is instead controlled with # CURLOPT_HTTP_VERSION. # # @example Set proxytype option. # easy.proxytype = :socks5 # # @param [ String ] value The value to set. # # @return [ void ] # # @deprecated Please use the proxy option with protocoll handler. def proxytype=(value) Ethon.logger.warn( "ETHON: Easy#proxytype= is deprecated. "+ "Please use Easy#proxy= with protocoll handlers." ) Curl.set_option(:proxytype, value_for(value, :string), handle) end # Pass a string as parameter, which should be [user name]:[password] # to use for the connection to the HTTP proxy. Use CURLOPT_PROXYAUTH # to decide the authentication method. # # @example Set proxyuserpwd option. # easy.proxyuserpwd = "user:password" # # @param [ String ] value The value to set. # # @return [ void ] def proxyuserpwd=(value) Curl.set_option(:proxyuserpwd, value_for(value, :string), handle) end # Data pointer to pass to the file read function. If you use the # CURLOPT_READFUNCTION option, this is the pointer you'll get as input. # If you don't specify a read callback but instead rely on the default # internal read function, this data must be a valid readable FILE *. # If you're using libcurl as a win32 DLL, you MUST use a # CURLOPT_READFUNCTION if you set this option. # This option was also known by the older name CURLOPT_INFILE, # the name CURLOPT_READDATA was introduced in 7.9.7. # # @example Set readdata option. # easy.readdata = value # # @param [ String ] value The value to set. # # @return [ void ] def readdata=(value) Curl.set_option(:readdata, value_for(value, :string), handle) end # Pass a pointer to a function that matches the following prototype: # size_t function( void *ptr, size_t size, size_t nmemb, void # *userdata); This function gets called by libcurl as soon as it needs # to read data in order to send it to the peer. The data area pointed # at by the pointer ptr may be filled with at most size multiplied with # nmemb number of bytes. Your function must return the actual number of # bytes that you stored in that memory area. Returning 0 will signal # end-of-file to the library and cause it to stop the current transfer. # If you stop the current transfer by returning 0 "pre-maturely" (i.e # before the server expected it, like when you've said you will upload # N bytes and you upload less than N bytes), you may experience that # the server "hangs" waiting for the rest of the data that won't come. # The read callback may return CURL_READFUNC_ABORT to stop the current # operation immediately, resulting in a CURLE_ABORTED_BY_CALLBACK error # code from the transfer (Added in 7.12.1) # From 7.18.0, the function can return CURL_READFUNC_PAUSE which then # will cause reading from this connection to become paused. See # curl_easy_pause(3) for further details. # Bugs: when doing TFTP uploads, you must return the exact amount of # data that the callback wants, or it will be considered the final # packet by the server end and the transfer will end there. # If you set this callback pointer to NULL, or don't set it at all, the # default internal read function will be used. It is doing an fread() # on the FILE * userdata set with CURLOPT_READDATA. # # @example Set readfunction option. # easy.readfunction = value # # @param [ String ] value The value to set. # # @return [ void ] def readfunction=(value) Curl.set_option(:readfunction, value_for(value, :string), handle) end # Pass a long as parameter. # This option determines whether libcurl verifies that the server cert # is for the server it is known as. # When negotiating a SSL connection, the server sends a certificate # indicating its identity. # When CURLOPT_SSL_VERIFYHOST is 2, that certificate must indicate that # the server is the server to which you meant to connect, or the # connection fails. # Curl considers the server the intended one when the Common Name field # or a Subject Alternate Name field in the certificate matches the host # name in the URL to which you told Curl to connect. # When the value is 1, the certificate must contain a Common Name # field, but it doesn't matter what name it says. (This is not # ordinarily a useful setting). # When the value is 0, the connection succeeds regardless of the names # in the certificate. # The default value for this option is 2. # This option controls checking the server's certificate's claimed # identity. The server could be lying. To control lying, see # CURLOPT_SSL_VERIFYPEER. If libcurl is built against NSS and # CURLOPT_SSL_VERIFYPEER is zero, CURLOPT_SSL_VERIFYHOST is ignored. # # @example Set ssl_verifyhost option. # easy.ssl_verifyhost = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def ssl_verifyhost=(value) Curl.set_option(:ssl_verifyhost, value_for(value, :int), handle) end # Pass a long as parameter. By default, curl assumes a value of 1. # This option determines whether curl verifies the authenticity of the # peer's certificate. A value of 1 means curl verifies; 0 (zero) means # it doesn't. # When negotiating a SSL connection, the server sends a certificate # indicating its identity. Curl verifies whether the certificate is # authentic, i.e. that you can trust that the server is who the # certificate says it is. This trust is based on a chain of digital # signatures, rooted in certification authority (CA) certificates you # supply. curl uses a default bundle of CA certificates (the path for # that is determined at build time) and you can specify alternate # certificates with the CURLOPT_CAINFO option or the CURLOPT_CAPATH # option. # When CURLOPT_SSL_VERIFYPEER is nonzero, and the verification fails to # prove that the certificate is authentic, the connection fails. When # the option is zero, the peer certificate verification succeeds # regardless. # Authenticating the certificate is not by itself very useful. You # typically want to ensure that the server, as authentically identified # by its certificate, is the server you mean to be talking to. Use # CURLOPT_SSL_VERIFYHOST to control that. The check that the host name # in the certificate is valid for the host name you're connecting to is # done independently of the CURLOPT_SSL_VERIFYPEER option. # # @example Set ssl_verifypeer option. # easy.ssl_verifypeer = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def ssl_verifypeer=(value) Curl.set_option(:ssl_verifypeer, value_for(value, :bool), handle) end # Pass a pointer to a zero terminated string as parameter. The string # should be the file name of your certificate. The default format is # "PEM" and can be changed with CURLOPT_SSLCERTTYPE. # With NSS this can also be the nickname of the certificate you wish to # authenticate with. If you want to use a file from the current # directory, please precede it with "./" prefix, in order to avoid # confusion with a nickname. # # @example Set sslcert option. # easy.sslcert = "name" # # @param [ String ] value The value to set. # # @return [ void ] def sslcert=(value) Curl.set_option(:sslcert, value_for(value, :string), handle) end # Pass a pointer to a zero terminated string as parameter. The string # should be the format of your certificate. Supported formats are "PEM" # and "DER". (Added in 7.9.3) # # @example Set sslcerttype option. # easy.sslcerttype = "PEM" # # @param [ String ] value The value to set. # # @return [ void ] def sslcerttype=(value) Curl.set_option(:sslcerttype, value_for(value, :string), handle) end # Pass a pointer to a zero terminated string as parameter. The string # should be the file name of your private key. The default format is # "PEM" and can be changed with CURLOPT_SSLKEYTYPE. # # @example Set sslkey option. # easy.sslkey = "/path/to/file" # # @param [ String ] value The value to set. # # @return [ void ] def sslkey=(value) Curl.set_option(:sslkey, value_for(value, :string), handle) end # Pass a pointer to a zero terminated string as parameter. The string # should be the format of your private key. Supported formats are # "PEM", "DER" and "ENG". # The format "ENG" enables you to load the private key from a crypto # engine. In this case CURLOPT_SSLKEY is used as an identifier passed # to the engine. You have to set the crypto engine with # CURLOPT_SSLENGINE. "DER" format key file currently does not work # because of a bug in OpenSSL. # # @example Set sslkeytype option. # easy.sslkeytype = "PEM" # # @param [ String ] value The value to set. # # @return [ void ] def sslkeytype=(value) Curl.set_option(:sslkeytype, value_for(value, :string), handle) end # Pass a long as parameter to control what version of SSL/TLS to # attempt to use. The available options are: # Sets sslversion option. # # @example Set sslversion option. # easy.sslversion = :sslv3 # # @param [ $type_doc ] value The value to set. # # @return [ void ] def sslversion=(value) Curl.set_option(:sslversion, value_for(value, :enum, :sslversion), handle) end # Pass a long as parameter containing the maximum time in seconds that # you allow the libcurl transfer operation to take. Normally, name # lookups can take a considerable time and limiting operations to less # than a few minutes risk aborting perfectly normal operations. This # option will cause curl to use the SIGALRM to enable time-outing # system calls. # In unix-like systems, this might cause signals to be used unless # CURLOPT_NOSIGNAL is set. # Default timeout is 0 (zero) which means it never times out. # # @example Set timeout option. # easy.timeout = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def timeout=(value) Curl.set_option(:timeout, value_for(value, :int), handle) end # Like CURLOPT_TIMEOUT but takes number of milliseconds instead. If # libcurl is built to use the standard system name resolver, that # portion of the transfer will still use full-second resolution for # timeouts with a minimum timeout allowed of one second. (Added in # 7.16.2) # # @example Set timeout_ms option. # easy.timeout_ms = 1 # # @param [ Integer ] value The value to set. # # @return [ void ] def timeout_ms=(value) Curl.set_option(:timeout_ms, value_for(value, :int), handle) end # A parameter set to 1 tells the library it can continue to send # authentication (user+password) when following locations, even # when hostname changed. This option is meaningful only when setting # CURLOPT_FOLLOWLOCATION. # # @example Set unrestricted auth. # easy.unrestricted_auth = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def unrestricted_auth=(value) Curl.set_option(:unrestricted_auth, value_for(value, :bool), handle) end # A parameter set to 1 tells the library to prepare for an upload. The # CURLOPT_READDATA and CURLOPT_INFILESIZE or CURLOPT_INFILESIZE_LARGE # options are also interesting for uploads. If the protocol is HTTP, # uploading means using the PUT request unless you tell libcurl # otherwise. # Using PUT with HTTP 1.1 implies the use of a "Expect: 100-continue" # header. You can disable this header with CURLOPT_HTTPHEADER as usual. # If you use PUT to a HTTP 1.1 server, you can upload data without # knowing the size before starting the transfer if you use chunked # encoding. You enable this by adding a header like "Transfer-Encoding: # chunked" with CURLOPT_HTTPHEADER. With HTTP 1.0 or without chunked # transfer, you must specify the size. # # @example Set upload option. # easy.upload = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def upload=(value) Curl.set_option(:upload, value_for(value, :bool), handle) end # Pass in a pointer to the actual URL to deal with. The parameter # should be a string to a zero terminated string which must be # URL-encoded in the following format: # scheme://host:port/path # For a greater explanation of the format please see RFC 3986. # If the given URL lacks the scheme, or protocol, part ("http://" or # "ftp://" etc), libcurl will attempt to resolve which protocol to use # based on the given host mame. If the protocol is not supported, # libcurl will return (CURLE_UNSUPPORTED_PROTOCOL) when you call # curl_easy_perform(3) or curl_multi_perform(3). Use # curl_version_info(3) for detailed information on which protocols are # supported. # The host part of the URL contains the address of the server that you # want to connect to. This can be the fully qualified domain name of # the server, the local network name of the machine on your network or # the IP address of the server or machine represented by either an IPv4 # or IPv6 address. For example: # http://www.example.com/ # http://hostname/ # http://192.168.0.1/ # http://[2001:1890:1112:1::20]/ # It is also possible to specify the user name and password as part of # the host, for some protocols, when connecting to servers that require # authentication. # For example the following types of authentication support this: # http://user:password@www.example.com # ftp://user:password@ftp.example.com # pop3://user:password@mail.example.com # The port is optional and when not specified libcurl will use the # default port based on the determined or specified protocol: 80 for # HTTP, 21 for FTP and 25 for SMTP, etc. The following examples show # how to specify the port: # http://www.example.com:8080/ - This will connect to a web server # using port 8080 rather than 80. # smtp://mail.example.com:587/ - This will connect to a SMTP server on # the alternative mail port. # The path part of the URL is protocol specific and whilst some # examples are given below this list is not conclusive: # HTTP # The path part of a HTTP request specifies the file to retrieve and # from what directory. If the directory is not specified then the web # server's root directory is used. If the file is omitted then the # default document will be retrieved for either the directory specified # or the root directory. The exact resource returned for each URL is # entirely dependent on the server's configuration. # http://www.example.com - This gets the main page from the web server. # http://www.example.com/index.html - This returns the main page by # explicitly requesting it. # http://www.example.com/contactus/ - This returns the default document # from the contactus directory. # FTP # The path part of an FTP request specifies the file to retrieve and # from what directory. If the file part is omitted then libcurl # downloads the directory listing for the directory specified. If the # directory is omitted then the directory listing for the root / home # directory will be returned. # ftp://ftp.example.com - This retrieves the directory listing for the # root directory. # ftp://ftp.example.com/readme.txt - This downloads the file readme.txt # from the root directory. # ftp://ftp.example.com/libcurl/readme.txt - This downloads readme.txt # from the libcurl directory. # ftp://user:password@ftp.example.com/readme.txt - This retrieves the # readme.txt file from the user's home directory. When a username and # password is specified, everything that is specified in the path part # is relative to the user's home directory. To retrieve files from the # root directory or a directory underneath the root directory then the # absolute path must be specified by prepending an additional forward # slash to the beginning of the path. # ftp://user:password@ftp.example.com//readme.txt - This retrieves the # readme.txt from the root directory when logging in as a specified # user. # SMTP # The path part of a SMTP request specifies the host name to present # during communication with the mail server. If the path is omitted # then libcurl will attempt to resolve the local computer's host name. # However, this may not return the fully qualified domain name that is # required by some mail servers and specifying this path allows you to # set an alternative name, such as your machine's fully qualified # domain name, which you might have obtained from an external function # such as gethostname or getaddrinfo. # smtp://mail.example.com - This connects to the mail server at # example.com and sends your local computer's host name in the HELO / # EHLO command. # smtp://mail.example.com/client.example.com - This will send # client.example.com in the HELO / EHLO command to the mail server at # example.com. # POP3 # The path part of a POP3 request specifies the mailbox (message) to # retrieve. If the mailbox is not specified then a list of waiting # messages is returned instead. # pop3://user:password@mail.example.com - This lists the available # messages pop3://user:password@mail.example.com/1 - This retrieves the # first message # SCP # The path part of a SCP request specifies the file to retrieve and # from what directory. The file part may not be omitted. The file is # taken as an absolute path from the root directory on the server. To # specify a path relative to the user's home directory on the server, # prepend ~/ to the path portion. If the user name is not embedded in # the URL, it can be set with the CURLOPT_USERPWD or CURLOPT_USERNAME # option. # scp://user@example.com/etc/issue - This specifies the file /etc/issue # scp://example.com/~/my-file - This specifies the file my-file in the # user's home directory on the server # SFTP # The path part of a SFTP request specifies the file to retrieve and # from what directory. If the file part is omitted then libcurl # downloads the directory listing for the directory specified. If the # path ends in a / then a directory listing is returned instead of a # file. If the path is omitted entirely then the directory listing for # the root / home directory will be returned. If the user name is not # embedded in the URL, it can be set with the CURLOPT_USERPWD or # CURLOPT_USERNAME option. # sftp://user:password@example.com/etc/issue - This specifies the file # /etc/issue # sftp://user@example.com/~/my-file - This specifies the file my-file # in the user's home directory # sftp://ssh.example.com/~/Documents/ - This requests a directory # listing of the Documents directory under the user's home directory # LDAP # The path part of a LDAP request can be used to specify the: # Distinguished Name, Attributes, Scope, Filter and Extension for a # LDAP search. Each field is separated by a question mark and when that # field is not required an empty string with the question mark # separator should be included. # ldap://ldap.example.com/o=My%20Organisation - This will perform a # LDAP search with the DN as My Organisation. # ldap://ldap.example.com/o=My%20Organisation?postalAddress - This will # perform the same search but will only return postalAddress attributes. # ldap://ldap.example.com/?rootDomainNamingContext - This specifies an # empty DN and requests information about the rootDomainNamingContext # attribute for an Active Directory server. # For more information about the individual components of a LDAP URL # please see RFC 4516. # NOTES # Starting with version 7.20.0, the fragment part of the URI will not # be sent as part of the path, which was previously the case. # CURLOPT_URL is the only option that must be set before # curl_easy_perform(3) is called. # CURLOPT_PROTOCOLS can be used to limit what protocols libcurl will # use for this transfer, independent of what libcurl has been compiled # to support. That may be useful if you accept the URL from an external # source and want to limit the accessibility. # # @example Set url option. # easy.url = "www.example.com" # # @param [ String ] value The value to set. # # @return [ void ] def url=(value) @url = value Curl.set_option(:url, value_for(value, :string), handle) end # Pass a pointer to a zero terminated string as parameter. It will be # used to set the User-Agent: header in the http request sent to the # remote server. This can be used to fool servers or scripts. You can # also set any custom header with CURLOPT_HTTPHEADER. # # @example Set useragent option. # easy.useragent = "UserAgent" # # @param [ String ] value The value to set. # # @return [ void ] def useragent=(value) Curl.set_option(:useragent, value_for(value, :string), handle) end # Pass a string as parameter, which should be [user name]:[password] to # use for the connection. Use CURLOPT_HTTPAUTH to decide the # authentication method. # When using NTLM, you can set the domain by prepending it to the user # name and separating the domain and name with a forward (/) or # backward slash (\). Like this: "domain/user:password" or # "domain\user:password". Some HTTP servers (on Windows) support this # style even for Basic authentication. # When using HTTP and CURLOPT_FOLLOWLOCATION, libcurl might perform # several requests to possibly different hosts. libcurl will only send # this user and password information to hosts using the initial host # name (unless CURLOPT_UNRESTRICTED_AUTH is set), so if libcurl follows # locations to other hosts it will not send the user and password to # those. This is enforced to prevent accidental information leakage. # # @example Set userpwd option. # easy.userpwd = "user:password" # # @param [ String ] value The value to set. # # @return [ void ] def userpwd=(value) Curl.set_option(:userpwd, value_for(value, :string), handle) end # Set the parameter to 1 to get the library to display a lot of verbose # information about its operations. Very useful for libcurl and/or # protocol debugging and understanding. The verbose information will be # sent to stderr, or the stream set with CURLOPT_STDERR. # You hardly ever want this set in production use, you will almost # always want this when you debug/report problems. Another neat option # for debugging is the CURLOPT_DEBUGFUNCTION. # Sets verbose option. # # @example Set verbose option. # easy.verbose = true # # @param [ Boolean ] value The value to set. # # @return [ void ] def verbose=(value) Curl.set_option(:verbose, value_for(value, :bool), handle) end private # Return the value to set to easy handle. It is converted with the help # of bool_options, enum_options and int_options. # # @example Return casted the value. # easy.value_for(:verbose) # # @param [ Symbol ] option The option to get the value from. # # @return [ Object ] The casted value. # # @raise [ Ethon::Errors::InvalidValue ] If specified option # points to an enum and the value doen't correspond to # the valid values. 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 type == :enum && option == :httpauth Curl::Auth.to_h.fetch(value) do raise Errors::InvalidValue.new(option, value) end elsif type == :enum && option == :sslversion Curl::SSLVersion.to_h.fetch(value) do raise Errors::InvalidValue.new(option, value) end elsif type == :enum && option == :http_version Curl::HTTPVersion.to_h.fetch(value) do raise Errors::InvalidValue.new(option, value) end elsif type == :enum && (option == :protocols || option == :redir_protocols) Array(value).map do |v| Curl::Protocols.to_h.fetch(v) do raise Errors::InvalidValue.new(option, v) end end.inject(:+) elsif type == :enum && option == :postredir Array(value).map do |v| Curl::Postredir.to_h.fetch(v) do raise Errors::InvalidValue.new(option, v) end end.inject(:+) elsif type == :enum && option == :proxytype Curl::Proxy.to_h.fetch(value) do raise Errors::InvalidValue.new(option, value) end elsif value.is_a?(String) Util.escape_zero_byte(value) else value end end end end end ethon-0.5.12/lib/ethon/easy/params.rb000066400000000000000000000010741213503576200173560ustar00rootroot00000000000000require '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.5.12/lib/ethon/easy/queryable.rb000066400000000000000000000057021213503576200200660ustar00rootroot00000000000000module 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) end # Return wether there are elements in the form or not. # # @example Return if form is empty. # form.empty? # # @return [ Boolean ] True if form 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) types = MIME::Types.type_for(filename) [ filename, types.empty? ? 'application/octet-stream' : types[0].to_s, File.expand_path(file.path) ] end private def recursively_generate_pairs(h, prefix, pairs) case h when Hash h.each_pair do |k,v| key = prefix.nil? ? k : "#{prefix}[#{k}]" pairs_for(v, key, pairs) end when Array h.each_with_index do |v, i| key = "#{prefix}[#{i}]" pairs_for(v, key, pairs) end end end def pairs_for(v, key, pairs) case v when Hash recursively_generate_pairs(v, key, pairs) when Array recursively_generate_pairs(v, key, pairs) when File, Tempfile pairs << [Util.escape_zero_byte(key), file_info(v)] else pairs << [Util.escape_zero_byte(key), Util.escape_zero_byte(v)] end end end end end ethon-0.5.12/lib/ethon/easy/response_callbacks.rb000066400000000000000000000022161213503576200217270ustar00rootroot00000000000000module 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_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) @on_complete.each{ |callback| callback.call(self) } end end end end end ethon-0.5.12/lib/ethon/easy/util.rb000066400000000000000000000011441213503576200170460ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors.rb000066400000000000000000000006351213503576200164500ustar00rootroot00000000000000require '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.5.12/lib/ethon/errors/000077500000000000000000000000001213503576200161175ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/errors/ethon_error.rb000066400000000000000000000002141213503576200207670ustar00rootroot00000000000000module Ethon module Errors # Default Ethon error class for all custom errors. class EthonError < StandardError end end end ethon-0.5.12/lib/ethon/errors/global_init.rb000066400000000000000000000003151213503576200207260ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/invalid_option.rb000066400000000000000000000003251213503576200214620ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/invalid_value.rb000066400000000000000000000003571213503576200212730ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/multi_add.rb000066400000000000000000000003761213503576200204140ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/multi_fdset.rb000066400000000000000000000003321213503576200207610ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/multi_remove.rb000066400000000000000000000004101213503576200211460ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/multi_timeout.rb000066400000000000000000000004701213503576200213450ustar00rootroot00000000000000module 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.5.12/lib/ethon/errors/select.rb000066400000000000000000000003141213503576200177210ustar00rootroot00000000000000module 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.5.12/lib/ethon/libc.rb000066400000000000000000000005511213503576200160420ustar00rootroot00000000000000module Ethon # FFI Wrapper module for Libc. # # @api private module Libc extend FFI::Library ffi_lib 'c' # :nodoc: def self.windows? !(RbConfig::CONFIG['host_os'] !~ /mingw|mswin|bccwin/) end unless windows? attach_function :getdtablesize, [], :int attach_function :free, [:pointer], :void end end end ethon-0.5.12/lib/ethon/loggable.rb000066400000000000000000000024061213503576200167060ustar00rootroot00000000000000# 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.5.12/lib/ethon/multi.rb000066400000000000000000000072201213503576200162630ustar00rootroot00000000000000require '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) # # # @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.5.12/lib/ethon/multi/000077500000000000000000000000001213503576200157355ustar00rootroot00000000000000ethon-0.5.12/lib/ethon/multi/operations.rb000066400000000000000000000116111213503576200204450ustar00rootroot00000000000000module 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 code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval) 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.5.12/lib/ethon/multi/options.rb000066400000000000000000000052161213503576200177610ustar00rootroot00000000000000module Ethon class Multi # This module contains the logic and knowledge about the # available options on multi. module Options # 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) 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) 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) 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) 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) 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) 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.5.12/lib/ethon/multi/stack.rb000066400000000000000000000023271213503576200173730ustar00rootroot00000000000000module 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.5.12/lib/ethon/version.rb000066400000000000000000000000721213503576200166140ustar00rootroot00000000000000module Ethon # Ethon version. VERSION = '0.5.12' end ethon-0.5.12/profile/000077500000000000000000000000001213503576200143605ustar00rootroot00000000000000ethon-0.5.12/profile/benchmarks.rb000066400000000000000000000053221213503576200170240ustar00rootroot00000000000000# encoding: utf-8 require 'ethon' require 'open-uri' require 'patron' require 'curb' require "net/http" require 'cgi' require 'benchmark' Benchmark.bm do |bm| [100_000].each do |i| puts "[ #{i} Creations]" bm.report("String.new ") do i.times { String.new } end bm.report("Easy.new ") do i.times { Ethon::Easy.new } end end GC.start [100_000].each do |i| puts "[ #{i} Escapes]" bm.report("CGI::escape ") do i.times { CGI::escape("まつもと") } end bm.report("Easy.escape ") do e = Ethon::Easy.new i.times { e.escape("まつもと") } end end GC.start [1000].each do |i| puts "[ #{i} Requests]" bm.report("net/http ") do uri = URI.parse("http://localhost:3001/") i.times { Net::HTTP.get_response(uri) } end bm.report("open-uri ") do i.times { open "http://localhost:3001/" } end bm.report("patron ") do sess = Patron::Session.new i.times do sess.base_url = "http://localhost:3001" sess.get("/") end end bm.report("patron reuse ") do sess = Patron::Session.new sess.base_url = "http://localhost:3001" i.times do sess.get("/") end end bm.report("curb reuse ") do easy = Curl::Easy.new("http://localhost:3001") i.times do easy.perform end end bm.report("Easy.perform ") do easy = Ethon::Easy.new i.times do easy.url = "http://localhost:3001/" easy.prepare easy.perform end end bm.report("Easy.perform reuse") do easy = Ethon::Easy.new easy.url = "http://localhost:3001/" easy.prepare i.times { easy.perform } end end GC.start puts "[ 4 delayed Requests ]" bm.report("net/http ") do 3.times do |i| uri = URI.parse("http://localhost:300#{i}/?delay=1") Net::HTTP.get_response(uri) end end bm.report("open-uri ") do 3.times do |i| open("http://localhost:300#{i}/?delay=1") end end bm.report("patron ") do sess = Patron::Session.new 3.times do |i| sess.base_url = "http://localhost:300#{i}/?delay=1" sess.get("/") end end bm.report("Easy.perform ") do easy = Ethon::Easy.new 3.times do |i| easy.url = "http://localhost:300#{i}/?delay=1" easy.prepare easy.perform end end bm.report("Multi.perform ") do multi = Ethon::Multi.new 3.times do |i| easy = Ethon::Easy.new easy.url = "http://localhost:300#{i}/?delay=1" easy.prepare multi.add(easy) end multi.perform end end ethon-0.5.12/profile/memory_leaks.rb000066400000000000000000000071231213503576200173770ustar00rootroot00000000000000require 'perf_spec_helper' require 'ethon' require 'ethon/easy' 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 endethon-0.5.12/profile/perf_spec_helper.rb000066400000000000000000000014751213503576200202210ustar00rootroot00000000000000#### SETUP require 'bundler' Bundler.setup require 'rspec' require 'support/localhost_server' require 'support/server' require '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_iethon-0.5.12/profile/support/000077500000000000000000000000001213503576200160745ustar00rootroot00000000000000ethon-0.5.12/profile/support/memory_test_helpers.rb000066400000000000000000000041741213503576200225200ustar00rootroot00000000000000require 'support/ruby_object_leak_tracker' require 'support/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 endethon-0.5.12/profile/support/os_memory_leak_tracker.rb000066400000000000000000000021501213503576200231370ustar00rootroot00000000000000class 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.5.12/profile/support/ruby_object_leak_tracker.rb000066400000000000000000000026131213503576200234410ustar00rootroot00000000000000class 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 ethon-0.5.12/spec/000077500000000000000000000000001213503576200136525ustar00rootroot00000000000000ethon-0.5.12/spec/ethon/000077500000000000000000000000001213503576200147675ustar00rootroot00000000000000ethon-0.5.12/spec/ethon/curl_spec.rb000066400000000000000000000013601213503576200172730ustar00rootroot00000000000000require '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 Ethon::Curl.should_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 { Ethon::Curl.should_receive(:global_init).and_return(0) } it "doesn't raises global init error" do expect{ Ethon::Curl.init }.to_not raise_error(Ethon::Errors::GlobalInit) end it "logs" do Ethon.logger.should_receive(:debug) Ethon::Curl.init end end end end ethon-0.5.12/spec/ethon/easy/000077500000000000000000000000001213503576200157305ustar00rootroot00000000000000ethon-0.5.12/spec/ethon/easy/callbacks_spec.rb000066400000000000000000000010631213503576200212060ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Easy::Callbacks do let!(:easy) { Ethon::Easy.new } describe "#set_callbacks" do before do Ethon::Curl.should_receive(:set_option).twice 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 end end ethon-0.5.12/spec/ethon/easy/form_spec.rb000066400000000000000000000034011213503576200202300ustar00rootroot00000000000000require '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_false 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_true 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 Ethon::Curl.should_receive(:formadd) form.materialize end end context "when query_pairs contains nil" do let(:pairs) { [['a', nil]] } it "adds params to form" do Ethon::Curl.should_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 Ethon::Curl.should_receive(:formadd) form.materialize end end end end ethon-0.5.12/spec/ethon/easy/header_spec.rb000066400000000000000000000036771213503576200205340ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Easy::Header do let(:easy) { Ethon::Easy.new } describe "#headers=" do let(:headers) { { 'User-Agent' => 'Ethon' } } it "sets header" do Ethon::Easy.any_instance.should_receive(:set_callbacks) Ethon::Curl.should_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.5.12/spec/ethon/easy/http/000077500000000000000000000000001213503576200167075ustar00rootroot00000000000000ethon-0.5.12/spec/ethon/easy/http/custom_spec.rb000066400000000000000000000113111213503576200215550ustar00rootroot00000000000000require '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 easy.should_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 easy.should_receive(:copypostfields=).with(encoded) custom.setup(easy) end it "sets postfieldsize" do easy.should_receive(:postfieldsize=).with{ |value| expect(value).to be(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.5.12/spec/ethon/easy/http/delete_spec.rb000066400000000000000000000007441213503576200215150ustar00rootroot00000000000000require '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.5.12/spec/ethon/easy/http/get_spec.rb000066400000000000000000000043331213503576200210300ustar00rootroot00000000000000require '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(:get) { described_class.new(url, {:params => params, :body => form}) } 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 easy.should_receive(:customrequest=).with("GET") get.setup(easy) end end context "when no body" do it "doesn't set customrequest" do easy.should_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 end end end ethon-0.5.12/spec/ethon/easy/http/head_spec.rb000066400000000000000000000034661213503576200211600ustar00rootroot00000000000000require '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 easy.should_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 easy.should_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.5.12/spec/ethon/easy/http/options_spec.rb000066400000000000000000000023641213503576200217460ustar00rootroot00000000000000require '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 easy.should_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.5.12/spec/ethon/easy/http/patch_spec.rb000066400000000000000000000023451213503576200213510ustar00rootroot00000000000000require '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 easy.should_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.5.12/spec/ethon/easy/http/post_spec.rb000066400000000000000000000134101213503576200212320ustar00rootroot00000000000000require '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(:post) { described_class.new(url, {: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 easy.should_receive(:postfieldsize=).with(0) post.setup(easy) end it "sets copy_postfields" do easy.should_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 it "sets postfieldsize" do easy.should_receive(:postfieldsize=).with(0) post.setup(easy) end it "sets copypostfields" do easy.should_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 easy.should_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 easy.should_receive(:copypostfields=).with(encoded) post.setup(easy) end it "sets postfieldsize" do easy.should_receive(:postfieldsize=).with{ |value| expect(value).to be(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 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.5.12/spec/ethon/easy/http/put_spec.rb000066400000000000000000000045161213503576200210640ustar00rootroot00000000000000require '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(:put) { described_class.new(url, {: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 easy.should_receive(:upload=).with(true) put.setup(easy) end it "sets infilesize" do easy.should_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 it "sets upload" do easy.should_receive(:upload=).with(true) put.setup(easy) end it "sets infilesize" do easy.should_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 easy.should_receive(:infilesize=).with(11) put.setup(easy) end it "sets readfunction" do easy.should_receive(:readfunction=) put.setup(easy) end it "sets upload" do easy.should_receive(:upload=).with(true) put.setup(easy) end context "when requesting" 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 end context "when params and body" end end ethon-0.5.12/spec/ethon/easy/http_spec.rb000066400000000000000000000023631213503576200202520ustar00rootroot00000000000000require '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) { mock(:setup) } let(:get_class) { Ethon::Easy::Http::Get } it "instanciates action" do get.should_receive(:setup) get_class.should_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 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.5.12/spec/ethon/easy/informations_spec.rb000066400000000000000000000031311213503576200217750ustar00rootroot00000000000000require '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 "#effective_url" do it "returns url" do expect(easy.effective_url).to eq("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 "#supports_zlib?" do it "returns true" do expect(easy.supports_zlib?).to be_true end end end ethon-0.5.12/spec/ethon/easy/operations_spec.rb000066400000000000000000000135061213503576200214570ustar00rootroot00000000000000require '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 } before do easy.url = url easy.timeout = timeout easy.connecttimeout = connect_timeout easy.followlocation = follow_location easy.maxredirs = max_redirs easy.userpwd = user_pwd easy.httpauth = http_auth easy.headers = headers easy.protocols = protocols easy.redir_protocols = redir_protocols easy.perform end it "calls Curl.easy_perform" do Ethon::Curl.should_receive(:easy_perform) 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 header" 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 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.5.12/spec/ethon/easy/options_spec.rb000066400000000000000000000117351213503576200207710ustar00rootroot00000000000000require '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 Ethon::Easy.any_instance.should_receive(:set_callbacks) Ethon::Curl.should_receive(:set_option).with do |option, _, _| expect(option).to be(name) end 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 else 1 end easy.method("#{name}=").call(value) end end end describe "#value_for" do context "when option in bool_options" do let(:option) { :verbose } context "when value true" do let(:value) { true } it "returns 1" do expect(easy.method(:value_for).call(value, :bool)).to eq(1) end end context "when value false" do let(:value) { false } it "returns 0" do expect(easy.method(:value_for).call(value, :bool)).to eq(0) end end end context "when value in enum_options" do let(:option) { :httpauth } let(:value) { :ntlm } context "when valid" do it "returns value from struct" do expect(easy.method(:value_for).call(value, :enum, option)).to eq(8) end end context "when invalid" do it "raises Errors::InvalidValue" do expect{ easy.method(:value_for).call(:fubar, :enum, :sslversion) }.to raise_error(Ethon::Errors::InvalidValue) end end context "when invalid protocol in array" do it "raises Errors::InvalidValue" do expect{ easy.method(:value_for).call([:fubar], :enum, :protocols) }.to raise_error(Ethon::Errors::InvalidValue) end end end context "when value in int_options" do let(:option) { :maxredirs } let(:value) { "2" } it "returns value casted to int" do expect(easy.method(:value_for).call(value, :int)).to eq(2) end end context "when value in unspecific_options" do let(:option) { :url } context "when value a string" do let(:value) { "www.example.\0com" } it "returns zero byte escaped string" do expect(easy.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(easy.method(:value_for).call(value, nil)).to eq(1) end end end end context "when requesting" do let(:url) { "localhost:3001" } let(:timeout) { nil } let(:timeout_ms) { nil } let(:connecttimeout) { nil } let(:connecttimeout_ms) { nil } before do easy.url = url easy.timeout = timeout easy.timeout_ms = timeout_ms easy.connecttimeout = connecttimeout easy.connecttimeout_ms = connecttimeout_ms easy.perform 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::Curl.version.match("c-ares") context "when timeout_ms" do let(:timeout_ms) { 900 } 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) { 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 end end end ethon-0.5.12/spec/ethon/easy/queryable_spec.rb000066400000000000000000000117211213503576200212620ustar00rootroot00000000000000# 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', 'まつもと' => '%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]} } 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 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) { Tempfile.new("fubar") } let(:file_info) { params.method(:file_info).call(file) } let(:hash) { {:a => {:b => [file]}} } it "transforms" do expect(pairs).to eq([["a[b][0]", file_info]]) 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 "escapes" 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 "escapes" 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_true end end context "when params not empty" do let(:hash) { {:a => 1} } it "returns false" do expect(params.empty?).to be_false end end end end ethon-0.5.12/spec/ethon/easy/response_callbacks_spec.rb000066400000000000000000000017251213503576200231310ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Easy::ResponseCallbacks do let(:easy) { Ethon::Easy.new } describe "#on_complete" do it "responds" do expect(easy).to respond_to(:on_complete) end context "when no block given" do it "returns @on_complete" do expect(easy.on_complete).to eq([]) end end context "when block given" do it "stores" do easy.on_complete { p 1 } expect(easy.instance_variable_get(:@on_complete)).to have(1).items end end context "when multiple blocks given" do it "stores" do easy.on_complete { p 1 } easy.on_complete { p 2 } expect(easy.instance_variable_get(:@on_complete)).to have(2).items end end end describe "#complete" do before do easy.on_complete {|r| String.new(r.url) } end it "executes blocks and passes self" do String.should_receive(:new).with(easy.url) easy.complete end end end ethon-0.5.12/spec/ethon/easy/util_spec.rb000066400000000000000000000010701213503576200202420ustar00rootroot00000000000000require '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.5.12/spec/ethon/easy_spec.rb000066400000000000000000000052031213503576200172670ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Easy do let(:easy) { Ethon::Easy.new } describe ".new" do it "inits curl" do Ethon::Curl.should_receive(:init) easy end context "when options are empty" do it "sets only callbacks" do Ethon::Easy.any_instance.should_receive(:set_callbacks) Ethon::Curl.should_receive(:set_option).never easy end end context "when options not empty" do context "when verbose is set" do let(:options) { { :verbose => true } } let(:easy) { Ethon::Easy.new(options) } it "sets verbose" do Ethon::Easy.any_instance.should_receive(:set_callbacks) Ethon::Curl.should_receive(:set_option).with do |option, value, _| expect(option).to be(:verbose) expect(value).to be(1) end easy end end end end describe "#set_attributes" do context "when options are empty" do it "sets nothing" do Ethon::Easy.any_instance.should_receive(:set_callbacks) Ethon::Curl.should_receive(:set_option).never easy end end context "when options aren't empty" do context "when valid key" do it "sets" do easy.should_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 hash" do easy.reset expect(easy.instance_variable_get(:@hash)).to be_nil end it "resets easy handle" do Ethon::Curl.should_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 end describe "#to_hash" do [ :return_code, :response_code, :response_headers, :response_body, :total_time, :starttransfer_time, :appconnect_time, :pretransfer_time, :connect_time, :namelookup_time, :effective_url, :primary_ip, :redirect_count ].each do |name| it "contains #{name}" do expect(easy.to_hash).to include(name) end 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.5.12/spec/ethon/libc_spec.rb000066400000000000000000000004301213503576200172340ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Libc do describe "#getdtablesize" do it "returns an integer" do expect(Ethon::Libc.getdtablesize).to be_a(Integer) end it "returns bigger zero" do expect(Ethon::Libc.getdtablesize).to_not be_zero end end end ethon-0.5.12/spec/ethon/loggable_spec.rb000066400000000000000000000005071213503576200201040ustar00rootroot00000000000000require "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.5.12/spec/ethon/multi/000077500000000000000000000000001213503576200161215ustar00rootroot00000000000000ethon-0.5.12/spec/ethon/multi/operations_spec.rb000066400000000000000000000165471213503576200216600ustar00rootroot00000000000000require '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 Ethon::Curl.should_receive(:multi_timeout).and_return(:ok) multi.instance_variable_set(:@timeout, mock(: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 { Ethon::Curl.should_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 { Ethon::Curl.should_receive(:multi_fdset).and_return(:ok) } it "doesn't raise" do expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error(Ethon::Errors::MultiFdset) end context "when max_fd -1" do let(:max_fd) { -1 } before do multi.instance_variable_set(:@max_fd, mock(:read_int => max_fd)) multi.should_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 { Ethon::Curl.should_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 { Ethon::Curl.should_receive(:select).and_return(0) } it "doesn't raise" do expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error(Ethon::Errors::Select) end end end end context "when code not ok" do before { Ethon::Curl.should_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 Ethon.logger.should_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_true 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_true 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_true 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_false 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_true 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 multi.instance_variable_get(:@fd_read).should_receive(:clear) end it "resets @fd_write" do multi.instance_variable_get(:@fd_write).should_receive(:clear) end it "resets @fd_excep" do multi.instance_variable_get(:@fd_excep).should_receive(:clear) end end describe "#check" do it { pending("untested") } end describe "#run" do it { pending("untested") } end describe "#trigger" do it "calls multi perform" do Ethon::Curl.should_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 Ethon::Curl.should_receive(:multi_perform).and_return(:ok) expect(multi.send(:trigger, pointer)).to eq(:ok) end end end ethon-0.5.12/spec/ethon/multi/options_spec.rb000066400000000000000000000032071213503576200211550ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Multi::Options do let(:multi) { Ethon::Multi.new } [ :maxconnects, :pipelining, :socketdata, :socketfunction, :timerdata, :timerfunction ].each do |name| describe "#{name}=" do it "responds_to" do expect(multi).to respond_to("#{name}=") end it "sets option" do Ethon::Curl.should_receive(:set_option).with do |option, _, _| expect(option).to be(name) end 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.5.12/spec/ethon/multi/stack_spec.rb000066400000000000000000000041211213503576200205630ustar00rootroot00000000000000require '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 Ethon::Curl.should_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 Ethon::Curl.should_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 Ethon::Curl.should_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 Ethon::Curl.should_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 Ethon::Curl.should_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.5.12/spec/ethon/multi_spec.rb000066400000000000000000000007411213503576200174620ustar00rootroot00000000000000require 'spec_helper' describe Ethon::Multi do describe ".new" do it "inits curl" do Ethon::Curl.should_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 Ethon::Multi.any_instance.should_receive(:pipelining=).with(true) Ethon::Multi.new(options) end end end end end ethon-0.5.12/spec/spec_helper.rb000066400000000000000000000011111213503576200164620ustar00rootroot00000000000000$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.5.12/spec/support/000077500000000000000000000000001213503576200153665ustar00rootroot00000000000000ethon-0.5.12/spec/support/localhost_server.rb000066400000000000000000000043571213503576200213020ustar00rootroot00000000000000require '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.5.12/spec/support/server.rb000066400000000000000000000053301213503576200172220ustar00rootroot00000000000000#!/usr/bin/env ruby require 'json' require 'zlib' require 'sinatra/base' TESTSERVER = Sinatra.new do set :logging, false 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