summaryrefslogtreecommitdiff
path: root/ruby_1_9_3/lib/net/http.rb
diff options
context:
space:
mode:
Diffstat (limited to 'ruby_1_9_3/lib/net/http.rb')
-rw-r--r--ruby_1_9_3/lib/net/http.rb2825
1 files changed, 0 insertions, 2825 deletions
diff --git a/ruby_1_9_3/lib/net/http.rb b/ruby_1_9_3/lib/net/http.rb
deleted file mode 100644
index 9e4fe6a120..0000000000
--- a/ruby_1_9_3/lib/net/http.rb
+++ /dev/null
@@ -1,2825 +0,0 @@
-#
-# = net/http.rb
-#
-# Copyright (c) 1999-2007 Yukihiro Matsumoto
-# Copyright (c) 1999-2007 Minero Aoki
-# Copyright (c) 2001 GOTOU Yuuzou
-#
-# Written and maintained by Minero Aoki <aamine@loveruby.net>.
-# HTTPS support added by GOTOU Yuuzou <gotoyuzo@notwork.org>.
-#
-# This file is derived from "http-access.rb".
-#
-# Documented by Minero Aoki; converted to RDoc by William Webber.
-#
-# This program is free software. You can re-distribute and/or
-# modify this program under the same terms of ruby itself ---
-# Ruby Distribution License or GNU General Public License.
-#
-# See Net::HTTP for an overview and examples.
-#
-
-require 'net/protocol'
-require 'uri'
-autoload :OpenSSL, 'openssl'
-
-module Net #:nodoc:
-
- # :stopdoc:
- class HTTPBadResponse < StandardError; end
- class HTTPHeaderSyntaxError < StandardError; end
- # :startdoc:
-
- # == An HTTP client API for Ruby.
- #
- # Net::HTTP provides a rich library which can be used to build HTTP
- # user-agents. For more details about HTTP see
- # [RFC2616](http://www.ietf.org/rfc/rfc2616.txt)
- #
- # Net::HTTP is designed to work closely with URI. URI::HTTP#host,
- # URI::HTTP#port and URI::HTTP#request_uri are designed to work with
- # Net::HTTP.
- #
- # If you are only performing a few GET requests you should try OpenURI.
- #
- # == Simple Examples
- #
- # All examples assume you have loaded Net::HTTP with:
- #
- # require 'net/http'
- #
- # This will also require 'uri' so you don't need to require it separately.
- #
- # The Net::HTTP methods in the following section do not persist
- # connections. They are not recommended if you are performing many HTTP
- # requests.
- #
- # === GET
- #
- # Net::HTTP.get('example.com', '/index.html') # => String
- #
- # === GET by URI
- #
- # uri = URI('http://example.com/index.html?count=10')
- # Net::HTTP.get(uri) # => String
- #
- # === GET with Dynamic Parameters
- #
- # uri = URI('http://example.com/index.html')
- # params = { :limit => 10, :page => 3 }
- # uri.query = URI.encode_www_form(params)
- #
- # res = Net::HTTP.get_response(uri)
- # puts res.body if res.is_a?(Net::HTTPSuccess)
- #
- # === POST
- #
- # uri = URI('http://www.example.com/search.cgi')
- # res = Net::HTTP.post_form(uri, 'q' => 'ruby', 'max' => '50')
- # puts res.body
- #
- # === POST with Multiple Values
- #
- # uri = URI('http://www.example.com/search.cgi')
- # res = Net::HTTP.post_form(uri, 'q' => ['ruby', 'perl'], 'max' => '50')
- # puts res.body
- #
- # == How to use Net::HTTP
- #
- # The following example code can be used as the basis of a HTTP user-agent
- # which can perform a variety of request types using persistent
- # connections.
- #
- # uri = URI('http://example.com/some_path?query=string')
- #
- # Net::HTTP.start(uri.host, uri.port) do |http|
- # request = Net::HTTP::Get.new uri.request_uri
- #
- # response = http.request request # Net::HTTPResponse object
- # end
- #
- # Net::HTTP::start immediately creates a connection to an HTTP server which
- # is kept open for the duration of the block. The connection will remain
- # open for multiple requests in the block if the server indicates it
- # supports persistent connections.
- #
- # The request types Net::HTTP supports are listed below in the section "HTTP
- # Request Classes".
- #
- # If you wish to re-use a connection across multiple HTTP requests without
- # automatically closing it you can use ::new instead of ::start. #request
- # will automatically open a connection to the server if one is not currently
- # open. You can manually close the connection with #finish.
- #
- # === Response Data
- #
- # uri = URI('http://example.com/index.html')
- # res = Net::HTTP.get_response(uri)
- #
- # # Headers
- # res['Set-Cookie'] # => String
- # res.get_fields('set-cookie') # => Array
- # res.to_hash['set-cookie'] # => Array
- # puts "Headers: #{res.to_hash.inspect}"
- #
- # # Status
- # puts res.code # => '200'
- # puts res.message # => 'OK'
- # puts res.class.name # => 'HTTPOK'
- #
- # # Body
- # puts res.body if res.response_body_permitted?
- #
- # === Following Redirection
- #
- # Each Net::HTTPResponse object belongs to a class for its response code.
- #
- # For example, all 2XX responses are instances of a Net::HTTPSuccess
- # subclass, a 3XX response is an instance of a Net::HTTPRedirection
- # subclass and a 200 response is an instance of the Net::HTTPOK class. For
- # details of response classes, see the section "HTTP Response Classes"
- # below.
- #
- # Using a case statement you can handle various types of responses properly:
- #
- # def fetch(uri_str, limit = 10)
- # # You should choose a better exception.
- # raise ArgumentError, 'too many HTTP redirects' if limit == 0
- #
- # response = Net::HTTP.get_response(URI(uri_str))
- #
- # case response
- # when Net::HTTPSuccess then
- # response
- # when Net::HTTPRedirection then
- # location = response['location']
- # warn "redirected to #{location}"
- # fetch(location, limit - 1)
- # else
- # response.value
- # end
- # end
- #
- # print fetch('http://www.ruby-lang.org')
- #
- # === POST
- #
- # A POST can be made using the Net::HTTP::Post request class. This example
- # creates a urlencoded POST body:
- #
- # uri = URI('http://www.example.com/todo.cgi')
- # req = Net::HTTP::Post.new(uri.path)
- # req.set_form_data('from' => '2005-01-01', 'to' => '2005-03-31')
- #
- # res = Net::HTTP.start(uri.hostname, uri.port) do |http|
- # http.request(req)
- # end
- #
- # case res
- # when Net::HTTPSuccess, Net::HTTPRedirection
- # # OK
- # else
- # res.value
- # end
- #
- # At this time Net::HTTP does not support multipart/form-data. To send
- # multipart/form-data use Net::HTTPRequest#body= and
- # Net::HTTPRequest#content_type=:
- #
- # req = Net::HTTP::Post.new(uri.path)
- # req.body = multipart_data
- # req.content_type = 'multipart/form-data'
- #
- # Other requests that can contain a body such as PUT can be created in the
- # same way using the corresponding request class (Net::HTTP::Put).
- #
- # === Setting Headers
- #
- # The following example performs a conditional GET using the
- # If-Modified-Since header. If the files has not been modified since the
- # time in the header a Not Modified response will be returned. See RFC 2616
- # section 9.3 for further details.
- #
- # uri = URI('http://example.com/cached_response')
- # file = File.stat 'cached_response'
- #
- # req = Net::HTTP::Get.new(uri.request_uri)
- # req['If-Modified-Since'] = file.mtime.rfc2822
- #
- # res = Net::HTTP.start(uri.hostname, uri.port) {|http|
- # http.request(req)
- # }
- #
- # open 'cached_response', 'w' do |io|
- # io.write res.body
- # end if res.is_a?(Net::HTTPSuccess)
- #
- # === Basic Authentication
- #
- # Basic authentication is performed according to
- # [RFC2617](http://www.ietf.org/rfc/rfc2617.txt)
- #
- # uri = URI('http://example.com/index.html?key=value')
- #
- # req = Net::HTTP::Get.new(uri.request_uri)
- # req.basic_auth 'user', 'pass'
- #
- # res = Net::HTTP.start(uri.hostname, uri.port) {|http|
- # http.request(req)
- # }
- # puts res.body
- #
- # === Streaming Response Bodies
- #
- # By default Net::HTTP reads an entire response into memory. If you are
- # handling large files or wish to implement a progress bar you can instead
- # stream the body directly to an IO.
- #
- # uri = URI('http://example.com/large_file')
- #
- # Net::HTTP.start(uri.host, uri.port) do |http|
- # request = Net::HTTP::Get.new uri.request_uri
- #
- # http.request request do |response|
- # open 'large_file', 'w' do |io|
- # response.read_body do |chunk|
- # io.write chunk
- # end
- # end
- # end
- # end
- #
- # === HTTPS
- #
- # HTTPS is enabled for an HTTP connection by Net::HTTP#use_ssl=.
- #
- # uri = URI('https://secure.example.com/some_path?query=string')
- #
- # Net::HTTP.start(uri.host, uri.port,
- # :use_ssl => uri.scheme == 'https').start do |http|
- # request = Net::HTTP::Get.new uri.request_uri
- #
- # response = http.request request # Net::HTTPResponse object
- # end
- #
- # In previous versions of ruby you would need to require 'net/https' to use
- # HTTPS. This is no longer true.
- #
- # === Proxies
- #
- # Net::HTTP::Proxy has the same methods as Net::HTTP but its instances always
- # connect via the proxy instead of directly to the given host.
- #
- # proxy_addr = 'your.proxy.host'
- # proxy_port = 8080
- #
- # Net::HTTP::Proxy(proxy_addr, proxy_port).start('www.example.com') {|http|
- # # always connect to your.proxy.addr:8080
- # }
- #
- # Net::HTTP::Proxy returns a Net::HTTP instance when proxy_addr is nil so
- # there is no need for conditional code.
- #
- # See Net::HTTP::Proxy for further details and examples such as proxies that
- # require a username and password.
- #
- # == HTTP Request Classes
- #
- # Here is the HTTP request class hierarchy.
- #
- # * Net::HTTPRequest
- # * Net::HTTP::Get
- # * Net::HTTP::Head
- # * Net::HTTP::Post
- # * Net::HTTP::Put
- # * Net::HTTP::Proppatch
- # * Net::HTTP::Lock
- # * Net::HTTP::Unlock
- # * Net::HTTP::Options
- # * Net::HTTP::Propfind
- # * Net::HTTP::Delete
- # * Net::HTTP::Move
- # * Net::HTTP::Copy
- # * Net::HTTP::Mkcol
- # * Net::HTTP::Trace
- #
- # == HTTP Response Classes
- #
- # Here is HTTP response class hierarchy. All classes are defined in Net
- # module and are subclasses of Net::HTTPResponse.
- #
- # HTTPUnknownResponse:: For unhandled HTTP extensions
- # HTTPInformation:: 1xx
- # HTTPContinue:: 100
- # HTTPSwitchProtocol:: 101
- # HTTPSuccess:: 2xx
- # HTTPOK:: 200
- # HTTPCreated:: 201
- # HTTPAccepted:: 202
- # HTTPNonAuthoritativeInformation:: 203
- # HTTPNoContent:: 204
- # HTTPResetContent:: 205
- # HTTPPartialContent:: 206
- # HTTPRedirection:: 3xx
- # HTTPMultipleChoice:: 300
- # HTTPMovedPermanently:: 301
- # HTTPFound:: 302
- # HTTPSeeOther:: 303
- # HTTPNotModified:: 304
- # HTTPUseProxy:: 305
- # HTTPTemporaryRedirect:: 307
- # HTTPClientError:: 4xx
- # HTTPBadRequest:: 400
- # HTTPUnauthorized:: 401
- # HTTPPaymentRequired:: 402
- # HTTPForbidden:: 403
- # HTTPNotFound:: 404
- # HTTPMethodNotAllowed:: 405
- # HTTPNotAcceptable:: 406
- # HTTPProxyAuthenticationRequired:: 407
- # HTTPRequestTimeOut:: 408
- # HTTPConflict:: 409
- # HTTPGone:: 410
- # HTTPLengthRequired:: 411
- # HTTPPreconditionFailed:: 412
- # HTTPRequestEntityTooLarge:: 413
- # HTTPRequestURITooLong:: 414
- # HTTPUnsupportedMediaType:: 415
- # HTTPRequestedRangeNotSatisfiable:: 416
- # HTTPExpectationFailed:: 417
- # HTTPServerError:: 5xx
- # HTTPInternalServerError:: 500
- # HTTPNotImplemented:: 501
- # HTTPBadGateway:: 502
- # HTTPServiceUnavailable:: 503
- # HTTPGatewayTimeOut:: 504
- # HTTPVersionNotSupported:: 505
- #
- # There is also the Net::HTTPBadResponse exception which is raised when
- # there is a protocol error.
- #
- class HTTP < Protocol
-
- # :stopdoc:
- Revision = %q$Revision$.split[1]
- HTTPVersion = '1.1'
- begin
- require 'zlib'
- require 'stringio' #for our purposes (unpacking gzip) lump these together
- HAVE_ZLIB=true
- rescue LoadError
- HAVE_ZLIB=false
- end
- # :startdoc:
-
- # Turns on net/http 1.2 (ruby 1.8) features.
- # Defaults to ON in ruby 1.8 or later.
- def HTTP.version_1_2
- true
- end
-
- # Returns true if net/http is in version 1.2 mode.
- # Defaults to true.
- def HTTP.version_1_2?
- true
- end
-
- def HTTP.version_1_1? #:nodoc:
- false
- end
-
- class << HTTP
- alias is_version_1_1? version_1_1? #:nodoc:
- alias is_version_1_2? version_1_2? #:nodoc:
- end
-
- #
- # short cut methods
- #
-
- #
- # Gets the body text from the target and outputs it to $stdout. The
- # target can either be specified as
- # (+uri+), or as (+host+, +path+, +port+ = 80); so:
- #
- # Net::HTTP.get_print URI('http://www.example.com/index.html')
- #
- # or:
- #
- # Net::HTTP.get_print 'www.example.com', '/index.html'
- #
- def HTTP.get_print(uri_or_host, path = nil, port = nil)
- get_response(uri_or_host, path, port) {|res|
- res.read_body do |chunk|
- $stdout.print chunk
- end
- }
- nil
- end
-
- # Sends a GET request to the target and returns the HTTP response
- # as a string. The target can either be specified as
- # (+uri+), or as (+host+, +path+, +port+ = 80); so:
- #
- # print Net::HTTP.get(URI('http://www.example.com/index.html'))
- #
- # or:
- #
- # print Net::HTTP.get('www.example.com', '/index.html')
- #
- def HTTP.get(uri_or_host, path = nil, port = nil)
- get_response(uri_or_host, path, port).body
- end
-
- # Sends a GET request to the target and returns the HTTP response
- # as a Net::HTTPResponse object. The target can either be specified as
- # (+uri+), or as (+host+, +path+, +port+ = 80); so:
- #
- # res = Net::HTTP.get_response(URI('http://www.example.com/index.html'))
- # print res.body
- #
- # or:
- #
- # res = Net::HTTP.get_response('www.example.com', '/index.html')
- # print res.body
- #
- def HTTP.get_response(uri_or_host, path = nil, port = nil, &block)
- if path
- host = uri_or_host
- new(host, port || HTTP.default_port).start {|http|
- return http.request_get(path, &block)
- }
- else
- uri = uri_or_host
- new(uri.hostname, uri.port).start {|http|
- return http.request_get(uri.request_uri, &block)
- }
- end
- end
-
- # Posts HTML form data to the specified URI object.
- # The form data must be provided as a Hash mapping from String to String.
- # Example:
- #
- # { "cmd" => "search", "q" => "ruby", "max" => "50" }
- #
- # This method also does Basic Authentication iff +url+.user exists.
- # But userinfo for authentication is deprecated (RFC3986).
- # So this feature will be removed.
- #
- # Example:
- #
- # require 'net/http'
- # require 'uri'
- #
- # HTTP.post_form URI('http://www.example.com/search.cgi'),
- # { "q" => "ruby", "max" => "50" }
- #
- def HTTP.post_form(url, params)
- req = Post.new(url.request_uri)
- req.form_data = params
- req.basic_auth url.user, url.password if url.user
- new(url.hostname, url.port).start {|http|
- http.request(req)
- }
- end
-
- #
- # HTTP session management
- #
-
- # The default port to use for HTTP requests; defaults to 80.
- def HTTP.default_port
- http_default_port()
- end
-
- # The default port to use for HTTP requests; defaults to 80.
- def HTTP.http_default_port
- 80
- end
-
- # The default port to use for HTTPS requests; defaults to 443.
- def HTTP.https_default_port
- 443
- end
-
- def HTTP.socket_type #:nodoc: obsolete
- BufferedIO
- end
-
- # call-seq:
- # HTTP.start(address, port, p_addr, p_port, p_user, p_pass, &block)
- # HTTP.start(address, port=nil, p_addr=nil, p_port=nil, p_user=nil, p_pass=nil, opt, &block)
- #
- # Creates a new Net::HTTP object, then additionally opens the TCP
- # connection and HTTP session.
- #
- # Arguments are the following:
- # _address_ :: hostname or IP address of the server
- # _port_ :: port of the server
- # _p_addr_ :: address of proxy
- # _p_port_ :: port of proxy
- # _p_user_ :: user of proxy
- # _p_pass_ :: pass of proxy
- # _opt_ :: optional hash
- #
- # _opt_ sets following values by its accessor.
- # The keys are ca_file, ca_path, cert, cert_store, ciphers,
- # close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout,
- # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
- # If you set :use_ssl as true, you can use https and default value of
- # verify_mode is set as OpenSSL::SSL::VERIFY_PEER.
- #
- # If the optional block is given, the newly
- # created Net::HTTP object is passed to it and closed when the
- # block finishes. In this case, the return value of this method
- # is the return value of the block. If no block is given, the
- # return value of this method is the newly created Net::HTTP object
- # itself, and the caller is responsible for closing it upon completion
- # using the finish() method.
- def HTTP.start(address, *arg, &block) # :yield: +http+
- arg.pop if opt = Hash.try_convert(arg[-1])
- port, p_addr, p_port, p_user, p_pass = *arg
- port = https_default_port if !port && opt && opt[:use_ssl]
- http = new(address, port, p_addr, p_port, p_user, p_pass)
-
- if opt
- if opt[:use_ssl]
- opt = {verify_mode: OpenSSL::SSL::VERIFY_PEER}.update(opt)
- end
- http.methods.grep(/\A(\w+)=\z/) do |meth|
- key = $1.to_sym
- opt.key?(key) or next
- http.__send__(meth, opt[key])
- end
- end
-
- http.start(&block)
- end
-
- class << HTTP
- alias newobj new
- end
-
- # Creates a new Net::HTTP object without opening a TCP connection or
- # HTTP session.
- # The +address+ should be a DNS hostname or IP address.
- # If +p_addr+ is given, creates a Net::HTTP object with proxy support.
- def HTTP.new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
- Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
- end
-
- # Creates a new Net::HTTP object for the specified server address,
- # without opening the TCP connection or initializing the HTTP session.
- # The +address+ should be a DNS hostname or IP address.
- def initialize(address, port = nil)
- @address = address
- @port = (port || HTTP.default_port)
- @curr_http_version = HTTPVersion
- @no_keepalive_server = false
- @close_on_empty_response = false
- @socket = nil
- @started = false
- @open_timeout = nil
- @read_timeout = 60
- @continue_timeout = nil
- @debug_output = nil
- @use_ssl = false
- @ssl_context = nil
- @enable_post_connection_check = true
- @compression = nil
- @sspi_enabled = false
- if defined?(SSL_ATTRIBUTES)
- SSL_ATTRIBUTES.each do |name|
- instance_variable_set "@#{name}", nil
- end
- end
- end
-
- def inspect
- "#<#{self.class} #{@address}:#{@port} open=#{started?}>"
- end
-
- # *WARNING* This method opens a serious security hole.
- # Never use this method in production code.
- #
- # Sets an output stream for debugging.
- #
- # http = Net::HTTP.new
- # http.set_debug_output $stderr
- # http.start { .... }
- #
- def set_debug_output(output)
- warn 'Net::HTTP#set_debug_output called after HTTP started' if started?
- @debug_output = output
- end
-
- # The DNS host name or IP address to connect to.
- attr_reader :address
-
- # The port number to connect to.
- attr_reader :port
-
- # Number of seconds to wait for the connection to open. Any number
- # may be used, including Floats for fractional seconds. If the HTTP
- # object cannot open a connection in this many seconds, it raises a
- # TimeoutError exception. The default value is +nil+.
- attr_accessor :open_timeout
-
- # Number of seconds to wait for one block to be read (via one read(2)
- # call). Any number may be used, including Floats for fractional
- # seconds. If the HTTP object cannot read data in this many seconds,
- # it raises a TimeoutError exception. The default value is 60 seconds.
- attr_reader :read_timeout
-
- # Setter for the read_timeout attribute.
- def read_timeout=(sec)
- @socket.read_timeout = sec if @socket
- @read_timeout = sec
- end
-
- # Seconds to wait for 100 Continue response. If the HTTP object does not
- # receive a response in this many seconds it sends the request body. The
- # default value is +nil+.
- attr_reader :continue_timeout
-
- # Setter for the continue_timeout attribute.
- def continue_timeout=(sec)
- @socket.continue_timeout = sec if @socket
- @continue_timeout = sec
- end
-
- # Returns true if the HTTP session has been started.
- def started?
- @started
- end
-
- alias active? started? #:nodoc: obsolete
-
- attr_accessor :close_on_empty_response
-
- # Returns true if SSL/TLS is being used with HTTP.
- def use_ssl?
- @use_ssl
- end
-
- # Turn on/off SSL.
- # This flag must be set before starting session.
- # If you change use_ssl value after session started,
- # a Net::HTTP object raises IOError.
- def use_ssl=(flag)
- flag = flag ? true : false
- if started? and @use_ssl != flag
- raise IOError, "use_ssl value changed, but session already started"
- end
- @use_ssl = flag
- end
-
- SSL_ATTRIBUTES = %w(
- ssl_version key cert ca_file ca_path cert_store ciphers
- verify_mode verify_callback verify_depth ssl_timeout
- )
-
- # Sets path of a CA certification file in PEM format.
- #
- # The file can contain several CA certificates.
- attr_accessor :ca_file
-
- # Sets path of a CA certification directory containing certifications in
- # PEM format.
- attr_accessor :ca_path
-
- # Sets an OpenSSL::X509::Certificate object as client certificate.
- # (This method is appeared in Michal Rokos's OpenSSL extension).
- attr_accessor :cert
-
- # Sets the X509::Store to verify peer certificate.
- attr_accessor :cert_store
-
- # Sets the available ciphers. See OpenSSL::SSL::SSLContext#ciphers=
- attr_accessor :ciphers
-
- # Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
- # (This method is appeared in Michal Rokos's OpenSSL extension.)
- attr_accessor :key
-
- # Sets the SSL timeout seconds.
- attr_accessor :ssl_timeout
-
- # Sets the SSL version. See OpenSSL::SSL::SSLContext#ssl_version=
- attr_accessor :ssl_version
-
- # Sets the verify callback for the server certification verification.
- attr_accessor :verify_callback
-
- # Sets the maximum depth for the certificate chain verification.
- attr_accessor :verify_depth
-
- # Sets the flags for server the certification verification at beginning of
- # SSL/TLS session.
- #
- # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable.
- attr_accessor :verify_mode
-
- # Returns the X.509 certificates the server presented.
- def peer_cert
- if not use_ssl? or not @socket
- return nil
- end
- @socket.io.peer_cert
- end
-
- # Opens a TCP connection and HTTP session.
- #
- # When this method is called with a block, it passes the Net::HTTP
- # object to the block, and closes the TCP connection and HTTP session
- # after the block has been executed.
- #
- # When called with a block, it returns the return value of the
- # block; otherwise, it returns self.
- #
- def start # :yield: http
- raise IOError, 'HTTP session already opened' if @started
- if block_given?
- begin
- do_start
- return yield(self)
- ensure
- do_finish
- end
- end
- do_start
- self
- end
-
- def do_start
- connect
- @started = true
- end
- private :do_start
-
- def connect
- D "opening connection to #{conn_address()}..."
- s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
- D "opened"
- if use_ssl?
- ssl_parameters = Hash.new
- iv_list = instance_variables
- SSL_ATTRIBUTES.each do |name|
- ivname = "@#{name}".intern
- if iv_list.include?(ivname) and
- value = instance_variable_get(ivname)
- ssl_parameters[name] = value
- end
- end
- @ssl_context = OpenSSL::SSL::SSLContext.new
- @ssl_context.set_params(ssl_parameters)
- s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
- s.sync_close = true
- end
- @socket = BufferedIO.new(s)
- @socket.read_timeout = @read_timeout
- @socket.continue_timeout = @continue_timeout
- @socket.debug_output = @debug_output
- if use_ssl?
- begin
- if proxy?
- @socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
- @address, @port, HTTPVersion)
- @socket.writeline "Host: #{@address}:#{@port}"
- if proxy_user
- credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
- credential.delete!("\r\n")
- @socket.writeline "Proxy-Authorization: Basic #{credential}"
- end
- @socket.writeline ''
- HTTPResponse.read_new(@socket).value
- end
- # Server Name Indication (SNI) RFC 3546
- s.hostname = @address if s.respond_to? :hostname=
- timeout(@open_timeout) { s.connect }
- if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
- s.post_connection_check(@address)
- end
- rescue => exception
- D "Conn close because of connect error #{exception}"
- @socket.close if @socket and not @socket.closed?
- raise exception
- end
- end
- on_connect
- end
- private :connect
-
- def on_connect
- end
- private :on_connect
-
- # Finishes the HTTP session and closes the TCP connection.
- # Raises IOError if the session has not been started.
- def finish
- raise IOError, 'HTTP session not yet started' unless started?
- do_finish
- end
-
- def do_finish
- @started = false
- @socket.close if @socket and not @socket.closed?
- @socket = nil
- end
- private :do_finish
-
- #
- # proxy
- #
-
- public
-
- # no proxy
- @is_proxy_class = false
- @proxy_addr = nil
- @proxy_port = nil
- @proxy_user = nil
- @proxy_pass = nil
-
- # Creates an HTTP proxy class which behaves like Net::HTTP, but
- # performs all access via the specified proxy.
- #
- # The arguments are the DNS name or IP address of the proxy host,
- # the port to use to access the proxy, and a username and password
- # if authorization is required to use the proxy.
- #
- # You can replace any use of the Net::HTTP class with use of the
- # proxy class created.
- #
- # If +p_addr+ is nil, this method returns self (a Net::HTTP object).
- #
- # # Example
- # proxy_class = Net::HTTP::Proxy('proxy.example.com', 8080)
- #
- # proxy_class.start('www.ruby-lang.org') {|http|
- # # connecting proxy.foo.org:8080
- # }
- #
- # You may use them to work with authorization-enabled proxies:
- #
- # proxy_host = 'your.proxy.example'
- # proxy_port = 8080
- # proxy_user = 'user'
- # proxy_pass = 'pass'
- #
- # proxy = Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
- # proxy.start('www.example.com') { |http|
- # # always connect to your.proxy.example:8080 using specified username
- # # and password
- # }
- #
- # Note that net/http does not use the HTTP_PROXY environment variable.
- # If you want to use a proxy, you must set it explicitly.
- #
- def HTTP.Proxy(p_addr, p_port = nil, p_user = nil, p_pass = nil)
- return self unless p_addr
- delta = ProxyDelta
- proxyclass = Class.new(self)
- proxyclass.module_eval {
- include delta
- # with proxy
- @is_proxy_class = true
- @proxy_address = p_addr
- @proxy_port = p_port || default_port()
- @proxy_user = p_user
- @proxy_pass = p_pass
- }
- proxyclass
- end
-
- class << HTTP
- # returns true if self is a class which was created by HTTP::Proxy.
- def proxy_class?
- @is_proxy_class
- end
-
- # Address of proxy host. If Net::HTTP does not use a proxy, nil.
- attr_reader :proxy_address
-
- # Port number of proxy host. If Net::HTTP does not use a proxy, nil.
- attr_reader :proxy_port
-
- # User name for accessing proxy. If Net::HTTP does not use a proxy, nil.
- attr_reader :proxy_user
-
- # User password for accessing proxy. If Net::HTTP does not use a proxy,
- # nil.
- attr_reader :proxy_pass
- end
-
- # True if self is a HTTP proxy class.
- def proxy?
- self.class.proxy_class?
- end
-
- # A convenience method for accessing value of proxy_address from Net::HTTP.
- def proxy_address
- self.class.proxy_address
- end
-
- # A convenience method for accessing value of proxy_port from Net::HTTP.
- def proxy_port
- self.class.proxy_port
- end
-
- # A convenience method for accessing value of proxy_user from Net::HTTP.
- def proxy_user
- self.class.proxy_user
- end
-
- # A convenience method for accessing value of proxy_pass from Net::HTTP.
- def proxy_pass
- self.class.proxy_pass
- end
-
- alias proxyaddr proxy_address #:nodoc: obsolete
- alias proxyport proxy_port #:nodoc: obsolete
-
- private
-
- # without proxy
-
- def conn_address
- address()
- end
-
- def conn_port
- port()
- end
-
- def edit_path(path)
- path
- end
-
- module ProxyDelta #:nodoc: internal use only
- private
-
- def conn_address
- proxy_address()
- end
-
- def conn_port
- proxy_port()
- end
-
- def edit_path(path)
- use_ssl? ? path : "http://#{addr_port()}#{path}"
- end
- end
-
- #
- # HTTP operations
- #
-
- public
-
- # Gets data from +path+ on the connected-to host.
- # +initheader+ must be a Hash like { 'Accept' => '*/*', ... },
- # and it defaults to an empty hash.
- # If +initheader+ doesn't have the key 'accept-encoding', then
- # a value of "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" is used,
- # so that gzip compression is used in preference to deflate
- # compression, which is used in preference to no compression.
- # Ruby doesn't have libraries to support the compress (Lempel-Ziv)
- # compression, so that is not supported. The intent of this is
- # to reduce bandwidth by default. If this routine sets up
- # compression, then it does the decompression also, removing
- # the header as well to prevent confusion. Otherwise
- # it leaves the body as it found it.
- #
- # This method returns a Net::HTTPResponse object.
- #
- # If called with a block, yields each fragment of the
- # entity body in turn as a string as it is read from
- # the socket. Note that in this case, the returned response
- # object will *not* contain a (meaningful) body.
- #
- # +dest+ argument is obsolete.
- # It still works but you must not use it.
- #
- # This method never raises an exception.
- #
- # response = http.get('/index.html')
- #
- # # using block
- # File.open('result.txt', 'w') {|f|
- # http.get('/~foo/') do |str|
- # f.write str
- # end
- # }
- #
- def get(path, initheader = {}, dest = nil, &block) # :yield: +body_segment+
- res = nil
- if HAVE_ZLIB
- unless initheader.keys.any?{|k| k.downcase == "accept-encoding"}
- initheader = initheader.merge({
- "accept-encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
- })
- @compression = true
- end
- end
- request(Get.new(path, initheader)) {|r|
- if r.key?("content-encoding") and @compression
- @compression = nil # Clear it till next set.
- the_body = r.read_body dest, &block
- case r["content-encoding"]
- when "gzip"
- r.body= Zlib::GzipReader.new(StringIO.new(the_body), encoding: "ASCII-8BIT").read
- r.delete("content-encoding")
- when "deflate"
- r.body= Zlib::Inflate.inflate(the_body);
- r.delete("content-encoding")
- when "identity"
- ; # nothing needed
- else
- ; # Don't do anything dramatic, unless we need to later
- end
- else
- r.read_body dest, &block
- end
- res = r
- }
- res
- end
-
- # Gets only the header from +path+ on the connected-to host.
- # +header+ is a Hash like { 'Accept' => '*/*', ... }.
- #
- # This method returns a Net::HTTPResponse object.
- #
- # This method never raises an exception.
- #
- # response = nil
- # Net::HTTP.start('some.www.server', 80) {|http|
- # response = http.head('/index.html')
- # }
- # p response['content-type']
- #
- def head(path, initheader = nil)
- request(Head.new(path, initheader))
- end
-
- # Posts +data+ (must be a String) to +path+. +header+ must be a Hash
- # like { 'Accept' => '*/*', ... }.
- #
- # This method returns a Net::HTTPResponse object.
- #
- # If called with a block, yields each fragment of the
- # entity body in turn as a string as it is read from
- # the socket. Note that in this case, the returned response
- # object will *not* contain a (meaningful) body.
- #
- # +dest+ argument is obsolete.
- # It still works but you must not use it.
- #
- # This method never raises exception.
- #
- # response = http.post('/cgi-bin/search.rb', 'query=foo')
- #
- # # using block
- # File.open('result.txt', 'w') {|f|
- # http.post('/cgi-bin/search.rb', 'query=foo') do |str|
- # f.write str
- # end
- # }
- #
- # You should set Content-Type: header field for POST.
- # If no Content-Type: field given, this method uses
- # "application/x-www-form-urlencoded" by default.
- #
- def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
- send_entity(path, data, initheader, dest, Post, &block)
- end
-
- # Sends a PATCH request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
- send_entity(path, data, initheader, dest, Patch, &block)
- end
-
- def put(path, data, initheader = nil) #:nodoc:
- request(Put.new(path, initheader), data)
- end
-
- # Sends a PROPPATCH request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def proppatch(path, body, initheader = nil)
- request(Proppatch.new(path, initheader), body)
- end
-
- # Sends a LOCK request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def lock(path, body, initheader = nil)
- request(Lock.new(path, initheader), body)
- end
-
- # Sends a UNLOCK request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def unlock(path, body, initheader = nil)
- request(Unlock.new(path, initheader), body)
- end
-
- # Sends a OPTIONS request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def options(path, initheader = nil)
- request(Options.new(path, initheader))
- end
-
- # Sends a PROPFIND request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def propfind(path, body = nil, initheader = {'Depth' => '0'})
- request(Propfind.new(path, initheader), body)
- end
-
- # Sends a DELETE request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def delete(path, initheader = {'Depth' => 'Infinity'})
- request(Delete.new(path, initheader))
- end
-
- # Sends a MOVE request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def move(path, initheader = nil)
- request(Move.new(path, initheader))
- end
-
- # Sends a COPY request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def copy(path, initheader = nil)
- request(Copy.new(path, initheader))
- end
-
- # Sends a MKCOL request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def mkcol(path, body = nil, initheader = nil)
- request(Mkcol.new(path, initheader), body)
- end
-
- # Sends a TRACE request to the +path+ and gets a response,
- # as an HTTPResponse object.
- def trace(path, initheader = nil)
- request(Trace.new(path, initheader))
- end
-
- # Sends a GET request to the +path+.
- # Returns the response as a Net::HTTPResponse object.
- #
- # When called with a block, passes an HTTPResponse object to the block.
- # The body of the response will not have been read yet;
- # the block can process it using HTTPResponse#read_body,
- # if desired.
- #
- # Returns the response.
- #
- # This method never raises Net::* exceptions.
- #
- # response = http.request_get('/index.html')
- # # The entity body is already read in this case.
- # p response['content-type']
- # puts response.body
- #
- # # Using a block
- # http.request_get('/index.html') {|response|
- # p response['content-type']
- # response.read_body do |str| # read body now
- # print str
- # end
- # }
- #
- def request_get(path, initheader = nil, &block) # :yield: +response+
- request(Get.new(path, initheader), &block)
- end
-
- # Sends a HEAD request to the +path+ and returns the response
- # as a Net::HTTPResponse object.
- #
- # Returns the response.
- #
- # This method never raises Net::* exceptions.
- #
- # response = http.request_head('/index.html')
- # p response['content-type']
- #
- def request_head(path, initheader = nil, &block)
- request(Head.new(path, initheader), &block)
- end
-
- # Sends a POST request to the +path+.
- #
- # Returns the response as a Net::HTTPResponse object.
- #
- # When called with a block, the block is passed an HTTPResponse
- # object. The body of that response will not have been read yet;
- # the block can process it using HTTPResponse#read_body, if desired.
- #
- # Returns the response.
- #
- # This method never raises Net::* exceptions.
- #
- # # example
- # response = http.request_post('/cgi-bin/nice.rb', 'datadatadata...')
- # p response.status
- # puts response.body # body is already read in this case
- #
- # # using block
- # http.request_post('/cgi-bin/nice.rb', 'datadatadata...') {|response|
- # p response.status
- # p response['content-type']
- # response.read_body do |str| # read body now
- # print str
- # end
- # }
- #
- def request_post(path, data, initheader = nil, &block) # :yield: +response+
- request Post.new(path, initheader), data, &block
- end
-
- def request_put(path, data, initheader = nil, &block) #:nodoc:
- request Put.new(path, initheader), data, &block
- end
-
- alias get2 request_get #:nodoc: obsolete
- alias head2 request_head #:nodoc: obsolete
- alias post2 request_post #:nodoc: obsolete
- alias put2 request_put #:nodoc: obsolete
-
-
- # Sends an HTTP request to the HTTP server.
- # Also sends a DATA string if +data+ is given.
- #
- # Returns a Net::HTTPResponse object.
- #
- # This method never raises Net::* exceptions.
- #
- # response = http.send_request('GET', '/index.html')
- # puts response.body
- #
- def send_request(name, path, data = nil, header = nil)
- r = HTTPGenericRequest.new(name,(data ? true : false),true,path,header)
- request r, data
- end
-
- # Sends an HTTPRequest object +req+ to the HTTP server.
- #
- # If +req+ is a Net::HTTP::Post or Net::HTTP::Put request containing
- # data, the data is also sent. Providing data for a Net::HTTP::Head or
- # Net::HTTP::Get request results in an ArgumentError.
- #
- # Returns an HTTPResponse object.
- #
- # When called with a block, passes an HTTPResponse object to the block.
- # The body of the response will not have been read yet;
- # the block can process it using HTTPResponse#read_body,
- # if desired.
- #
- # This method never raises Net::* exceptions.
- #
- def request(req, body = nil, &block) # :yield: +response+
- unless started?
- start {
- req['connection'] ||= 'close'
- return request(req, body, &block)
- }
- end
- if proxy_user()
- req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl?
- end
- req.set_body_internal body
- res = transport_request(req, &block)
- if sspi_auth?(res)
- sspi_auth(req)
- res = transport_request(req, &block)
- end
- res
- end
-
- private
-
- # Executes a request which uses a representation
- # and returns its body.
- def send_entity(path, data, initheader, dest, type, &block)
- res = nil
- request(type.new(path, initheader), data) {|r|
- r.read_body dest, &block
- res = r
- }
- res
- end
-
- def transport_request(req)
- begin_transport req
- res = catch(:response) {
- req.exec @socket, @curr_http_version, edit_path(req.path)
- begin
- res = HTTPResponse.read_new(@socket)
- end while res.kind_of?(HTTPContinue)
- res.reading_body(@socket, req.response_body_permitted?) {
- yield res if block_given?
- }
- res
- }
- end_transport req, res
- res
- rescue => exception
- D "Conn close because of error #{exception}"
- @socket.close if @socket and not @socket.closed?
- raise exception
- end
-
- def begin_transport(req)
- connect if @socket.closed?
- if not req.response_body_permitted? and @close_on_empty_response
- req['connection'] ||= 'close'
- end
- req['host'] ||= addr_port()
- end
-
- def end_transport(req, res)
- @curr_http_version = res.http_version
- if @socket.closed?
- D 'Conn socket closed'
- elsif not res.body and @close_on_empty_response
- D 'Conn close'
- @socket.close
- elsif keep_alive?(req, res)
- D 'Conn keep-alive'
- else
- D 'Conn close'
- @socket.close
- end
- end
-
- def keep_alive?(req, res)
- return false if req.connection_close?
- if @curr_http_version <= '1.0'
- res.connection_keep_alive?
- else # HTTP/1.1 or later
- not res.connection_close?
- end
- end
-
- def sspi_auth?(res)
- return false unless @sspi_enabled
- if res.kind_of?(HTTPProxyAuthenticationRequired) and
- proxy? and res["Proxy-Authenticate"].include?("Negotiate")
- begin
- require 'win32/sspi'
- true
- rescue LoadError
- false
- end
- else
- false
- end
- end
-
- def sspi_auth(req)
- n = Win32::SSPI::NegotiateAuth.new
- req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
- # Some versions of ISA will close the connection if this isn't present.
- req["Connection"] = "Keep-Alive"
- req["Proxy-Connection"] = "Keep-Alive"
- res = transport_request(req)
- authphrase = res["Proxy-Authenticate"] or return res
- req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication(authphrase)}"
- rescue => err
- raise HTTPAuthenticationError.new('HTTP authentication failed', err)
- end
-
- #
- # utils
- #
-
- private
-
- def addr_port
- if use_ssl?
- address() + (port == HTTP.https_default_port ? '' : ":#{port()}")
- else
- address() + (port == HTTP.http_default_port ? '' : ":#{port()}")
- end
- end
-
- def D(msg)
- return unless @debug_output
- @debug_output << msg
- @debug_output << "\n"
- end
-
- end
-
- HTTPSession = HTTP
-
-
- # The HTTPHeader module defines methods for reading and writing
- # HTTP headers.
- #
- # It is used as a mixin by other classes, to provide hash-like
- # access to HTTP header values. Unlike raw hash access, HTTPHeader
- # provides access via case-insensitive keys. It also provides
- # methods for accessing commonly-used HTTP header values in more
- # convenient formats.
- #
- module HTTPHeader
-
- def initialize_http_header(initheader)
- @header = {}
- return unless initheader
- initheader.each do |key, value|
- warn "net/http: warning: duplicated HTTP header: #{key}" if key?(key) and $VERBOSE
- @header[key.downcase] = [value.strip]
- end
- end
-
- def size #:nodoc: obsolete
- @header.size
- end
-
- alias length size #:nodoc: obsolete
-
- # Returns the header field corresponding to the case-insensitive key.
- # For example, a key of "Content-Type" might return "text/html"
- def [](key)
- a = @header[key.downcase] or return nil
- a.join(', ')
- end
-
- # Sets the header field corresponding to the case-insensitive key.
- def []=(key, val)
- unless val
- @header.delete key.downcase
- return val
- end
- @header[key.downcase] = [val]
- end
-
- # [Ruby 1.8.3]
- # Adds a value to a named header field, instead of replacing its value.
- # Second argument +val+ must be a String.
- # See also #[]=, #[] and #get_fields.
- #
- # request.add_field 'X-My-Header', 'a'
- # p request['X-My-Header'] #=> "a"
- # p request.get_fields('X-My-Header') #=> ["a"]
- # request.add_field 'X-My-Header', 'b'
- # p request['X-My-Header'] #=> "a, b"
- # p request.get_fields('X-My-Header') #=> ["a", "b"]
- # request.add_field 'X-My-Header', 'c'
- # p request['X-My-Header'] #=> "a, b, c"
- # p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
- #
- def add_field(key, val)
- if @header.key?(key.downcase)
- @header[key.downcase].push val
- else
- @header[key.downcase] = [val]
- end
- end
-
- # [Ruby 1.8.3]
- # Returns an array of header field strings corresponding to the
- # case-insensitive +key+. This method allows you to get duplicated
- # header fields without any processing. See also #[].
- #
- # p response.get_fields('Set-Cookie')
- # #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23",
- # "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"]
- # p response['Set-Cookie']
- # #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"
- #
- def get_fields(key)
- return nil unless @header[key.downcase]
- @header[key.downcase].dup
- end
-
- # Returns the header field corresponding to the case-insensitive key.
- # Returns the default value +args+, or the result of the block, or
- # raises an IndexError if there's no header field named +key+
- # See Hash#fetch
- def fetch(key, *args, &block) #:yield: +key+
- a = @header.fetch(key.downcase, *args, &block)
- a.kind_of?(Array) ? a.join(', ') : a
- end
-
- # Iterates through the header names and values, passing in the name
- # and value to the code block supplied.
- #
- # Example:
- #
- # response.header.each_header {|key,value| puts "#{key} = #{value}" }
- #
- def each_header #:yield: +key+, +value+
- block_given? or return enum_for(__method__)
- @header.each do |k,va|
- yield k, va.join(', ')
- end
- end
-
- alias each each_header
-
- # Iterates through the header names in the header, passing
- # each header name to the code block.
- def each_name(&block) #:yield: +key+
- block_given? or return enum_for(__method__)
- @header.each_key(&block)
- end
-
- alias each_key each_name
-
- # Iterates through the header names in the header, passing
- # capitalized header names to the code block.
- #
- # Note that header names are capitalized systematically;
- # capitalization may not match that used by the remote HTTP
- # server in its response.
- def each_capitalized_name #:yield: +key+
- block_given? or return enum_for(__method__)
- @header.each_key do |k|
- yield capitalize(k)
- end
- end
-
- # Iterates through header values, passing each value to the
- # code block.
- def each_value #:yield: +value+
- block_given? or return enum_for(__method__)
- @header.each_value do |va|
- yield va.join(', ')
- end
- end
-
- # Removes a header field, specified by case-insensitive key.
- def delete(key)
- @header.delete(key.downcase)
- end
-
- # true if +key+ header exists.
- def key?(key)
- @header.key?(key.downcase)
- end
-
- # Returns a Hash consisting of header names and values.
- # e.g.
- # {"cache-control" => "private",
- # "content-type" => "text/html",
- # "date" => "Wed, 22 Jun 2005 22:11:50 GMT"}
- def to_hash
- @header.dup
- end
-
- # As for #each_header, except the keys are provided in capitalized form.
- #
- # Note that header names are capitalized systematically;
- # capitalization may not match that used by the remote HTTP
- # server in its response.
- def each_capitalized
- block_given? or return enum_for(__method__)
- @header.each do |k,v|
- yield capitalize(k), v.join(', ')
- end
- end
-
- alias canonical_each each_capitalized
-
- def capitalize(name)
- name.split(/-/).map {|s| s.capitalize }.join('-')
- end
- private :capitalize
-
- # Returns an Array of Range objects which represent the Range:
- # HTTP header field, or +nil+ if there is no such header.
- def range
- return nil unless @header['range']
- self['Range'].split(/,/).map {|spec|
- m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match(spec) or
- raise HTTPHeaderSyntaxError, "wrong Range: #{spec}"
- d1 = m[1].to_i
- d2 = m[2].to_i
- if m[1] and m[2] then d1..d2
- elsif m[1] then d1..-1
- elsif m[2] then -d2..-1
- else
- raise HTTPHeaderSyntaxError, 'range is not specified'
- end
- }
- end
-
- # Sets the HTTP Range: header.
- # Accepts either a Range object as a single argument,
- # or a beginning index and a length from that index.
- # Example:
- #
- # req.range = (0..1023)
- # req.set_range 0, 1023
- #
- def set_range(r, e = nil)
- unless r
- @header.delete 'range'
- return r
- end
- r = (r...r+e) if e
- case r
- when Numeric
- n = r.to_i
- rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
- when Range
- first = r.first
- last = r.last
- last -= 1 if r.exclude_end?
- if last == -1
- rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
- else
- raise HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
- raise HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
- raise HTTPHeaderSyntaxError, 'must be .first < .last' if first > last
- rangestr = "#{first}-#{last}"
- end
- else
- raise TypeError, 'Range/Integer is required'
- end
- @header['range'] = ["bytes=#{rangestr}"]
- r
- end
-
- alias range= set_range
-
- # Returns an Integer object which represents the HTTP Content-Length:
- # header field, or +nil+ if that field was not provided.
- def content_length
- return nil unless key?('Content-Length')
- len = self['Content-Length'].slice(/\d+/) or
- raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
- len.to_i
- end
-
- def content_length=(len)
- unless len
- @header.delete 'content-length'
- return nil
- end
- @header['content-length'] = [len.to_i.to_s]
- end
-
- # Returns "true" if the "transfer-encoding" header is present and
- # set to "chunked". This is an HTTP/1.1 feature, allowing the
- # the content to be sent in "chunks" without at the outset
- # stating the entire content length.
- def chunked?
- return false unless @header['transfer-encoding']
- field = self['Transfer-Encoding']
- (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
- end
-
- # Returns a Range object which represents the value of the Content-Range:
- # header field.
- # For a partial entity body, this indicates where this fragment
- # fits inside the full entity body, as range of byte offsets.
- def content_range
- return nil unless @header['content-range']
- m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or
- raise HTTPHeaderSyntaxError, 'wrong Content-Range format'
- m[1].to_i .. m[2].to_i
- end
-
- # The length of the range represented in Content-Range: header.
- def range_length
- r = content_range() or return nil
- r.end - r.begin + 1
- end
-
- # Returns a content type string such as "text/html".
- # This method returns nil if Content-Type: header field does not exist.
- def content_type
- return nil unless main_type()
- if sub_type()
- then "#{main_type()}/#{sub_type()}"
- else main_type()
- end
- end
-
- # Returns a content type string such as "text".
- # This method returns nil if Content-Type: header field does not exist.
- def main_type
- return nil unless @header['content-type']
- self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
- end
-
- # Returns a content type string such as "html".
- # This method returns nil if Content-Type: header field does not exist
- # or sub-type is not given (e.g. "Content-Type: text").
- def sub_type
- return nil unless @header['content-type']
- _, sub = *self['Content-Type'].split(';').first.to_s.split('/')
- return nil unless sub
- sub.strip
- end
-
- # Any parameters specified for the content type, returned as a Hash.
- # For example, a header of Content-Type: text/html; charset=EUC-JP
- # would result in type_params returning {'charset' => 'EUC-JP'}
- def type_params
- result = {}
- list = self['Content-Type'].to_s.split(';')
- list.shift
- list.each do |param|
- k, v = *param.split('=', 2)
- result[k.strip] = v.strip
- end
- result
- end
-
- # Sets the content type in an HTTP header.
- # The +type+ should be a full HTTP content type, e.g. "text/html".
- # The +params+ are an optional Hash of parameters to add after the
- # content type, e.g. {'charset' => 'iso-8859-1'}
- def set_content_type(type, params = {})
- @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
- end
-
- alias content_type= set_content_type
-
- # Set header fields and a body from HTML form data.
- # +params+ should be an Array of Arrays or
- # a Hash containing HTML form data.
- # Optional argument +sep+ means data record separator.
- #
- # Values are URL encoded as necessary and the content-type is set to
- # application/x-www-form-urlencoded
- #
- # Example:
- # http.form_data = {"q" => "ruby", "lang" => "en"}
- # http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"}
- # http.set_form_data({"q" => "ruby", "lang" => "en"}, ';')
- #
- def set_form_data(params, sep = '&')
- query = URI.encode_www_form(params)
- query.gsub!(/&/, sep) if sep != '&'
- self.body = query
- self.content_type = 'application/x-www-form-urlencoded'
- end
-
- alias form_data= set_form_data
-
- # Set a HTML form data set.
- # +params+ is the form data set; it is an Array of Arrays or a Hash
- # +enctype is the type to encode the form data set.
- # It is application/x-www-form-urlencoded or multipart/form-data.
- # +formpot+ is an optional hash to specify the detail.
- #
- # boundary:: the boundary of the multipart message
- # charset:: the charset of the message. All names and the values of
- # non-file fields are encoded as the charset.
- #
- # Each item of params is an array and contains following items:
- # +name+:: the name of the field
- # +value+:: the value of the field, it should be a String or a File
- # +opt+:: an optional hash to specify additional information
- #
- # Each item is a file field or a normal field.
- # If +value+ is a File object or the +opt+ have a filename key,
- # the item is treated as a file field.
- #
- # If Transfer-Encoding is set as chunked, this send the request in
- # chunked encoding. Because chunked encoding is HTTP/1.1 feature,
- # you must confirm the server to support HTTP/1.1 before sending it.
- #
- # Example:
- # http.set_form([["q", "ruby"], ["lang", "en"]])
- #
- # See also RFC 2388, RFC 2616, HTML 4.01, and HTML5
- #
- def set_form(params, enctype='application/x-www-form-urlencoded', formopt={})
- @body_data = params
- @body = nil
- @body_stream = nil
- @form_option = formopt
- case enctype
- when /\Aapplication\/x-www-form-urlencoded\z/i,
- /\Amultipart\/form-data\z/i
- self.content_type = enctype
- else
- raise ArgumentError, "invalid enctype: #{enctype}"
- end
- end
-
- # Set the Authorization: header for "Basic" authorization.
- def basic_auth(account, password)
- @header['authorization'] = [basic_encode(account, password)]
- end
-
- # Set Proxy-Authorization: header for "Basic" authorization.
- def proxy_basic_auth(account, password)
- @header['proxy-authorization'] = [basic_encode(account, password)]
- end
-
- def basic_encode(account, password)
- 'Basic ' + ["#{account}:#{password}"].pack('m').delete("\r\n")
- end
- private :basic_encode
-
- def connection_close?
- tokens(@header['connection']).include?('close') or
- tokens(@header['proxy-connection']).include?('close')
- end
-
- def connection_keep_alive?
- tokens(@header['connection']).include?('keep-alive') or
- tokens(@header['proxy-connection']).include?('keep-alive')
- end
-
- def tokens(vals)
- return [] unless vals
- vals.map {|v| v.split(',') }.flatten\
- .reject {|str| str.strip.empty? }\
- .map {|tok| tok.strip.downcase }
- end
- private :tokens
-
- end
-
-
- # HTTPGenericRequest is the parent of the HTTPRequest class.
- # Do not use this directly; use a subclass of HTTPRequest.
- #
- # Mixes in the HTTPHeader module to provide easier access to HTTP headers.
- #
- class HTTPGenericRequest
-
- include HTTPHeader
-
- def initialize(m, reqbody, resbody, path, initheader = nil)
- @method = m
- @request_has_body = reqbody
- @response_has_body = resbody
- raise ArgumentError, "no HTTP request path given" unless path
- raise ArgumentError, "HTTP request path is empty" if path.empty?
- @path = path
- initialize_http_header initheader
- self['Accept'] ||= '*/*'
- self['User-Agent'] ||= 'Ruby'
- @body = nil
- @body_stream = nil
- @body_data = nil
- end
-
- attr_reader :method
- attr_reader :path
-
- def inspect
- "\#<#{self.class} #{@method}>"
- end
-
- def request_body_permitted?
- @request_has_body
- end
-
- def response_body_permitted?
- @response_has_body
- end
-
- def body_exist?
- warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?" if $VERBOSE
- response_body_permitted?
- end
-
- attr_reader :body
-
- def body=(str)
- @body = str
- @body_stream = nil
- @body_data = nil
- str
- end
-
- attr_reader :body_stream
-
- def body_stream=(input)
- @body = nil
- @body_stream = input
- @body_data = nil
- input
- end
-
- def set_body_internal(str) #:nodoc: internal use only
- raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream)
- self.body = str if str
- end
-
- #
- # write
- #
-
- def exec(sock, ver, path) #:nodoc: internal use only
- if @body
- send_request_with_body sock, ver, path, @body
- elsif @body_stream
- send_request_with_body_stream sock, ver, path, @body_stream
- elsif @body_data
- send_request_with_body_data sock, ver, path, @body_data
- else
- write_header sock, ver, path
- end
- end
-
- private
-
- def send_request_with_body(sock, ver, path, body)
- self.content_length = body.bytesize
- delete 'Transfer-Encoding'
- supply_default_content_type
- write_header sock, ver, path
- wait_for_continue sock, ver if sock.continue_timeout
- sock.write body
- end
-
- def send_request_with_body_stream(sock, ver, path, f)
- unless content_length() or chunked?
- raise ArgumentError,
- "Content-Length not given and Transfer-Encoding is not `chunked'"
- end
- supply_default_content_type
- write_header sock, ver, path
- wait_for_continue sock, ver if sock.continue_timeout
- if chunked?
- while s = f.read(1024)
- sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
- end
- sock.write "0\r\n\r\n"
- else
- while s = f.read(1024)
- sock.write s
- end
- end
- end
-
- def send_request_with_body_data(sock, ver, path, params)
- if /\Amultipart\/form-data\z/i !~ self.content_type
- self.content_type = 'application/x-www-form-urlencoded'
- return send_request_with_body(sock, ver, path, URI.encode_www_form(params))
- end
-
- opt = @form_option.dup
- require 'securerandom' unless defined?(SecureRandom)
- opt[:boundary] ||= SecureRandom.urlsafe_base64(40)
- self.set_content_type(self.content_type, boundary: opt[:boundary])
- if chunked?
- write_header sock, ver, path
- encode_multipart_form_data(sock, params, opt)
- else
- require 'tempfile'
- file = Tempfile.new('multipart')
- file.binmode
- encode_multipart_form_data(file, params, opt)
- file.rewind
- self.content_length = file.size
- write_header sock, ver, path
- IO.copy_stream(file, sock)
- end
- end
-
- def encode_multipart_form_data(out, params, opt)
- charset = opt[:charset]
- boundary = opt[:boundary]
- require 'securerandom' unless defined?(SecureRandom)
- boundary ||= SecureRandom.urlsafe_base64(40)
- chunked_p = chunked?
-
- buf = ''
- params.each do |key, value, h={}|
- key = quote_string(key, charset)
- filename =
- h.key?(:filename) ? h[:filename] :
- value.respond_to?(:to_path) ? File.basename(value.to_path) :
- nil
-
- buf << "--#{boundary}\r\n"
- if filename
- filename = quote_string(filename, charset)
- type = h[:content_type] || 'application/octet-stream'
- buf << "Content-Disposition: form-data; " \
- "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \
- "Content-Type: #{type}\r\n\r\n"
- if !out.respond_to?(:write) || !value.respond_to?(:read)
- # if +out+ is not an IO or +value+ is not an IO
- buf << (value.respond_to?(:read) ? value.read : value)
- elsif value.respond_to?(:size) && chunked_p
- # if +out+ is an IO and +value+ is a File, use IO.copy_stream
- flush_buffer(out, buf, chunked_p)
- out << "%x\r\n" % value.size if chunked_p
- IO.copy_stream(value, out)
- out << "\r\n" if chunked_p
- else
- # +out+ is an IO, and +value+ is not a File but an IO
- flush_buffer(out, buf, chunked_p)
- 1 while flush_buffer(out, value.read(4096), chunked_p)
- end
- else
- # non-file field:
- # HTML5 says, "The parts of the generated multipart/form-data
- # resource that correspond to non-file fields must not have a
- # Content-Type header specified."
- buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
- buf << (value.respond_to?(:read) ? value.read : value)
- end
- buf << "\r\n"
- end
- buf << "--#{boundary}--\r\n"
- flush_buffer(out, buf, chunked_p)
- out << "0\r\n\r\n" if chunked_p
- end
-
- def quote_string(str, charset)
- str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset
- str = str.gsub(/[\\"]/, '\\\\\&')
- end
-
- def flush_buffer(out, buf, chunked_p)
- return unless buf
- out << "%x\r\n"%buf.bytesize if chunked_p
- out << buf
- out << "\r\n" if chunked_p
- buf.clear
- end
-
- def supply_default_content_type
- return if content_type()
- warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
- set_content_type 'application/x-www-form-urlencoded'
- end
-
- ##
- # Waits up to the continue timeout for a response from the server provided
- # we're speaking HTTP 1.1 and are expecting a 100-continue response.
-
- def wait_for_continue(sock, ver)
- if ver >= '1.1' and @header['expect'] and
- @header['expect'].include?('100-continue')
- if IO.select([sock.io], nil, nil, sock.continue_timeout)
- res = HTTPResponse.read_new(sock)
- unless res.kind_of?(Net::HTTPContinue)
- throw :response, res
- end
- end
- end
- end
-
- def write_header(sock, ver, path)
- buf = "#{@method} #{path} HTTP/#{ver}\r\n"
- each_capitalized do |k,v|
- buf << "#{k}: #{v}\r\n"
- end
- buf << "\r\n"
- sock.write buf
- end
-
- end
-
-
- #
- # HTTP request class.
- # This class wraps together the request header and the request path.
- # You cannot use this class directly. Instead, you should use one of its
- # subclasses: Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Head.
- #
- class HTTPRequest < HTTPGenericRequest
-
- # Creates HTTP request object.
- def initialize(path, initheader = nil)
- super self.class::METHOD,
- self.class::REQUEST_HAS_BODY,
- self.class::RESPONSE_HAS_BODY,
- path, initheader
- end
- end
-
-
- class HTTP # reopen
- #
- # HTTP/1.1 methods --- RFC2616
- #
-
- # See Net::HTTPGenericRequest for attributes and methods.
- # See Net::HTTP for usage examples.
- class Get < HTTPRequest
- METHOD = 'GET'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- # See Net::HTTP for usage examples.
- class Head < HTTPRequest
- METHOD = 'HEAD'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = false
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- # See Net::HTTP for usage examples.
- class Post < HTTPRequest
- METHOD = 'POST'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- # See Net::HTTP for usage examples.
- class Put < HTTPRequest
- METHOD = 'PUT'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- # See Net::HTTP for usage examples.
- class Delete < HTTPRequest
- METHOD = 'DELETE'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Options < HTTPRequest
- METHOD = 'OPTIONS'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = false
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Trace < HTTPRequest
- METHOD = 'TRACE'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end
-
- #
- # PATCH method --- RFC5789
- #
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Patch < HTTPRequest
- METHOD = 'PATCH'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- #
- # WebDAV methods --- RFC2518
- #
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Propfind < HTTPRequest
- METHOD = 'PROPFIND'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Proppatch < HTTPRequest
- METHOD = 'PROPPATCH'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Mkcol < HTTPRequest
- METHOD = 'MKCOL'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Copy < HTTPRequest
- METHOD = 'COPY'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Move < HTTPRequest
- METHOD = 'MOVE'
- REQUEST_HAS_BODY = false
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Lock < HTTPRequest
- METHOD = 'LOCK'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
-
- # See Net::HTTPGenericRequest for attributes and methods.
- class Unlock < HTTPRequest
- METHOD = 'UNLOCK'
- REQUEST_HAS_BODY = true
- RESPONSE_HAS_BODY = true
- end
- end
-
-
- ###
- ### Response
- ###
-
- # HTTP exception class.
- # You cannot use HTTPExceptions directly; instead, you must use
- # its subclasses.
- module HTTPExceptions
- def initialize(msg, res) #:nodoc:
- super msg
- @response = res
- end
- attr_reader :response
- alias data response #:nodoc: obsolete
- end
- class HTTPError < ProtocolError
- include HTTPExceptions
- end
- class HTTPRetriableError < ProtoRetriableError
- include HTTPExceptions
- end
- class HTTPServerException < ProtoServerError
- # We cannot use the name "HTTPServerError", it is the name of the response.
- include HTTPExceptions
- end
- class HTTPFatalError < ProtoFatalError
- include HTTPExceptions
- end
-
-
- # HTTP response class.
- #
- # This class wraps together the response header and the response body (the
- # entity requested).
- #
- # It mixes in the HTTPHeader module, which provides access to response
- # header values both via hash-like methods and via individual readers.
- #
- # Note that each possible HTTP response code defines its own
- # HTTPResponse subclass. These are listed below.
- #
- # All classes are
- # defined under the Net module. Indentation indicates inheritance.
- #
- # xxx HTTPResponse
- #
- # 1xx HTTPInformation
- # 100 HTTPContinue
- # 101 HTTPSwitchProtocol
- #
- # 2xx HTTPSuccess
- # 200 HTTPOK
- # 201 HTTPCreated
- # 202 HTTPAccepted
- # 203 HTTPNonAuthoritativeInformation
- # 204 HTTPNoContent
- # 205 HTTPResetContent
- # 206 HTTPPartialContent
- #
- # 3xx HTTPRedirection
- # 300 HTTPMultipleChoice
- # 301 HTTPMovedPermanently
- # 302 HTTPFound
- # 303 HTTPSeeOther
- # 304 HTTPNotModified
- # 305 HTTPUseProxy
- # 307 HTTPTemporaryRedirect
- #
- # 4xx HTTPClientError
- # 400 HTTPBadRequest
- # 401 HTTPUnauthorized
- # 402 HTTPPaymentRequired
- # 403 HTTPForbidden
- # 404 HTTPNotFound
- # 405 HTTPMethodNotAllowed
- # 406 HTTPNotAcceptable
- # 407 HTTPProxyAuthenticationRequired
- # 408 HTTPRequestTimeOut
- # 409 HTTPConflict
- # 410 HTTPGone
- # 411 HTTPLengthRequired
- # 412 HTTPPreconditionFailed
- # 413 HTTPRequestEntityTooLarge
- # 414 HTTPRequestURITooLong
- # 415 HTTPUnsupportedMediaType
- # 416 HTTPRequestedRangeNotSatisfiable
- # 417 HTTPExpectationFailed
- #
- # 5xx HTTPServerError
- # 500 HTTPInternalServerError
- # 501 HTTPNotImplemented
- # 502 HTTPBadGateway
- # 503 HTTPServiceUnavailable
- # 504 HTTPGatewayTimeOut
- # 505 HTTPVersionNotSupported
- #
- # xxx HTTPUnknownResponse
- #
- class HTTPResponse
- # true if the response has a body.
- def HTTPResponse.body_permitted?
- self::HAS_BODY
- end
-
- def HTTPResponse.exception_type # :nodoc: internal use only
- self::EXCEPTION_TYPE
- end
- end # reopened after
-
- # :stopdoc:
-
- class HTTPUnknownResponse < HTTPResponse
- HAS_BODY = true
- EXCEPTION_TYPE = HTTPError
- end
- class HTTPInformation < HTTPResponse # 1xx
- HAS_BODY = false
- EXCEPTION_TYPE = HTTPError
- end
- class HTTPSuccess < HTTPResponse # 2xx
- HAS_BODY = true
- EXCEPTION_TYPE = HTTPError
- end
- class HTTPRedirection < HTTPResponse # 3xx
- HAS_BODY = true
- EXCEPTION_TYPE = HTTPRetriableError
- end
- class HTTPClientError < HTTPResponse # 4xx
- HAS_BODY = true
- EXCEPTION_TYPE = HTTPServerException # for backward compatibility
- end
- class HTTPServerError < HTTPResponse # 5xx
- HAS_BODY = true
- EXCEPTION_TYPE = HTTPFatalError # for backward compatibility
- end
-
- class HTTPContinue < HTTPInformation # 100
- HAS_BODY = false
- end
- class HTTPSwitchProtocol < HTTPInformation # 101
- HAS_BODY = false
- end
-
- class HTTPOK < HTTPSuccess # 200
- HAS_BODY = true
- end
- class HTTPCreated < HTTPSuccess # 201
- HAS_BODY = true
- end
- class HTTPAccepted < HTTPSuccess # 202
- HAS_BODY = true
- end
- class HTTPNonAuthoritativeInformation < HTTPSuccess # 203
- HAS_BODY = true
- end
- class HTTPNoContent < HTTPSuccess # 204
- HAS_BODY = false
- end
- class HTTPResetContent < HTTPSuccess # 205
- HAS_BODY = false
- end
- class HTTPPartialContent < HTTPSuccess # 206
- HAS_BODY = true
- end
-
- class HTTPMultipleChoice < HTTPRedirection # 300
- HAS_BODY = true
- end
- class HTTPMovedPermanently < HTTPRedirection # 301
- HAS_BODY = true
- end
- class HTTPFound < HTTPRedirection # 302
- HAS_BODY = true
- end
- HTTPMovedTemporarily = HTTPFound
- class HTTPSeeOther < HTTPRedirection # 303
- HAS_BODY = true
- end
- class HTTPNotModified < HTTPRedirection # 304
- HAS_BODY = false
- end
- class HTTPUseProxy < HTTPRedirection # 305
- HAS_BODY = false
- end
- # 306 unused
- class HTTPTemporaryRedirect < HTTPRedirection # 307
- HAS_BODY = true
- end
-
- class HTTPBadRequest < HTTPClientError # 400
- HAS_BODY = true
- end
- class HTTPUnauthorized < HTTPClientError # 401
- HAS_BODY = true
- end
- class HTTPPaymentRequired < HTTPClientError # 402
- HAS_BODY = true
- end
- class HTTPForbidden < HTTPClientError # 403
- HAS_BODY = true
- end
- class HTTPNotFound < HTTPClientError # 404
- HAS_BODY = true
- end
- class HTTPMethodNotAllowed < HTTPClientError # 405
- HAS_BODY = true
- end
- class HTTPNotAcceptable < HTTPClientError # 406
- HAS_BODY = true
- end
- class HTTPProxyAuthenticationRequired < HTTPClientError # 407
- HAS_BODY = true
- end
- class HTTPRequestTimeOut < HTTPClientError # 408
- HAS_BODY = true
- end
- class HTTPConflict < HTTPClientError # 409
- HAS_BODY = true
- end
- class HTTPGone < HTTPClientError # 410
- HAS_BODY = true
- end
- class HTTPLengthRequired < HTTPClientError # 411
- HAS_BODY = true
- end
- class HTTPPreconditionFailed < HTTPClientError # 412
- HAS_BODY = true
- end
- class HTTPRequestEntityTooLarge < HTTPClientError # 413
- HAS_BODY = true
- end
- class HTTPRequestURITooLong < HTTPClientError # 414
- HAS_BODY = true
- end
- HTTPRequestURITooLarge = HTTPRequestURITooLong
- class HTTPUnsupportedMediaType < HTTPClientError # 415
- HAS_BODY = true
- end
- class HTTPRequestedRangeNotSatisfiable < HTTPClientError # 416
- HAS_BODY = true
- end
- class HTTPExpectationFailed < HTTPClientError # 417
- HAS_BODY = true
- end
-
- class HTTPInternalServerError < HTTPServerError # 500
- HAS_BODY = true
- end
- class HTTPNotImplemented < HTTPServerError # 501
- HAS_BODY = true
- end
- class HTTPBadGateway < HTTPServerError # 502
- HAS_BODY = true
- end
- class HTTPServiceUnavailable < HTTPServerError # 503
- HAS_BODY = true
- end
- class HTTPGatewayTimeOut < HTTPServerError # 504
- HAS_BODY = true
- end
- class HTTPVersionNotSupported < HTTPServerError # 505
- HAS_BODY = true
- end
-
- # :startdoc:
-
-
- class HTTPResponse # reopen
-
- CODE_CLASS_TO_OBJ = {
- '1' => HTTPInformation,
- '2' => HTTPSuccess,
- '3' => HTTPRedirection,
- '4' => HTTPClientError,
- '5' => HTTPServerError
- }
- CODE_TO_OBJ = {
- '100' => HTTPContinue,
- '101' => HTTPSwitchProtocol,
-
- '200' => HTTPOK,
- '201' => HTTPCreated,
- '202' => HTTPAccepted,
- '203' => HTTPNonAuthoritativeInformation,
- '204' => HTTPNoContent,
- '205' => HTTPResetContent,
- '206' => HTTPPartialContent,
-
- '300' => HTTPMultipleChoice,
- '301' => HTTPMovedPermanently,
- '302' => HTTPFound,
- '303' => HTTPSeeOther,
- '304' => HTTPNotModified,
- '305' => HTTPUseProxy,
- '307' => HTTPTemporaryRedirect,
-
- '400' => HTTPBadRequest,
- '401' => HTTPUnauthorized,
- '402' => HTTPPaymentRequired,
- '403' => HTTPForbidden,
- '404' => HTTPNotFound,
- '405' => HTTPMethodNotAllowed,
- '406' => HTTPNotAcceptable,
- '407' => HTTPProxyAuthenticationRequired,
- '408' => HTTPRequestTimeOut,
- '409' => HTTPConflict,
- '410' => HTTPGone,
- '411' => HTTPLengthRequired,
- '412' => HTTPPreconditionFailed,
- '413' => HTTPRequestEntityTooLarge,
- '414' => HTTPRequestURITooLong,
- '415' => HTTPUnsupportedMediaType,
- '416' => HTTPRequestedRangeNotSatisfiable,
- '417' => HTTPExpectationFailed,
-
- '500' => HTTPInternalServerError,
- '501' => HTTPNotImplemented,
- '502' => HTTPBadGateway,
- '503' => HTTPServiceUnavailable,
- '504' => HTTPGatewayTimeOut,
- '505' => HTTPVersionNotSupported
- }
-
- class << HTTPResponse
- def read_new(sock) #:nodoc: internal use only
- httpv, code, msg = read_status_line(sock)
- res = response_class(code).new(httpv, code, msg)
- each_response_header(sock) do |k,v|
- res.add_field k, v
- end
- res
- end
-
- private
-
- def read_status_line(sock)
- str = sock.readline
- m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str) or
- raise HTTPBadResponse, "wrong status line: #{str.dump}"
- m.captures
- end
-
- def response_class(code)
- CODE_TO_OBJ[code] or
- CODE_CLASS_TO_OBJ[code[0,1]] or
- HTTPUnknownResponse
- end
-
- def each_response_header(sock)
- key = value = nil
- while true
- line = sock.readuntil("\n", true).sub(/\s+\z/, '')
- break if line.empty?
- if line[0] == ?\s or line[0] == ?\t and value
- value << ' ' unless value.empty?
- value << line.strip
- else
- yield key, value if key
- key, value = line.strip.split(/\s*:\s*/, 2)
- raise HTTPBadResponse, 'wrong header line format' if value.nil?
- end
- end
- yield key, value if key
- end
- end
-
- # next is to fix bug in RDoc, where the private inside class << self
- # spills out.
- public
-
- include HTTPHeader
-
- def initialize(httpv, code, msg) #:nodoc: internal use only
- @http_version = httpv
- @code = code
- @message = msg
- initialize_http_header nil
- @body = nil
- @read = false
- end
-
- # The HTTP version supported by the server.
- attr_reader :http_version
-
- # The HTTP result code string. For example, '302'. You can also
- # determine the response type by examining which response subclass
- # the response object is an instance of.
- attr_reader :code
-
- # The HTTP result message sent by the server. For example, 'Not Found'.
- attr_reader :message
- alias msg message # :nodoc: obsolete
-
- def inspect
- "#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
- end
-
- #
- # response <-> exception relationship
- #
-
- def code_type #:nodoc:
- self.class
- end
-
- def error! #:nodoc:
- raise error_type().new(@code + ' ' + @message.dump, self)
- end
-
- def error_type #:nodoc:
- self.class::EXCEPTION_TYPE
- end
-
- # Raises an HTTP error if the response is not 2xx (success).
- def value
- error! unless self.kind_of?(HTTPSuccess)
- end
-
- #
- # header (for backward compatibility only; DO NOT USE)
- #
-
- def response #:nodoc:
- warn "#{caller(1)[0]}: warning: HTTPResponse#response is obsolete" if $VERBOSE
- self
- end
-
- def header #:nodoc:
- warn "#{caller(1)[0]}: warning: HTTPResponse#header is obsolete" if $VERBOSE
- self
- end
-
- def read_header #:nodoc:
- warn "#{caller(1)[0]}: warning: HTTPResponse#read_header is obsolete" if $VERBOSE
- self
- end
-
- #
- # body
- #
-
- def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
- @socket = sock
- @body_exist = reqmethodallowbody && self.class.body_permitted?
- begin
- yield
- self.body # ensure to read body
- ensure
- @socket = nil
- end
- end
-
- # Gets the entity body returned by the remote HTTP server.
- #
- # If a block is given, the body is passed to the block, and
- # the body is provided in fragments, as it is read in from the socket.
- #
- # Calling this method a second or subsequent time for the same
- # HTTPResponse object will return the value already read.
- #
- # http.request_get('/index.html') {|res|
- # puts res.read_body
- # }
- #
- # http.request_get('/index.html') {|res|
- # p res.read_body.object_id # 538149362
- # p res.read_body.object_id # 538149362
- # }
- #
- # # using iterator
- # http.request_get('/index.html') {|res|
- # res.read_body do |segment|
- # print segment
- # end
- # }
- #
- def read_body(dest = nil, &block)
- if @read
- raise IOError, "#{self.class}\#read_body called twice" if dest or block
- return @body
- end
- to = procdest(dest, block)
- stream_check
- if @body_exist
- read_body_0 to
- @body = to
- else
- @body = nil
- end
- @read = true
-
- @body
- end
-
- # Returns the full entity body.
- #
- # Calling this method a second or subsequent time will return the
- # string already read.
- #
- # http.request_get('/index.html') {|res|
- # puts res.body
- # }
- #
- # http.request_get('/index.html') {|res|
- # p res.body.object_id # 538149362
- # p res.body.object_id # 538149362
- # }
- #
- def body
- read_body()
- end
-
- # Because it may be necessary to modify the body, Eg, decompression
- # this method facilitates that.
- def body=(value)
- @body = value
- end
-
- alias entity body #:nodoc: obsolete
-
- private
-
- def read_body_0(dest)
- if chunked?
- read_chunked dest
- return
- end
- clen = content_length()
- if clen
- @socket.read clen, dest, true # ignore EOF
- return
- end
- clen = range_length()
- if clen
- @socket.read clen, dest
- return
- end
- @socket.read_all dest
- end
-
- def read_chunked(dest)
- len = nil
- total = 0
- while true
- line = @socket.readline
- hexlen = line.slice(/[0-9a-fA-F]+/) or
- raise HTTPBadResponse, "wrong chunk size line: #{line}"
- len = hexlen.hex
- break if len == 0
- begin
- @socket.read len, dest
- ensure
- total += len
- @socket.read 2 # \r\n
- end
- end
- until @socket.readline.empty?
- # none
- end
- end
-
- def stream_check
- raise IOError, 'attempt to read body out of block' if @socket.closed?
- end
-
- def procdest(dest, block)
- raise ArgumentError, 'both arg and block given for HTTP method' \
- if dest and block
- if block
- ReadAdapter.new(block)
- else
- dest || ''
- end
- end
-
- end
-
-
- # :enddoc:
-
- #--
- # for backward compatibility
- class HTTP
- ProxyMod = ProxyDelta
- end
- module NetPrivate
- HTTPRequest = ::Net::HTTPRequest
- end
-
- HTTPInformationCode = HTTPInformation
- HTTPSuccessCode = HTTPSuccess
- HTTPRedirectionCode = HTTPRedirection
- HTTPRetriableCode = HTTPRedirection
- HTTPClientErrorCode = HTTPClientError
- HTTPFatalErrorCode = HTTPClientError
- HTTPServerErrorCode = HTTPServerError
- HTTPResponceReceiver = HTTPResponse
-
-end # module Net