httparty 0.10.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httparty might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +92 -0
  4. data/.rubocop_todo.yml +124 -0
  5. data/.simplecov +1 -0
  6. data/.travis.yml +5 -4
  7. data/CONTRIBUTING.md +23 -0
  8. data/Gemfile +9 -5
  9. data/Guardfile +3 -3
  10. data/History +109 -8
  11. data/README.md +21 -21
  12. data/Rakefile +5 -10
  13. data/bin/httparty +21 -14
  14. data/docs/README.md +100 -0
  15. data/examples/README.md +67 -0
  16. data/examples/aaws.rb +9 -9
  17. data/examples/basic.rb +6 -10
  18. data/examples/crack.rb +3 -3
  19. data/examples/custom_parsers.rb +1 -4
  20. data/examples/delicious.rb +12 -12
  21. data/examples/google.rb +2 -2
  22. data/examples/headers_and_user_agents.rb +2 -2
  23. data/examples/logging.rb +36 -0
  24. data/examples/nokogiri_html_parser.rb +0 -3
  25. data/examples/rescue_json.rb +17 -0
  26. data/examples/rubyurl.rb +3 -3
  27. data/examples/stackexchange.rb +24 -0
  28. data/examples/tripit_sign_in.rb +20 -9
  29. data/examples/twitter.rb +11 -11
  30. data/examples/whoismyrep.rb +2 -2
  31. data/features/command_line.feature +90 -2
  32. data/features/digest_authentication.feature +10 -0
  33. data/features/handles_compressed_responses.feature +8 -0
  34. data/features/handles_multiple_formats.feature +23 -0
  35. data/features/steps/env.rb +16 -11
  36. data/features/steps/httparty_response_steps.rb +40 -10
  37. data/features/steps/httparty_steps.rb +19 -3
  38. data/features/steps/mongrel_helper.rb +35 -2
  39. data/features/steps/remote_service_steps.rb +31 -8
  40. data/features/supports_read_timeout_option.feature +13 -0
  41. data/httparty.gemspec +9 -6
  42. data/lib/httparty/connection_adapter.rb +76 -11
  43. data/lib/httparty/cookie_hash.rb +3 -4
  44. data/lib/httparty/exceptions.rb +10 -4
  45. data/lib/httparty/hash_conversions.rb +19 -17
  46. data/lib/httparty/logger/apache_formatter.rb +22 -0
  47. data/lib/httparty/logger/curl_formatter.rb +91 -0
  48. data/lib/httparty/logger/logger.rb +26 -0
  49. data/lib/httparty/module_inheritable_attributes.rb +1 -1
  50. data/lib/httparty/net_digest_auth.rb +69 -18
  51. data/lib/httparty/parser.rb +15 -11
  52. data/lib/httparty/request.rb +186 -47
  53. data/lib/httparty/response/headers.rb +2 -2
  54. data/lib/httparty/response.rb +44 -9
  55. data/lib/httparty/version.rb +1 -1
  56. data/lib/httparty.rb +187 -65
  57. data/script/release +42 -0
  58. data/spec/fixtures/twitter.csv +2 -0
  59. data/spec/httparty/connection_adapter_spec.rb +334 -62
  60. data/spec/httparty/cookie_hash_spec.rb +53 -23
  61. data/spec/httparty/exception_spec.rb +45 -0
  62. data/spec/httparty/hash_conversions_spec.rb +49 -0
  63. data/spec/httparty/logger/apache_formatter_spec.rb +41 -0
  64. data/spec/httparty/logger/curl_formatter_spec.rb +119 -0
  65. data/spec/httparty/logger/logger_spec.rb +38 -0
  66. data/spec/httparty/net_digest_auth_spec.rb +148 -23
  67. data/spec/httparty/parser_spec.rb +48 -41
  68. data/spec/httparty/request_spec.rb +845 -151
  69. data/spec/httparty/response_spec.rb +147 -70
  70. data/spec/httparty/ssl_spec.rb +33 -21
  71. data/spec/httparty_spec.rb +337 -186
  72. data/spec/spec_helper.rb +38 -9
  73. data/spec/support/ssl_test_helper.rb +10 -10
  74. data/spec/support/ssl_test_server.rb +21 -21
  75. data/spec/support/stub_response.rb +20 -14
  76. data/website/index.html +3 -3
  77. metadata +46 -37
  78. data/lib/httparty/core_extensions.rb +0 -32
  79. data/spec/spec.opts +0 -2
