[rubygems/rubygems] Vendor net-http and net-protocol in RubyGems
https://github.com/rubygems/rubygems/commit/99d91c9ed2
This commit is contained in:
parent
6cefad7704
commit
ce924ce1fb
@ -84,7 +84,7 @@ module Gem::GemcutterUtilities
|
|||||||
# If +allowed_push_host+ metadata is present, then it will only allow that host.
|
# If +allowed_push_host+ metadata is present, then it will only allow that host.
|
||||||
|
|
||||||
def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, credentials: {}, &block)
|
def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, scope: nil, credentials: {}, &block)
|
||||||
require "net/http"
|
require_relative "net/http"
|
||||||
|
|
||||||
self.host = host if host
|
self.host = host if host
|
||||||
unless self.host
|
unless self.host
|
||||||
@ -119,7 +119,7 @@ module Gem::GemcutterUtilities
|
|||||||
end
|
end
|
||||||
|
|
||||||
def mfa_unauthorized?(response)
|
def mfa_unauthorized?(response)
|
||||||
response.is_a?(Net::HTTPUnauthorized) && response.body.start_with?("You have enabled multifactor authentication")
|
response.is_a?(Gem::Net::HTTPUnauthorized) && response.body.start_with?("You have enabled multifactor authentication")
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_scope(scope)
|
def update_scope(scope)
|
||||||
@ -208,13 +208,13 @@ module Gem::GemcutterUtilities
|
|||||||
|
|
||||||
def with_response(response, error_prefix = nil)
|
def with_response(response, error_prefix = nil)
|
||||||
case response
|
case response
|
||||||
when Net::HTTPSuccess then
|
when Gem::Net::HTTPSuccess then
|
||||||
if block_given?
|
if block_given?
|
||||||
yield response
|
yield response
|
||||||
else
|
else
|
||||||
say clean_text(response.body)
|
say clean_text(response.body)
|
||||||
end
|
end
|
||||||
when Net::HTTPPermanentRedirect, Net::HTTPRedirection then
|
when Gem::Net::HTTPPermanentRedirect, Gem::Net::HTTPRedirection then
|
||||||
message = "The request has redirected permanently to #{response["location"]}. Please check your defined push host URL."
|
message = "The request has redirected permanently to #{response["location"]}. Please check your defined push host URL."
|
||||||
message = "#{error_prefix}: #{message}" if error_prefix
|
message = "#{error_prefix}: #{message}" if error_prefix
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ module Gem::GemcutterUtilities
|
|||||||
private
|
private
|
||||||
|
|
||||||
def request_with_otp(method, uri, &block)
|
def request_with_otp(method, uri, &block)
|
||||||
request_method = Net::HTTP.const_get method.to_s.capitalize
|
request_method = Gem::Net::HTTP.const_get method.to_s.capitalize
|
||||||
|
|
||||||
Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req|
|
Gem::RemoteFetcher.fetcher.request(uri, request_method) do |req|
|
||||||
req["OTP"] = otp if otp
|
req["OTP"] = otp if otp
|
||||||
@ -297,7 +297,7 @@ module Gem::GemcutterUtilities
|
|||||||
request.basic_auth credentials[:email], credentials[:password]
|
request.basic_auth credentials[:email], credentials[:password]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
response.is_a?(Net::HTTPSuccess) ? response.body : nil
|
response.is_a?(Gem::Net::HTTPSuccess) ? response.body : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def pretty_host(host)
|
def pretty_host(host)
|
||||||
@ -366,6 +366,6 @@ module Gem::GemcutterUtilities
|
|||||||
end
|
end
|
||||||
|
|
||||||
def api_key_forbidden?(response)
|
def api_key_forbidden?(response)
|
||||||
response.is_a?(Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access")
|
response.is_a?(Gem::Net::HTTPForbidden) && response.body.start_with?("The API key doesn't have access")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
##
|
##
|
||||||
# The WebauthnListener Response class is used by the WebauthnListener to create
|
# The WebauthnListener Response class is used by the WebauthnListener to create
|
||||||
# responses to be sent to the Gem host. It creates a Net::HTTPResponse instance
|
# responses to be sent to the Gem host. It creates a Gem::Net::HTTPResponse instance
|
||||||
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
|
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
|
||||||
# Net::HTTPResponse instances cannot be directly sent over a socket.
|
# Gem::Net::HTTPResponse instances cannot be directly sent over a socket.
|
||||||
#
|
#
|
||||||
# Types of response classes:
|
# Types of response classes:
|
||||||
# - OkResponse
|
# - OkResponse
|
||||||
@ -60,7 +60,7 @@ module Gem::GemcutterUtilities
|
|||||||
def body; end
|
def body; end
|
||||||
|
|
||||||
def build_http_response
|
def build_http_response
|
||||||
response_class = Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
|
response_class = Gem::Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
|
||||||
@http_response = response_class.new("1.1", code, reason_phrase)
|
@http_response = response_class.new("1.1", code, reason_phrase)
|
||||||
@http_response.instance_variable_set(:@read, true)
|
@http_response.instance_variable_set(:@read, true)
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ module Gem::GemcutterUtilities
|
|||||||
Timeout.timeout(TIMEOUT_IN_SECONDS) do
|
Timeout.timeout(TIMEOUT_IN_SECONDS) do
|
||||||
loop do
|
loop do
|
||||||
response = webauthn_verification_poll_response(webauthn_url, credentials)
|
response = webauthn_verification_poll_response(webauthn_url, credentials)
|
||||||
raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Net::HTTPSuccess)
|
raise Gem::WebauthnVerificationError, response.message unless response.is_a?(Gem::Net::HTTPSuccess)
|
||||||
|
|
||||||
require "json"
|
require "json"
|
||||||
parsed_response = JSON.parse(response.body)
|
parsed_response = JSON.parse(response.body)
|
||||||
|
22
lib/rubygems/net-http/LICENSE.txt
Normal file
22
lib/rubygems/net-http/LICENSE.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
2496
lib/rubygems/net-http/lib/net/http.rb
Normal file
2496
lib/rubygems/net-http/lib/net/http.rb
Normal file
File diff suppressed because it is too large
Load Diff
40
lib/rubygems/net-http/lib/net/http/backward.rb
Normal file
40
lib/rubygems/net-http/lib/net/http/backward.rb
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
# for backward compatibility
|
||||||
|
|
||||||
|
# :enddoc:
|
||||||
|
|
||||||
|
class Gem::Net::HTTP
|
||||||
|
ProxyMod = ProxyDelta
|
||||||
|
deprecate_constant :ProxyMod
|
||||||
|
end
|
||||||
|
|
||||||
|
module Gem::Net::NetPrivate
|
||||||
|
HTTPRequest = ::Gem::Net::HTTPRequest
|
||||||
|
deprecate_constant :HTTPRequest
|
||||||
|
end
|
||||||
|
|
||||||
|
module Gem::Net
|
||||||
|
HTTPSession = HTTP
|
||||||
|
|
||||||
|
HTTPInformationCode = HTTPInformation
|
||||||
|
HTTPSuccessCode = HTTPSuccess
|
||||||
|
HTTPRedirectionCode = HTTPRedirection
|
||||||
|
HTTPRetriableCode = HTTPRedirection
|
||||||
|
HTTPClientErrorCode = HTTPClientError
|
||||||
|
HTTPFatalErrorCode = HTTPClientError
|
||||||
|
HTTPServerErrorCode = HTTPServerError
|
||||||
|
HTTPResponseReceiver = HTTPResponse
|
||||||
|
|
||||||
|
HTTPResponceReceiver = HTTPResponse # Typo since 2001
|
||||||
|
|
||||||
|
deprecate_constant :HTTPSession,
|
||||||
|
:HTTPInformationCode,
|
||||||
|
:HTTPSuccessCode,
|
||||||
|
:HTTPRedirectionCode,
|
||||||
|
:HTTPRetriableCode,
|
||||||
|
:HTTPClientErrorCode,
|
||||||
|
:HTTPFatalErrorCode,
|
||||||
|
:HTTPServerErrorCode,
|
||||||
|
:HTTPResponseReceiver,
|
||||||
|
:HTTPResponceReceiver
|
||||||
|
end
|
34
lib/rubygems/net-http/lib/net/http/exceptions.rb
Normal file
34
lib/rubygems/net-http/lib/net/http/exceptions.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Gem::Net
|
||||||
|
# Gem::Net::HTTP exception class.
|
||||||
|
# You cannot use Gem::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
|
414
lib/rubygems/net-http/lib/net/http/generic_request.rb
Normal file
414
lib/rubygems/net-http/lib/net/http/generic_request.rb
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
|
# \HTTPGenericRequest is the parent of the Gem::Net::HTTPRequest class.
|
||||||
|
#
|
||||||
|
# Do not use this directly; instead, use a subclass of Gem::Net::HTTPRequest.
|
||||||
|
#
|
||||||
|
# == About the Examples
|
||||||
|
#
|
||||||
|
# :include: doc/net-http/examples.rdoc
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTPGenericRequest
|
||||||
|
|
||||||
|
include Gem::Net::HTTPHeader
|
||||||
|
|
||||||
|
def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc:
|
||||||
|
@method = m
|
||||||
|
@request_has_body = reqbody
|
||||||
|
@response_has_body = resbody
|
||||||
|
|
||||||
|
if URI === uri_or_path then
|
||||||
|
raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path
|
||||||
|
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 << ":" << @uri.port.to_s if @uri.port != @uri.default_port
|
||||||
|
@path = uri_or_path.request_uri
|
||||||
|
raise ArgumentError, "no HTTP request path given" unless @path
|
||||||
|
else
|
||||||
|
@uri = nil
|
||||||
|
host = nil
|
||||||
|
raise ArgumentError, "no HTTP request path given" unless uri_or_path
|
||||||
|
raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty?
|
||||||
|
@path = uri_or_path.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
@decode_content = false
|
||||||
|
|
||||||
|
if Gem::Net::HTTP::HAVE_ZLIB then
|
||||||
|
if !initheader ||
|
||||||
|
!initheader.keys.any? { |k|
|
||||||
|
%w[accept-encoding range].include? k.downcase
|
||||||
|
} then
|
||||||
|
@decode_content = true if @response_has_body
|
||||||
|
initheader = initheader ? initheader.dup : {}
|
||||||
|
initheader["accept-encoding"] =
|
||||||
|
"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
initialize_http_header initheader
|
||||||
|
self['Accept'] ||= '*/*'
|
||||||
|
self['User-Agent'] ||= 'Ruby'
|
||||||
|
self['Host'] ||= host if host
|
||||||
|
@body = nil
|
||||||
|
@body_stream = nil
|
||||||
|
@body_data = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the string method name for the request:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Get.new(uri).method # => "GET"
|
||||||
|
# Gem::Net::HTTP::Post.new(uri).method # => "POST"
|
||||||
|
#
|
||||||
|
attr_reader :method
|
||||||
|
|
||||||
|
# Returns the string path for the request:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Get.new(uri).path # => "/"
|
||||||
|
# Gem::Net::HTTP::Post.new('example.com').path # => "example.com"
|
||||||
|
#
|
||||||
|
attr_reader :path
|
||||||
|
|
||||||
|
# Returns the URI object for the request, or +nil+ if none:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Get.new(uri).uri
|
||||||
|
# # => #<URI::HTTPS https://jsonplaceholder.typicode.com/>
|
||||||
|
# Gem::Net::HTTP::Get.new('example.com').uri # => nil
|
||||||
|
#
|
||||||
|
attr_reader :uri
|
||||||
|
|
||||||
|
# Returns +false+ if the request's header <tt>'Accept-Encoding'</tt>
|
||||||
|
# has been set manually or deleted
|
||||||
|
# (indicating that the user intends to handle encoding in the response),
|
||||||
|
# +true+ otherwise:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
|
||||||
|
# req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
|
||||||
|
# req.decode_content # => true
|
||||||
|
# req['Accept-Encoding'] = 'foo'
|
||||||
|
# req.decode_content # => false
|
||||||
|
# req.delete('Accept-Encoding')
|
||||||
|
# req.decode_content # => false
|
||||||
|
#
|
||||||
|
attr_reader :decode_content
|
||||||
|
|
||||||
|
# Returns a string representation of the request:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Post.new(uri).inspect # => "#<Gem::Net::HTTP::Post POST>"
|
||||||
|
#
|
||||||
|
def inspect
|
||||||
|
"\#<#{self.class} #{@method}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Don't automatically decode response content-encoding if the user indicates
|
||||||
|
# they want to handle it.
|
||||||
|
|
||||||
|
def []=(key, val) # :nodoc:
|
||||||
|
@decode_content = false if key.downcase == 'accept-encoding'
|
||||||
|
|
||||||
|
super key, val
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns whether the request may have a body:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Post.new(uri).request_body_permitted? # => true
|
||||||
|
# Gem::Net::HTTP::Get.new(uri).request_body_permitted? # => false
|
||||||
|
#
|
||||||
|
def request_body_permitted?
|
||||||
|
@request_has_body
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns whether the response may have a body:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP::Post.new(uri).response_body_permitted? # => true
|
||||||
|
# Gem::Net::HTTP::Head.new(uri).response_body_permitted? # => false
|
||||||
|
#
|
||||||
|
def response_body_permitted?
|
||||||
|
@response_has_body
|
||||||
|
end
|
||||||
|
|
||||||
|
def body_exist? # :nodoc:
|
||||||
|
warn "Gem::Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE
|
||||||
|
response_body_permitted?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the string body for the request, or +nil+ if there is none:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri)
|
||||||
|
# req.body # => nil
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
|
||||||
|
#
|
||||||
|
attr_reader :body
|
||||||
|
|
||||||
|
# Sets the body for the request:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri)
|
||||||
|
# req.body # => nil
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
|
||||||
|
#
|
||||||
|
def body=(str)
|
||||||
|
@body = str
|
||||||
|
@body_stream = nil
|
||||||
|
@body_data = nil
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the body stream object for the request, or +nil+ if there is none:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
|
||||||
|
# req.body_stream # => nil
|
||||||
|
# require 'stringio'
|
||||||
|
# req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
|
||||||
|
# req.body_stream # => #<StringIO:0x0000027d1e5affa8>
|
||||||
|
#
|
||||||
|
attr_reader :body_stream
|
||||||
|
|
||||||
|
# Sets the body stream for the request:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
|
||||||
|
# req.body_stream # => nil
|
||||||
|
# require 'stringio'
|
||||||
|
# req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
|
||||||
|
# req.body_stream # => #<StringIO:0x0000027d1e5affa8>
|
||||||
|
#
|
||||||
|
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
|
||||||
|
if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted?
|
||||||
|
self.body = ''
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
def update_uri(addr, port, ssl) # :nodoc: internal use only
|
||||||
|
# reflect the connection and @path to @uri
|
||||||
|
return unless @uri
|
||||||
|
|
||||||
|
if ssl
|
||||||
|
scheme = 'https'
|
||||||
|
klass = URI::HTTPS
|
||||||
|
else
|
||||||
|
scheme = 'http'
|
||||||
|
klass = URI::HTTP
|
||||||
|
end
|
||||||
|
|
||||||
|
if host = self['host']
|
||||||
|
host.sub!(/:.*/m, '')
|
||||||
|
elsif host = @uri.host
|
||||||
|
else
|
||||||
|
host = addr
|
||||||
|
end
|
||||||
|
# convert the class of the URI
|
||||||
|
if @uri.is_a?(klass)
|
||||||
|
@uri.host = host
|
||||||
|
@uri.port = port
|
||||||
|
else
|
||||||
|
@uri = klass.new(
|
||||||
|
scheme, @uri.userinfo,
|
||||||
|
host, port, nil,
|
||||||
|
@uri.path, nil, @uri.query, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
class Chunker #:nodoc:
|
||||||
|
def initialize(sock)
|
||||||
|
@sock = sock
|
||||||
|
@prev = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(buf)
|
||||||
|
# avoid memcpy() of buf, buf can huge and eat memory bandwidth
|
||||||
|
rv = buf.bytesize
|
||||||
|
@sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n")
|
||||||
|
rv
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish
|
||||||
|
@sock.write("0\r\n\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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?
|
||||||
|
chunker = Chunker.new(sock)
|
||||||
|
IO.copy_stream(f, chunker)
|
||||||
|
chunker.finish
|
||||||
|
else
|
||||||
|
IO.copy_stream(f, sock)
|
||||||
|
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)
|
||||||
|
file.close(true)
|
||||||
|
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.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: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 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 sock.io.to_io.wait_readable(sock.continue_timeout)
|
||||||
|
res = Gem::Net::HTTPResponse.read_new(sock)
|
||||||
|
unless res.kind_of?(Gem::Net::HTTPContinue)
|
||||||
|
res.decode_content = @decode_content
|
||||||
|
throw :response, res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_header(sock, ver, path)
|
||||||
|
reqline = "#{@method} #{path} HTTP/#{ver}"
|
||||||
|
if /[\r\n]/ =~ reqline
|
||||||
|
raise ArgumentError, "A Request-Line must not contain CR or LF"
|
||||||
|
end
|
||||||
|
buf = +''
|
||||||
|
buf << reqline << "\r\n"
|
||||||
|
each_capitalized do |k,v|
|
||||||
|
buf << "#{k}: #{v}\r\n"
|
||||||
|
end
|
||||||
|
buf << "\r\n"
|
||||||
|
sock.write buf
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
981
lib/rubygems/net-http/lib/net/http/header.rb
Normal file
981
lib/rubygems/net-http/lib/net/http/header.rb
Normal file
@ -0,0 +1,981 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
|
# The \HTTPHeader module provides access to \HTTP headers.
|
||||||
|
#
|
||||||
|
# The module is included in:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPGenericRequest (and therefore Gem::Net::HTTPRequest).
|
||||||
|
# - Gem::Net::HTTPResponse.
|
||||||
|
#
|
||||||
|
# The headers are a hash-like collection of key/value pairs called _fields_.
|
||||||
|
#
|
||||||
|
# == Request and Response Fields
|
||||||
|
#
|
||||||
|
# Headers may be included in:
|
||||||
|
#
|
||||||
|
# - A Gem::Net::HTTPRequest object:
|
||||||
|
# the object's headers will be sent with the request.
|
||||||
|
# Any fields may be defined in the request;
|
||||||
|
# see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
|
||||||
|
# - A Gem::Net::HTTPResponse object:
|
||||||
|
# the objects headers are usually those returned from the host.
|
||||||
|
# Fields may be retrieved from the object;
|
||||||
|
# see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters]
|
||||||
|
# and {Iterators}[rdoc-ref:Gem::Net::HTTPHeader@Iterators].
|
||||||
|
#
|
||||||
|
# Exactly which fields should be sent or expected depends on the host;
|
||||||
|
# see:
|
||||||
|
#
|
||||||
|
# - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
|
||||||
|
# - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields].
|
||||||
|
#
|
||||||
|
# == About the Examples
|
||||||
|
#
|
||||||
|
# :include: doc/net-http/examples.rdoc
|
||||||
|
#
|
||||||
|
# == Fields
|
||||||
|
#
|
||||||
|
# A header field is a key/value pair.
|
||||||
|
#
|
||||||
|
# === Field Keys
|
||||||
|
#
|
||||||
|
# A field key may be:
|
||||||
|
#
|
||||||
|
# - A string: Key <tt>'Accept'</tt> is treated as if it were
|
||||||
|
# <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>.
|
||||||
|
# - A symbol: Key <tt>:Accept</tt> is treated as if it were
|
||||||
|
# <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req[:accept] # => "*/*"
|
||||||
|
# req['Accept'] # => "*/*"
|
||||||
|
# req['ACCEPT'] # => "*/*"
|
||||||
|
#
|
||||||
|
# req['accept'] = 'text/html'
|
||||||
|
# req[:accept] = 'text/html'
|
||||||
|
# req['ACCEPT'] = 'text/html'
|
||||||
|
#
|
||||||
|
# === Field Values
|
||||||
|
#
|
||||||
|
# A field value may be returned as an array of strings or as a string:
|
||||||
|
#
|
||||||
|
# - These methods return field values as arrays:
|
||||||
|
#
|
||||||
|
# - #get_fields: Returns the array value for the given key,
|
||||||
|
# or +nil+ if it does not exist.
|
||||||
|
# - #to_hash: Returns a hash of all header fields:
|
||||||
|
# each key is a field name; its value is the array value for the field.
|
||||||
|
#
|
||||||
|
# - These methods return field values as string;
|
||||||
|
# the string value for a field is equivalent to
|
||||||
|
# <tt>self[key.downcase.to_s].join(', '))</tt>:
|
||||||
|
#
|
||||||
|
# - #[]: Returns the string value for the given key,
|
||||||
|
# or +nil+ if it does not exist.
|
||||||
|
# - #fetch: Like #[], but accepts a default value
|
||||||
|
# to be returned if the key does not exist.
|
||||||
|
#
|
||||||
|
# The field value may be set:
|
||||||
|
#
|
||||||
|
# - #[]=: Sets the value for the given key;
|
||||||
|
# the given value may be a string, a symbol, an array, or a hash.
|
||||||
|
# - #add_field: Adds a given value to a value for the given key
|
||||||
|
# (not overwriting the existing value).
|
||||||
|
# - #delete: Deletes the field for the given key.
|
||||||
|
#
|
||||||
|
# Example field values:
|
||||||
|
#
|
||||||
|
# - \String:
|
||||||
|
#
|
||||||
|
# req['Accept'] = 'text/html' # => "text/html"
|
||||||
|
# req['Accept'] # => "text/html"
|
||||||
|
# req.get_fields('Accept') # => ["text/html"]
|
||||||
|
#
|
||||||
|
# - \Symbol:
|
||||||
|
#
|
||||||
|
# req['Accept'] = :text # => :text
|
||||||
|
# req['Accept'] # => "text"
|
||||||
|
# req.get_fields('Accept') # => ["text"]
|
||||||
|
#
|
||||||
|
# - Simple array:
|
||||||
|
#
|
||||||
|
# req[:foo] = %w[bar baz bat]
|
||||||
|
# req[:foo] # => "bar, baz, bat"
|
||||||
|
# req.get_fields(:foo) # => ["bar", "baz", "bat"]
|
||||||
|
#
|
||||||
|
# - Simple hash:
|
||||||
|
#
|
||||||
|
# req[:foo] = {bar: 0, baz: 1, bat: 2}
|
||||||
|
# req[:foo] # => "bar, 0, baz, 1, bat, 2"
|
||||||
|
# req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"]
|
||||||
|
#
|
||||||
|
# - Nested:
|
||||||
|
#
|
||||||
|
# req[:foo] = [%w[bar baz], {bat: 0, bam: 1}]
|
||||||
|
# req[:foo] # => "bar, baz, bat, 0, bam, 1"
|
||||||
|
# req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"]
|
||||||
|
#
|
||||||
|
# req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}}
|
||||||
|
# req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1"
|
||||||
|
# req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"]
|
||||||
|
#
|
||||||
|
# == Convenience Methods
|
||||||
|
#
|
||||||
|
# Various convenience methods retrieve values, set values, query values,
|
||||||
|
# set form values, or iterate over fields.
|
||||||
|
#
|
||||||
|
# === Setters
|
||||||
|
#
|
||||||
|
# \Method #[]= can set any field, but does little to validate the new value;
|
||||||
|
# some of the other setter methods provide some validation:
|
||||||
|
#
|
||||||
|
# - #[]=: Sets the string or array value for the given key.
|
||||||
|
# - #add_field: Creates or adds to the array value for the given key.
|
||||||
|
# - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>.
|
||||||
|
# - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>.
|
||||||
|
# - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>.
|
||||||
|
# - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>.
|
||||||
|
# - #set_range: Sets the value for field <tt>'Range'</tt>.
|
||||||
|
#
|
||||||
|
# === Form Setters
|
||||||
|
#
|
||||||
|
# - #set_form: Sets an HTML form data set.
|
||||||
|
# - #set_form_data: Sets header fields and a body from HTML form data.
|
||||||
|
#
|
||||||
|
# === Getters
|
||||||
|
#
|
||||||
|
# \Method #[] can retrieve the value of any field that exists,
|
||||||
|
# but always as a string;
|
||||||
|
# some of the other getter methods return something different
|
||||||
|
# from the simple string value:
|
||||||
|
#
|
||||||
|
# - #[]: Returns the string field value for the given key.
|
||||||
|
# - #content_length: Returns the integer value of field <tt>'Content-Length'</tt>.
|
||||||
|
# - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>.
|
||||||
|
# - #content_type: Returns the string value of field <tt>'Content-Type'</tt>.
|
||||||
|
# - #fetch: Returns the string field value for the given key.
|
||||||
|
# - #get_fields: Returns the array field value for the given +key+.
|
||||||
|
# - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>.
|
||||||
|
# - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>.
|
||||||
|
# - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+.
|
||||||
|
# - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>.
|
||||||
|
# - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>.
|
||||||
|
#
|
||||||
|
# === Queries
|
||||||
|
#
|
||||||
|
# - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>.
|
||||||
|
# - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>.
|
||||||
|
# - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>.
|
||||||
|
# - #key?: Returns whether a given key exists.
|
||||||
|
#
|
||||||
|
# === Iterators
|
||||||
|
#
|
||||||
|
# - #each_capitalized: Passes each field capitalized-name/value pair to the block.
|
||||||
|
# - #each_capitalized_name: Passes each capitalized field name to the block.
|
||||||
|
# - #each_header: Passes each field name/value pair to the block.
|
||||||
|
# - #each_name: Passes each field name to the block.
|
||||||
|
# - #each_value: Passes each string field value to the block.
|
||||||
|
#
|
||||||
|
module Gem::Net::HTTPHeader
|
||||||
|
MAX_KEY_LENGTH = 1024
|
||||||
|
MAX_FIELD_LENGTH = 65536
|
||||||
|
|
||||||
|
def initialize_http_header(initheader) #:nodoc:
|
||||||
|
@header = {}
|
||||||
|
return unless initheader
|
||||||
|
initheader.each do |key, value|
|
||||||
|
warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE
|
||||||
|
if value.nil?
|
||||||
|
warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE
|
||||||
|
else
|
||||||
|
value = value.strip # raise error for invalid byte sequences
|
||||||
|
if key.to_s.bytesize > MAX_KEY_LENGTH
|
||||||
|
raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
|
||||||
|
end
|
||||||
|
if value.to_s.bytesize > MAX_FIELD_LENGTH
|
||||||
|
raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
|
||||||
|
end
|
||||||
|
if value.count("\r\n") > 0
|
||||||
|
raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF"
|
||||||
|
end
|
||||||
|
@header[key.downcase.to_s] = [value]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def size #:nodoc: obsolete
|
||||||
|
@header.size
|
||||||
|
end
|
||||||
|
|
||||||
|
alias length size #:nodoc: obsolete
|
||||||
|
|
||||||
|
# Returns the string field value for the case-insensitive field +key+,
|
||||||
|
# or +nil+ if there is no such key;
|
||||||
|
# see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['Connection'] # => "keep-alive"
|
||||||
|
# res['Nosuch'] # => nil
|
||||||
|
#
|
||||||
|
# Note that some field values may be retrieved via convenience methods;
|
||||||
|
# see {Getters}[rdoc-ref:Gem::Net::HTTPHeader@Getters].
|
||||||
|
def [](key)
|
||||||
|
a = @header[key.downcase.to_s] or return nil
|
||||||
|
a.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the value for the case-insensitive +key+ to +val+,
|
||||||
|
# overwriting the previous value if the field exists;
|
||||||
|
# see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req['Accept'] # => "*/*"
|
||||||
|
# req['Accept'] = 'text/html'
|
||||||
|
# req['Accept'] # => "text/html"
|
||||||
|
#
|
||||||
|
# Note that some field values may be set via convenience methods;
|
||||||
|
# see {Setters}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
|
||||||
|
def []=(key, val)
|
||||||
|
unless val
|
||||||
|
@header.delete key.downcase.to_s
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
set_field(key, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Adds value +val+ to the value array for field +key+ if the field exists;
|
||||||
|
# creates the field with the given +key+ and +val+ if it does not exist.
|
||||||
|
# see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.add_field('Foo', 'bar')
|
||||||
|
# req['Foo'] # => "bar"
|
||||||
|
# req.add_field('Foo', 'baz')
|
||||||
|
# req['Foo'] # => "bar, baz"
|
||||||
|
# req.add_field('Foo', %w[baz bam])
|
||||||
|
# req['Foo'] # => "bar, baz, baz, bam"
|
||||||
|
# req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"]
|
||||||
|
#
|
||||||
|
def add_field(key, val)
|
||||||
|
stringified_downcased_key = key.downcase.to_s
|
||||||
|
if @header.key?(stringified_downcased_key)
|
||||||
|
append_field_value(@header[stringified_downcased_key], val)
|
||||||
|
else
|
||||||
|
set_field(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def set_field(key, val)
|
||||||
|
case val
|
||||||
|
when Enumerable
|
||||||
|
ary = []
|
||||||
|
append_field_value(ary, val)
|
||||||
|
@header[key.downcase.to_s] = ary
|
||||||
|
else
|
||||||
|
val = val.to_s # for compatibility use to_s instead of to_str
|
||||||
|
if val.b.count("\r\n") > 0
|
||||||
|
raise ArgumentError, 'header field value cannot include CR/LF'
|
||||||
|
end
|
||||||
|
@header[key.downcase.to_s] = [val]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def append_field_value(ary, val)
|
||||||
|
case val
|
||||||
|
when Enumerable
|
||||||
|
val.each{|x| append_field_value(ary, x)}
|
||||||
|
else
|
||||||
|
val = val.to_s
|
||||||
|
if /[\r\n]/n.match?(val.b)
|
||||||
|
raise ArgumentError, 'header field value cannot include CR/LF'
|
||||||
|
end
|
||||||
|
ary.push val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the array field value for the given +key+,
|
||||||
|
# or +nil+ if there is no such field;
|
||||||
|
# see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.get_fields('Connection') # => ["keep-alive"]
|
||||||
|
# res.get_fields('Nosuch') # => nil
|
||||||
|
#
|
||||||
|
def get_fields(key)
|
||||||
|
stringified_downcased_key = key.downcase.to_s
|
||||||
|
return nil unless @header[stringified_downcased_key]
|
||||||
|
@header[stringified_downcased_key].dup
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# fetch(key, default_val = nil) {|key| ... } -> object
|
||||||
|
# fetch(key, default_val = nil) -> value or default_val
|
||||||
|
#
|
||||||
|
# With a block, returns the string value for +key+ if it exists;
|
||||||
|
# otherwise returns the value of the block;
|
||||||
|
# ignores the +default_val+;
|
||||||
|
# see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
#
|
||||||
|
# # Field exists; block not called.
|
||||||
|
# res.fetch('Connection') do |value|
|
||||||
|
# fail 'Cannot happen'
|
||||||
|
# end # => "keep-alive"
|
||||||
|
#
|
||||||
|
# # Field does not exist; block called.
|
||||||
|
# res.fetch('Nosuch') do |value|
|
||||||
|
# value.downcase
|
||||||
|
# end # => "nosuch"
|
||||||
|
#
|
||||||
|
# With no block, returns the string value for +key+ if it exists;
|
||||||
|
# otherwise, returns +default_val+ if it was given;
|
||||||
|
# otherwise raises an exception:
|
||||||
|
#
|
||||||
|
# res.fetch('Connection', 'Foo') # => "keep-alive"
|
||||||
|
# res.fetch('Nosuch', 'Foo') # => "Foo"
|
||||||
|
# res.fetch('Nosuch') # Raises KeyError.
|
||||||
|
#
|
||||||
|
def fetch(key, *args, &block) #:yield: +key+
|
||||||
|
a = @header.fetch(key.downcase.to_s, *args, &block)
|
||||||
|
a.kind_of?(Array) ? a.join(', ') : a
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calls the block with each key/value pair:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.each_header do |key, value|
|
||||||
|
# p [key, value] if key.start_with?('c')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
#
|
||||||
|
# ["content-type", "application/json; charset=utf-8"]
|
||||||
|
# ["connection", "keep-alive"]
|
||||||
|
# ["cache-control", "max-age=43200"]
|
||||||
|
# ["cf-cache-status", "HIT"]
|
||||||
|
# ["cf-ray", "771d17e9bc542cf5-ORD"]
|
||||||
|
#
|
||||||
|
# Returns an enumerator if no block is given.
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#each is an alias for Gem::Net::HTTPHeader#each_header.
|
||||||
|
def each_header #:yield: +key+, +value+
|
||||||
|
block_given? or return enum_for(__method__) { @header.size }
|
||||||
|
@header.each do |k,va|
|
||||||
|
yield k, va.join(', ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias each each_header
|
||||||
|
|
||||||
|
# Calls the block with each field key:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.each_key do |key|
|
||||||
|
# p key if key.start_with?('c')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
#
|
||||||
|
# "content-type"
|
||||||
|
# "connection"
|
||||||
|
# "cache-control"
|
||||||
|
# "cf-cache-status"
|
||||||
|
# "cf-ray"
|
||||||
|
#
|
||||||
|
# Returns an enumerator if no block is given.
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#each_name is an alias for Gem::Net::HTTPHeader#each_key.
|
||||||
|
def each_name(&block) #:yield: +key+
|
||||||
|
block_given? or return enum_for(__method__) { @header.size }
|
||||||
|
@header.each_key(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias each_key each_name
|
||||||
|
|
||||||
|
# Calls the block with each capitalized field name:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.each_capitalized_name do |key|
|
||||||
|
# p key if key.start_with?('C')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
#
|
||||||
|
# "Content-Type"
|
||||||
|
# "Connection"
|
||||||
|
# "Cache-Control"
|
||||||
|
# "Cf-Cache-Status"
|
||||||
|
# "Cf-Ray"
|
||||||
|
#
|
||||||
|
# The capitalization is system-dependent;
|
||||||
|
# see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html].
|
||||||
|
#
|
||||||
|
# Returns an enumerator if no block is given.
|
||||||
|
def each_capitalized_name #:yield: +key+
|
||||||
|
block_given? or return enum_for(__method__) { @header.size }
|
||||||
|
@header.each_key do |k|
|
||||||
|
yield capitalize(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calls the block with each string field value:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.each_value do |value|
|
||||||
|
# p value if value.start_with?('c')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
#
|
||||||
|
# "chunked"
|
||||||
|
# "cf-q-config;dur=6.0000002122251e-06"
|
||||||
|
# "cloudflare"
|
||||||
|
#
|
||||||
|
# Returns an enumerator if no block is given.
|
||||||
|
def each_value #:yield: +value+
|
||||||
|
block_given? or return enum_for(__method__) { @header.size }
|
||||||
|
@header.each_value do |va|
|
||||||
|
yield va.join(', ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes the header for the given case-insensitive +key+
|
||||||
|
# (see {Fields}[rdoc-ref:Gem::Net::HTTPHeader@Fields]);
|
||||||
|
# returns the deleted value, or +nil+ if no such field exists:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.delete('Accept') # => ["*/*"]
|
||||||
|
# req.delete('Nosuch') # => nil
|
||||||
|
#
|
||||||
|
def delete(key)
|
||||||
|
@header.delete(key.downcase.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.key?('Accept') # => true
|
||||||
|
# req.key?('Nosuch') # => false
|
||||||
|
#
|
||||||
|
def key?(key)
|
||||||
|
@header.key?(key.downcase.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a hash of the key/value pairs:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.to_hash
|
||||||
|
# # =>
|
||||||
|
# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
|
||||||
|
# "accept"=>["*/*"],
|
||||||
|
# "user-agent"=>["Ruby"],
|
||||||
|
# "host"=>["jsonplaceholder.typicode.com"]}
|
||||||
|
#
|
||||||
|
def to_hash
|
||||||
|
@header.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
# Like #each_header, but the keys are returned in capitalized form.
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#canonical_each is an alias for Gem::Net::HTTPHeader#each_capitalized.
|
||||||
|
def each_capitalized
|
||||||
|
block_given? or return enum_for(__method__) { @header.size }
|
||||||
|
@header.each do |k,v|
|
||||||
|
yield capitalize(k), v.join(', ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias canonical_each each_capitalized
|
||||||
|
|
||||||
|
def capitalize(name)
|
||||||
|
name.to_s.split(/-/).map {|s| s.capitalize }.join('-')
|
||||||
|
end
|
||||||
|
private :capitalize
|
||||||
|
|
||||||
|
# Returns an array of Range objects that represent
|
||||||
|
# the value of field <tt>'Range'</tt>,
|
||||||
|
# or +nil+ if there is no such field;
|
||||||
|
# see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req['Range'] = 'bytes=0-99,200-299,400-499'
|
||||||
|
# req.range # => [0..99, 200..299, 400..499]
|
||||||
|
# req.delete('Range')
|
||||||
|
# req.range # # => nil
|
||||||
|
#
|
||||||
|
def range
|
||||||
|
return nil unless @header['range']
|
||||||
|
|
||||||
|
value = self['Range']
|
||||||
|
# byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec )
|
||||||
|
# *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] )
|
||||||
|
# corrected collected ABNF
|
||||||
|
# http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1
|
||||||
|
# http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C
|
||||||
|
# http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5
|
||||||
|
unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
byte_range_set = $1
|
||||||
|
result = byte_range_set.split(/,/).map {|spec|
|
||||||
|
m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'"
|
||||||
|
d1 = m[1].to_i
|
||||||
|
d2 = m[2].to_i
|
||||||
|
if m[1] and m[2]
|
||||||
|
if d1 > d2
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'"
|
||||||
|
end
|
||||||
|
d1..d2
|
||||||
|
elsif m[1]
|
||||||
|
d1..-1
|
||||||
|
elsif m[2]
|
||||||
|
-d2..-1
|
||||||
|
else
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'range is not specified'
|
||||||
|
end
|
||||||
|
}
|
||||||
|
# if result.empty?
|
||||||
|
# byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec
|
||||||
|
# but above regexp already denies it.
|
||||||
|
if result.size == 1 && result[0].begin == 0 && result[0].end == -1
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length'
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# set_range(length) -> length
|
||||||
|
# set_range(offset, length) -> range
|
||||||
|
# set_range(begin..length) -> range
|
||||||
|
#
|
||||||
|
# Sets the value for field <tt>'Range'</tt>;
|
||||||
|
# see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
|
||||||
|
#
|
||||||
|
# With argument +length+:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.set_range(100) # => 100
|
||||||
|
# req['Range'] # => "bytes=0-99"
|
||||||
|
#
|
||||||
|
# With arguments +offset+ and +length+:
|
||||||
|
#
|
||||||
|
# req.set_range(100, 100) # => 100...200
|
||||||
|
# req['Range'] # => "bytes=100-199"
|
||||||
|
#
|
||||||
|
# With argument +range+:
|
||||||
|
#
|
||||||
|
# req.set_range(100..199) # => 100..199
|
||||||
|
# req['Range'] # => "bytes=100-199"
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#range= is an alias for Gem::Net::HTTPHeader#set_range.
|
||||||
|
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.end
|
||||||
|
last -= 1 if r.exclude_end?
|
||||||
|
if last == -1
|
||||||
|
rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
|
||||||
|
else
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
|
||||||
|
raise Gem::Net::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 the value of field <tt>'Content-Length'</tt> as an integer,
|
||||||
|
# or +nil+ if there is no such field;
|
||||||
|
# see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/nosuch/1')
|
||||||
|
# res.content_length # => 2
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res.content_length # => nil
|
||||||
|
#
|
||||||
|
def content_length
|
||||||
|
return nil unless key?('Content-Length')
|
||||||
|
len = self['Content-Length'].slice(/\d+/) or
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Length format'
|
||||||
|
len.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the value of field <tt>'Content-Length'</tt> to the given numeric;
|
||||||
|
# see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]:
|
||||||
|
#
|
||||||
|
# _uri = uri.dup
|
||||||
|
# hostname = _uri.hostname # => "jsonplaceholder.typicode.com"
|
||||||
|
# _uri.path = '/posts' # => "/posts"
|
||||||
|
# req = Gem::Net::HTTP::Post.new(_uri) # => #<Gem::Net::HTTP::Post POST>
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.content_length = req.body.size # => 42
|
||||||
|
# req.content_type = 'application/json'
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end # => #<Gem::Net::HTTPCreated 201 Created readbody=true>
|
||||||
|
#
|
||||||
|
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 field <tt>'Transfer-Encoding'</tt>
|
||||||
|
# exists and has value <tt>'chunked'</tt>,
|
||||||
|
# +false+ otherwise;
|
||||||
|
# see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['Transfer-Encoding'] # => "chunked"
|
||||||
|
# res.chunked? # => true
|
||||||
|
#
|
||||||
|
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 representing the value of field
|
||||||
|
# <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
|
||||||
|
# see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['Content-Range'] # => nil
|
||||||
|
# res['Content-Range'] = 'bytes 0-499/1000'
|
||||||
|
# res['Content-Range'] # => "bytes 0-499/1000"
|
||||||
|
# res.content_range # => 0..499
|
||||||
|
#
|
||||||
|
def content_range
|
||||||
|
return nil unless @header['content-range']
|
||||||
|
m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or
|
||||||
|
raise Gem::Net::HTTPHeaderSyntaxError, 'wrong Content-Range format'
|
||||||
|
return unless m[1] == 'bytes'
|
||||||
|
m[2].to_i .. m[3].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the integer representing length of the value of field
|
||||||
|
# <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
|
||||||
|
# see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['Content-Range'] # => nil
|
||||||
|
# res['Content-Range'] = 'bytes 0-499/1000'
|
||||||
|
# res.range_length # => 500
|
||||||
|
#
|
||||||
|
def range_length
|
||||||
|
r = content_range() or return nil
|
||||||
|
r.end - r.begin + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the {media type}[https://en.wikipedia.org/wiki/Media_type]
|
||||||
|
# from the value of field <tt>'Content-Type'</tt>,
|
||||||
|
# or +nil+ if no such field exists;
|
||||||
|
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['content-type'] # => "application/json; charset=utf-8"
|
||||||
|
# res.content_type # => "application/json"
|
||||||
|
#
|
||||||
|
def content_type
|
||||||
|
main = main_type()
|
||||||
|
return nil unless main
|
||||||
|
|
||||||
|
sub = sub_type()
|
||||||
|
if sub
|
||||||
|
"#{main}/#{sub}"
|
||||||
|
else
|
||||||
|
main
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the leading ('type') part of the
|
||||||
|
# {media type}[https://en.wikipedia.org/wiki/Media_type]
|
||||||
|
# from the value of field <tt>'Content-Type'</tt>,
|
||||||
|
# or +nil+ if no such field exists;
|
||||||
|
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['content-type'] # => "application/json; charset=utf-8"
|
||||||
|
# res.main_type # => "application"
|
||||||
|
#
|
||||||
|
def main_type
|
||||||
|
return nil unless @header['content-type']
|
||||||
|
self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the trailing ('subtype') part of the
|
||||||
|
# {media type}[https://en.wikipedia.org/wiki/Media_type]
|
||||||
|
# from the value of field <tt>'Content-Type'</tt>,
|
||||||
|
# or +nil+ if no such field exists;
|
||||||
|
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['content-type'] # => "application/json; charset=utf-8"
|
||||||
|
# res.sub_type # => "json"
|
||||||
|
#
|
||||||
|
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
|
||||||
|
|
||||||
|
# Returns the trailing ('parameters') part of the value of field <tt>'Content-Type'</tt>,
|
||||||
|
# or +nil+ if no such field exists;
|
||||||
|
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(hostname, '/todos/1')
|
||||||
|
# res['content-type'] # => "application/json; charset=utf-8"
|
||||||
|
# res.type_params # => {"charset"=>"utf-8"}
|
||||||
|
#
|
||||||
|
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 value of field <tt>'Content-Type'</tt>;
|
||||||
|
# returns the new value;
|
||||||
|
# see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# req.set_content_type('application/json') # => ["application/json"]
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#content_type= is an alias for Gem::Net::HTTPHeader#set_content_type.
|
||||||
|
def set_content_type(type, params = {})
|
||||||
|
@header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
|
||||||
|
end
|
||||||
|
|
||||||
|
alias content_type= set_content_type
|
||||||
|
|
||||||
|
# Sets the request body to a URL-encoded string derived from argument +params+,
|
||||||
|
# and sets request header field <tt>'Content-Type'</tt>
|
||||||
|
# to <tt>'application/x-www-form-urlencoded'</tt>.
|
||||||
|
#
|
||||||
|
# The resulting request is suitable for HTTP request +POST+ or +PUT+.
|
||||||
|
#
|
||||||
|
# Argument +params+ must be suitable for use as argument +enum+ to
|
||||||
|
# {URI.encode_www_form}[https://docs.ruby-lang.org/en/master/URI.html#method-c-encode_www_form].
|
||||||
|
#
|
||||||
|
# With only argument +params+ given,
|
||||||
|
# sets the body to a URL-encoded string with the default separator <tt>'&'</tt>:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Post.new('example.com')
|
||||||
|
#
|
||||||
|
# req.set_form_data(q: 'ruby', lang: 'en')
|
||||||
|
# req.body # => "q=ruby&lang=en"
|
||||||
|
# req['Content-Type'] # => "application/x-www-form-urlencoded"
|
||||||
|
#
|
||||||
|
# req.set_form_data([['q', 'ruby'], ['lang', 'en']])
|
||||||
|
# req.body # => "q=ruby&lang=en"
|
||||||
|
#
|
||||||
|
# req.set_form_data(q: ['ruby', 'perl'], lang: 'en')
|
||||||
|
# req.body # => "q=ruby&q=perl&lang=en"
|
||||||
|
#
|
||||||
|
# req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']])
|
||||||
|
# req.body # => "q=ruby&q=perl&lang=en"
|
||||||
|
#
|
||||||
|
# With string argument +sep+ also given,
|
||||||
|
# uses that string as the separator:
|
||||||
|
#
|
||||||
|
# req.set_form_data({q: 'ruby', lang: 'en'}, '|')
|
||||||
|
# req.body # => "q=ruby|lang=en"
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPHeader#form_data= is an alias for Gem::Net::HTTPHeader#set_form_data.
|
||||||
|
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
|
||||||
|
|
||||||
|
# Stores form data to be used in a +POST+ or +PUT+ request.
|
||||||
|
#
|
||||||
|
# The form data given in +params+ consists of zero or more fields;
|
||||||
|
# each field is:
|
||||||
|
#
|
||||||
|
# - A scalar value.
|
||||||
|
# - A name/value pair.
|
||||||
|
# - An IO stream opened for reading.
|
||||||
|
#
|
||||||
|
# Argument +params+ should be an
|
||||||
|
# {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
|
||||||
|
# (method <tt>params.map</tt> will be called),
|
||||||
|
# and is often an array or hash.
|
||||||
|
#
|
||||||
|
# First, we set up a request:
|
||||||
|
#
|
||||||
|
# _uri = uri.dup
|
||||||
|
# _uri.path ='/posts'
|
||||||
|
# req = Gem::Net::HTTP::Post.new(_uri)
|
||||||
|
#
|
||||||
|
# <b>Argument +params+ As an Array</b>
|
||||||
|
#
|
||||||
|
# When +params+ is an array,
|
||||||
|
# each of its elements is a subarray that defines a field;
|
||||||
|
# the subarray may contain:
|
||||||
|
#
|
||||||
|
# - One string:
|
||||||
|
#
|
||||||
|
# req.set_form([['foo'], ['bar'], ['baz']])
|
||||||
|
#
|
||||||
|
# - Two strings:
|
||||||
|
#
|
||||||
|
# req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]])
|
||||||
|
#
|
||||||
|
# - When argument +enctype+ (see below) is given as
|
||||||
|
# <tt>'multipart/form-data'</tt>:
|
||||||
|
#
|
||||||
|
# - A string name and an IO stream opened for reading:
|
||||||
|
#
|
||||||
|
# require 'stringio'
|
||||||
|
# req.set_form([['file', StringIO.new('Ruby is cool.')]])
|
||||||
|
#
|
||||||
|
# - A string name, an IO stream opened for reading,
|
||||||
|
# and an options hash, which may contain these entries:
|
||||||
|
#
|
||||||
|
# - +:filename+: The name of the file to use.
|
||||||
|
# - +:content_type+: The content type of the uploaded file.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# req.set_form([['file', file, {filename: "other-filename.foo"}]]
|
||||||
|
#
|
||||||
|
# The various forms may be mixed:
|
||||||
|
#
|
||||||
|
# req.set_form(['foo', %w[bar 1], ['file', file]])
|
||||||
|
#
|
||||||
|
# <b>Argument +params+ As a Hash</b>
|
||||||
|
#
|
||||||
|
# When +params+ is a hash,
|
||||||
|
# each of its entries is a name/value pair that defines a field:
|
||||||
|
#
|
||||||
|
# - The name is a string.
|
||||||
|
# - The value may be:
|
||||||
|
#
|
||||||
|
# - +nil+.
|
||||||
|
# - Another string.
|
||||||
|
# - An IO stream opened for reading
|
||||||
|
# (only when argument +enctype+ -- see below -- is given as
|
||||||
|
# <tt>'multipart/form-data'</tt>).
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# # Nil-valued fields.
|
||||||
|
# req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil})
|
||||||
|
#
|
||||||
|
# # String-valued fields.
|
||||||
|
# req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2})
|
||||||
|
#
|
||||||
|
# # IO-valued field.
|
||||||
|
# require 'stringio'
|
||||||
|
# req.set_form({'file' => StringIO.new('Ruby is cool.')})
|
||||||
|
#
|
||||||
|
# # Mixture of fields.
|
||||||
|
# req.set_form({'foo' => nil, 'bar' => 1, 'file' => file})
|
||||||
|
#
|
||||||
|
# Optional argument +enctype+ specifies the value to be given
|
||||||
|
# to field <tt>'Content-Type'</tt>, and must be one of:
|
||||||
|
#
|
||||||
|
# - <tt>'application/x-www-form-urlencoded'</tt> (the default).
|
||||||
|
# - <tt>'multipart/form-data'</tt>;
|
||||||
|
# see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578].
|
||||||
|
#
|
||||||
|
# Optional argument +formopt+ is a hash of options
|
||||||
|
# (applicable only when argument +enctype+
|
||||||
|
# is <tt>'multipart/form-data'</tt>)
|
||||||
|
# that may include the following entries:
|
||||||
|
#
|
||||||
|
# - +:boundary+: The value is the boundary string for the multipart message.
|
||||||
|
# If not given, the boundary is a random string.
|
||||||
|
# See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1].
|
||||||
|
# - +:charset+: Value is the character set for the form submission.
|
||||||
|
# Field names and values of non-file fields should be encoded with this charset.
|
||||||
|
#
|
||||||
|
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
|
||||||
|
|
||||||
|
# Sets header <tt>'Authorization'</tt> using the given
|
||||||
|
# +account+ and +password+ strings:
|
||||||
|
#
|
||||||
|
# req.basic_auth('my_account', 'my_password')
|
||||||
|
# req['Authorization']
|
||||||
|
# # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
|
||||||
|
#
|
||||||
|
def basic_auth(account, password)
|
||||||
|
@header['authorization'] = [basic_encode(account, password)]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets header <tt>'Proxy-Authorization'</tt> using the given
|
||||||
|
# +account+ and +password+ strings:
|
||||||
|
#
|
||||||
|
# req.proxy_basic_auth('my_account', 'my_password')
|
||||||
|
# req['Proxy-Authorization']
|
||||||
|
# # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
|
||||||
|
#
|
||||||
|
def proxy_basic_auth(account, password)
|
||||||
|
@header['proxy-authorization'] = [basic_encode(account, password)]
|
||||||
|
end
|
||||||
|
|
||||||
|
def basic_encode(account, password)
|
||||||
|
'Basic ' + ["#{account}:#{password}"].pack('m0')
|
||||||
|
end
|
||||||
|
private :basic_encode
|
||||||
|
|
||||||
|
# Returns whether the HTTP session is to be closed.
|
||||||
|
def connection_close?
|
||||||
|
token = /(?:\A|,)\s*close\s*(?:\z|,)/i
|
||||||
|
@header['connection']&.grep(token) {return true}
|
||||||
|
@header['proxy-connection']&.grep(token) {return true}
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns whether the HTTP session is to be kept alive.
|
||||||
|
def connection_keep_alive?
|
||||||
|
token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
|
||||||
|
@header['connection']&.grep(token) {return true}
|
||||||
|
@header['proxy-connection']&.grep(token) {return true}
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
17
lib/rubygems/net-http/lib/net/http/proxy_delta.rb
Normal file
17
lib/rubygems/net-http/lib/net/http/proxy_delta.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
module Gem::Net::HTTP::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
|
||||||
|
|
88
lib/rubygems/net-http/lib/net/http/request.rb
Normal file
88
lib/rubygems/net-http/lib/net/http/request.rb
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This class is the base class for \Gem::Net::HTTP request classes.
|
||||||
|
# The class should not be used directly;
|
||||||
|
# instead you should use its subclasses, listed below.
|
||||||
|
#
|
||||||
|
# == Creating a Request
|
||||||
|
#
|
||||||
|
# An request object may be created with either a URI or a string hostname:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('https://jsonplaceholder.typicode.com/')
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri.hostname) # => #<Gem::Net::HTTP::Get GET>
|
||||||
|
#
|
||||||
|
# And with any of the subclasses:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Head.new(uri) # => #<Gem::Net::HTTP::Head HEAD>
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
|
||||||
|
# req = Gem::Net::HTTP::Put.new(uri) # => #<Gem::Net::HTTP::Put PUT>
|
||||||
|
# # ...
|
||||||
|
#
|
||||||
|
# The new instance is suitable for use as the argument to Gem::Net::HTTP#request.
|
||||||
|
#
|
||||||
|
# == Request Headers
|
||||||
|
#
|
||||||
|
# A new request object has these header fields by default:
|
||||||
|
#
|
||||||
|
# req.to_hash
|
||||||
|
# # =>
|
||||||
|
# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
|
||||||
|
# "accept"=>["*/*"],
|
||||||
|
# "user-agent"=>["Ruby"],
|
||||||
|
# "host"=>["jsonplaceholder.typicode.com"]}
|
||||||
|
#
|
||||||
|
# See:
|
||||||
|
#
|
||||||
|
# - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding]
|
||||||
|
# and {Compression and Decompression}[rdoc-ref:Gem::Net::HTTP@Compression+and+Decompression].
|
||||||
|
# - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header].
|
||||||
|
# - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header].
|
||||||
|
# - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header].
|
||||||
|
#
|
||||||
|
# You can add headers or override default headers:
|
||||||
|
#
|
||||||
|
# # res = Gem::Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'})
|
||||||
|
#
|
||||||
|
# This class (and therefore its subclasses) also includes (indirectly)
|
||||||
|
# module Gem::Net::HTTPHeader, which gives access to its
|
||||||
|
# {methods for setting headers}[rdoc-ref:Gem::Net::HTTPHeader@Setters].
|
||||||
|
#
|
||||||
|
# == Request Subclasses
|
||||||
|
#
|
||||||
|
# Subclasses for HTTP requests:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP::Get
|
||||||
|
# - Gem::Net::HTTP::Head
|
||||||
|
# - Gem::Net::HTTP::Post
|
||||||
|
# - Gem::Net::HTTP::Put
|
||||||
|
# - Gem::Net::HTTP::Delete
|
||||||
|
# - Gem::Net::HTTP::Options
|
||||||
|
# - Gem::Net::HTTP::Trace
|
||||||
|
# - Gem::Net::HTTP::Patch
|
||||||
|
#
|
||||||
|
# Subclasses for WebDAV requests:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP::Propfind
|
||||||
|
# - Gem::Net::HTTP::Proppatch
|
||||||
|
# - Gem::Net::HTTP::Mkcol
|
||||||
|
# - Gem::Net::HTTP::Copy
|
||||||
|
# - Gem::Net::HTTP::Move
|
||||||
|
# - Gem::Net::HTTP::Lock
|
||||||
|
# - Gem::Net::HTTP::Unlock
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTPRequest < Gem::Net::HTTPGenericRequest
|
||||||
|
# Creates an HTTP request object for +path+.
|
||||||
|
#
|
||||||
|
# +initheader+ are the default headers to use. Gem::Net::HTTP adds
|
||||||
|
# Accept-Encoding to enable compression of the response body unless
|
||||||
|
# Accept-Encoding or Range are supplied in +initheader+.
|
||||||
|
|
||||||
|
def initialize(path, initheader = nil)
|
||||||
|
super self.class::METHOD,
|
||||||
|
self.class::REQUEST_HAS_BODY,
|
||||||
|
self.class::RESPONSE_HAS_BODY,
|
||||||
|
path, initheader
|
||||||
|
end
|
||||||
|
end
|
425
lib/rubygems/net-http/lib/net/http/requests.rb
Normal file
425
lib/rubygems/net-http/lib/net/http/requests.rb
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# HTTP/1.1 methods --- RFC2616
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri) # => #<Gem::Net::HTTP::Get GET>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: optional.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP.get: sends +GET+ request, returns response body.
|
||||||
|
# - Gem::Net::HTTP#get: sends +GET+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'GET'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Head.new(uri) # => #<Gem::Net::HTTP::Head HEAD>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: optional.
|
||||||
|
# - Response body: no.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'HEAD'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# uri.path = '/posts'
|
||||||
|
# req = Gem::Net::HTTP::Post.new(uri) # => #<Gem::Net::HTTP::Post POST>
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.content_type = 'application/json'
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: yes.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP.post: sends +POST+ request, returns response object.
|
||||||
|
# - Gem::Net::HTTP#post: sends +POST+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'POST'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# uri.path = '/posts'
|
||||||
|
# req = Gem::Net::HTTP::Put.new(uri) # => #<Gem::Net::HTTP::Put PUT>
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.content_type = 'application/json'
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: yes.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'PUT'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# uri.path = '/posts/1'
|
||||||
|
# req = Gem::Net::HTTP::Delete.new(uri) # => #<Gem::Net::HTTP::Delete DELETE>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: optional.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'DELETE'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Options.new(uri) # => #<Gem::Net::HTTP::Options OPTIONS>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: optional.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'OPTIONS'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Trace.new(uri) # => #<Gem::Net::HTTP::Trace TRACE>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: no.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'TRACE'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# uri.path = '/posts'
|
||||||
|
# req = Gem::Net::HTTP::Patch.new(uri) # => #<Gem::Net::HTTP::Patch PATCH>
|
||||||
|
# req.body = '{"title": "foo","body": "bar","userId": 1}'
|
||||||
|
# req.content_type = 'application/json'
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Properties:
|
||||||
|
#
|
||||||
|
# - Request body: yes.
|
||||||
|
# - Response body: yes.
|
||||||
|
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
|
||||||
|
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
|
||||||
|
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'PATCH'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# WebDAV methods --- RFC2518
|
||||||
|
#
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Propfind.new(uri) # => #<Gem::Net::HTTP::Propfind PROPFIND>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'PROPFIND'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Proppatch.new(uri) # => #<Gem::Net::HTTP::Proppatch PROPPATCH>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'PROPPATCH'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Mkcol.new(uri) # => #<Gem::Net::HTTP::Mkcol MKCOL>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'MKCOL'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Copy.new(uri) # => #<Gem::Net::HTTP::Copy COPY>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'COPY'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Move.new(uri) # => #<Gem::Net::HTTP::Move MOVE>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'MOVE'
|
||||||
|
REQUEST_HAS_BODY = false
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Lock.new(uri) # => #<Gem::Net::HTTP::Lock LOCK>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'LOCK'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# \Class for representing
|
||||||
|
# {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]:
|
||||||
|
#
|
||||||
|
# require 'rubygems/net-http/lib/net/http'
|
||||||
|
# uri = URI('http://example.com')
|
||||||
|
# hostname = uri.hostname # => "example.com"
|
||||||
|
# req = Gem::Net::HTTP::Unlock.new(uri) # => #<Gem::Net::HTTP::Unlock UNLOCK>
|
||||||
|
# res = Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# See {Request Headers}[rdoc-ref:Gem::Net::HTTPRequest@Request+Headers].
|
||||||
|
#
|
||||||
|
# Related:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest
|
||||||
|
METHOD = 'UNLOCK'
|
||||||
|
REQUEST_HAS_BODY = true
|
||||||
|
RESPONSE_HAS_BODY = true
|
||||||
|
end
|
||||||
|
|
738
lib/rubygems/net-http/lib/net/http/response.rb
Normal file
738
lib/rubygems/net-http/lib/net/http/response.rb
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This class is the base class for \Gem::Net::HTTP response classes.
|
||||||
|
#
|
||||||
|
# == About the Examples
|
||||||
|
#
|
||||||
|
# :include: doc/net-http/examples.rdoc
|
||||||
|
#
|
||||||
|
# == Returned Responses
|
||||||
|
#
|
||||||
|
# \Method Gem::Net::HTTP.get_response returns
|
||||||
|
# an instance of one of the subclasses of \Gem::Net::HTTPResponse:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP.get_response(uri)
|
||||||
|
# # => #<Gem::Net::HTTPOK 200 OK readbody=true>
|
||||||
|
# Gem::Net::HTTP.get_response(hostname, '/nosuch')
|
||||||
|
# # => #<Gem::Net::HTTPNotFound 404 Not Found readbody=true>
|
||||||
|
#
|
||||||
|
# As does method Gem::Net::HTTP#request:
|
||||||
|
#
|
||||||
|
# req = Gem::Net::HTTP::Get.new(uri)
|
||||||
|
# Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# http.request(req)
|
||||||
|
# end # => #<Gem::Net::HTTPOK 200 OK readbody=true>
|
||||||
|
#
|
||||||
|
# \Class \Gem::Net::HTTPResponse includes module Gem::Net::HTTPHeader,
|
||||||
|
# which provides access to response header values via (among others):
|
||||||
|
#
|
||||||
|
# - \Hash-like method <tt>[]</tt>.
|
||||||
|
# - Specific reader methods, such as +content_type+.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# res = Gem::Net::HTTP.get_response(uri) # => #<Gem::Net::HTTPOK 200 OK readbody=true>
|
||||||
|
# res['Content-Type'] # => "text/html; charset=UTF-8"
|
||||||
|
# res.content_type # => "text/html"
|
||||||
|
#
|
||||||
|
# == Response Subclasses
|
||||||
|
#
|
||||||
|
# \Class \Gem::Net::HTTPResponse has a subclass for each
|
||||||
|
# {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes].
|
||||||
|
# You can look up the response class for a given code:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTPResponse::CODE_TO_OBJ['200'] # => Gem::Net::HTTPOK
|
||||||
|
# Gem::Net::HTTPResponse::CODE_TO_OBJ['400'] # => Gem::Net::HTTPBadRequest
|
||||||
|
# Gem::Net::HTTPResponse::CODE_TO_OBJ['404'] # => Gem::Net::HTTPNotFound
|
||||||
|
#
|
||||||
|
# And you can retrieve the status code for a response object:
|
||||||
|
#
|
||||||
|
# Gem::Net::HTTP.get_response(uri).code # => "200"
|
||||||
|
# Gem::Net::HTTP.get_response(hostname, '/nosuch').code # => "404"
|
||||||
|
#
|
||||||
|
# The response subclasses (indentation shows class hierarchy):
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPUnknownResponse (for unhandled \HTTP extensions).
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPInformation:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPContinue (100)
|
||||||
|
# - Gem::Net::HTTPSwitchProtocol (101)
|
||||||
|
# - Gem::Net::HTTPProcessing (102)
|
||||||
|
# - Gem::Net::HTTPEarlyHints (103)
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPSuccess:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPOK (200)
|
||||||
|
# - Gem::Net::HTTPCreated (201)
|
||||||
|
# - Gem::Net::HTTPAccepted (202)
|
||||||
|
# - Gem::Net::HTTPNonAuthoritativeInformation (203)
|
||||||
|
# - Gem::Net::HTTPNoContent (204)
|
||||||
|
# - Gem::Net::HTTPResetContent (205)
|
||||||
|
# - Gem::Net::HTTPPartialContent (206)
|
||||||
|
# - Gem::Net::HTTPMultiStatus (207)
|
||||||
|
# - Gem::Net::HTTPAlreadyReported (208)
|
||||||
|
# - Gem::Net::HTTPIMUsed (226)
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPRedirection:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPMultipleChoices (300)
|
||||||
|
# - Gem::Net::HTTPMovedPermanently (301)
|
||||||
|
# - Gem::Net::HTTPFound (302)
|
||||||
|
# - Gem::Net::HTTPSeeOther (303)
|
||||||
|
# - Gem::Net::HTTPNotModified (304)
|
||||||
|
# - Gem::Net::HTTPUseProxy (305)
|
||||||
|
# - Gem::Net::HTTPTemporaryRedirect (307)
|
||||||
|
# - Gem::Net::HTTPPermanentRedirect (308)
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPClientError:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPBadRequest (400)
|
||||||
|
# - Gem::Net::HTTPUnauthorized (401)
|
||||||
|
# - Gem::Net::HTTPPaymentRequired (402)
|
||||||
|
# - Gem::Net::HTTPForbidden (403)
|
||||||
|
# - Gem::Net::HTTPNotFound (404)
|
||||||
|
# - Gem::Net::HTTPMethodNotAllowed (405)
|
||||||
|
# - Gem::Net::HTTPNotAcceptable (406)
|
||||||
|
# - Gem::Net::HTTPProxyAuthenticationRequired (407)
|
||||||
|
# - Gem::Net::HTTPRequestTimeOut (408)
|
||||||
|
# - Gem::Net::HTTPConflict (409)
|
||||||
|
# - Gem::Net::HTTPGone (410)
|
||||||
|
# - Gem::Net::HTTPLengthRequired (411)
|
||||||
|
# - Gem::Net::HTTPPreconditionFailed (412)
|
||||||
|
# - Gem::Net::HTTPRequestEntityTooLarge (413)
|
||||||
|
# - Gem::Net::HTTPRequestURITooLong (414)
|
||||||
|
# - Gem::Net::HTTPUnsupportedMediaType (415)
|
||||||
|
# - Gem::Net::HTTPRequestedRangeNotSatisfiable (416)
|
||||||
|
# - Gem::Net::HTTPExpectationFailed (417)
|
||||||
|
# - Gem::Net::HTTPMisdirectedRequest (421)
|
||||||
|
# - Gem::Net::HTTPUnprocessableEntity (422)
|
||||||
|
# - Gem::Net::HTTPLocked (423)
|
||||||
|
# - Gem::Net::HTTPFailedDependency (424)
|
||||||
|
# - Gem::Net::HTTPUpgradeRequired (426)
|
||||||
|
# - Gem::Net::HTTPPreconditionRequired (428)
|
||||||
|
# - Gem::Net::HTTPTooManyRequests (429)
|
||||||
|
# - Gem::Net::HTTPRequestHeaderFieldsTooLarge (431)
|
||||||
|
# - Gem::Net::HTTPUnavailableForLegalReasons (451)
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPServerError:
|
||||||
|
#
|
||||||
|
# - Gem::Net::HTTPInternalServerError (500)
|
||||||
|
# - Gem::Net::HTTPNotImplemented (501)
|
||||||
|
# - Gem::Net::HTTPBadGateway (502)
|
||||||
|
# - Gem::Net::HTTPServiceUnavailable (503)
|
||||||
|
# - Gem::Net::HTTPGatewayTimeOut (504)
|
||||||
|
# - Gem::Net::HTTPVersionNotSupported (505)
|
||||||
|
# - Gem::Net::HTTPVariantAlsoNegotiates (506)
|
||||||
|
# - Gem::Net::HTTPInsufficientStorage (507)
|
||||||
|
# - Gem::Net::HTTPLoopDetected (508)
|
||||||
|
# - Gem::Net::HTTPNotExtended (510)
|
||||||
|
# - Gem::Net::HTTPNetworkAuthenticationRequired (511)
|
||||||
|
#
|
||||||
|
# There is also the Gem::Net::HTTPBadResponse exception which is raised when
|
||||||
|
# there is a protocol error.
|
||||||
|
#
|
||||||
|
class Gem::Net::HTTPResponse
|
||||||
|
class << self
|
||||||
|
# true if the response has a body.
|
||||||
|
def body_permitted?
|
||||||
|
self::HAS_BODY
|
||||||
|
end
|
||||||
|
|
||||||
|
def exception_type # :nodoc: internal use only
|
||||||
|
self::EXCEPTION_TYPE
|
||||||
|
end
|
||||||
|
|
||||||
|
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 Gem::Net::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
|
||||||
|
Gem::Net::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 Gem::Net::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 Gem::Net::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
|
||||||
|
@uri = nil
|
||||||
|
@decode_content = false
|
||||||
|
@body_encoding = false
|
||||||
|
@ignore_eof = true
|
||||||
|
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
|
||||||
|
|
||||||
|
# The URI used to fetch this response. The response URI is only available
|
||||||
|
# if a URI was used to create the request.
|
||||||
|
attr_reader :uri
|
||||||
|
|
||||||
|
# Set to true automatically when the request did not contain an
|
||||||
|
# Accept-Encoding header from the user.
|
||||||
|
attr_accessor :decode_content
|
||||||
|
|
||||||
|
# Returns the value set by body_encoding=, or +false+ if none;
|
||||||
|
# see #body_encoding=.
|
||||||
|
attr_reader :body_encoding
|
||||||
|
|
||||||
|
# Sets the encoding that should be used when reading the body:
|
||||||
|
#
|
||||||
|
# - If the given value is an Encoding object, that encoding will be used.
|
||||||
|
# - Otherwise if the value is a string, the value of
|
||||||
|
# {Encoding#find(value)}[https://docs.ruby-lang.org/en/master/Encoding.html#method-c-find]
|
||||||
|
# will be used.
|
||||||
|
# - Otherwise an encoding will be deduced from the body itself.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# http = Gem::Net::HTTP.new(hostname)
|
||||||
|
# req = Gem::Net::HTTP::Get.new('/')
|
||||||
|
#
|
||||||
|
# http.request(req) do |res|
|
||||||
|
# p res.body.encoding # => #<Encoding:ASCII-8BIT>
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# http.request(req) do |res|
|
||||||
|
# res.body_encoding = "UTF-8"
|
||||||
|
# p res.body.encoding # => #<Encoding:UTF-8>
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
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
|
||||||
|
|
||||||
|
#
|
||||||
|
# response <-> exception relationship
|
||||||
|
#
|
||||||
|
|
||||||
|
def code_type #:nodoc:
|
||||||
|
self.class
|
||||||
|
end
|
||||||
|
|
||||||
|
def error! #:nodoc:
|
||||||
|
message = @code
|
||||||
|
message = "#{message} #{@message.dump}" if @message
|
||||||
|
raise error_type().new(message, 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?(Gem::Net::HTTPSuccess)
|
||||||
|
end
|
||||||
|
|
||||||
|
def uri= uri # :nodoc:
|
||||||
|
@uri = uri.dup if uri
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# header (for backward compatibility only; DO NOT USE)
|
||||||
|
#
|
||||||
|
|
||||||
|
def response #:nodoc:
|
||||||
|
warn "Gem::Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def header #:nodoc:
|
||||||
|
warn "Gem::Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_header #:nodoc:
|
||||||
|
warn "Gem::Net::HTTPResponse#read_header is obsolete", uplevel: 1 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.
|
||||||
|
#
|
||||||
|
# If +dest+ argument is given, response is read into that variable,
|
||||||
|
# with <code>dest#<<</code> method (it could be String or IO, or any
|
||||||
|
# other object responding to <code><<</code>).
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
return if @body.nil?
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Returns the string response body;
|
||||||
|
# note that repeated calls for the unmodified body return a cached string:
|
||||||
|
#
|
||||||
|
# path = '/todos/1'
|
||||||
|
# Gem::Net::HTTP.start(hostname) do |http|
|
||||||
|
# res = http.get(path)
|
||||||
|
# p res.body
|
||||||
|
# p http.head(path).body # No body.
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
#
|
||||||
|
# "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}"
|
||||||
|
# nil
|
||||||
|
#
|
||||||
|
def body
|
||||||
|
read_body()
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sets the body of the response to the given value.
|
||||||
|
def body=(value)
|
||||||
|
@body = value
|
||||||
|
end
|
||||||
|
|
||||||
|
alias entity body #:nodoc: obsolete
|
||||||
|
|
||||||
|
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<xml[ \t\r\n]+
|
||||||
|
version[ \t\r\n]*=[ \t\r\n]*(?:"[0-9.]+"|'[0-9.]*')[ \t\r\n]+
|
||||||
|
encoding[ \t\r\n]*=[ \t\r\n]*
|
||||||
|
(?:"([A-Za-z][\-A-Za-z0-9._]*)"|'([A-Za-z][\-A-Za-z0-9._]*)')/x =~ str
|
||||||
|
encoding = $1 || $2 || Encoding::UTF_8
|
||||||
|
when %r{text/html.*}
|
||||||
|
sniff_encoding(str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return encoding
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def sniff_encoding(str, encoding=nil)
|
||||||
|
# the encoding sniffing algorithm
|
||||||
|
# http://www.w3.org/TR/html5/parsing.html#determining-the-character-encoding
|
||||||
|
if enc = scanning_meta(str)
|
||||||
|
enc
|
||||||
|
# 6. last visited page or something
|
||||||
|
# 7. frequency
|
||||||
|
elsif str.ascii_only?
|
||||||
|
Encoding::US_ASCII
|
||||||
|
elsif str.dup.force_encoding(Encoding::UTF_8).valid_encoding?
|
||||||
|
Encoding::UTF_8
|
||||||
|
end
|
||||||
|
# 8. implementation-defined or user-specified
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def check_bom(str)
|
||||||
|
case str.byteslice(0, 2)
|
||||||
|
when "\xFE\xFF"
|
||||||
|
return Encoding::UTF_16BE
|
||||||
|
when "\xFF\xFE"
|
||||||
|
return Encoding::UTF_16LE
|
||||||
|
end
|
||||||
|
if "\xEF\xBB\xBF" == str.byteslice(0, 3)
|
||||||
|
return Encoding::UTF_8
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def scanning_meta(str)
|
||||||
|
require 'strscan'
|
||||||
|
ss = StringScanner.new(str)
|
||||||
|
if ss.scan_until(/<meta[\t\n\f\r ]*/)
|
||||||
|
attrs = {} # attribute_list
|
||||||
|
got_pragma = false
|
||||||
|
need_pragma = nil
|
||||||
|
charset = nil
|
||||||
|
|
||||||
|
# step: Attributes
|
||||||
|
while attr = get_attribute(ss)
|
||||||
|
name, value = *attr
|
||||||
|
next if attrs[name]
|
||||||
|
attrs[name] = true
|
||||||
|
case name
|
||||||
|
when 'http-equiv'
|
||||||
|
got_pragma = true if value == 'content-type'
|
||||||
|
when 'content'
|
||||||
|
encoding = extracting_encodings_from_meta_elements(value)
|
||||||
|
unless charset
|
||||||
|
charset = encoding
|
||||||
|
end
|
||||||
|
need_pragma = true
|
||||||
|
when 'charset'
|
||||||
|
need_pragma = false
|
||||||
|
charset = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# step: Processing
|
||||||
|
return if need_pragma.nil?
|
||||||
|
return if need_pragma && !got_pragma
|
||||||
|
|
||||||
|
charset = Encoding.find(charset) rescue nil
|
||||||
|
return unless charset
|
||||||
|
charset = Encoding::UTF_8 if charset == Encoding::UTF_16
|
||||||
|
return charset # tentative
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_attribute(ss)
|
||||||
|
ss.scan(/[\t\n\f\r \/]*/)
|
||||||
|
if ss.peek(1) == '>'
|
||||||
|
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
|
||||||
|
# Content-Encoding is not supported or zlib is missing, the plain socket is
|
||||||
|
# yielded.
|
||||||
|
#
|
||||||
|
# If a Content-Range header is present, a plain socket is yielded as the
|
||||||
|
# bytes in the range may not be a complete deflate block.
|
||||||
|
|
||||||
|
def inflater # :nodoc:
|
||||||
|
return yield @socket unless Gem::Net::HTTP::HAVE_ZLIB
|
||||||
|
return yield @socket unless @decode_content
|
||||||
|
return yield @socket if self['content-range']
|
||||||
|
|
||||||
|
v = self['content-encoding']
|
||||||
|
case v&.downcase
|
||||||
|
when 'deflate', 'gzip', 'x-gzip' then
|
||||||
|
self.delete 'content-encoding'
|
||||||
|
|
||||||
|
inflate_body_io = Inflater.new(@socket)
|
||||||
|
|
||||||
|
begin
|
||||||
|
yield inflate_body_io
|
||||||
|
success = true
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when 'none', 'identity' then
|
||||||
|
self.delete 'content-encoding'
|
||||||
|
|
||||||
|
yield @socket
|
||||||
|
else
|
||||||
|
yield @socket
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_body_0(dest)
|
||||||
|
inflater do |inflate_body_io|
|
||||||
|
if chunked?
|
||||||
|
read_chunked dest, inflate_body_io
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
@socket = inflate_body_io
|
||||||
|
|
||||||
|
clen = content_length()
|
||||||
|
if clen
|
||||||
|
@socket.read clen, dest, @ignore_eof
|
||||||
|
return
|
||||||
|
end
|
||||||
|
clen = range_length()
|
||||||
|
if clen
|
||||||
|
@socket.read clen, dest
|
||||||
|
return
|
||||||
|
end
|
||||||
|
@socket.read_all dest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF,
|
||||||
|
# etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip
|
||||||
|
# encoded.
|
||||||
|
#
|
||||||
|
# See RFC 2616 section 3.6.1 for definitions
|
||||||
|
|
||||||
|
def read_chunked(dest, chunk_data_io) # :nodoc:
|
||||||
|
total = 0
|
||||||
|
while true
|
||||||
|
line = @socket.readline
|
||||||
|
hexlen = line.slice(/[0-9a-fA-F]+/) or
|
||||||
|
raise Gem::Net::HTTPBadResponse, "wrong chunk size line: #{line}"
|
||||||
|
len = hexlen.hex
|
||||||
|
break if len == 0
|
||||||
|
begin
|
||||||
|
chunk_data_io.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.nil? || @socket.closed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def procdest(dest, block)
|
||||||
|
raise ArgumentError, 'both arg and block given for HTTP method' if
|
||||||
|
dest and block
|
||||||
|
if block
|
||||||
|
Gem::Net::ReadAdapter.new(block)
|
||||||
|
else
|
||||||
|
dest || +''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Inflater is a wrapper around Gem::Net::BufferedIO that transparently inflates
|
||||||
|
# zlib and gzip streams.
|
||||||
|
|
||||||
|
class Inflater # :nodoc:
|
||||||
|
|
||||||
|
##
|
||||||
|
# Creates a new Inflater wrapping +socket+
|
||||||
|
|
||||||
|
def initialize socket
|
||||||
|
@socket = socket
|
||||||
|
# zlib with automatic gzip detection
|
||||||
|
@inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Finishes the inflate stream.
|
||||||
|
|
||||||
|
def finish
|
||||||
|
return if @inflate.total_in == 0
|
||||||
|
@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 Gem::Net::ReadAdapter that inflates each read chunk into +dest+.
|
||||||
|
#
|
||||||
|
# This allows a large response body to be inflated without storing the
|
||||||
|
# entire body in memory.
|
||||||
|
|
||||||
|
def inflate_adapter(dest)
|
||||||
|
if dest.respond_to?(:set_encoding)
|
||||||
|
dest.set_encoding(Encoding::ASCII_8BIT)
|
||||||
|
elsif dest.respond_to?(:force_encoding)
|
||||||
|
dest.force_encoding(Encoding::ASCII_8BIT)
|
||||||
|
end
|
||||||
|
block = proc do |compressed_chunk|
|
||||||
|
@inflate.inflate(compressed_chunk) do |chunk|
|
||||||
|
compressed_chunk.clear
|
||||||
|
dest << chunk
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Gem::Net::ReadAdapter.new(block)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Reads +clen+ bytes from the socket, inflates them, then writes them to
|
||||||
|
# +dest+. +ignore_eof+ is passed down to Gem::Net::BufferedIO#read
|
||||||
|
#
|
||||||
|
# Unlike Gem::Net::BufferedIO#read, this method returns more than +clen+ bytes.
|
||||||
|
# At this time there is no way for a user of Gem::Net::HTTPResponse to read a
|
||||||
|
# specific number of bytes from the HTTP response body, so this internal
|
||||||
|
# API does not return the same number of bytes as were requested.
|
||||||
|
#
|
||||||
|
# See https://bugs.ruby-lang.org/issues/6492 for further discussion.
|
||||||
|
|
||||||
|
def read clen, dest, ignore_eof = false
|
||||||
|
temp_dest = inflate_adapter(dest)
|
||||||
|
|
||||||
|
@socket.read clen, temp_dest, ignore_eof
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Reads the rest of the socket, inflates it, then writes it to +dest+.
|
||||||
|
|
||||||
|
def read_all dest
|
||||||
|
temp_dest = inflate_adapter(dest)
|
||||||
|
|
||||||
|
@socket.read_all temp_dest
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
1174
lib/rubygems/net-http/lib/net/http/responses.rb
Normal file
1174
lib/rubygems/net-http/lib/net/http/responses.rb
Normal file
File diff suppressed because it is too large
Load Diff
84
lib/rubygems/net-http/lib/net/http/status.rb
Normal file
84
lib/rubygems/net-http/lib/net/http/status.rb
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../http'
|
||||||
|
|
||||||
|
if $0 == __FILE__
|
||||||
|
require 'open-uri'
|
||||||
|
File.foreach(__FILE__) do |line|
|
||||||
|
puts line
|
||||||
|
break if line.start_with?('end')
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
puts "Gem::Net::HTTP::STATUS_CODES = {"
|
||||||
|
url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv"
|
||||||
|
URI(url).read.each_line do |line|
|
||||||
|
code, mes, = line.split(',')
|
||||||
|
next if ['(Unused)', 'Unassigned', 'Description'].include?(mes)
|
||||||
|
puts " #{code} => '#{mes}',"
|
||||||
|
end
|
||||||
|
puts "} # :nodoc:"
|
||||||
|
end
|
||||||
|
|
||||||
|
Gem::Net::HTTP::STATUS_CODES = {
|
||||||
|
100 => 'Continue',
|
||||||
|
101 => 'Switching Protocols',
|
||||||
|
102 => 'Processing',
|
||||||
|
103 => 'Early Hints',
|
||||||
|
200 => 'OK',
|
||||||
|
201 => 'Created',
|
||||||
|
202 => 'Accepted',
|
||||||
|
203 => 'Non-Authoritative Information',
|
||||||
|
204 => 'No Content',
|
||||||
|
205 => 'Reset Content',
|
||||||
|
206 => 'Partial Content',
|
||||||
|
207 => 'Multi-Status',
|
||||||
|
208 => 'Already Reported',
|
||||||
|
226 => 'IM Used',
|
||||||
|
300 => 'Multiple Choices',
|
||||||
|
301 => 'Moved Permanently',
|
||||||
|
302 => 'Found',
|
||||||
|
303 => 'See Other',
|
||||||
|
304 => 'Not Modified',
|
||||||
|
305 => 'Use Proxy',
|
||||||
|
307 => 'Temporary Redirect',
|
||||||
|
308 => 'Permanent Redirect',
|
||||||
|
400 => 'Bad Request',
|
||||||
|
401 => 'Unauthorized',
|
||||||
|
402 => 'Payment Required',
|
||||||
|
403 => 'Forbidden',
|
||||||
|
404 => 'Not Found',
|
||||||
|
405 => 'Method Not Allowed',
|
||||||
|
406 => 'Not Acceptable',
|
||||||
|
407 => 'Proxy Authentication Required',
|
||||||
|
408 => 'Request Timeout',
|
||||||
|
409 => 'Conflict',
|
||||||
|
410 => 'Gone',
|
||||||
|
411 => 'Length Required',
|
||||||
|
412 => 'Precondition Failed',
|
||||||
|
413 => 'Content Too Large',
|
||||||
|
414 => 'URI Too Long',
|
||||||
|
415 => 'Unsupported Media Type',
|
||||||
|
416 => 'Range Not Satisfiable',
|
||||||
|
417 => 'Expectation Failed',
|
||||||
|
421 => 'Misdirected Request',
|
||||||
|
422 => 'Unprocessable Content',
|
||||||
|
423 => 'Locked',
|
||||||
|
424 => 'Failed Dependency',
|
||||||
|
425 => 'Too Early',
|
||||||
|
426 => 'Upgrade Required',
|
||||||
|
428 => 'Precondition Required',
|
||||||
|
429 => 'Too Many Requests',
|
||||||
|
431 => 'Request Header Fields Too Large',
|
||||||
|
451 => 'Unavailable For Legal Reasons',
|
||||||
|
500 => 'Internal Server Error',
|
||||||
|
501 => 'Not Implemented',
|
||||||
|
502 => 'Bad Gateway',
|
||||||
|
503 => 'Service Unavailable',
|
||||||
|
504 => 'Gateway Timeout',
|
||||||
|
505 => 'HTTP Version Not Supported',
|
||||||
|
506 => 'Variant Also Negotiates',
|
||||||
|
507 => 'Insufficient Storage',
|
||||||
|
508 => 'Loop Detected',
|
||||||
|
510 => 'Not Extended (OBSOLETED)',
|
||||||
|
511 => 'Network Authentication Required',
|
||||||
|
} # :nodoc:
|
23
lib/rubygems/net-http/lib/net/https.rb
Normal file
23
lib/rubygems/net-http/lib/net/https.rb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
=begin
|
||||||
|
|
||||||
|
= net/https -- SSL/TLS enhancement for Gem::Net::HTTP.
|
||||||
|
|
||||||
|
This file has been merged with net/http. There is no longer any need to
|
||||||
|
require 'rubygems/net-http/lib/net/https' to use HTTPS.
|
||||||
|
|
||||||
|
See Gem::Net::HTTP for details on how to make HTTPS connections.
|
||||||
|
|
||||||
|
== Info
|
||||||
|
'OpenSSL for Ruby 2' project
|
||||||
|
Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
== Licence
|
||||||
|
This program is licensed under the same licence as Ruby.
|
||||||
|
(See the file 'LICENCE'.)
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
require_relative 'http'
|
||||||
|
require 'openssl'
|
544
lib/rubygems/net-protocol/lib/net/protocol.rb
Normal file
544
lib/rubygems/net-protocol/lib/net/protocol.rb
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
|
# = net/protocol.rb
|
||||||
|
#
|
||||||
|
#--
|
||||||
|
# Copyright (c) 1999-2004 Yukihiro Matsumoto
|
||||||
|
# Copyright (c) 1999-2004 Minero Aoki
|
||||||
|
#
|
||||||
|
# written and maintained by Minero Aoki <aamine@loveruby.net>
|
||||||
|
#
|
||||||
|
# This program is free software. You can re-distribute and/or
|
||||||
|
# modify this program under the same terms as Ruby itself,
|
||||||
|
# Ruby Distribute License or GNU General Public License.
|
||||||
|
#
|
||||||
|
# $Id$
|
||||||
|
#++
|
||||||
|
#
|
||||||
|
# WARNING: This file is going to remove.
|
||||||
|
# Do not rely on the implementation written in this file.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'socket'
|
||||||
|
require 'timeout'
|
||||||
|
require 'io/wait'
|
||||||
|
|
||||||
|
module Gem::Net # :nodoc:
|
||||||
|
|
||||||
|
class Protocol #:nodoc: internal use only
|
||||||
|
VERSION = "0.2.2"
|
||||||
|
|
||||||
|
private
|
||||||
|
def Protocol.protocol_param(name, val)
|
||||||
|
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||||
|
def #{name}
|
||||||
|
#{val}
|
||||||
|
end
|
||||||
|
End
|
||||||
|
end
|
||||||
|
|
||||||
|
def ssl_socket_connect(s, timeout)
|
||||||
|
if timeout
|
||||||
|
while true
|
||||||
|
raise Gem::Net::OpenTimeout if timeout <= 0
|
||||||
|
start = Process.clock_gettime Process::CLOCK_MONOTONIC
|
||||||
|
# to_io is required because SSLSocket doesn't have wait_readable yet
|
||||||
|
case s.connect_nonblock(exception: false)
|
||||||
|
when :wait_readable; s.to_io.wait_readable(timeout)
|
||||||
|
when :wait_writable; s.to_io.wait_writable(timeout)
|
||||||
|
else; break
|
||||||
|
end
|
||||||
|
timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||||
|
end
|
||||||
|
else
|
||||||
|
s.connect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolError < StandardError; end
|
||||||
|
class ProtoSyntaxError < ProtocolError; end
|
||||||
|
class ProtoFatalError < ProtocolError; end
|
||||||
|
class ProtoUnknownError < ProtocolError; end
|
||||||
|
class ProtoServerError < ProtocolError; end
|
||||||
|
class ProtoAuthError < ProtocolError; end
|
||||||
|
class ProtoCommandError < ProtocolError; end
|
||||||
|
class ProtoRetriableError < ProtocolError; end
|
||||||
|
ProtocRetryError = ProtoRetriableError
|
||||||
|
|
||||||
|
##
|
||||||
|
# OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot
|
||||||
|
# be created within the open_timeout.
|
||||||
|
|
||||||
|
class OpenTimeout < Timeout::Error; end
|
||||||
|
|
||||||
|
##
|
||||||
|
# ReadTimeout, a subclass of Timeout::Error, is raised if a chunk of the
|
||||||
|
# response cannot be read within the read_timeout.
|
||||||
|
|
||||||
|
class ReadTimeout < Timeout::Error
|
||||||
|
def initialize(io = nil)
|
||||||
|
@io = io
|
||||||
|
end
|
||||||
|
attr_reader :io
|
||||||
|
|
||||||
|
def message
|
||||||
|
msg = super
|
||||||
|
if @io
|
||||||
|
msg = "#{msg} with #{@io.inspect}"
|
||||||
|
end
|
||||||
|
msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# WriteTimeout, a subclass of Timeout::Error, is raised if a chunk of the
|
||||||
|
# response cannot be written within the write_timeout. Not raised on Windows.
|
||||||
|
|
||||||
|
class WriteTimeout < Timeout::Error
|
||||||
|
def initialize(io = nil)
|
||||||
|
@io = io
|
||||||
|
end
|
||||||
|
attr_reader :io
|
||||||
|
|
||||||
|
def message
|
||||||
|
msg = super
|
||||||
|
if @io
|
||||||
|
msg = "#{msg} with #{@io.inspect}"
|
||||||
|
end
|
||||||
|
msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class BufferedIO #:nodoc: internal use only
|
||||||
|
def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil)
|
||||||
|
@io = io
|
||||||
|
@read_timeout = read_timeout
|
||||||
|
@write_timeout = write_timeout
|
||||||
|
@continue_timeout = continue_timeout
|
||||||
|
@debug_output = debug_output
|
||||||
|
@rbuf = ''.b
|
||||||
|
@rbuf_empty = true
|
||||||
|
@rbuf_offset = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :io
|
||||||
|
attr_accessor :read_timeout
|
||||||
|
attr_accessor :write_timeout
|
||||||
|
attr_accessor :continue_timeout
|
||||||
|
attr_accessor :debug_output
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<#{self.class} io=#{@io}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def eof?
|
||||||
|
@io.eof?
|
||||||
|
end
|
||||||
|
|
||||||
|
def closed?
|
||||||
|
@io.closed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@io.close
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read
|
||||||
|
#
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
|
def read(len, dest = ''.b, ignore_eof = false)
|
||||||
|
LOG "reading #{len} bytes..."
|
||||||
|
read_bytes = 0
|
||||||
|
begin
|
||||||
|
while read_bytes + rbuf_size < len
|
||||||
|
if s = rbuf_consume_all
|
||||||
|
read_bytes += s.bytesize
|
||||||
|
dest << s
|
||||||
|
end
|
||||||
|
rbuf_fill
|
||||||
|
end
|
||||||
|
s = rbuf_consume(len - read_bytes)
|
||||||
|
read_bytes += s.bytesize
|
||||||
|
dest << s
|
||||||
|
rescue EOFError
|
||||||
|
raise unless ignore_eof
|
||||||
|
end
|
||||||
|
LOG "read #{read_bytes} bytes"
|
||||||
|
dest
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_all(dest = ''.b)
|
||||||
|
LOG 'reading all...'
|
||||||
|
read_bytes = 0
|
||||||
|
begin
|
||||||
|
while true
|
||||||
|
if s = rbuf_consume_all
|
||||||
|
read_bytes += s.bytesize
|
||||||
|
dest << s
|
||||||
|
end
|
||||||
|
rbuf_fill
|
||||||
|
end
|
||||||
|
rescue EOFError
|
||||||
|
;
|
||||||
|
end
|
||||||
|
LOG "read #{read_bytes} bytes"
|
||||||
|
dest
|
||||||
|
end
|
||||||
|
|
||||||
|
def readuntil(terminator, ignore_eof = false)
|
||||||
|
offset = @rbuf_offset
|
||||||
|
begin
|
||||||
|
until idx = @rbuf.index(terminator, offset)
|
||||||
|
offset = @rbuf.bytesize
|
||||||
|
rbuf_fill
|
||||||
|
end
|
||||||
|
return rbuf_consume(idx + terminator.bytesize - @rbuf_offset)
|
||||||
|
rescue EOFError
|
||||||
|
raise unless ignore_eof
|
||||||
|
return rbuf_consume
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def readline
|
||||||
|
readuntil("\n").chop
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
BUFSIZE = 1024 * 16
|
||||||
|
|
||||||
|
def rbuf_fill
|
||||||
|
tmp = @rbuf_empty ? @rbuf : nil
|
||||||
|
case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false)
|
||||||
|
when String
|
||||||
|
@rbuf_empty = false
|
||||||
|
if rv.equal?(tmp)
|
||||||
|
@rbuf_offset = 0
|
||||||
|
else
|
||||||
|
@rbuf << rv
|
||||||
|
rv.clear
|
||||||
|
end
|
||||||
|
return
|
||||||
|
when :wait_readable
|
||||||
|
(io = @io.to_io).wait_readable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io)
|
||||||
|
# continue looping
|
||||||
|
when :wait_writable
|
||||||
|
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
|
||||||
|
# http://www.openssl.org/support/faq.html#PROG10
|
||||||
|
(io = @io.to_io).wait_writable(@read_timeout) or raise Gem::Net::ReadTimeout.new(io)
|
||||||
|
# continue looping
|
||||||
|
when nil
|
||||||
|
raise EOFError, 'end of file reached'
|
||||||
|
end while true
|
||||||
|
end
|
||||||
|
|
||||||
|
def rbuf_flush
|
||||||
|
if @rbuf_empty
|
||||||
|
@rbuf.clear
|
||||||
|
@rbuf_offset = 0
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def rbuf_size
|
||||||
|
@rbuf.bytesize - @rbuf_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def rbuf_consume_all
|
||||||
|
rbuf_consume if rbuf_size > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def rbuf_consume(len = nil)
|
||||||
|
if @rbuf_offset == 0 && (len.nil? || len == @rbuf.bytesize)
|
||||||
|
s = @rbuf
|
||||||
|
@rbuf = ''.b
|
||||||
|
@rbuf_offset = 0
|
||||||
|
@rbuf_empty = true
|
||||||
|
elsif len.nil?
|
||||||
|
s = @rbuf.byteslice(@rbuf_offset..-1)
|
||||||
|
@rbuf = ''.b
|
||||||
|
@rbuf_offset = 0
|
||||||
|
@rbuf_empty = true
|
||||||
|
else
|
||||||
|
s = @rbuf.byteslice(@rbuf_offset, len)
|
||||||
|
@rbuf_offset += len
|
||||||
|
@rbuf_empty = @rbuf_offset == @rbuf.bytesize
|
||||||
|
rbuf_flush
|
||||||
|
end
|
||||||
|
|
||||||
|
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
|
||||||
|
s
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write
|
||||||
|
#
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
|
def write(*strs)
|
||||||
|
writing {
|
||||||
|
write0(*strs)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
alias << write
|
||||||
|
|
||||||
|
def writeline(str)
|
||||||
|
writing {
|
||||||
|
write0 str + "\r\n"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def writing
|
||||||
|
@written_bytes = 0
|
||||||
|
@debug_output << '<- ' if @debug_output
|
||||||
|
yield
|
||||||
|
@debug_output << "\n" if @debug_output
|
||||||
|
bytes = @written_bytes
|
||||||
|
@written_bytes = nil
|
||||||
|
bytes
|
||||||
|
end
|
||||||
|
|
||||||
|
def write0(*strs)
|
||||||
|
@debug_output << strs.map(&:dump).join if @debug_output
|
||||||
|
orig_written_bytes = @written_bytes
|
||||||
|
strs.each_with_index do |str, i|
|
||||||
|
need_retry = true
|
||||||
|
case len = @io.write_nonblock(str, exception: false)
|
||||||
|
when Integer
|
||||||
|
@written_bytes += len
|
||||||
|
len -= str.bytesize
|
||||||
|
if len == 0
|
||||||
|
if strs.size == i+1
|
||||||
|
return @written_bytes - orig_written_bytes
|
||||||
|
else
|
||||||
|
need_retry = false
|
||||||
|
# next string
|
||||||
|
end
|
||||||
|
elsif len < 0
|
||||||
|
str = str.byteslice(len, -len)
|
||||||
|
else # len > 0
|
||||||
|
need_retry = false
|
||||||
|
# next string
|
||||||
|
end
|
||||||
|
# continue looping
|
||||||
|
when :wait_writable
|
||||||
|
(io = @io.to_io).wait_writable(@write_timeout) or raise Gem::Net::WriteTimeout.new(io)
|
||||||
|
# continue looping
|
||||||
|
end while need_retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Logging
|
||||||
|
#
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def LOG_off
|
||||||
|
@save_debug_out = @debug_output
|
||||||
|
@debug_output = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def LOG_on
|
||||||
|
@debug_output = @save_debug_out
|
||||||
|
end
|
||||||
|
|
||||||
|
def LOG(msg)
|
||||||
|
return unless @debug_output
|
||||||
|
@debug_output << msg + "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class InternetMessageIO < BufferedIO #:nodoc: internal use only
|
||||||
|
def initialize(*, **)
|
||||||
|
super
|
||||||
|
@wbuf = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read
|
||||||
|
#
|
||||||
|
|
||||||
|
def each_message_chunk
|
||||||
|
LOG 'reading message...'
|
||||||
|
LOG_off()
|
||||||
|
read_bytes = 0
|
||||||
|
while (line = readuntil("\r\n")) != ".\r\n"
|
||||||
|
read_bytes += line.size
|
||||||
|
yield line.delete_prefix('.')
|
||||||
|
end
|
||||||
|
LOG_on()
|
||||||
|
LOG "read message (#{read_bytes} bytes)"
|
||||||
|
end
|
||||||
|
|
||||||
|
# *library private* (cannot handle 'break')
|
||||||
|
def each_list_item
|
||||||
|
while (str = readuntil("\r\n")) != ".\r\n"
|
||||||
|
yield str.chop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_message_0(src)
|
||||||
|
prev = @written_bytes
|
||||||
|
each_crlf_line(src) do |line|
|
||||||
|
write0 dot_stuff(line)
|
||||||
|
end
|
||||||
|
@written_bytes - prev
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write
|
||||||
|
#
|
||||||
|
|
||||||
|
def write_message(src)
|
||||||
|
LOG "writing message from #{src.class}"
|
||||||
|
LOG_off()
|
||||||
|
len = writing {
|
||||||
|
using_each_crlf_line {
|
||||||
|
write_message_0 src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_on()
|
||||||
|
LOG "wrote #{len} bytes"
|
||||||
|
len
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_message_by_block(&block)
|
||||||
|
LOG 'writing message from block'
|
||||||
|
LOG_off()
|
||||||
|
len = writing {
|
||||||
|
using_each_crlf_line {
|
||||||
|
begin
|
||||||
|
block.call(WriteAdapter.new(self.method(:write_message_0)))
|
||||||
|
rescue LocalJumpError
|
||||||
|
# allow `break' from writer block
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_on()
|
||||||
|
LOG "wrote #{len} bytes"
|
||||||
|
len
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def dot_stuff(s)
|
||||||
|
s.sub(/\A\./, '..')
|
||||||
|
end
|
||||||
|
|
||||||
|
def using_each_crlf_line
|
||||||
|
@wbuf = ''.b
|
||||||
|
yield
|
||||||
|
if not @wbuf.empty? # unterminated last line
|
||||||
|
write0 dot_stuff(@wbuf.chomp) + "\r\n"
|
||||||
|
elsif @written_bytes == 0 # empty src
|
||||||
|
write0 "\r\n"
|
||||||
|
end
|
||||||
|
write0 ".\r\n"
|
||||||
|
@wbuf = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_crlf_line(src)
|
||||||
|
buffer_filling(@wbuf, src) do
|
||||||
|
while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/)
|
||||||
|
yield line.chomp("\n") + "\r\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def buffer_filling(buf, src)
|
||||||
|
case src
|
||||||
|
when String # for speeding up.
|
||||||
|
0.step(src.size - 1, 1024) do |i|
|
||||||
|
buf << src[i, 1024]
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
when File # for speeding up.
|
||||||
|
while s = src.read(1024)
|
||||||
|
buf << s
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
else # generic reader
|
||||||
|
src.each do |str|
|
||||||
|
buf << str
|
||||||
|
yield if buf.size > 1024
|
||||||
|
end
|
||||||
|
yield unless buf.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# The writer adapter class
|
||||||
|
#
|
||||||
|
class WriteAdapter
|
||||||
|
def initialize(writer)
|
||||||
|
@writer = writer
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<#{self.class} writer=#{@writer.inspect}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(str)
|
||||||
|
@writer.call(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias print write
|
||||||
|
|
||||||
|
def <<(str)
|
||||||
|
write str
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts(str = '')
|
||||||
|
write str.chomp("\n") + "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def printf(*args)
|
||||||
|
write sprintf(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class ReadAdapter #:nodoc: internal use only
|
||||||
|
def initialize(block)
|
||||||
|
@block = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<#{self.class}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def <<(str)
|
||||||
|
call_block(str, &@block) if @block
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# This method is needed because @block must be called by yield,
|
||||||
|
# not Proc#call. You can see difference when using `break' in
|
||||||
|
# the block.
|
||||||
|
def call_block(str)
|
||||||
|
yield str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
module NetPrivate #:nodoc: obsolete
|
||||||
|
Socket = ::Gem::Net::InternetMessageIO
|
||||||
|
end
|
||||||
|
|
||||||
|
end # module Gem::Net
|
3
lib/rubygems/net/http.rb
Normal file
3
lib/rubygems/net/http.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative "../net-http/lib/net/http"
|
@ -74,7 +74,7 @@ class Gem::RemoteFetcher
|
|||||||
|
|
||||||
def initialize(proxy=nil, dns=nil, headers={})
|
def initialize(proxy=nil, dns=nil, headers={})
|
||||||
require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled
|
require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled
|
||||||
require "net/http"
|
require_relative "net/http"
|
||||||
require "stringio"
|
require "stringio"
|
||||||
require "uri"
|
require "uri"
|
||||||
|
|
||||||
@ -210,17 +210,17 @@ class Gem::RemoteFetcher
|
|||||||
# HTTP Fetcher. Dispatched by +fetch_path+. Use it instead.
|
# HTTP Fetcher. Dispatched by +fetch_path+. Use it instead.
|
||||||
|
|
||||||
def fetch_http(uri, last_modified = nil, head = false, depth = 0)
|
def fetch_http(uri, last_modified = nil, head = false, depth = 0)
|
||||||
fetch_type = head ? Net::HTTP::Head : Net::HTTP::Get
|
fetch_type = head ? Gem::Net::HTTP::Head : Gem::Net::HTTP::Get
|
||||||
response = request uri, fetch_type, last_modified do |req|
|
response = request uri, fetch_type, last_modified do |req|
|
||||||
headers.each {|k,v| req.add_field(k,v) }
|
headers.each {|k,v| req.add_field(k,v) }
|
||||||
end
|
end
|
||||||
|
|
||||||
case response
|
case response
|
||||||
when Net::HTTPOK, Net::HTTPNotModified then
|
when Gem::Net::HTTPOK, Gem::Net::HTTPNotModified then
|
||||||
response.uri = uri
|
response.uri = uri
|
||||||
head ? response : response.body
|
head ? response : response.body
|
||||||
when Net::HTTPMovedPermanently, Net::HTTPFound, Net::HTTPSeeOther,
|
when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther,
|
||||||
Net::HTTPTemporaryRedirect then
|
Gem::Net::HTTPTemporaryRedirect then
|
||||||
raise FetchError.new("too many redirects", uri) if depth > 10
|
raise FetchError.new("too many redirects", uri) if depth > 10
|
||||||
|
|
||||||
unless location = response["Location"]
|
unless location = response["Location"]
|
||||||
@ -305,8 +305,8 @@ class Gem::RemoteFetcher
|
|||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Performs a Net::HTTP request of type +request_class+ on +uri+ returning
|
# Performs a Gem::Net::HTTP request of type +request_class+ on +uri+ returning
|
||||||
# a Net::HTTP response object. request maintains a table of persistent
|
# a Gem::Net::HTTP response object. request maintains a table of persistent
|
||||||
# connections to reduce connect overhead.
|
# connections to reduce connect overhead.
|
||||||
|
|
||||||
def request(uri, request_class, last_modified = nil)
|
def request(uri, request_class, last_modified = nil)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "net/http"
|
require_relative "net/http"
|
||||||
require_relative "user_interaction"
|
require_relative "user_interaction"
|
||||||
|
|
||||||
class Gem::Request
|
class Gem::Request
|
||||||
@ -205,7 +205,7 @@ class Gem::Request
|
|||||||
if request.response_body_permitted? && file_name =~ /\.gem$/
|
if request.response_body_permitted? && file_name =~ /\.gem$/
|
||||||
reporter = ui.download_reporter
|
reporter = ui.download_reporter
|
||||||
response = connection.request(request) do |incomplete_response|
|
response = connection.request(request) do |incomplete_response|
|
||||||
if Net::HTTPOK === incomplete_response
|
if Gem::Net::HTTPOK === incomplete_response
|
||||||
reporter.fetch(file_name, incomplete_response.content_length)
|
reporter.fetch(file_name, incomplete_response.content_length)
|
||||||
downloaded = 0
|
downloaded = 0
|
||||||
data = String.new
|
data = String.new
|
||||||
@ -228,7 +228,7 @@ class Gem::Request
|
|||||||
end
|
end
|
||||||
|
|
||||||
verbose "#{response.code} #{response.message}"
|
verbose "#{response.code} #{response.message}"
|
||||||
rescue Net::HTTPBadResponse
|
rescue Gem::Net::HTTPBadResponse
|
||||||
verbose "bad response"
|
verbose "bad response"
|
||||||
|
|
||||||
reset connection
|
reset connection
|
||||||
@ -237,11 +237,11 @@ class Gem::Request
|
|||||||
|
|
||||||
bad_response = true
|
bad_response = true
|
||||||
retry
|
retry
|
||||||
rescue Net::HTTPFatalError
|
rescue Gem::Net::HTTPFatalError
|
||||||
verbose "fatal error"
|
verbose "fatal error"
|
||||||
|
|
||||||
raise Gem::RemoteFetcher::FetchError.new("fatal error", @uri)
|
raise Gem::RemoteFetcher::FetchError.new("fatal error", @uri)
|
||||||
# HACK: work around EOFError bug in Net::HTTP
|
# HACK: work around EOFError bug in Gem::Net::HTTP
|
||||||
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
|
# NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible
|
||||||
# to install gems.
|
# to install gems.
|
||||||
rescue EOFError, Timeout::Error,
|
rescue EOFError, Timeout::Error,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Gem::Request::ConnectionPools # :nodoc:
|
class Gem::Request::ConnectionPools # :nodoc:
|
||||||
@client = Net::HTTP
|
@client = Gem::Net::HTTP
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
attr_accessor :client
|
attr_accessor :client
|
||||||
|
@ -140,7 +140,7 @@ class Gem::S3URISigner
|
|||||||
end
|
end
|
||||||
|
|
||||||
def ec2_metadata_credentials_json
|
def ec2_metadata_credentials_json
|
||||||
require "net/http"
|
require_relative "net/http"
|
||||||
require_relative "request"
|
require_relative "request"
|
||||||
require_relative "request/connection_pools"
|
require_relative "request/connection_pools"
|
||||||
require "json"
|
require "json"
|
||||||
@ -154,11 +154,11 @@ class Gem::S3URISigner
|
|||||||
def ec2_metadata_request(url)
|
def ec2_metadata_request(url)
|
||||||
uri = URI(url)
|
uri = URI(url)
|
||||||
@request_pool ||= create_request_pool(uri)
|
@request_pool ||= create_request_pool(uri)
|
||||||
request = Gem::Request.new(uri, Net::HTTP::Get, nil, @request_pool)
|
request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool)
|
||||||
response = request.fetch
|
response = request.fetch
|
||||||
|
|
||||||
case response
|
case response
|
||||||
when Net::HTTPOK then
|
when Gem::Net::HTTPOK then
|
||||||
JSON.parse(response.body)
|
JSON.parse(response.body)
|
||||||
else
|
else
|
||||||
raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
|
raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative "helper"
|
require_relative "helper"
|
||||||
require "net/http"
|
require "rubygems/net/http"
|
||||||
require "rubygems/openssl"
|
require "rubygems/openssl"
|
||||||
|
|
||||||
unless Gem::HAVE_OPENSSL
|
unless Gem::HAVE_OPENSSL
|
||||||
@ -28,7 +28,7 @@ class TestGemBundledCA < Gem::TestCase
|
|||||||
|
|
||||||
def assert_https(host)
|
def assert_https(host)
|
||||||
assert true
|
assert true
|
||||||
http = Net::HTTP.new(host, 443)
|
http = Gem::Net::HTTP.new(host, 443)
|
||||||
http.use_ssl = true
|
http.use_ssl = true
|
||||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
http.cert_store = bundled_certificate_store
|
http.cert_store = bundled_certificate_store
|
||||||
|
@ -44,7 +44,7 @@ EOF
|
|||||||
@cmd.show_owners("freewill")
|
@cmd.show_owners("freewill")
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal Net::HTTP::Get, @stub_fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Get, @stub_fetcher.last_request.class
|
||||||
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
||||||
|
|
||||||
assert_match(/Owners for gem: freewill/, @stub_ui.output)
|
assert_match(/Owners for gem: freewill/, @stub_ui.output)
|
||||||
@ -165,7 +165,7 @@ EOF
|
|||||||
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
@cmd.add_owners("freewill", ["user-new1@example.com"])
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @stub_fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @stub_fetcher.last_request.class
|
||||||
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
||||||
assert_equal "email=user-new1%40example.com", @stub_fetcher.last_request.body
|
assert_equal "email=user-new1%40example.com", @stub_fetcher.last_request.body
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ EOF
|
|||||||
@cmd.remove_owners("freewill", ["user-remove1@example.com"])
|
@cmd.remove_owners("freewill", ["user-remove1@example.com"])
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal Net::HTTP::Delete, @stub_fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Delete, @stub_fetcher.last_request.class
|
||||||
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
|
||||||
assert_equal "email=user-remove1%40example.com", @stub_fetcher.last_request.body
|
assert_equal "email=user-remove1%40example.com", @stub_fetcher.last_request.body
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
assert_match(/Pushing gem to #{@host}.../, @ui.output)
|
assert_match(/Pushing gem to #{@host}.../, @ui.output)
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
|
||||||
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
||||||
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
||||||
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
||||||
@ -77,7 +77,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
@cmd.execute
|
@cmd.execute
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
|
||||||
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
||||||
assert_equal "application/octet-stream",
|
assert_equal "application/octet-stream",
|
||||||
@fetcher.last_request["Content-Type"]
|
@fetcher.last_request["Content-Type"]
|
||||||
@ -96,7 +96,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
@cmd.execute
|
@cmd.execute
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
|
||||||
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
||||||
assert_equal "application/octet-stream",
|
assert_equal "application/octet-stream",
|
||||||
@fetcher.last_request["Content-Type"]
|
@fetcher.last_request["Content-Type"]
|
||||||
@ -116,7 +116,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
@cmd.execute
|
@cmd.execute
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
|
||||||
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
||||||
assert_equal "application/octet-stream",
|
assert_equal "application/octet-stream",
|
||||||
@fetcher.last_request["Content-Type"]
|
@fetcher.last_request["Content-Type"]
|
||||||
@ -319,7 +319,7 @@ class TestGemCommandsPushCommand < Gem::TestCase
|
|||||||
|
|
||||||
assert_match(/Pushing gem to #{host}.../, @ui.output)
|
assert_match(/Pushing gem to #{host}.../, @ui.output)
|
||||||
|
|
||||||
assert_equal Net::HTTP::Post, @fetcher.last_request.class
|
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
|
||||||
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
assert_equal Gem.read_binary(@path), @fetcher.last_request.body
|
||||||
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
assert_equal File.size(@path), @fetcher.last_request["Content-Length"].to_i
|
||||||
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
assert_equal "application/octet-stream", @fetcher.last_request["Content-Type"]
|
||||||
|
@ -488,7 +488,7 @@ class TestGemDependencyInstaller < Gem::TestCase
|
|||||||
@fetcher.data["http://gems.example.com/gems/a-1.gem"] = a1_data
|
@fetcher.data["http://gems.example.com/gems/a-1.gem"] = a1_data
|
||||||
|
|
||||||
# compact index is available
|
# compact index is available
|
||||||
compact_index_response = Net::HTTPResponse.new "1.1", 200, "OK"
|
compact_index_response = Gem::Net::HTTPResponse.new "1.1", 200, "OK"
|
||||||
compact_index_response.uri = URI("http://gems.example.com")
|
compact_index_response.uri = URI("http://gems.example.com")
|
||||||
@fetcher.data["http://gems.example.com/"] = compact_index_response
|
@fetcher.data["http://gems.example.com/"] = compact_index_response
|
||||||
|
|
||||||
|
@ -659,13 +659,13 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg==
|
|||||||
def fetcher.request(uri, request_class, last_modified = nil)
|
def fetcher.request(uri, request_class, last_modified = nil)
|
||||||
url = "http://gems.example.com/redirect"
|
url = "http://gems.example.com/redirect"
|
||||||
if defined? @requested
|
if defined? @requested
|
||||||
res = Net::HTTPOK.new nil, 200, nil
|
res = Gem::Net::HTTPOK.new nil, 200, nil
|
||||||
def res.body
|
def res.body
|
||||||
"real_path"
|
"real_path"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@requested = true
|
@requested = true
|
||||||
res = Net::HTTPMovedPermanently.new nil, 301, nil
|
res = Gem::Net::HTTPMovedPermanently.new nil, 301, nil
|
||||||
res.add_field "Location", url
|
res.add_field "Location", url
|
||||||
end
|
end
|
||||||
res
|
res
|
||||||
@ -683,7 +683,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg==
|
|||||||
|
|
||||||
def fetcher.request(uri, request_class, last_modified = nil)
|
def fetcher.request(uri, request_class, last_modified = nil)
|
||||||
url = "http://gems.example.com/redirect"
|
url = "http://gems.example.com/redirect"
|
||||||
res = Net::HTTPMovedPermanently.new nil, 301, nil
|
res = Gem::Net::HTTPMovedPermanently.new nil, 301, nil
|
||||||
res.add_field "Location", url
|
res.add_field "Location", url
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
@ -701,7 +701,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg==
|
|||||||
url = "http://gems.example.com/redirect"
|
url = "http://gems.example.com/redirect"
|
||||||
|
|
||||||
def fetcher.request(uri, request_class, last_modified = nil)
|
def fetcher.request(uri, request_class, last_modified = nil)
|
||||||
res = Net::HTTPMovedPermanently.new nil, 301, nil
|
res = Gem::Net::HTTPMovedPermanently.new nil, 301, nil
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -728,7 +728,7 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg==
|
|||||||
|
|
||||||
def fetcher.request(uri, request_class, last_modified = nil)
|
def fetcher.request(uri, request_class, last_modified = nil)
|
||||||
$fetched_uri = uri
|
$fetched_uri = uri
|
||||||
res = Net::HTTPOK.new nil, 200, nil
|
res = Gem::Net::HTTPOK.new nil, 200, nil
|
||||||
def res.body
|
def res.body
|
||||||
"success"
|
"success"
|
||||||
end
|
end
|
||||||
@ -958,8 +958,8 @@ PeIQQkFng2VVot/WAQbv3ePqWq07g1BBcwIBAg==
|
|||||||
@fetcher = fetcher
|
@fetcher = fetcher
|
||||||
|
|
||||||
assert_throws :block_called do
|
assert_throws :block_called do
|
||||||
fetcher.request URI("http://example"), Net::HTTP::Get do |req|
|
fetcher.request URI("http://example"), Gem::Net::HTTP::Get do |req|
|
||||||
assert_kind_of Net::HTTPGenericRequest, req
|
assert_kind_of Gem::Net::HTTPGenericRequest, req
|
||||||
throw :block_called
|
throw :block_called
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -96,7 +96,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_configure_connection_for_https
|
def test_configure_connection_for_https
|
||||||
connection = Net::HTTP.new "localhost", 443
|
connection = Gem::Net::HTTP.new "localhost", 443
|
||||||
|
|
||||||
request = Class.new(Gem::Request) do
|
request = Class.new(Gem::Request) do
|
||||||
def self.get_cert_files
|
def self.get_cert_files
|
||||||
@ -115,7 +115,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
ssl_ca_cert = Gem.configuration.ssl_ca_cert
|
ssl_ca_cert = Gem.configuration.ssl_ca_cert
|
||||||
Gem.configuration.ssl_ca_cert = CA_CERT_FILE
|
Gem.configuration.ssl_ca_cert = CA_CERT_FILE
|
||||||
|
|
||||||
connection = Net::HTTP.new "localhost", 443
|
connection = Gem::Net::HTTP.new "localhost", 443
|
||||||
|
|
||||||
request = Class.new(Gem::Request) do
|
request = Class.new(Gem::Request) do
|
||||||
def self.get_cert_files
|
def self.get_cert_files
|
||||||
@ -193,7 +193,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
def test_fetch
|
def test_fetch
|
||||||
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
||||||
response = util_stub_net_http(body: :junk, code: 200) do
|
response = util_stub_net_http(body: :junk, code: 200) do
|
||||||
@request = make_request(uri, Net::HTTP::Get, nil, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, nil, nil)
|
||||||
|
|
||||||
@request.fetch
|
@request.fetch
|
||||||
end
|
end
|
||||||
@ -207,7 +207,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
uri = Gem::Uri.new(URI.parse("https://user:pass@example.rubygems/specs.#{Gem.marshal_version}"))
|
uri = Gem::Uri.new(URI.parse("https://user:pass@example.rubygems/specs.#{Gem.marshal_version}"))
|
||||||
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
||||||
use_ui @ui do
|
use_ui @ui do
|
||||||
@request = make_request(uri, Net::HTTP::Get, nil, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, nil, nil)
|
||||||
@request.fetch
|
@request.fetch
|
||||||
end
|
end
|
||||||
c
|
c
|
||||||
@ -224,7 +224,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
|
|
||||||
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
||||||
use_ui @ui do
|
use_ui @ui do
|
||||||
@request = make_request(uri, Net::HTTP::Get, nil, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, nil, nil)
|
||||||
@request.fetch
|
@request.fetch
|
||||||
end
|
end
|
||||||
c
|
c
|
||||||
@ -241,7 +241,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
|
|
||||||
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
conn = util_stub_net_http(body: :junk, code: 200) do |c|
|
||||||
use_ui @ui do
|
use_ui @ui do
|
||||||
@request = make_request(uri, Net::HTTP::Get, nil, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, nil, nil)
|
||||||
@request.fetch
|
@request.fetch
|
||||||
end
|
end
|
||||||
c
|
c
|
||||||
@ -255,7 +255,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
def test_fetch_head
|
def test_fetch_head
|
||||||
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
||||||
response = util_stub_net_http(body: "", code: 200) do |_conn|
|
response = util_stub_net_http(body: "", code: 200) do |_conn|
|
||||||
@request = make_request(uri, Net::HTTP::Get, nil, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, nil, nil)
|
||||||
@request.fetch
|
@request.fetch
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ class TestGemRequest < Gem::TestCase
|
|||||||
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
uri = Gem::Uri.new(URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}"))
|
||||||
t = Time.utc(2013, 1, 2, 3, 4, 5)
|
t = Time.utc(2013, 1, 2, 3, 4, 5)
|
||||||
conn, response = util_stub_net_http(body: "", code: 304) do |c|
|
conn, response = util_stub_net_http(body: "", code: 304) do |c|
|
||||||
@request = make_request(uri, Net::HTTP::Get, t, nil)
|
@request = make_request(uri, Gem::Net::HTTP::Get, t, nil)
|
||||||
[c, @request.fetch]
|
[c, @request.fetch]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -437,19 +437,19 @@ class TestGemRequirement < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_marshal_load_attack
|
def test_marshal_load_attack
|
||||||
wa = Net::WriteAdapter.allocate
|
wa = Gem::Net::WriteAdapter.allocate
|
||||||
wa.instance_variable_set(:@socket, self.class)
|
wa.instance_variable_set(:@socket, self.class)
|
||||||
wa.instance_variable_set(:@method_id, :exploit)
|
wa.instance_variable_set(:@method_id, :exploit)
|
||||||
request_set = Gem::RequestSet.allocate
|
request_set = Gem::RequestSet.allocate
|
||||||
request_set.instance_variable_set(:@git_set, "id")
|
request_set.instance_variable_set(:@git_set, "id")
|
||||||
request_set.instance_variable_set(:@sets, wa)
|
request_set.instance_variable_set(:@sets, wa)
|
||||||
wa = Net::WriteAdapter.allocate
|
wa = Gem::Net::WriteAdapter.allocate
|
||||||
wa.instance_variable_set(:@socket, request_set)
|
wa.instance_variable_set(:@socket, request_set)
|
||||||
wa.instance_variable_set(:@method_id, :resolve)
|
wa.instance_variable_set(:@method_id, :resolve)
|
||||||
ent = Gem::Package::TarReader::Entry.allocate
|
ent = Gem::Package::TarReader::Entry.allocate
|
||||||
ent.instance_variable_set(:@read, 0)
|
ent.instance_variable_set(:@read, 0)
|
||||||
ent.instance_variable_set(:@header, "aaa")
|
ent.instance_variable_set(:@header, "aaa")
|
||||||
io = Net::BufferedIO.allocate
|
io = Gem::Net::BufferedIO.allocate
|
||||||
io.instance_variable_set(:@io, ent)
|
io.instance_variable_set(:@io, ent)
|
||||||
io.instance_variable_set(:@debug_output, wa)
|
io.instance_variable_set(:@debug_output, wa)
|
||||||
reader = Gem::Package::TarReader.allocate
|
reader = Gem::Package::TarReader.allocate
|
||||||
|
@ -43,7 +43,7 @@ class TestGemSource < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_dependency_resolver_set_bundler_api
|
def test_dependency_resolver_set_bundler_api
|
||||||
response = Net::HTTPResponse.new "1.1", 200, "OK"
|
response = Gem::Net::HTTPResponse.new "1.1", 200, "OK"
|
||||||
response.uri = URI("http://example")
|
response.uri = URI("http://example")
|
||||||
|
|
||||||
@fetcher.data[@gem_repo] = response
|
@fetcher.data[@gem_repo] = response
|
||||||
|
@ -21,7 +21,7 @@ class TestGemSourceSubpathProblem < Gem::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_dependency_resolver_set
|
def test_dependency_resolver_set
|
||||||
response = Net::HTTPResponse.new "1.1", 200, "OK"
|
response = Gem::Net::HTTPResponse.new "1.1", 200, "OK"
|
||||||
response.uri = URI("http://example")
|
response.uri = URI("http://example")
|
||||||
|
|
||||||
@fetcher.data["#{@gem_repo}/"] = response
|
@fetcher.data["#{@gem_repo}/"] = response
|
||||||
|
@ -35,15 +35,15 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
|
|
||||||
def test_wait_for_otp_code_get_follows_options
|
def test_wait_for_otp_code_get_follows_options
|
||||||
wait_for_otp_code
|
wait_for_otp_code
|
||||||
assert Gem::MockBrowser.options(URI("http://localhost:#{@port}?code=xyz")).is_a? Net::HTTPNoContent
|
assert Gem::MockBrowser.options(URI("http://localhost:#{@port}?code=xyz")).is_a? Gem::Net::HTTPNoContent
|
||||||
assert Gem::MockBrowser.get(URI("http://localhost:#{@port}?code=xyz")).is_a? Net::HTTPOK
|
assert Gem::MockBrowser.get(URI("http://localhost:#{@port}?code=xyz")).is_a? Gem::Net::HTTPOK
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_wait_for_otp_code_options_request
|
def test_wait_for_otp_code_options_request
|
||||||
wait_for_otp_code
|
wait_for_otp_code
|
||||||
response = Gem::MockBrowser.options URI("http://localhost:#{@port}?code=xyz")
|
response = Gem::MockBrowser.options URI("http://localhost:#{@port}?code=xyz")
|
||||||
|
|
||||||
assert response.is_a? Net::HTTPNoContent
|
assert response.is_a? Gem::Net::HTTPNoContent
|
||||||
assert_equal Gem.host, response["access-control-allow-origin"]
|
assert_equal Gem.host, response["access-control-allow-origin"]
|
||||||
assert_equal "POST", response["access-control-allow-methods"]
|
assert_equal "POST", response["access-control-allow-methods"]
|
||||||
assert_equal "Content-Type, Authorization, x-csrf-token", response["access-control-allow-headers"]
|
assert_equal "Content-Type, Authorization, x-csrf-token", response["access-control-allow-headers"]
|
||||||
@ -54,7 +54,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
wait_for_otp_code
|
wait_for_otp_code
|
||||||
response = Gem::MockBrowser.get URI("http://localhost:#{@port}?code=xyz")
|
response = Gem::MockBrowser.get URI("http://localhost:#{@port}?code=xyz")
|
||||||
|
|
||||||
assert response.is_a? Net::HTTPOK
|
assert response.is_a? Gem::Net::HTTPOK
|
||||||
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
||||||
assert_equal "7", response["Content-Length"]
|
assert_equal "7", response["Content-Length"]
|
||||||
assert_equal Gem.host, response["access-control-allow-origin"]
|
assert_equal Gem.host, response["access-control-allow-origin"]
|
||||||
@ -72,7 +72,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
response = Gem::MockBrowser.post URI("http://localhost:#{@port}?code=xyz")
|
response = Gem::MockBrowser.post URI("http://localhost:#{@port}?code=xyz")
|
||||||
|
|
||||||
assert response
|
assert response
|
||||||
assert response.is_a? Net::HTTPMethodNotAllowed
|
assert response.is_a? Gem::Net::HTTPMethodNotAllowed
|
||||||
assert_equal "GET, OPTIONS", response["allow"]
|
assert_equal "GET, OPTIONS", response["allow"]
|
||||||
assert_equal "close", response["Connection"]
|
assert_equal "close", response["Connection"]
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
wait_for_otp_code_expect_error_with_message("Security device verification failed: Page at /path not found.")
|
wait_for_otp_code_expect_error_with_message("Security device verification failed: Page at /path not found.")
|
||||||
response = Gem::MockBrowser.post URI("http://localhost:#{@port}/path?code=xyz")
|
response = Gem::MockBrowser.post URI("http://localhost:#{@port}/path?code=xyz")
|
||||||
|
|
||||||
assert response.is_a? Net::HTTPNotFound
|
assert response.is_a? Gem::Net::HTTPNotFound
|
||||||
assert_equal "close", response["Connection"]
|
assert_equal "close", response["Connection"]
|
||||||
|
|
||||||
@thread.join
|
@thread.join
|
||||||
@ -95,7 +95,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
|
wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
|
||||||
response = Gem::MockBrowser.get URI("http://localhost:#{@port}")
|
response = Gem::MockBrowser.get URI("http://localhost:#{@port}")
|
||||||
|
|
||||||
assert response.is_a? Net::HTTPBadRequest
|
assert response.is_a? Gem::Net::HTTPBadRequest
|
||||||
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
||||||
assert_equal "22", response["Content-Length"]
|
assert_equal "22", response["Content-Length"]
|
||||||
assert_equal "close", response["Connection"]
|
assert_equal "close", response["Connection"]
|
||||||
@ -109,7 +109,7 @@ class WebauthnListenerTest < Gem::TestCase
|
|||||||
wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
|
wait_for_otp_code_expect_error_with_message("Security device verification failed: Did not receive OTP from https://rubygems.org.")
|
||||||
response = Gem::MockBrowser.get URI("http://localhost:#{@port}?param=xyz")
|
response = Gem::MockBrowser.get URI("http://localhost:#{@port}?param=xyz")
|
||||||
|
|
||||||
assert response.is_a? Net::HTTPBadRequest
|
assert response.is_a? Gem::Net::HTTPBadRequest
|
||||||
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
assert_equal "text/plain; charset=utf-8", response["Content-Type"]
|
||||||
assert_equal "22", response["Content-Length"]
|
assert_equal "22", response["Content-Length"]
|
||||||
assert_equal "close", response["Connection"]
|
assert_equal "close", response["Connection"]
|
||||||
|
@ -65,7 +65,7 @@ class Gem::FakeFetcher
|
|||||||
def create_response(uri)
|
def create_response(uri)
|
||||||
data = find_data(uri)
|
data = find_data(uri)
|
||||||
response = data.respond_to?(:call) ? data.call : data
|
response = data.respond_to?(:call) ? data.call : data
|
||||||
raise TypeError, "#{response.class} is not a type of Net::HTTPResponse" unless response.is_a?(Net::HTTPResponse)
|
raise TypeError, "#{response.class} is not a type of Gem::Net::HTTPResponse" unless response.is_a?(Gem::Net::HTTPResponse)
|
||||||
|
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
@ -164,7 +164,7 @@ class Gem::FakeFetcher
|
|||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# The HTTPResponseFactory allows easy creation of Net::HTTPResponse instances in RubyGems tests:
|
# The HTTPResponseFactory allows easy creation of Gem::Net::HTTPResponse instances in RubyGems tests:
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
@ -178,7 +178,7 @@ end
|
|||||||
|
|
||||||
class Gem::HTTPResponseFactory
|
class Gem::HTTPResponseFactory
|
||||||
def self.create(body:, code:, msg:, headers: {})
|
def self.create(body:, code:, msg:, headers: {})
|
||||||
response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
|
response = Gem::Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
|
||||||
response.instance_variable_set(:@body, body)
|
response.instance_variable_set(:@body, body)
|
||||||
response.instance_variable_set(:@read, true)
|
response.instance_variable_set(:@read, true)
|
||||||
headers.each {|name, value| response[name] = value }
|
headers.each {|name, value| response[name] = value }
|
||||||
@ -201,23 +201,23 @@ end
|
|||||||
|
|
||||||
class Gem::MockBrowser
|
class Gem::MockBrowser
|
||||||
def self.options(uri)
|
def self.options(uri)
|
||||||
options = Net::HTTP::Options.new(uri)
|
options = Gem::Net::HTTP::Options.new(uri)
|
||||||
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
Gem::Net::HTTP.start(uri.hostname, uri.port) do |http|
|
||||||
http.request(options)
|
http.request(options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get(uri)
|
def self.get(uri)
|
||||||
get = Net::HTTP::Get.new(uri)
|
get = Gem::Net::HTTP::Get.new(uri)
|
||||||
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
Gem::Net::HTTP.start(uri.hostname, uri.port) do |http|
|
||||||
http.request(get)
|
http.request(get)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.post(uri, content_type: "application/x-www-form-urlencoded")
|
def self.post(uri, content_type: "application/x-www-form-urlencoded")
|
||||||
headers = { "content-type" => content_type } if content_type
|
headers = { "content-type" => content_type } if content_type
|
||||||
post = Net::HTTP::Post.new(uri, headers)
|
post = Gem::Net::HTTP::Post.new(uri, headers)
|
||||||
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
Gem::Net::HTTP.start(uri.hostname, uri.port) do |http|
|
||||||
http.request(post)
|
http.request(post)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user