httsoiree 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/Gemfile +14 -0
- data/Guardfile +16 -0
- data/History +303 -0
- data/MIT-LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +12 -0
- data/bin/httparty +117 -0
- data/cucumber.yml +1 -0
- data/examples/aaws.rb +32 -0
- data/examples/basic.rb +28 -0
- data/examples/crack.rb +19 -0
- data/examples/custom_parsers.rb +67 -0
- data/examples/delicious.rb +37 -0
- data/examples/google.rb +16 -0
- data/examples/headers_and_user_agents.rb +6 -0
- data/examples/logging.rb +38 -0
- data/examples/nokogiri_html_parser.rb +22 -0
- data/examples/rubyurl.rb +14 -0
- data/examples/stackexchange.rb +24 -0
- data/examples/tripit_sign_in.rb +33 -0
- data/examples/twitter.rb +31 -0
- data/examples/whoismyrep.rb +10 -0
- data/features/basic_authentication.feature +20 -0
- data/features/command_line.feature +7 -0
- data/features/deals_with_http_error_codes.feature +26 -0
- data/features/digest_authentication.feature +20 -0
- data/features/handles_compressed_responses.feature +27 -0
- data/features/handles_multiple_formats.feature +57 -0
- data/features/steps/env.rb +22 -0
- data/features/steps/httparty_response_steps.rb +52 -0
- data/features/steps/httparty_steps.rb +43 -0
- data/features/steps/mongrel_helper.rb +94 -0
- data/features/steps/remote_service_steps.rb +74 -0
- data/features/supports_read_timeout_option.feature +13 -0
- data/features/supports_redirection.feature +22 -0
- data/features/supports_timeout_option.feature +13 -0
- data/httparty.gemspec +25 -0
- data/lib/httparty/connection_adapter.rb +188 -0
- data/lib/httparty/cookie_hash.rb +22 -0
- data/lib/httparty/core_extensions.rb +32 -0
- data/lib/httparty/exceptions.rb +29 -0
- data/lib/httparty/hash_conversions.rb +51 -0
- data/lib/httparty/logger/apache_logger.rb +22 -0
- data/lib/httparty/logger/curl_logger.rb +48 -0
- data/lib/httparty/logger/logger.rb +18 -0
- data/lib/httparty/module_inheritable_attributes.rb +56 -0
- data/lib/httparty/net_digest_auth.rb +84 -0
- data/lib/httparty/parser.rb +141 -0
- data/lib/httparty/request.rb +339 -0
- data/lib/httparty/response/headers.rb +31 -0
- data/lib/httparty/response.rb +72 -0
- data/lib/httparty/version.rb +3 -0
- data/lib/httparty.rb +618 -0
- data/lib/httsoiree.rb +3 -0
- data/script/release +42 -0
- data/spec/fixtures/delicious.xml +23 -0
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +3 -0
- data/spec/fixtures/ssl/generate.sh +29 -0
- data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
- data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
- data/spec/fixtures/ssl/generated/ca.crt +16 -0
- data/spec/fixtures/ssl/generated/ca.key +15 -0
- data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
- data/spec/fixtures/ssl/generated/server.crt +13 -0
- data/spec/fixtures/ssl/generated/server.key +15 -0
- data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
- data/spec/fixtures/twitter.csv +2 -0
- data/spec/fixtures/twitter.json +1 -0
- data/spec/fixtures/twitter.xml +403 -0
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
- data/spec/httparty/connection_adapter_spec.rb +370 -0
- data/spec/httparty/cookie_hash_spec.rb +83 -0
- data/spec/httparty/exception_spec.rb +23 -0
- data/spec/httparty/logger/apache_logger_spec.rb +41 -0
- data/spec/httparty/logger/curl_logger_spec.rb +18 -0
- data/spec/httparty/logger/logger_spec.rb +22 -0
- data/spec/httparty/net_digest_auth_spec.rb +152 -0
- data/spec/httparty/parser_spec.rb +165 -0
- data/spec/httparty/request_spec.rb +774 -0
- data/spec/httparty/response_spec.rb +221 -0
- data/spec/httparty/ssl_spec.rb +74 -0
- data/spec/httparty_spec.rb +783 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/ssl_test_helper.rb +47 -0
- data/spec/support/ssl_test_server.rb +80 -0
- data/spec/support/stub_response.rb +43 -0
- data/website/css/common.css +47 -0
- data/website/index.html +73 -0
- metadata +215 -0
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            module HTTParty
         | 