@@ -0,0 +1,22 @@
1
+ module HTTParty
2
+ module Logger
3
+ class ApacheFormatter #:nodoc:
4
+ TAG_NAME = HTTParty.name
5
+
6
+ attr_accessor :level, :logger, :current_time
7
+
8
+ def initialize(logger, level)
9
+ @logger = logger
10
+ @level = level.to_sym
11
+ end
12
+
13
+ def format(request, response)
14
+ current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
15
+ http_method = request.http_method.name.split("::").last.upcase
16
+ path = request.path.to_s
17
+ content_length = response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
18
+ @logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,91 @@
1
+ module HTTParty
2
+ module Logger
3
+ class CurlFormatter #:nodoc:
4
+ TAG_NAME = HTTParty.name
5
+ OUT = '>'.freeze
6
+ IN = '<'.freeze
7
+
8
+ attr_accessor :level, :logger
9
+
10
+ def initialize(logger, level)
11
+ @logger = logger
12
+ @level = level.to_sym
13
+ @messages = []
14
+ end
15
+
16
+ def format(request, response)
17
+ @request = request
18
+ @response = response
19
+
20
+ log_request
21
+ log_response
22
+
23
+ logger.send level, messages.join("\n")
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :request, :response
29
+ attr_accessor :messages
30
+
31
+ def log_request
32
+ log_url
33
+ log_headers
34
+ log_query
35
+ log OUT, request.raw_body if request.raw_body
36
+ log OUT
37
+ end
38
+
39
+ def log_response
40
+ log IN, "HTTP/#{response.http_version} #{response.code}"
41
+ log_response_headers
42
+ log IN, "\n#{response.body}"
43
+ log IN
44
+ end
45
+
46
+ def log_url
47
+ http_method = request.http_method.name.split("::").last.upcase
48
+ uri = if request.options[:base_uri]
49
+ request.options[:base_uri] + request.path.path
50
+ else
51
+ request.path.to_s
52
+ end
53
+
54
+ log OUT, "#{http_method} #{uri}"
55
+ end
56
+
57
+ def log_headers
58
+ return unless request.options[:headers] && request.options[:headers].size > 0
59
+
60
+ log OUT, 'Headers: '
61
+ log_hash request.options[:headers]
62
+ end
63
+
64
+ def log_query
65
+ return unless request.options[:query]
66
+
67
+ log OUT, 'Query: '
68
+ log_hash request.options[:query]
69
+ end
70
+
71
+ def log_response_headers
72
+ headers = response.respond_to?(:headers) ? response.headers : response
73
+ response.each_header do |response_header|
74
+ log IN, "#{response_header.capitalize}: #{headers[response_header]}"
75
+ end
76
+ end
77
+
78
+ def log_hash(hash)
79
+ hash.each { |k, v| log(OUT, "#{k}: #{v}") }
80
+ end
81
+
82
+ def log(direction, line = '')
83
+ messages << "[#{TAG_NAME}] [#{time}] #{direction} #{line}"
84
+ end
85
+
86
+ def time
87
+ @time ||= Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,26 @@
1
+ require 'httparty/logger/apache_formatter'
2
+ require 'httparty/logger/curl_formatter'
3
+
4
+ module HTTParty
5
+ module Logger
6
+ def self.formatters
7
+ @formatters ||= {
8
+ :curl => Logger::CurlFormatter,
9
+ :apache => Logger::ApacheFormatter
10
+ }
11
+ end
12
+
13
+ def self.add_formatter(name, formatter)
14
+ raise HTTParty::Error.new("Log Formatter with name #{name} already exists") if formatters.include?(name)
15
+ formatters.merge!(name.to_sym => formatter)
16
+ end
17
+
18
+ def self.build(logger, level, formatter)
19
+ level ||= :info
20
+ formatter ||= :apache
21
+
22
+ logger_klass = formatters[formatter] || Logger::ApacheFormatter
23
+ logger_klass.new(logger, level)
24
+ end
25
+ end
26
+ end
@@ -10,7 +10,7 @@ module HTTParty
10
10
 
