Permalink
Browse files

* Security fix introduced at 2.1.3.

      * get_content/post_content of httpclient/2.1.3 may send secure cookies
        for a https site to non-secure (non-https) site when the https site
        redirects the request to a non-https site.  httpclient/2.1.3 caches
        request object and reuses it for redirection.  It should not be cached
        and recreated for each time as httpclient <= 2.1.2 and http-access2.
      * I realized this bug when I was reading open-uri story on
        [ruby-core:21205].  Ruby users should use open-uri rather than using
        net/http directly wherever possible.
  • Loading branch information...
1 parent f0336f6 commit 5fb443680ffeba5bfa6540c70284c1cb4afe8c6a nahi committed Jan 8, 2009
View
@@ -1,5 +1,5 @@
httpclient - HTTP accessing library.
-Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
'httpclient' gives something like the functionality of libwww-perl (LWP) in
Ruby. 'httpclient' formerly known as 'http-access2'.
@@ -68,6 +68,27 @@ See HTTPClient for documentation.
You can also check sample/howto.rb how to use APIs.
+== Download
+
+* Stable: http://dev.ctor.org/download/httpclient-2.1.3.1.tar.gz (tar + gzip)
+* Stable: http://dev.ctor.org/download/httpclient-2.1.3.1.zip (ZIP)
+
+* Older versions: http://dev.ctor.org/download/archive/
+
+* Gem repository for stable version
+ * (at default remove source at rubyforge.org)
+* Gem repository for development version
+ * http://dev.ctor.org/download/
+
+* svn: http://dev.ctor.org/svn/http-access2/trunk/
+
+=== Gem
+
+You can install httpclient with rubygems.
+
+ % gem install httpclient --source http://dev.ctor.org/download/
+
+
== Bug report or Feature request
Please file a ticket at the project web site.
@@ -81,6 +102,18 @@ Thanks in advance.
== Changes
+ Jan 8, 2009 - version 2.1.3.1
+
+ * Security fix introduced at 2.1.3.
+ * get_content/post_content of httpclient/2.1.3 may send secure cookies
+ for a https site to non-secure (non-https) site when the https site
+ redirects the request to a non-https site. httpclient/2.1.3 caches
+ request object and reuses it for redirection. It should not be cached
+ and recreated for each time as httpclient <= 2.1.2 and http-access2.
+ * I realized this bug when I was reading open-uri story on
+ [ruby-core:21205]. Ruby users should use open-uri rather than using
+ net/http directly wherever possible.
+
Dec 29, 2008 - version 2.1.3
* Features
* Proxy Authentication for SSL.
View
@@ -1,8 +1,8 @@
require 'rubygems'
SPEC = Gem::Specification.new do |s|
s.name = "httpclient"
- s.version = "2.1.2"
- s.date = "2007-09-22"
+ s.version = "2.1.3.1"
+ s.date = "2009-01-08"
s.author = "NAKAMURA, Hiroshi"
s.email = "nahi@ruby-lang.org"
s.homepage = "http://dev.ctor.org/httpclient"
View
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -199,7 +199,7 @@
# ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
#
class HTTPClient
- VERSION = '2.1.3'
+ VERSION = '2.1.3.1'
RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
/: (\S+) (\S+)/ =~ %q$Id$
LIB_NAME = "(#{$1}/#{$2}, #{RUBY_VERSION_STRING})"
@@ -511,9 +511,7 @@ def redirect_uri_callback=(redirect_uri_callback)
# use get method. get returns HTTP::Message as a response and you need to
# follow HTTP redirect by yourself if you need.
def get_content(uri, query = nil, extheader = {}, &block)
- uri = urify(uri)
- req = create_request('GET', uri, query, nil, extheader)
- follow_redirect(req, &block).content
+ follow_redirect(:get, uri, query, nil, extheader, &block).content
end
# Posts a content.
@@ -539,9 +537,7 @@ def get_content(uri, query = nil, extheader = {}, &block)
# If you need to get full HTTP response including HTTP status and headers,
# use post method.
def post_content(uri, body = nil, extheader = {}, &block)
- uri = urify(uri)
- req = create_request('POST', uri, nil, body, extheader)
- follow_redirect(req, &block).content
+ follow_redirect(:post, uri, nil, body, extheader, &block).content
end
# A method for redirect uri callback. How to use:
@@ -551,6 +547,9 @@ def post_content(uri, body = nil, extheader = {}, &block)
# in HTTP header. (raises BadResponseError instead)
def strict_redirect_uri_callback(uri, res)
newuri = URI.parse(res.header['location'][0])
+ if https?(uri) && !https?(newuri)
+ raise BadResponseError.new("redirecting to non-https resource")
+ end
unless newuri.is_a?(URI::HTTP)
raise BadResponseError.new("unexpected location: #{newuri}", res)
end
@@ -565,6 +564,9 @@ def strict_redirect_uri_callback(uri, res)
# in HTTP header.
def default_redirect_uri_callback(uri, res)
newuri = URI.parse(res.header['location'][0])
+ if https?(uri) && !https?(newuri)
+ raise BadResponseError.new("redirecting to non-https resource")
+ end
unless newuri.is_a?(URI::HTTP)
newuri = uri + newuri
STDERR.puts("could be a relative URI in location header which is not recommended")
@@ -652,7 +654,7 @@ def trace(uri, query = nil, body = nil, extheader = {}, &block)
def request(method, uri, query = nil, body = nil, extheader = {}, &block)
uri = urify(uri)
proxy = no_proxy?(uri) ? nil : @proxy
- req = create_request(method.to_s.upcase, uri, query, body, extheader)
+ req = create_request(method, uri, query, body, extheader)
if block
filtered_block = proc { |res, str|
block.call(str)
@@ -722,7 +724,7 @@ def trace_async(uri, query = nil, body = nil, extheader = {})
def request_async(method, uri, query = nil, body = nil, extheader = {})
uri = urify(uri)
proxy = no_proxy?(uri) ? nil : @proxy
- req = create_request(method.to_s.upcase, uri, query, body, extheader)
+ req = create_request(method, uri, query, body, extheader)
do_request_async(req, proxy)
end
@@ -802,21 +804,26 @@ def getenv(name)
ENV[name.downcase] || ENV[name.upcase]
end
- def follow_redirect(req, &block)
- retry_number = 0
+ def follow_redirect(method, uri, query, body, extheader, &block)
+ uri = urify(uri)
if block
filtered_block = proc { |r, str|
block.call(str) if HTTP::Status.successful?(r.status)
}
end
+ if HTTP::Message.file?(body)
+ pos = body.pos rescue nil
+ end
+ retry_number = 0
while retry_number < @follow_redirect_count
+ body.pos = pos if pos
+ req = create_request(method, uri, query, body, extheader)
proxy = no_proxy?(req.header.request_uri) ? nil : @proxy
res = do_request(req, proxy, &filtered_block)
if HTTP::Status.successful?(res.status)
return res
elsif HTTP::Status.redirect?(res.status)
uri = urify(@redirect_uri_callback.call(req.header.request_uri, res))
- req.header.request_uri = uri
retry_number += 1
else
raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
@@ -834,6 +841,7 @@ def protect_keep_alive_disconnected
end
def create_request(method, uri, query, body, extheader)
+ method = method.to_s.upcase
if extheader.is_a?(Hash)
extheader = extheader.to_a
else
@@ -913,6 +921,10 @@ def no_proxy?(uri)
false
end
+ def https?(uri)
+ uri.scheme.downcase == 'https'
+ end
+
# !! CAUTION !!
# Method 'do_get*' runs under MT conditon. Be careful to change.
def do_get_block(req, proxy, conn, &block)
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -1,5 +1,5 @@
# HTTPClient - HTTP client library.
-# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
+# Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
@@ -306,6 +306,42 @@ def test_broken_header
assert_equal('message body 1', @client.get_content('http://somewhere'))
end
+ def test_redirect_non_https
+ url = @url + 'redirect1'
+ https_url = URI.parse(url)
+ https_url.scheme = 'https'
+ #
+ redirect_to_http = "HTTP/1.0 302 OK\nLocation: #{url}\n\n"
+ redirect_to_https = "HTTP/1.0 302 OK\nLocation: #{https_url}\n\n"
+ #
+ # https -> http is denied
+ @client.test_loopback_http_response << redirect_to_http
+ assert_raises(HTTPClient::BadResponseError) do
+ @client.get_content(https_url)
+ end
+ #
+ # http -> http is OK
+ @client.reset_all
+ @client.test_loopback_http_response << redirect_to_http
+ assert_equal('hello', @client.get_content(url))
+ #
+ # http -> https is OK
+ @client.reset_all
+ @client.test_loopback_http_response << redirect_to_https
+ assert_raises(OpenSSL::SSL::SSLError) do
+ # trying to normal endpoint with SSL -> SSL negotiation failure
+ @client.get_content(url)
+ end
+ #
+ # https -> https is OK
+ @client.reset_all
+ @client.test_loopback_http_response << redirect_to_https
+ assert_raises(OpenSSL::SSL::SSLError) do
+ # trying to normal endpoint with SSL -> SSL negotiation failure
+ @client.get_content(https_url)
+ end
+ end
+
def test_redirect_relative
@client.test_loopback_http_response << "HTTP/1.0 302 OK\nLocation: hello\n\n"
assert_equal('hello', @client.get_content(@url + 'redirect1'))

0 comments on commit 5fb4436

Please sign in to comment.