| 2 | 
            +
              module ModuleInheritableAttributes #:nodoc:
         | 
| 3 | 
            +
                def self.included(base)
         | 
| 4 | 
            +
                  base.extend(ClassMethods)
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # borrowed from Rails 3.2 ActiveSupport
         | 
| 8 | 
            +
                def self.hash_deep_dup(hash)
         | 
| 9 | 
            +
                  duplicate = hash.dup
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  duplicate.each_pair do |key, value|
         | 
| 12 | 
            +
                    duplicate[key] = if value.is_a?(Hash)
         | 
| 13 | 
            +
                      hash_deep_dup(value)
         | 
| 14 | 
            +
                    elsif value.is_a?(Proc)
         | 
| 15 | 
            +
                      duplicate[key] = value.dup
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      value
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  duplicate
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                module ClassMethods #:nodoc:
         | 
| 25 | 
            +
                  def mattr_inheritable(*args)
         | 
| 26 | 
            +
                    @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
         | 
| 27 | 
            +
                    @mattr_inheritable_attrs += args
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    args.each do |arg|
         | 
| 30 | 
            +
                      module_eval %(class << self; attr_accessor :#{arg} end)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    @mattr_inheritable_attrs
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def inherited(subclass)
         | 
| 37 | 
            +
                    super
         | 
| 38 | 
            +
                    @mattr_inheritable_attrs.each do |inheritable_attribute|
         | 
| 39 | 
            +
                      ivar = "@#{inheritable_attribute}"
         | 
| 40 | 
            +
                      subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      if instance_variable_get(ivar).respond_to?(:merge)
         | 
| 43 | 
            +
                        method = <<-EOM
         | 
| 44 | 
            +
                          def self.#{inheritable_attribute}
         | 
| 45 | 
            +
                            duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar})
         | 
| 46 | 
            +
                            #{ivar} = superclass.#{inheritable_attribute}.merge(duplicate)
         | 
| 47 | 
            +
                          end
         | 
| 48 | 
            +
                        EOM
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                        subclass.class_eval method
         | 
| 51 | 
            +
                      end
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            require 'digest/md5'
         | 
| 2 | 
            +
            require 'net/http'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Net
         | 
| 5 | 
            +
              module HTTPHeader
         | 
| 6 | 
            +
                def digest_auth(username, password, response)
         | 
| 7 | 
            +
                  @header['Authorization'] = DigestAuthenticator.new(username, password,
         | 
| 8 | 
            +
                      @method, @path, response).authorization_header
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
             | 
| 12 | 
            +
                class DigestAuthenticator
         | 
| 13 | 
            +
                  def initialize(username, password, method, path, response_header)
         | 
| 14 | 
            +
                    @username = username
         | 
| 15 | 
            +
                    @password = password
         | 
| 16 | 
            +
                    @method   = method
         | 
| 17 | 
            +
                    @path     = path
         | 
| 18 | 
            +
                    @response = parse(response_header)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def authorization_header
         | 
| 22 | 
            +
                    @cnonce = md5(random)
         | 
| 23 | 
            +
                    header = [
         | 
| 24 | 
            +
                      %Q(Digest username="#{@username}"),
         | 
| 25 | 
            +
                      %Q(realm="#{@response['realm']}"),
         | 
| 26 | 
            +
                      %Q(nonce="#{@response['nonce']}"),
         | 
| 27 | 
            +
                      %Q(uri="#{@path}"),
         | 
| 28 | 
            +
                      %Q(response="#{request_digest}"),
         | 
| 29 | 
            +
                    ]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    if qop_present?
         | 
