diff --git a/lib/httparty.rb b/lib/httparty.rb index 00a105e..beb01f2 100644 --- a/lib/httparty.rb +++ b/lib/httparty.rb @@ -10,9 +10,11 @@ $:.unshift(File.dirname(__FILE__)) unless dir = File.expand_path(File.join(File.dirname(__FILE__), 'httparty')) require dir + '/core_ext' - + module HTTParty class UnsupportedFormat < StandardError; end + + class RedirectionTooDeep < StandardError; end def self.included(base) base.extend ClassMethods @@ -107,6 +109,9 @@ module HTTParty # basic_auth => :username and :password to use as basic http authentication (overrides @auth class instance variable) # Raises exception Net::XXX (http error code) if an http error occured def send_request(method, path, options={}) #:nodoc: + options = {:limit => 5}.merge(options) + options[:limit] = 0 if options.delete(:no_follow) + raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0 raise ArgumentError, 'only get, post, put and delete methods are supported' unless %w[get post put delete].include?(method.to_s) raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash) raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash) @@ -130,6 +135,9 @@ module HTTParty case response when Net::HTTPSuccess parse_response(response.body) + when Net::HTTPRedirection + options[:limit] -= 1 + send_request(method, response['location'], options) else response.instance_eval { class << self; attr_accessor :body_parsed; end } begin; response.body_parsed = parse_response(response.body); rescue; end diff --git a/spec/httparty_spec.rb b/spec/httparty_spec.rb index aee471b..89f8a21 100644 --- a/spec/httparty_spec.rb +++ b/spec/httparty_spec.rb @@ -138,5 +138,78 @@ describe HTTParty do response.stub!(:body).and_return("") Foo.send(:send_request, 'get', 'bar').should be_nil end + + describe "that respond with redirects" do + def setup_http + @http = Net::HTTP.new('localhost', 80) + Foo.stub!(:http).and_return(@http) + @redirect = Net::HTTPFound.new("1.1", 302, "") + @redirect.stub!(:[]).with('location').and_return('/foo') + @ok = Net::HTTPOK.new("1.1", 200, "Content for you") + @ok.stub!(:body).and_return({"foo" => "bar"}.to_xml) + @http.should_receive(:request).and_return(@redirect, @ok) + Foo.headers.clear + Foo.format :xml + end + + it "should handle redirects for GET transparently" do + setup_http + Foo.get('/foo/').should == {"hash" => {"foo" => "bar"}} + end + + it "should handle redirects for POST transparently" do + setup_http + Foo.post('/foo/', {:foo => :bar}).should == {"hash" => {"foo" => "bar"}} + end + + it "should handle redirects for DELETE transparently" do + setup_http + Foo.delete('/foo/').should == {"hash" => {"foo" => "bar"}} + end + + it "should handle redirects for PUT transparently" do + setup_http + Foo.put('/foo/').should == {"hash" => {"foo" => "bar"}} + end + + it "should prevent infinite loops" do + http = Net::HTTP.new('localhost', 80) + Foo.stub!(:http).and_return(http) + redirect = Net::HTTPFound.new("1.1", "302", "Look, over there!") + redirect.stub!(:[]).with('location').and_return('/foo') + http.stub!(:request).and_return(redirect) + + lambda do + Foo.send(:send_request, 'get', '/foo') + end.should raise_error(HTTParty::RedirectionTooDeep) + end + + describe "with explicit override of automatic redirect handling" do + + it "should fail with redirected GET" do + lambda do + Foo.get('/foo', :no_follow => true) + end.should raise_error(HTTParty::RedirectionTooDeep) + end + + it "should fail with redirected POST" do + lambda do + Foo.post('/foo', :no_follow => true) + end.should raise_error(HTTParty::RedirectionTooDeep) + end + + it "should fail with redirected DELETE" do + lambda do + Foo.delete('/foo', :no_follow => true) + end + end + + it "should fail with redirected PUT" do + lambda do + Foo.put('/foo', :no_follow => true) + end + end + end + end end end \ No newline at end of file