httparty 0.5.2 → 0.6.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.

@@ -0,0 +1,35 @@
1
+ require 'digest/md5'
2
+ require 'net/http'
3
+
4
+ module Net
5
+ module HTTPHeader
6
+ def digest_auth(user, password, response)
7
+ response['www-authenticate'] =~ /^(\w+) (.*)/
8
+
9
+ params = {}
10
+ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
11
+ params.merge!("cnonce" => Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))))
12
+
13
+ a_1 = Digest::MD5.hexdigest("#{user}:#{params['realm']}:#{password}")
14
+ a_2 = Digest::MD5.hexdigest("#{@method}:#{@path}")
15
+
16
+ request_digest = Digest::MD5.hexdigest(
17
+ [a_1, params['nonce'], "0", params['cnonce'], params['qop'], a_2].join(":")
18
+ )
19
+
20
+ header = [
21
+ %Q(Digest username="#{user}"),
22
+ %Q(realm="#{params['realm']}"),
23
+ %Q(qop="#{params['qop']}"),
24
+ %Q(uri="#{@path}"),
25
+ %Q(nonce="#{params['nonce']}"),
26
+ %Q(nc="0"),
27
+ %Q(cnonce="#{params['cnonce']}"),
28
+ %Q(opaque="#{params['opaque']}"),
29
+ %Q(response="#{request_digest}")
30
+ ]
31
+
32
+ @header['Authorization'] = header
33
+ end
34
+ end
35
+ end
@@ -1,5 +1,3 @@
1
- require 'uri'
2
-
3
1
  module HTTParty
4
2
  class Request #:nodoc:
5
3
  SupportedHTTPMethods = [
@@ -13,7 +11,7 @@ module HTTParty
13
11
 
14
12
  SupportedURISchemes = [URI::HTTP, URI::HTTPS]
15
13
 
16
- attr_accessor :http_method, :path, :options, :last_response
14
+ attr_accessor :http_method, :path, :options, :last_response, :redirect
17
15
 
18
16
  def initialize(http_method, path, o={})
19
17
  self.http_method = http_method
@@ -33,7 +31,7 @@ module HTTParty
33
31
  new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
34
32
 
35
33
  # avoid double query string on redirects [#12]
36
- unless @redirect
34
+ unless redirect
37
35
  new_uri.query = query_string(new_uri)
38
36
  end
39
37
 
@@ -45,14 +43,13 @@ module HTTParty
45
43
  end
46
44
 
47
45
  def format
48
- options[:format]
46
+ options[:format] || (format_from_mimetype(last_response['content-type']) if last_response)
49
47
  end
50
48
 
51
49
  def parser
52
50
  options[:parser]
53
51
  end
54
52
 
55
-
56
53
  def perform
57
54
  validate
58
55
  setup_raw_request
@@ -62,133 +59,157 @@ module HTTParty
62
59
 
63
60
  private
64
61
 
65
- def http
66
- http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
67
- http.use_ssl = ssl_implied?
68
-
69
- if options[:timeout] && options[:timeout].is_a?(Integer)
70
- http.open_timeout = options[:timeout]
71
- http.read_timeout = options[:timeout]
72
- end
62
+ def http
63
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
64
+ http.use_ssl = ssl_implied?
73
65
 
74
- if options[:pem] && http.use_ssl?
75
- http.cert = OpenSSL::X509::Certificate.new(options[:pem])
76
- http.key = OpenSSL::PKey::RSA.new(options[:pem])
77
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
78
- else
79
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
80
- end
81
-
82
- if options[:debug_output]
83
- http.set_debug_output(options[:debug_output])
84
- end
85
-
86
- http
66
+ if options[:timeout] && options[:timeout].is_a?(Integer)
67
+ http.open_timeout = options[:timeout]
68
+ http.read_timeout = options[:timeout]
87
69
  end
88
70
 
89
- def ssl_implied?
90
- uri.port == 443 || uri.instance_of?(URI::HTTPS)
71
+ if options[:pem] && http.use_ssl?
72
+ http.cert = OpenSSL::X509::Certificate.new(options[:pem])
73
+ http.key = OpenSSL::PKey::RSA.new(options[:pem])
74
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
75
+ else
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
91
77
  end
92
78
 
93
- def body
94
- options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
79
+ if options[:debug_output]
80
+ http.set_debug_output(options[:debug_output])
95
81
  end
96
82
 
97
- def username
98
- options[:basic_auth][:username]
99
- end
83
+ http
84
+ end
100
85
 
101
- def password
102
- options[:basic_auth][:password]
103
- end
86
+ def ssl_implied?
87
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
88
+ end
104
89
 
105
- def setup_raw_request
106
- @raw_request = http_method.new(uri.request_uri)
107
- @raw_request.body = body if body
108
- @raw_request.initialize_http_header(options[:headers])
109
- @raw_request.basic_auth(username, password) if options[:basic_auth]
110
- end
90
+ def body
91
+ options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
92
+ end
111
93
 
112
- def perform_actual_request
113
- http.request(@raw_request)
114
- end
94
+ def credentials
95
+ options[:basic_auth] || options[:digest_auth]
96
+ end
97
+
98
+ def username
99
+ credentials[:username]
100
+ end
101
+
102
+ def password
103
+ credentials[:password]
104
+ end
115
105
 
116
- def get_response
117
- self.last_response = perform_actual_request
118
- options[:format] ||= format_from_mimetype(last_response['content-type'])
106
+ def setup_raw_request
107
+ @raw_request = http_method.new(uri.request_uri)
108
+ @raw_request.body = body if body
109
+ @raw_request.initialize_http_header(options[:headers])
110
+ @raw_request.basic_auth(username, password) if options[:basic_auth]
111
+ setup_digest_auth if options[:digest_auth]
112
+ end
113
+
114
+ def setup_digest_auth
115
+ res = http.head(uri.request_uri, options[:headers])
116
+ if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
117
+ @raw_request.digest_auth(username, password, res)
119
118
  end
119
+ end
120
120
 
121
- def query_string(uri)
122
- query_string_parts = []
123
- query_string_parts << uri.query unless uri.query.nil?
121
+ def perform_actual_request
122
+ http.request(@raw_request)
123
+ end
124
124
 
125
- if options[:query].is_a?(Hash)
126
- query_string_parts << options[:default_params].merge(options[:query]).to_params
127
- else
128
- query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
129
- query_string_parts << options[:query] unless options[:query].nil?
130
- end
125
+ def get_response
126
+ self.last_response = perform_actual_request
127
+ end
128
+
129
+ def query_string(uri)
130
+ query_string_parts = []
131
+ query_string_parts << uri.query unless uri.query.nil?
131
132
 
132
- query_string_parts.size > 0 ? query_string_parts.join('&') : nil
133
+ if options[:query].is_a?(Hash)
134
+ query_string_parts << options[:default_params].merge(options[:query]).to_params
135
+ else
136
+ query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
137
+ query_string_parts << options[:query] unless options[:query].nil?
133
138
  end
134
139
 
135
- # Raises exception Net::XXX (http error code) if an http error occured
136
- def handle_response
137
- case last_response
138
- when Net::HTTPMultipleChoice, # 300
139
- Net::HTTPMovedPermanently, # 301
140
- Net::HTTPFound, # 302
141
- Net::HTTPSeeOther, # 303
142
- Net::HTTPUseProxy, # 305
143
- Net::HTTPTemporaryRedirect
144
- if last_response.key?('location')
145
- options[:limit] -= 1
146
- self.path = last_response['location']
147
- @redirect = true
148
- self.http_method = Net::HTTP::Get
149
- capture_cookies(last_response)
150
- perform
151
- else
152
- last_response
153
- end
140
+ query_string_parts.size > 0 ? query_string_parts.join('&') : nil
141
+ end
142
+
143
+ # Raises exception Net::XXX (http error code) if an http error occured
144
+ def handle_response
145
+ handle_deflation
146
+ case last_response
147
+ when Net::HTTPMultipleChoice, # 300
148
+ Net::HTTPMovedPermanently, # 301
149
+ Net::HTTPFound, # 302
150
+ Net::HTTPSeeOther, # 303
151
+ Net::HTTPUseProxy, # 305
152
+ Net::HTTPTemporaryRedirect
153
+ if last_response.key?('location')
154
+ options[:limit] -= 1
155
+ self.path = last_response['location']
156
+ self.redirect = true
157
+ self.http_method = Net::HTTP::Get unless options[:maintain_method_across_redirects]
158
+ capture_cookies(last_response)
159
+ perform
154
160
  else
155
- Response.new(parse_response(last_response.body), last_response.body, last_response.code, last_response.message, last_response.to_hash)
161
+ last_response
156
162
  end
163
+ else
164
+ Response.new(last_response, parse_response(last_response.body))
157
165
  end
166
+ end
158
167
 
159
- def parse_response(body)
160
- parser.call(body, format)
168
+ # Inspired by Ruby 1.9
169
+ def handle_deflation
170
+ case last_response["content-encoding"]
171
+ when "gzip"
172
+ body_io = StringIO.new(last_response.body)
173
+ last_response.body.replace Zlib::GzipReader.new(body_io).read
174
+ when "deflate"
175
+ last_response.body.replace Zlib::Inflate.inflate(last_response.body)
161
176
  end
177
+ end
162
178
 
163
- def capture_cookies(response)
164
- return unless response['Set-Cookie']
165
- cookies_hash = HTTParty::CookieHash.new()
166
- cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
167
- cookies_hash.add_cookies(response['Set-Cookie'])
168
- options[:headers] ||= {}
169
- options[:headers]['Cookie'] = cookies_hash.to_cookie_string
170
- end
179
+ def parse_response(body)
180
+ parser.call(body, format)
181
+ end
171
182
 
172
- # Uses the HTTP Content-Type header to determine the format of the
173
- # response It compares the MIME type returned to the types stored in the
174
- # SupportedFormats hash
175
- def format_from_mimetype(mimetype)
176
- if mimetype && parser.respond_to?(:format_from_mimetype)
177
- parser.format_from_mimetype(mimetype)
178
- end
183
+ def capture_cookies(response)
184
+ return unless response['Set-Cookie']
185
+ cookies_hash = HTTParty::CookieHash.new()
186
+ cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
187
+ cookies_hash.add_cookies(response['Set-Cookie'])
188
+ options[:headers] ||= {}
189
+ options[:headers]['Cookie'] = cookies_hash.to_cookie_string
190
+ end
191
+
192
+ # Uses the HTTP Content-Type header to determine the format of the
193
+ # response It compares the MIME type returned to the types stored in the
194
+ # SupportedFormats hash
195
+ def format_from_mimetype(mimetype)
196
+ if mimetype && parser.respond_to?(:format_from_mimetype)
197
+ parser.format_from_mimetype(mimetype)
179
198
  end
199
+ end
180
200
 
181
201
  def validate
182
202
  raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
183
203
  raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
184
204
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
205
+ raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
185
206
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
207
+ raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].is_a?(Hash)
186
208
  raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