| 32 | 
            +
                      fields = [
         | 
| 33 | 
            +
                        %Q(cnonce="#{@cnonce}"),
         | 
| 34 | 
            +
                        %Q(qop="#{@response['qop']}"),
         | 
| 35 | 
            +
                        %Q(nc=00000001)
         | 
| 36 | 
            +
                      ]
         | 
| 37 | 
            +
                      fields.each { |field| header << field }
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    header << %Q(opaque="#{@response['opaque']}") if opaque_present?
         | 
| 41 | 
            +
                    header
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def parse(response_header)
         | 
| 47 | 
            +
                    response_header['www-authenticate'] =~ /Digest (.*)/
         | 
| 48 | 
            +
                    params = {}
         | 
| 49 | 
            +
                    $1.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
         | 
| 50 | 
            +
                    params
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def opaque_present?
         | 
| 54 | 
            +
                    @response.has_key?('opaque') and not @response['opaque'].empty?
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def qop_present?
         | 
| 58 | 
            +
                    @response.has_key?('qop') and not @response['qop'].empty?
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def random
         | 
| 62 | 
            +
                    "%x" % (Time.now.to_i + rand(65535))
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def request_digest
         | 
| 66 | 
            +
                    a = [md5(a1), @response['nonce'], md5(a2)]
         | 
| 67 | 
            +
                    a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
         | 
| 68 | 
            +
                    md5(a.join(":"))
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def md5(str)
         | 
| 72 | 
            +
                    Digest::MD5.hexdigest(str)
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def a1
         | 
| 76 | 
            +
                    [@username, @response['realm'], @password].join(":")
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def a2
         | 
| 80 | 
            +
                    [@method, @path].join(":")
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
| @@ -0,0 +1,141 @@ | |
| 1 | 
            +
            module HTTParty
         | 
| 2 | 
            +
              # The default parser used by HTTParty, supports xml, json, html, csv and
         | 
| 3 | 
            +
              # plain text.
         | 
| 4 | 
            +
              #
         | 
| 5 | 
            +
              # == Custom Parsers
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # If you'd like to do your own custom parsing, subclassing HTTParty::Parser
         | 
| 8 | 
            +
              # will make that process much easier. There are a few different ways you can
         | 
| 9 | 
            +
              # utilize HTTParty::Parser as a superclass.
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              # @example Intercept the parsing for all formats
         | 
| 12 | 
            +
              #   class SimpleParser < HTTParty::Parser
         | 
| 13 | 
            +
              #     def parse
         | 
| 14 | 
            +
              #       perform_parsing
         | 
| 15 | 
            +
              #     end
         | 
| 16 | 
            +
              #   end
         | 
| 17 | 
            +
              #
         | 
| 18 | 
            +
              # @example Add the atom format and parsing method to the default parser
         | 
| 19 | 
            +
              #   class AtomParsingIncluded < HTTParty::Parser
         | 
| 20 | 
            +
              #     SupportedFormats.merge!(
         | 
| 21 | 
            +
              #       {"application/atom+xml" => :atom}
         | 
| 22 | 
            +
              #     )
         | 
| 23 | 
            +
              #
         | 
| 24 | 
            +
              #     def atom
         | 
| 25 | 
            +
              #       perform_atom_parsing
         | 
| 26 | 
            +
              #     end
         | 
| 27 | 
            +
              #   end
         | 
| 28 | 
            +
              #
         | 
| 29 | 
            +
              # @example Only support the atom format
         | 
| 30 | 
            +
              #   class ParseOnlyAtom < HTTParty::Parser
         | 
| 31 | 
            +
              #     SupportedFormats = {"application/atom+xml" => :atom}
         | 
| 32 | 
            +
              #
         | 
| 33 | 
            +
              #     def atom
         | 