11
11
  duplicate.each_pair do |key, value|
12
12
  duplicate[key] = if value.is_a?(Hash)
13
- hash_deep_dup(value)
13
+ hash_deep_dup(value)
14
14
  elsif value.is_a?(Proc)
15
15
  duplicate[key] = value.dup
16
16
  else
@@ -4,10 +4,22 @@ require 'net/http'
4
4
  module Net
5
5
  module HTTPHeader
6
6
  def digest_auth(username, password, response)
7
- @header['Authorization'] = DigestAuthenticator.new(username, password,
8
- @method, @path, response).authorization_header
7
+ authenticator = DigestAuthenticator.new(
8
+ username,
9
+ password,
10
+ @method,
11
+ @path,
12
+ response
13
+ )
14
+
15
+ @header['Authorization'] = authenticator.authorization_header
16
+ @header['cookie'] = append_cookies(authenticator) if response['Set-Cookie']
9
17
  end
10
18
 
19
+ def append_cookies(authenticator)
20
+ cookies = @header['cookie'] ? @header['cookie'] : []
21
+ cookies.concat(authenticator.cookie_header)
22
+ end
11
23
 
12
24
  class DigestAuthenticator
13
25
  def initialize(username, password, method, path, response_header)
@@ -16,50 +28,76 @@ module Net
16
28
  @method = method
17
29
  @path = path
18
30
  @response = parse(response_header)
31
+ @cookies = parse_cookies(response_header)
19
32
  end
20
33
 
21
34
  def authorization_header
22
35
  @cnonce = md5(random)
23
36
  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}"),
37
+ %(Digest username="#{@username}"),
38
+ %(realm="#{@response['realm']}"),
39
+ %(nonce="#{@response['nonce']}"),
40
+ %(uri="#{@path}"),
41
+ %(response="#{request_digest}")
29
42
  ]
30
43
 
44
+ header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
45
+
31
46
  if qop_present?
32
47
  fields = [
33
- %Q(cnonce="#{@cnonce}"),
34
- %Q(qop="#{@response['qop']}"),
35
- %Q(nc="00000001")
48
+ %(cnonce="#{@cnonce}"),
49
+ %(qop="#{@response['qop']}"),
50
+ "nc=00000001"
36
51
  ]
37
52
  fields.each { |field| header << field }
38
53
  end
39
54
 
40
- header << %Q(opaque="#{@response['opaque']}") if opaque_present?
55
+ header << %(opaque="#{@response['opaque']}") if opaque_present?
41
56
  header
42
57
  end
43
58
 
44
- private
59
+ def cookie_header
60
+ @cookies
61
+ end
62
+
63
+ private
45
64
 
46
65
  def parse(response_header)
47
- response_header['www-authenticate'] =~ /^(\w+) (.*)/
66
+ header = response_header['www-authenticate']
67
+ .gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
68
+
69
+ header =~ /Digest (.*)/
48
70
  params = {}
49
- $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
71
+ if $1
72
+ non_quoted = $1.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
73
+ non_quoted.gsub(/(\w+)=([^,]*)/) { params[$1] = $2 }
74
+ end
50
75
  params