187
209
  end
188
210
 
189
- def post?
190
- Net::HTTP::Post == http_method
191
- end
211
+ def post?
212
+ Net::HTTP::Post == http_method
213
+ end
192
214
  end
193
215
  end
194
-
@@ -1,18 +1,63 @@
1
1
  module HTTParty
2
2
  class Response < HTTParty::BasicObject #:nodoc:
3
- attr_accessor :body, :code, :message, :headers
4
- attr_reader :delegate
5
-
6
- def initialize(delegate, body, code, message, headers={})
7
- @delegate = delegate
8
- @body = body
9
- @code = code.to_i
10
- @message = message
11
- @headers = headers
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)
27
+ super || @header.respond_to?(method)
28
+ end
29
+ end
30
+
31
+ attr_reader :response, :parsed_response, :body, :headers
32
+
33
+ def initialize(response, parsed_response)
34
+ @response = response
35
+ @body = response.body
36
+ @parsed_response = parsed_response
37
+ @headers = Headers.new(response.to_hash)
38
+ end
39
+
40
+ def class
41
+ Response
42
+ end
43
+
44
+ def code
45
+ response.code.to_i
46
+ end
47
+
48
+ def inspect
49
+ inspect_id = "%x" % (object_id * 2)
50
+ %(#<#{self.class}:0x#{inspect_id} @parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
12
51
  end
13
52
 
14
53
  def method_missing(name, *args, &block)
15
- @delegate.send(name, *args, &block)
54
+ if parsed_response.respond_to?(name)
55
+ parsed_response.send(name, *args, &block)
56
+ elsif response.respond_to?(name)
57
+ response.send(name, *args, &block)
58
+ else
59
+ super
60
+ end
16
61
  end
17
62
  end
18
63
  end
@@ -19,9 +19,34 @@ describe HTTParty::Request do
19
19
  end
20
20
 
21
21
  describe "#format" do
22
- it "should return the correct parsing format" do
23
- @request.format.should == :xml
22
+ context "request yet to be made" do
23
+ it "returns format option" do
24
+ request = HTTParty::Request.new 'get', '/', :format => :xml
25
+ request.format.should == :xml
26
+ end
27
+
28
+ it "returns nil format" do
29
+ request = HTTParty::Request.new 'get', '/'
30
+ request.format.should be_nil
31
+ end
32
+ end
33
+
34
+ context "request has been made" do
35
+ it "returns format option" do
36
+ request = HTTParty::Request.new 'get', '/', :format => :xml
37
+ request.last_response = stub
38
+ request.format.should == :xml
39
+ end
40
+
41
+ it "returns the content-type from the last response when the option is not set" do
42
+ request = HTTParty::Request.new 'get', '/'
43
+ response = stub
44
+ response.should_receive(:[]).with('content-type').and_return('text/json')
45
+ request.last_response = response
46
+ request.format.should == :json
47
+ end
24
48
  end
49
+
25
50
  end
26
51
 
27
52
  context "options" do
@@ -30,6 +55,17 @@ describe HTTParty::Request do
30
55
  @request.send(:setup_raw_request)
31
56
  @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
32
57
  end
58
+
59
+ it "should use digest auth when configured" do
60
+ FakeWeb.register_uri(:head, "http://api.foo.com/v1",
61
+ :www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
62
+
63
+ @request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
64
+ @request.send(:setup_raw_request)
65
+
66
+ raw_request = @request.instance_variable_get(:@raw_request)
67
+ raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
68
+ end
33
69
  end
34
70
 
35
71
  describe "#uri" do
@@ -353,6 +389,13 @@ describe HTTParty::Request do
353
389
  @request.perform.should == {"hash" => {"foo" => "bar"}}
354
390
  @request.http_method.should == Net::HTTP::Get
355
391
  end
392
+
393
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
394
+ @request.options[:maintain_method_across_redirects] = true
395
+ @request.http_method = Net::HTTP::Delete
396
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
397
+ @request.http_method.should == Net::HTTP::Delete
398
+ end
356
399
  end
357
400
 
358
401
  describe "infinitely" do
@@ -373,5 +416,25 @@ describe HTTParty::Request do
373
416
  }.should raise_error(ArgumentError)
374
417
  end
375
418
  end
419
+
420
+ describe "argument validation" do
421
+ it "should raise argument error if basic_auth and digest_auth are both present" do
422
+ lambda {
423
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
424
+ }.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
425
+ end
426
+
427
+ it "should raise argument error if basic_auth is not a hash" do
428
+ lambda {
429
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
430
+ }.should raise_error(ArgumentError, ":basic_auth must be a hash")
431
+ end
432
+
433
+ it "should raise argument error if digest_auth is not a hash" do
434
+ lambda {
435
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
436
+ }.should raise_error(ArgumentError, ":digest_auth must be a hash")
437
+ end
438
+ end
376
439
  end
377
440