| 34 | 
            +
              #       perform_atom_parsing
         | 
| 35 | 
            +
              #     end
         | 
| 36 | 
            +
              #   end
         | 
| 37 | 
            +
              #
         | 
| 38 | 
            +
              # @abstract Read the Custom Parsers section for more information.
         | 
| 39 | 
            +
              class Parser
         | 
| 40 | 
            +
                SupportedFormats = {
         | 
| 41 | 
            +
                  'text/xml'               => :xml,
         | 
| 42 | 
            +
                  'application/xml'        => :xml,
         | 
| 43 | 
            +
                  'application/json'       => :json,
         | 
| 44 | 
            +
                  'text/json'              => :json,
         | 
| 45 | 
            +
                  'application/javascript' => :plain,
         | 
| 46 | 
            +
                  'text/javascript'        => :plain,
         | 
| 47 | 
            +
                  'text/html'              => :html,
         | 
| 48 | 
            +
                  'text/plain'             => :plain,
         | 
| 49 | 
            +
                  'text/csv'               => :csv,
         | 
| 50 | 
            +
                  'application/csv'        => :csv,
         | 
| 51 | 
            +
                  'text/comma-separated-values' => :csv
         | 
| 52 | 
            +
                }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # The response body of the request
         | 
| 55 | 
            +
                # @return [String]
         | 
| 56 | 
            +
                attr_reader :body
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                # The intended parsing format for the request
         | 
| 59 | 
            +
                # @return [Symbol] e.g. :json
         | 
| 60 | 
            +
                attr_reader :format
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # Instantiate the parser and call {#parse}.
         | 
| 63 | 
            +
                # @param [String] body the response body
         | 
| 64 | 
            +
                # @param [Symbol] format the response format
         | 
| 65 | 
            +
                # @return parsed response
         | 
| 66 | 
            +
                def self.call(body, format)
         | 
| 67 | 
            +
                  new(body, format).parse
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                # @return [Hash] the SupportedFormats hash
         | 
| 71 | 
            +
                def self.formats
         | 
| 72 | 
            +
                  const_get(:SupportedFormats)
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                # @param [String] mimetype response MIME type
         | 
| 76 | 
            +
                # @return [Symbol]
         | 
| 77 | 
            +
                # @return [nil] mime type not supported
         | 
| 78 | 
            +
                def self.format_from_mimetype(mimetype)
         | 
| 79 | 
            +
                  formats[formats.keys.detect {|k| mimetype.include?(k)}]
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                # @return [Array<Symbol>] list of supported formats
         | 
| 83 | 
            +
                def self.supported_formats
         | 
| 84 | 
            +
                  formats.values.uniq
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # @param [Symbol] format e.g. :json, :xml
         | 
| 88 | 
            +
                # @return [Boolean]
         | 
| 89 | 
            +
                def self.supports_format?(format)
         | 
| 90 | 
            +
                  supported_formats.include?(format)
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def initialize(body, format)
         | 
| 94 | 
            +
                  @body = body
         | 
| 95 | 
            +
                  @format = format
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                # @return [Object] the parsed body
         | 
| 99 | 
            +
                # @return [nil] when the response body is nil, an empty string, spaces only or "null"
         | 
| 100 | 
            +
                def parse
         | 
| 101 | 
            +
                  return nil if body.nil? || body.strip.empty? || body == "null"
         | 
| 102 | 
            +
                  if supports_format?
         | 
| 103 | 
            +
                    parse_supported_format
         | 
| 104 | 
            +
                  else
         | 
| 105 | 
            +
                    body
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                protected
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def xml
         | 
| 112 | 
            +
                  MultiXml.parse(body)
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                def json
         | 
| 116 | 
            +
                  JSON.load(body, nil)
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def csv
         | 
| 120 | 
            +
                  CSV.parse(body)
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def html
         | 
| 124 | 
            +
                  body
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def plain
         | 
| 128 | 
            +
                  body
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                def supports_format?
         | 
