diff --git a/lib/net/http.rb b/lib/net/http.rb index 002e3cf10e..b602d2d0b0 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -22,6 +22,7 @@ require 'net/protocol' require 'uri' +require 'resolv' autoload :OpenSSL, 'openssl' module Net #:nodoc: @@ -132,7 +133,7 @@ module Net #:nodoc: # puts res.class.name # => 'HTTPOK' # # # Body - # puts res.body if res.response_body_permitted? + # puts res.body # # === Following Redirection # @@ -396,7 +397,7 @@ module Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.2.0" + VERSION = "0.3.0" Revision = %q$Revision$.split[1] HTTPVersion = '1.1' begin @@ -697,6 +698,8 @@ module Net #:nodoc: @continue_timeout = nil @max_retries = 1 @debug_output = nil + @response_body_encoding = false + @ignore_eof = true @proxy_from_env = false @proxy_uri = nil @@ -744,6 +747,18 @@ module Net #:nodoc: # The local port used to establish the connection. attr_accessor :local_port + # The encoding to use for the response body. If Encoding, uses the + # specified encoding. If other true value, tries to detect the response + # body encoding. + attr_reader :response_body_encoding + + # Set the encoding to use for the response body. If given a String, find + # the related Encoding. + def response_body_encoding=(value) + value = Encoding.find(value) if value.is_a?(String) + @response_body_encoding = value + end + attr_writer :proxy_from_env attr_writer :proxy_address attr_writer :proxy_port @@ -825,6 +840,10 @@ module Net #:nodoc: # The default value is 2 seconds. attr_accessor :keep_alive_timeout + # Whether to ignore EOF when reading response bodies with defined + # Content-Length headers. For backwards compatibility, the default is true. + attr_accessor :ignore_eof + # Returns true if the HTTP session has been started. def started? @started @@ -1033,21 +1052,42 @@ module Net #:nodoc: end end @ssl_context.set_params(ssl_parameters) - @ssl_context.session_cache_mode = - OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | - OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE - @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } + unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby + @ssl_context.session_cache_mode = + OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | + OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE + end + if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby + @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } + end + + # Still do the post_connection_check below even if connecting + # to IP address + verify_hostname = @ssl_context.verify_hostname + + # Server Name Indication (SNI) RFC 3546/6066 + case @address + when Resolv::IPv4::Regex, Resolv::IPv6::Regex + # don't set SNI, as IP addresses in SNI is not valid + # per RFC 6066, section 3. + + # Avoid openssl warning + @ssl_context.verify_hostname = false + else + ssl_host_address = @address + end + debug "starting SSL for #{conn_addr}:#{conn_port}..." s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s.sync_close = true - # Server Name Indication (SNI) RFC 3546 - s.hostname = @address if s.respond_to? :hostname= + s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address + if @ssl_session and Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout s.session = @ssl_session end ssl_socket_connect(s, @open_timeout) - if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && @ssl_context.verify_hostname + if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname s.post_connection_check(@address) end debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}" @@ -1182,16 +1222,9 @@ module Net #:nodoc: end end - # [Bug #12921] - if /linux|freebsd|darwin/ =~ RUBY_PLATFORM - ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE = true - else - ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE = false - end - # The username of the proxy server, if one is configured. def proxy_user - if ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE && @proxy_from_env + if @proxy_from_env user = proxy_uri&.user unescape(user) if user else @@ -1201,7 +1234,7 @@ module Net #:nodoc: # The password of the proxy server, if one is configured. def proxy_pass - if ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE && @proxy_from_env + if @proxy_from_env pass = proxy_uri&.password unescape(pass) if pass else @@ -1575,6 +1608,8 @@ module Net #:nodoc: begin res = HTTPResponse.read_new(@socket) res.decode_content = req.decode_content + res.body_encoding = @response_body_encoding + res.ignore_eof = @ignore_eof end while res.kind_of?(HTTPInformation) res.uri = req.uri diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index da5f7a70fc..9c425cae16 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -1,33 +1,34 @@ # frozen_string_literal: false -# Net::HTTP exception class. -# You cannot use Net::HTTPExceptions directly; instead, you must use -# its subclasses. -module Net::HTTPExceptions - def initialize(msg, res) #:nodoc: - super msg - @response = res - end - attr_reader :response - alias data response #:nodoc: obsolete -end -class Net::HTTPError < Net::ProtocolError - include Net::HTTPExceptions -end -class Net::HTTPRetriableError < Net::ProtoRetriableError - include Net::HTTPExceptions -end -class Net::HTTPServerException < Net::ProtoServerError - # We cannot use the name "HTTPServerError", it is the name of the response. - include Net::HTTPExceptions -end - -# for compatibility -Net::HTTPClientException = Net::HTTPServerException - -class Net::HTTPFatalError < Net::ProtoFatalError - include Net::HTTPExceptions -end - module Net + # Net::HTTP exception class. + # You cannot use Net::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 HTTPClientException < ProtoServerError + include HTTPExceptions + end + + class HTTPFatalError < ProtoFatalError + include HTTPExceptions + end + + # We cannot use the name "HTTPServerError", it is the name of the response. + HTTPServerException = HTTPClientException # :nodoc: deprecate_constant(:HTTPServerException) end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index 313de6ac92..d56835c76f 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -15,7 +15,8 @@ class Net::HTTPGenericRequest if URI === uri_or_path then raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path - raise ArgumentError, "no host component for URI" unless uri_or_path.hostname + hostname = uri_or_path.hostname + raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup host = @uri.hostname.dup host << ":".freeze << @uri.port.to_s if @uri.port != @uri.default_port diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index a8901e79cb..b0ec4b0625 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -338,9 +338,10 @@ module Net::HTTPHeader # fits inside the full entity body, as range of byte offsets. def content_range return nil unless @header['content-range'] - m = %ri.match(self['Content-Range']) or + m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format' - m[1].to_i .. m[2].to_i + return unless m[1] == 'bytes' + m[2].to_i .. m[3].to_i end # The length of the range represented in Content-Range: header. diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec index dd4fc2b17e..a7f122fc0e 100644 --- a/lib/net/http/net-http.gemspec +++ b/lib/net/http/net-http.gemspec @@ -30,6 +30,5 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.require_paths = ["lib"] - spec.add_dependency "net-protocol" spec.add_dependency "uri" end diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 08eaeb2cac..f8b522f1ff 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -84,6 +84,8 @@ class Net::HTTPResponse @read = false @uri = nil @decode_content = false + @body_encoding = false + @ignore_eof = true end # The HTTP version supported by the server. @@ -106,6 +108,22 @@ class Net::HTTPResponse # Accept-Encoding header from the user. attr_accessor :decode_content + # The encoding to use for the response body. If Encoding, use that encoding. + # If other true value, attempt to detect the appropriate encoding, and use + # that. + attr_reader :body_encoding + + # Set the encoding to use for the response body. If given a String, find + # the related Encoding. + def body_encoding=(value) + value = Encoding.find(value) if value.is_a?(String) + @body_encoding = value + end + + # Whether to ignore EOF when reading bodies with a specified Content-Length + # header. + attr_accessor :ignore_eof + def inspect "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end @@ -214,6 +232,17 @@ class Net::HTTPResponse end @read = true + case enc = @body_encoding + when Encoding, false, nil + # Encoding: force given encoding + # false/nil: do not force encoding + else + # other value: detect encoding from body + enc = detect_encoding(@body) + end + + @body.force_encoding(enc) if enc + @body end @@ -245,6 +274,141 @@ class Net::HTTPResponse private + # :nodoc: + def detect_encoding(str, encoding=nil) + if encoding + elsif encoding = type_params['charset'] + elsif encoding = check_bom(str) + else + encoding = case content_type&.downcase + when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml} + /\A' + ss.getch + return nil + end + name = ss.scan(/[^=\t\n\f\r \/>]*/) + name.downcase! + raise if name.empty? + ss.skip(/[\t\n\f\r ]*/) + if ss.getch != '=' + value = '' + return [name, value] + end + ss.skip(/[\t\n\f\r ]*/) + case ss.peek(1) + when '"' + ss.getch + value = ss.scan(/[^"]+/) + value.downcase! + ss.getch + when "'" + ss.getch + value = ss.scan(/[^']+/) + value.downcase! + ss.getch + when '>' + value = '' + else + value = ss.scan(/[^\t\n\f\r >]+/) + value.downcase! + end + [name, value] + end + + def extracting_encodings_from_meta_elements(value) + # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element + if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value + return $1 || $2 || $3 + end + return nil + end + ## # Checks for a supported Content-Encoding header and yields an Inflate # wrapper for this response's socket when zlib is present. If the @@ -272,6 +436,9 @@ class Net::HTTPResponse ensure begin inflate_body_io.finish + if self['content-length'] + self['content-length'] = inflate_body_io.bytes_inflated.to_s + end rescue => err # Ignore #finish's error if there is an exception from yield raise err if success @@ -297,7 +464,7 @@ class Net::HTTPResponse clen = content_length() if clen - @socket.read clen, dest, true # ignore EOF + @socket.read clen, dest, @ignore_eof return end clen = range_length() @@ -373,6 +540,14 @@ class Net::HTTPResponse @inflate.finish end + ## + # The number of bytes inflated, used to update the Content-Length of + # the response. + + def bytes_inflated + @inflate.total_out + end + ## # Returns a Net::ReadAdapter that inflates each read chunk into +dest+. # diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 50352032df..02a2fdaa4c 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -1,231 +1,238 @@ # frozen_string_literal: true -# :stopdoc: +#-- # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml -class Net::HTTPUnknownResponse < Net::HTTPResponse - HAS_BODY = true - EXCEPTION_TYPE = Net::HTTPError -end -class Net::HTTPInformation < Net::HTTPResponse # 1xx - HAS_BODY = false - EXCEPTION_TYPE = Net::HTTPError -end -class Net::HTTPSuccess < Net::HTTPResponse # 2xx - HAS_BODY = true - EXCEPTION_TYPE = Net::HTTPError -end -class Net::HTTPRedirection < Net::HTTPResponse # 3xx - HAS_BODY = true - EXCEPTION_TYPE = Net::HTTPRetriableError -end -class Net::HTTPClientError < Net::HTTPResponse # 4xx - HAS_BODY = true - EXCEPTION_TYPE = Net::HTTPClientException # for backward compatibility -end -class Net::HTTPServerError < Net::HTTPResponse # 5xx - HAS_BODY = true - EXCEPTION_TYPE = Net::HTTPFatalError # for backward compatibility -end -class Net::HTTPContinue < Net::HTTPInformation # 100 - HAS_BODY = false -end -class Net::HTTPSwitchProtocol < Net::HTTPInformation # 101 - HAS_BODY = false -end -class Net::HTTPProcessing < Net::HTTPInformation # 102 - HAS_BODY = false -end -class Net::HTTPEarlyHints < Net::HTTPInformation # 103 - RFC 8297 - HAS_BODY = false -end +module Net + # :stopdoc: -class Net::HTTPOK < Net::HTTPSuccess # 200 - HAS_BODY = true -end -class Net::HTTPCreated < Net::HTTPSuccess # 201 - HAS_BODY = true -end -class Net::HTTPAccepted < Net::HTTPSuccess # 202 - HAS_BODY = true -end -class Net::HTTPNonAuthoritativeInformation < Net::HTTPSuccess # 203 - HAS_BODY = true -end -class Net::HTTPNoContent < Net::HTTPSuccess # 204 - HAS_BODY = false -end -class Net::HTTPResetContent < Net::HTTPSuccess # 205 - HAS_BODY = false -end -class Net::HTTPPartialContent < Net::HTTPSuccess # 206 - HAS_BODY = true -end -class Net::HTTPMultiStatus < Net::HTTPSuccess # 207 - RFC 4918 - HAS_BODY = true -end -class Net::HTTPAlreadyReported < Net::HTTPSuccess # 208 - RFC 5842 - HAS_BODY = true -end -class Net::HTTPIMUsed < Net::HTTPSuccess # 226 - RFC 3229 - HAS_BODY = true -end + 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 = HTTPClientException # + end + class HTTPServerError < HTTPResponse # 5xx + HAS_BODY = true + EXCEPTION_TYPE = HTTPFatalError # + end -class Net::HTTPMultipleChoices < Net::HTTPRedirection # 300 - HAS_BODY = true -end -Net::HTTPMultipleChoice = Net::HTTPMultipleChoices -class Net::HTTPMovedPermanently < Net::HTTPRedirection # 301 - HAS_BODY = true -end -class Net::HTTPFound < Net::HTTPRedirection # 302 - HAS_BODY = true -end -Net::HTTPMovedTemporarily = Net::HTTPFound -class Net::HTTPSeeOther < Net::HTTPRedirection # 303 - HAS_BODY = true -end -class Net::HTTPNotModified < Net::HTTPRedirection # 304 - HAS_BODY = false -end -class Net::HTTPUseProxy < Net::HTTPRedirection # 305 - HAS_BODY = false -end -# 306 Switch Proxy - no longer unused -class Net::HTTPTemporaryRedirect < Net::HTTPRedirection # 307 - HAS_BODY = true -end -class Net::HTTPPermanentRedirect < Net::HTTPRedirection # 308 - HAS_BODY = true -end + class HTTPContinue < HTTPInformation # 100 + HAS_BODY = false + end + class HTTPSwitchProtocol < HTTPInformation # 101 + HAS_BODY = false + end + class HTTPProcessing < HTTPInformation # 102 + HAS_BODY = false + end + class HTTPEarlyHints < HTTPInformation # 103 - RFC 8297 + HAS_BODY = false + end -class Net::HTTPBadRequest < Net::HTTPClientError # 400 - HAS_BODY = true -end -class Net::HTTPUnauthorized < Net::HTTPClientError # 401 - HAS_BODY = true -end -class Net::HTTPPaymentRequired < Net::HTTPClientError # 402 - HAS_BODY = true -end -class Net::HTTPForbidden < Net::HTTPClientError # 403 - HAS_BODY = true -end -class Net::HTTPNotFound < Net::HTTPClientError # 404 - HAS_BODY = true -end -class Net::HTTPMethodNotAllowed < Net::HTTPClientError # 405 - HAS_BODY = true -end -class Net::HTTPNotAcceptable < Net::HTTPClientError # 406 - HAS_BODY = true -end -class Net::HTTPProxyAuthenticationRequired < Net::HTTPClientError # 407 - HAS_BODY = true -end -class Net::HTTPRequestTimeout < Net::HTTPClientError # 408 - HAS_BODY = true -end -Net::HTTPRequestTimeOut = Net::HTTPRequestTimeout -class Net::HTTPConflict < Net::HTTPClientError # 409 - HAS_BODY = true -end -class Net::HTTPGone < Net::HTTPClientError # 410 - HAS_BODY = true -end -class Net::HTTPLengthRequired < Net::HTTPClientError # 411 - HAS_BODY = true -end -class Net::HTTPPreconditionFailed < Net::HTTPClientError # 412 - HAS_BODY = true -end -class Net::HTTPPayloadTooLarge < Net::HTTPClientError # 413 - HAS_BODY = true -end -Net::HTTPRequestEntityTooLarge = Net::HTTPPayloadTooLarge -class Net::HTTPURITooLong < Net::HTTPClientError # 414 - HAS_BODY = true -end -Net::HTTPRequestURITooLong = Net::HTTPURITooLong -Net::HTTPRequestURITooLarge = Net::HTTPRequestURITooLong -class Net::HTTPUnsupportedMediaType < Net::HTTPClientError # 415 - HAS_BODY = true -end -class Net::HTTPRangeNotSatisfiable < Net::HTTPClientError # 416 - HAS_BODY = true -end -Net::HTTPRequestedRangeNotSatisfiable = Net::HTTPRangeNotSatisfiable -class Net::HTTPExpectationFailed < Net::HTTPClientError # 417 - HAS_BODY = true -end -# 418 I'm a teapot - RFC 2324; a joke RFC -# 420 Enhance Your Calm - Twitter -class Net::HTTPMisdirectedRequest < Net::HTTPClientError # 421 - RFC 7540 - HAS_BODY = true -end -class Net::HTTPUnprocessableEntity < Net::HTTPClientError # 422 - RFC 4918 - HAS_BODY = true -end -class Net::HTTPLocked < Net::HTTPClientError # 423 - RFC 4918 - HAS_BODY = true -end -class Net::HTTPFailedDependency < Net::HTTPClientError # 424 - RFC 4918 - HAS_BODY = true -end -# 425 Unordered Collection - existed only in draft -class Net::HTTPUpgradeRequired < Net::HTTPClientError # 426 - RFC 2817 - HAS_BODY = true -end -class Net::HTTPPreconditionRequired < Net::HTTPClientError # 428 - RFC 6585 - HAS_BODY = true -end -class Net::HTTPTooManyRequests < Net::HTTPClientError # 429 - RFC 6585 - HAS_BODY = true -end -class Net::HTTPRequestHeaderFieldsTooLarge < Net::HTTPClientError # 431 - RFC 6585 - HAS_BODY = true -end -class Net::HTTPUnavailableForLegalReasons < Net::HTTPClientError # 451 - RFC 7725 - HAS_BODY = true -end -# 444 No Response - Nginx -# 449 Retry With - Microsoft -# 450 Blocked by Windows Parental Controls - Microsoft -# 499 Client Closed Request - Nginx + 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 HTTPMultiStatus < HTTPSuccess # 207 - RFC 4918 + HAS_BODY = true + end + class HTTPAlreadyReported < HTTPSuccess # 208 - RFC 5842 + HAS_BODY = true + end + class HTTPIMUsed < HTTPSuccess # 226 - RFC 3229 + HAS_BODY = true + end -class Net::HTTPInternalServerError < Net::HTTPServerError # 500 - HAS_BODY = true -end -class Net::HTTPNotImplemented < Net::HTTPServerError # 501 - HAS_BODY = true -end -class Net::HTTPBadGateway < Net::HTTPServerError # 502 - HAS_BODY = true -end -class Net::HTTPServiceUnavailable < Net::HTTPServerError # 503 - HAS_BODY = true -end -class Net::HTTPGatewayTimeout < Net::HTTPServerError # 504 - HAS_BODY = true -end -Net::HTTPGatewayTimeOut = Net::HTTPGatewayTimeout -class Net::HTTPVersionNotSupported < Net::HTTPServerError # 505 - HAS_BODY = true -end -class Net::HTTPVariantAlsoNegotiates < Net::HTTPServerError # 506 - HAS_BODY = true -end -class Net::HTTPInsufficientStorage < Net::HTTPServerError # 507 - RFC 4918 - HAS_BODY = true -end -class Net::HTTPLoopDetected < Net::HTTPServerError # 508 - RFC 5842 - HAS_BODY = true -end -# 509 Bandwidth Limit Exceeded - Apache bw/limited extension -class Net::HTTPNotExtended < Net::HTTPServerError # 510 - RFC 2774 - HAS_BODY = true -end -class Net::HTTPNetworkAuthenticationRequired < Net::HTTPServerError # 511 - RFC 6585 - HAS_BODY = true + class HTTPMultipleChoices < HTTPRedirection # 300 + HAS_BODY = true + end + HTTPMultipleChoice = HTTPMultipleChoices + 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 Switch Proxy - no longer unused + class HTTPTemporaryRedirect < HTTPRedirection # 307 + HAS_BODY = true + end + class HTTPPermanentRedirect < HTTPRedirection # 308 + 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 + HTTPRequestTimeOut = HTTPRequestTimeout + 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 HTTPPayloadTooLarge < HTTPClientError # 413 + HAS_BODY = true + end + HTTPRequestEntityTooLarge = HTTPPayloadTooLarge + class HTTPURITooLong < HTTPClientError # 414 + HAS_BODY = true + end + HTTPRequestURITooLong = HTTPURITooLong + HTTPRequestURITooLarge = HTTPRequestURITooLong + class HTTPUnsupportedMediaType < HTTPClientError # 415 + HAS_BODY = true + end + class HTTPRangeNotSatisfiable < HTTPClientError # 416 + HAS_BODY = true + end + HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable + class HTTPExpectationFailed < HTTPClientError # 417 + HAS_BODY = true + end + # 418 I'm a teapot - RFC 2324; a joke RFC + # 420 Enhance Your Calm - Twitter + class HTTPMisdirectedRequest < HTTPClientError # 421 - RFC 7540 + HAS_BODY = true + end + class HTTPUnprocessableEntity < HTTPClientError # 422 - RFC 4918 + HAS_BODY = true + end + class HTTPLocked < HTTPClientError # 423 - RFC 4918 + HAS_BODY = true + end + class HTTPFailedDependency < HTTPClientError # 424 - RFC 4918 + HAS_BODY = true + end + # 425 Unordered Collection - existed only in draft + class HTTPUpgradeRequired < HTTPClientError # 426 - RFC 2817 + HAS_BODY = true + end + class HTTPPreconditionRequired < HTTPClientError # 428 - RFC 6585 + HAS_BODY = true + end + class HTTPTooManyRequests < HTTPClientError # 429 - RFC 6585 + HAS_BODY = true + end + class HTTPRequestHeaderFieldsTooLarge < HTTPClientError # 431 - RFC 6585 + HAS_BODY = true + end + class HTTPUnavailableForLegalReasons < HTTPClientError # 451 - RFC 7725 + HAS_BODY = true + end + # 444 No Response - Nginx + # 449 Retry With - Microsoft + # 450 Blocked by Windows Parental Controls - Microsoft + # 499 Client Closed Request - Nginx + + 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 + HTTPGatewayTimeOut = HTTPGatewayTimeout + class HTTPVersionNotSupported < HTTPServerError # 505 + HAS_BODY = true + end + class HTTPVariantAlsoNegotiates < HTTPServerError # 506 + HAS_BODY = true + end + class HTTPInsufficientStorage < HTTPServerError # 507 - RFC 4918 + HAS_BODY = true + end + class HTTPLoopDetected < HTTPServerError # 508 - RFC 5842 + HAS_BODY = true + end + # 509 Bandwidth Limit Exceeded - Apache bw/limited extension + class HTTPNotExtended < HTTPServerError # 510 - RFC 2774 + HAS_BODY = true + end + class HTTPNetworkAuthenticationRequired < HTTPServerError # 511 - RFC 6585 + HAS_BODY = true + end + + # :startdoc: end class Net::HTTPResponse @@ -303,5 +310,3 @@ class Net::HTTPResponse '511' => Net::HTTPNetworkAuthenticationRequired, } end - -# :startdoc: diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index b5156078a4..0508645ac5 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -178,13 +178,8 @@ class TestNetHTTP < Test::Unit::TestCase http = Net::HTTP.new 'hostname.example' assert_equal true, http.proxy? - if Net::HTTP::ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE - assert_equal 'foo', http.proxy_user - assert_equal 'bar', http.proxy_pass - else - assert_nil http.proxy_user - assert_nil http.proxy_pass - end + assert_equal 'foo', http.proxy_user + assert_equal 'bar', http.proxy_pass end end @@ -195,13 +190,8 @@ class TestNetHTTP < Test::Unit::TestCase http = Net::HTTP.new 'hostname.example' assert_equal true, http.proxy? - if Net::HTTP::ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE - assert_equal "Y\\X", http.proxy_user - assert_equal "R%S] ?X", http.proxy_pass - else - assert_nil http.proxy_user - assert_nil http.proxy_pass - end + assert_equal "Y\\X", http.proxy_user + assert_equal "R%S] ?X", http.proxy_pass end end @@ -1294,3 +1284,87 @@ class TestNetHTTPLocalBind < Test::Unit::TestCase end end +class TestNetHTTPForceEncoding < Test::Unit::TestCase + CONFIG = { + 'host' => 'localhost', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def fe_request(force_enc, content_type=nil) + @server.mount_proc('/fe') do |req, res| + res['Content-Type'] = content_type if content_type + res.body = "hello\u1234" + end + + http = Net::HTTP.new(config('host'), config('port')) + http.local_host = Addrinfo.tcp(config('host'), config('port')).ip_address + assert_not_nil(http.local_host) + assert_nil(http.local_port) + + http.response_body_encoding = force_enc + http.get('/fe') + end + + def test_response_body_encoding_false + res = fe_request(false) + assert_equal("hello\u1234".b, res.body) + assert_equal(Encoding::ASCII_8BIT, res.body.encoding) + end + + def test_response_body_encoding_true_without_content_type + res = fe_request(true) + assert_equal("hello\u1234".b, res.body) + assert_equal(Encoding::ASCII_8BIT, res.body.encoding) + end + + def test_response_body_encoding_true_with_content_type + res = fe_request(true, 'text/html; charset=utf-8') + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end + + def test_response_body_encoding_string_without_content_type + res = fe_request('utf-8') + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end + + def test_response_body_encoding_encoding_without_content_type + res = fe_request(Encoding::UTF_8) + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end +end + +class TestNetHTTPPartialResponse < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def test_partial_response + str = "0123456789" + @server.mount_proc('/') do |req, res| + res.status = 200 + res['Content-Type'] = 'text/plain' + + res.body = str + res['Content-Length'] = str.length + 1 + end + @server.mount_proc('/show_ip') { |req, res| res.body = req.remote_ip } + + http = Net::HTTP.new(config('host'), config('port')) + res = http.get('/') + assert_equal(str, res.body) + + http = Net::HTTP.new(config('host'), config('port')) + http.ignore_eof = false + assert_raise(EOFError) {http.get('/')} + end +end diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb index cfbe36bcfd..b1ca9e8225 100644 --- a/test/net/http/test_httpheader.rb +++ b/test/net/http/test_httpheader.rb @@ -308,6 +308,18 @@ class HTTPHeaderTest < Test::Unit::TestCase end def test_content_range + @c['Content-Range'] = "bytes 0-499/1000" + assert_equal 0..499, @c.content_range + @c['Content-Range'] = "bytes 1-500/1000" + assert_equal 1..500, @c.content_range + @c['Content-Range'] = "bytes 1-1/1000" + assert_equal 1..1, @c.content_range + @c['Content-Range'] = "tokens 1-1/1000" + assert_equal nil, @c.content_range + + try_invalid_content_range "invalid" + try_invalid_content_range "bytes 123-abc" + try_invalid_content_range "bytes abc-123" end def test_range_length @@ -317,6 +329,15 @@ class HTTPHeaderTest < Test::Unit::TestCase assert_equal 500, @c.range_length @c['Content-Range'] = "bytes 1-1/1000" assert_equal 1, @c.range_length + @c['Content-Range'] = "tokens 1-1/1000" + assert_equal nil, @c.range_length + + try_invalid_content_range "bytes 1-1/abc" + end + + def try_invalid_content_range(s) + @c['Content-Range'] = "#{s}" + assert_raise(Net::HTTPHeaderSyntaxError, s){ @c.content_range } end def test_chunked? diff --git a/test/net/http/test_httpresponse.rb b/test/net/http/test_httpresponse.rb index 1a78907284..394b4c5bfa 100644 --- a/test/net/http/test_httpresponse.rb +++ b/test/net/http/test_httpresponse.rb @@ -54,6 +54,241 @@ EOS assert_equal 'hello', body end + def test_read_body_body_encoding_false + body = "hello\u1234" + io = dummy_io(<hello\u1234" + io = dummy_io(<hello\u1234" + io = dummy_io(<