51
76
  end
52
77
 
78
+ def parse_cookies(response_header)
79
+ return [] unless response_header['Set-Cookie']
80
+
81
+ cookies = response_header['Set-Cookie'].split('; ')
82
+
83
+ cookies.reduce([]) do |ret, cookie|
84
+ ret << cookie
85
+ ret
86
+ end
87
+
88
+ cookies
89
+ end
90
+
53
91
  def opaque_present?
54
- @response.has_key?('opaque') and not @response['opaque'].empty?
92
+ @response.key?('opaque') && !@response['opaque'].empty?
55
93
  end
56
94
 
57
95
  def qop_present?
58
- @response.has_key?('qop') and not @response['qop'].empty?
96
+ @response.key?('qop') && !@response['qop'].empty?
59
97
  end
60
98
 
61
99
  def random
62
- "%x" % (Time.now.to_i + rand(65535))
100
+ format "%x", (Time.now.to_i + rand(65535))
63
101
  end
64
102
 
65
103
  def request_digest
@@ -72,8 +110,21 @@ module Net
72
110
  Digest::MD5.hexdigest(str)
73
111
  end
74
112
 
113
+ def algorithm_present?
114
+ @response.key?('algorithm') && !@response['algorithm'].empty?
115
+ end
116
+
117
+ def use_md5_sess?
118
+ algorithm_present? && @response['algorithm'] == 'MD5-sess'
119
+ end
120
+
75
121
  def a1
76
- [@username, @response['realm'], @password].join(":")
122
+ a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
123
+ if use_md5_sess?
124
+ [ md5(a1_user_realm_pwd), @response['nonce'], @cnonce ].join(':')
125
+ else
126
+ a1_user_realm_pwd
127
+ end
77
128
  end
78
129
 
79
130
  def a2
@@ -1,5 +1,5 @@
1
1
  module HTTParty
2
- # The default parser used by HTTParty, supports xml, json, html, and
2
+ # The default parser used by HTTParty, supports xml, json, html, csv and
3
3
  # plain text.
4
4
  #
5
5
  # == Custom Parsers
@@ -42,10 +42,13 @@ module HTTParty
42
42
  'application/xml' => :xml,
43
43
  'application/json' => :json,
44
44
  'text/json' => :json,
45
- 'application/javascript' => :json,
46
- 'text/javascript' => :json,
45
+ 'application/javascript' => :plain,
46
+ 'text/javascript' => :plain,
47
47
  'text/html' => :html,
48
- 'text/plain' => :plain
48
+ 'text/plain' => :plain,
49
+ 'text/csv' => :csv,
50
+ 'application/csv' => :csv,
51
+ 'text/comma-separated-values' => :csv
49
52
  }
50
53
 
51
54
  # The response body of the request
@@ -95,7 +98,9 @@ module HTTParty
95
98
  # @return [Object] the parsed body
96
99
  # @return [nil] when the response body is nil, an empty string, spaces only or "null"
97
100
  def parse
98
- return nil if body.nil? || body.strip.empty? || body == "null"
101
+ return nil if body.nil?
102
+ return nil if body == "null"
103
+ return nil if body.valid_encoding? && body.strip.empty?
99
104
  if supports_format?
100
105
  parse_supported_format
101
106
  else
@@ -110,12 +115,11 @@ module HTTParty
110
115
  end
111
116
 
112
117
  def json
113
- # https://github.com/sferik/rails/commit/5e62670131dfa1718eaf21ff8dd3371395a5f1cc
114
- if MultiJson.respond_to?(:adapter)
115
- MultiJson.load(body)
116
- else
117
- MultiJson.decode(body)
118
- end
118
+ JSON.parse(body, :quirks_mode => true, :allow_nan => true)
119
+ end
120
+
121
+ def csv
122
+ CSV.parse(body)
119
123
  end
120
124
 
121
125
  def html