| 132 | 
            +
                  self.class.supports_format?(format)
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                def parse_supported_format
         | 
| 136 | 
            +
                  send(format)
         | 
| 137 | 
            +
                rescue NoMethodError => e
         | 
| 138 | 
            +
                  raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
            end
         | 
| @@ -0,0 +1,339 @@ | |
| 1 | 
            +
            module HTTParty
         | 
| 2 | 
            +
              class Request #:nodoc:
         | 
| 3 | 
            +
                SupportedHTTPMethods = [
         | 
| 4 | 
            +
                  Net::HTTP::Get,
         | 
| 5 | 
            +
                  Net::HTTP::Post,
         | 
| 6 | 
            +
                  Net::HTTP::Patch,
         | 
| 7 | 
            +
                  Net::HTTP::Put,
         | 
| 8 | 
            +
                  Net::HTTP::Delete,
         | 
| 9 | 
            +
                  Net::HTTP::Head,
         | 
| 10 | 
            +
                  Net::HTTP::Options,
         | 
| 11 | 
            +
                  Net::HTTP::Move,
         | 
| 12 | 
            +
                  Net::HTTP::Copy
         | 
| 13 | 
            +
                ]
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                SupportedURISchemes  = [URI::HTTP, URI::HTTPS, URI::Generic]
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                NON_RAILS_QUERY_STRING_NORMALIZER = Proc.new do |query|
         | 
| 18 | 
            +
                  Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
         | 
| 19 | 
            +
                    if value.nil?
         | 
| 20 | 
            +
                      key.to_s
         | 
| 21 | 
            +
                    elsif value.respond_to?(:to_ary)
         | 
| 22 | 
            +
                      value.to_ary.map {|v| "#{key}=#{URI.encode(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}"}
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      HashConversions.to_params(key => value)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end.flatten.join('&')
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
         | 
| 30 | 
            +
                attr_reader :path
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def initialize(http_method, path, o={})
         | 
| 33 | 
            +
                  self.http_method = http_method
         | 
| 34 | 
            +
                  self.path = path
         | 
| 35 | 
            +
                  self.options = {
         | 
| 36 | 
            +
                    limit: o.delete(:no_follow) ? 1 : 5,
         | 
| 37 | 
            +
                    assume_utf16_is_big_endian: true,
         | 
| 38 | 
            +
                    default_params: {},
         | 
| 39 | 
            +
                    follow_redirects: true,
         | 
| 40 | 
            +
                    parser: Parser,
         | 
| 41 | 
            +
                    connection_adapter: ConnectionAdapter
         | 
| 42 | 
            +
                  }.merge(o)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def path=(uri)
         | 
| 46 | 
            +
                  @path = URI(uri)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def request_uri(uri)
         | 
| 50 | 
            +
                  if uri.respond_to? :request_uri
         | 
| 51 | 
            +
                    uri.request_uri
         | 
| 52 | 
            +
                  else
         | 
| 53 | 
            +
                    uri.path
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def uri
         | 
| 58 | 
            +
                  new_uri = path.relative? ? URI.parse("#{base_uri}#{path}") : path.clone
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  # avoid double query string on redirects [#12]
         | 
| 61 | 
            +
                  unless redirect
         | 
| 62 | 
            +
                    new_uri.query = query_string(new_uri)
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  unless SupportedURISchemes.include? new_uri.class
         | 
| 66 | 
            +
                    raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP, HTTPS or Generic"
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  @last_uri = new_uri
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def base_uri
         | 
| 73 | 
            +
                  redirect ? "#{@last_uri.scheme}://#{@last_uri.host}" : options[:base_uri]
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def format
         | 
| 77 | 
            +
                  options[:format] || (format_from_mimetype(last_response['content-type']) if last_response)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def parser
         | 
| 81 | 
            +
                  options[:parser]
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def connection_adapter
         | 
| 85 | 
            +
                  options[:connection_adapter]
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def perform(&block)
         | 
| 89 | 
            +
                  validate
         | 
| 90 | 
            +
                  setup_raw_request
         | 
| 91 | 
            +
                  chunked_body = nil
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  self.last_response = http.request(@raw_request) do |http_response|
         | 
| 94 | 
            +
                    if block
         | 
| 95 | 
            +
                      chunks = []
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      http_response.read_body do |fragment|
         | 
| 98 | 
            +
                        chunks << fragment
         | 
| 99 | 
            +
                        block.call(fragment)
         | 
| 100 | 
            +
                      end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                      chunked_body = chunks.join
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  handle_deflation unless http_method == Net::HTTP::Head
         | 
| 107 | 
            +
                  handle_response(chunked_body, &block)
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def raw_body
         | 
| 111 | 
            +
                  @raw_request.body
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                private
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def http
         | 
| 117 | 
            +
                  connection_adapter.call(uri, options)
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def body
         | 
| 121 | 
            +
                  options[:body].respond_to?(:to_hash) ? normalize_query(options[:body]) : options[:body]
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def credentials
         | 
| 125 | 
            +
                  (options[:basic_auth] || options[:digest_auth]).to_hash
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                def username
         | 
| 129 | 
            +
                  credentials[:username]
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def password
         | 
| 133 | 
            +
                  credentials[:password]
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def normalize_query(query)
         | 
| 137 | 
            +
                  if query_string_normalizer
         | 
| 138 | 
            +
                    query_string_normalizer.call(query)
         | 
| 139 | 
            +
                  else
         | 
| 140 | 
            +
                    HashConversions.to_params(query)
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                def query_string_normalizer
         | 
| 145 | 
            +
                  options[:query_string_normalizer]
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                def setup_raw_request
         | 
| 149 | 
            +
                  @raw_request = http_method.new(request_uri(uri))
         | 
| 150 | 
            +
                  @raw_request.body = body if body
         | 
| 151 | 
            +
                  @raw_request.body_stream = options[:body_stream] if options[:body_stream]
         | 
| 152 | 
            +
                  @raw_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
         | 
| 153 | 
            +
                  @raw_request.basic_auth(username, password) if options[:basic_auth]
         | 
| 154 | 
            +
                  setup_digest_auth if options[:digest_auth]
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                def setup_digest_auth
         | 
| 158 | 
            +
                  auth_request = http_method.new(uri.request_uri)
         | 
| 159 | 
            +
                  auth_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
         | 
| 160 | 
            +
                  res = http.request(auth_request)
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
         | 
| 163 | 
            +
                    @raw_request.digest_auth(username, password, res)
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                def query_string(uri)
         | 
| 168 | 
            +
                  query_string_parts = []
         | 
| 169 | 
            +
                  query_string_parts << uri.query unless uri.query.nil?
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  if options[:query].respond_to?(:to_hash)
         | 
| 172 | 
            +
                    query_string_parts << normalize_query(options[:default_params].merge(options[:query].to_hash))
         | 
| 173 | 
            +
                  else
         | 
| 174 | 
            +
                    query_string_parts << normalize_query(options[:default_params]) unless options[:default_params].empty?
         | 
| 175 | 
            +
                    query_string_parts << options[:query] unless options[:query].nil?
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
         | 
| 179 | 
            +
                  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                def get_charset
         | 
| 183 | 
            +
                  content_type = last_response["content-type"]
         | 
| 184 | 
            +
                  if content_type.nil?
         | 
| 185 | 
            +
                    return nil
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  if content_type =~ /;\s*charset\s*=\s*([^=,;"\s]+)/i
         | 
| 189 | 
            +
                    return $1
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  if content_type =~ /;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i
         | 
| 193 | 
            +
                    return $1.gsub(/\\(.)/, '\1')
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                  nil
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def encode_with_ruby_encoding(body, charset)
         | 
| 200 | 
            +
                  begin
         | 
| 201 | 
            +
                    encoding = Encoding.find(charset)
         | 
| 202 | 
            +
                    body.force_encoding(encoding)
         | 
| 203 | 
            +
                  rescue
         | 
| 204 | 
            +
                    body
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                def assume_utf16_is_big_endian
         | 
| 209 | 
            +
                  options[:assume_utf16_is_big_endian]
         | 
| 210 | 
            +
                end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                def encode_utf_16(body)
         | 
| 213 | 
            +
                  if body.bytesize >= 2
         | 
| 214 | 
            +
                    if body.getbyte(0) == 0xFF && body.getbyte(1) == 0xFE
         | 
| 215 | 
            +
                      return body.force_encoding("UTF-16LE")
         | 
| 216 | 
            +
                    elsif body.getbyte(0) == 0xFE && body.getbyte(1) == 0xFF
         | 
| 217 | 
            +
                      return body.force_encoding("UTF-16BE")
         | 
| 218 | 
            +
                    end
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  if assume_utf16_is_big_endian
         | 
| 222 | 
            +
                    body.force_encoding("UTF-16BE")
         | 
| 223 | 
            +
                  else
         | 
| 224 | 
            +
                    body.force_encoding("UTF-16LE")
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                def _encode_body(body)
         | 
| 230 | 
            +
                  charset = get_charset
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                  if charset.nil?
         | 
| 233 | 
            +
                    return body
         | 
| 234 | 
            +
                  end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                  if "utf-16".casecmp(charset) == 0
         | 
| 237 | 
            +
                    encode_utf_16(body)
         | 
| 238 | 
            +
                  else
         | 
| 239 | 
            +
                    encode_with_ruby_encoding(body, charset)
         | 
| 240 | 
            +
                  end
         | 
| 241 | 
            +
                end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                def encode_body(body)
         | 
| 244 | 
            +
                  if "".respond_to?(:encoding)
         | 
| 245 | 
            +
                    _encode_body(body)
         | 
| 246 | 
            +
                  else
         | 
| 247 | 
            +
                    body
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
                end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                def handle_response(body, &block)
         | 
| 252 | 
            +
                  if response_redirects?
         | 
| 253 | 
            +
                    options[:limit] -= 1
         | 
| 254 | 
            +
                    if options[:logger]
         | 
| 255 | 
            +
                      logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
         | 
| 256 | 
            +
                      logger.format(self, last_response)
         | 
| 257 | 
            +
                    end
         | 
| 258 | 
            +
                    self.path = last_response['location']
         | 
| 259 | 
            +
                    self.redirect = true
         | 
| 260 | 
            +
                    if last_response.class == Net::HTTPSeeOther
         | 
| 261 | 
            +
                      unless options[:maintain_method_across_redirects] and options[:resend_on_redirect]
         | 
| 262 | 
            +
                        self.http_method = Net::HTTP::Get
         | 
| 263 | 
            +
                      end
         | 
| 264 | 
            +
                    else
         | 
| 265 | 
            +
                      unless options[:maintain_method_across_redirects]
         | 
| 266 | 
            +
                        self.http_method = Net::HTTP::Get
         | 
| 267 | 
            +
                      end
         | 
| 268 | 
            +
                    end
         | 
| 269 | 
            +
                    capture_cookies(last_response)
         | 
| 270 | 
            +
                    perform(&block)
         | 
| 271 | 
            +
                  else
         | 
| 272 | 
            +
                    body = body || last_response.body
         | 
| 273 | 
            +
                    body = encode_body(body)
         | 
| 274 | 
            +
                    Response.new(self, last_response, lambda { parse_response(body) }, body: body)
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
                end
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                # Inspired by Ruby 1.9
         | 
| 279 | 
            +
                def handle_deflation
         | 
| 280 | 
            +
                  case last_response["content-encoding"]
         | 
| 281 | 
            +
                  when "gzip", "x-gzip"
         | 
| 282 | 
            +
                    body_io = StringIO.new(last_response.body)
         | 
| 283 | 
            +
                    last_response.body.replace Zlib::GzipReader.new(body_io).read
         | 
| 284 | 
            +
                    last_response.delete('content-encoding')
         | 
| 285 | 
            +
                  when "deflate"
         | 
| 286 | 
            +
                    last_response.body.replace Zlib::Inflate.inflate(last_response.body)
         | 
| 287 | 
            +
                    last_response.delete('content-encoding')
         | 
| 288 | 
            +
                  end
         | 
| 289 | 
            +
                end
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                def response_redirects?
         | 
| 292 | 
            +
                  case last_response
         | 
| 293 | 
            +
                  when Net::HTTPMultipleChoice, # 300
         | 
| 294 | 
            +
                       Net::HTTPMovedPermanently, # 301
         | 
| 295 | 
            +
                       Net::HTTPFound, # 302
         | 
| 296 | 
            +
                       Net::HTTPSeeOther, # 303
         | 
| 297 | 
            +
                       Net::HTTPUseProxy, # 305
         | 
| 298 | 
            +
                       Net::HTTPTemporaryRedirect
         | 
| 299 | 
            +
                    options[:follow_redirects] && last_response.key?('location')
         | 
| 300 | 
            +
                  end
         | 
| 301 | 
            +
                end
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                def parse_response(body)
         | 
| 304 | 
            +
                  parser.call(body, format)
         | 
| 305 | 
            +
                end
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                def capture_cookies(response)
         | 
| 308 | 
            +
                  return unless response['Set-Cookie']
         | 
| 309 | 
            +
                  cookies_hash = HTTParty::CookieHash.new()
         | 
| 310 | 
            +
                  cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
         | 
| 311 | 
            +
                  response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
         | 
| 312 | 
            +
                  options[:headers] ||= {}
         | 
| 313 | 
            +
                  options[:headers]['Cookie'] = cookies_hash.to_cookie_string
         | 
| 314 | 
            +
                end
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                # Uses the HTTP Content-Type header to determine the format of the
         | 
| 317 | 
            +
                # response It compares the MIME type returned to the types stored in the
         | 
| 318 | 
            +
                # SupportedFormats hash
         | 
| 319 | 
            +
                def format_from_mimetype(mimetype)
         | 
| 320 | 
            +
                  if mimetype && parser.respond_to?(:format_from_mimetype)
         | 
| 321 | 
            +
                    parser.format_from_mimetype(mimetype)
         | 
| 322 | 
            +
                  end
         | 
| 323 | 
            +
                end
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                def validate
         | 
| 326 | 
            +
                  raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
         | 
| 327 | 
            +
                  raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
         | 
| 328 | 
            +
                  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
         | 
| 329 | 
            +
                  raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
         | 
| 330 | 
            +
                  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
         | 
| 331 | 
            +
                  raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
         | 
| 332 | 
            +
                  raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
         | 
| 333 | 
            +
                end
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                def post?
         | 
| 336 | 
            +
                  Net::HTTP::Post == http_method
         | 
| 337 | 
            +
                end
         | 
| 338 | 
            +
              end
         | 
| 339 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            module HTTParty
         | 
| 2 | 
            +
              class Response #:nodoc:
         | 
| 3 | 
            +
                class Headers
         | 
| 4 | 
            +
                  include ::Net::HTTPHeader
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(header = {})
         | 
| 7 | 
            +
                    @header = header
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def ==(other)
         | 
| 11 | 
            +
                    @header == other
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def inspect
         | 
| 15 | 
            +
                    @header.inspect
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def method_missing(name, *args, &block)
         | 
| 19 | 
            +
                    if @header.respond_to?(name)
         | 
| 20 | 
            +
                      @header.send(name, *args, &block)
         | 
| 21 | 
            +
                    else
         | 
| 22 | 
            +
                      super
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def respond_to?(method, include_all = false)
         | 
| 27 | 
            +
                    super || @header.respond_to?(method, include_all)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         |