├── .github ├── dependabot.yml └── workflows │ ├── push_gem.yml │ └── test.yml ├── .gitignore ├── BSDL ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── doc └── net-http │ ├── examples.rdoc │ └── included_getters.rdoc ├── lib └── net │ ├── http.rb │ ├── http │ ├── exceptions.rb │ ├── generic_request.rb │ ├── header.rb │ ├── proxy_delta.rb │ ├── request.rb │ ├── requests.rb │ ├── response.rb │ ├── responses.rb │ └── status.rb │ └── https.rb ├── net-http.gemspec └── test ├── lib └── helper.rb └── net ├── fixtures ├── Makefile ├── cacert.pem ├── dhparams.pem ├── server.crt └── server.key └── http ├── test_buffered_io.rb ├── test_http.rb ├── test_http_request.rb ├── test_httpheader.rb ├── test_httpresponse.rb ├── test_httpresponses.rb ├── test_https.rb ├── test_https_proxy.rb └── utils.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/net-http' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/net-http 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | engine: cruby 10 | min_version: 2.6 11 | 12 | test: 13 | needs: ruby-versions 14 | name: test (${{ matrix.ruby }} / ${{ matrix.os }}) 15 | strategy: 16 | matrix: 17 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 18 | os: [ ubuntu-latest, macos-latest, windows-latest ] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby }} 26 | - name: Install dependencies 27 | run: bundle install 28 | - name: Run test 29 | run: rake test 30 | - name: Build 31 | run: rake build 32 | env: 33 | LANG: C 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /pkg/ 6 | /spec/reports/ 7 | /tmp/ 8 | /Gemfile.lock 9 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rake" 6 | gem "test-unit" 7 | gem "test-unit-ruby-core" 8 | gem "webrick" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Net::HTTP 2 | 3 | Net::HTTP provides a rich library which can be used to build HTTP 4 | user-agents. For more details about HTTP see 5 | [RFC9110 HTTP Semantics](https://www.ietf.org/rfc/rfc9110.html) and 6 | [RFC9112 HTTP/1.1](https://www.ietf.org/rfc/rfc9112.html). 7 | 8 | Net::HTTP is designed to work closely with URI. URI::HTTP#host, 9 | URI::HTTP#port and URI::HTTP#request_uri are designed to work with 10 | Net::HTTP. 11 | 12 | If you are only performing a few GET requests you should try OpenURI. 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'net-http' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle install 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install net-http 29 | 30 | ## Usage 31 | 32 | All examples assume you have loaded Net::HTTP with: 33 | 34 | ```ruby 35 | require 'net/http' 36 | ``` 37 | 38 | This will also require 'uri' so you don't need to require it separately. 39 | 40 | The Net::HTTP methods in the following section do not persist 41 | connections. They are not recommended if you are performing many HTTP 42 | requests. 43 | 44 | ### GET 45 | 46 | ```ruby 47 | Net::HTTP.get('example.com', '/index.html') # => String 48 | ``` 49 | 50 | ### GET by URI 51 | 52 | ```ruby 53 | uri = URI('http://example.com/index.html?count=10') 54 | Net::HTTP.get(uri) # => String 55 | ``` 56 | 57 | ### GET with Dynamic Parameters 58 | 59 | ```ruby 60 | uri = URI('http://example.com/index.html') 61 | params = { :limit => 10, :page => 3 } 62 | uri.query = URI.encode_www_form(params) 63 | 64 | res = Net::HTTP.get_response(uri) 65 | puts res.body if res.is_a?(Net::HTTPSuccess) 66 | ``` 67 | 68 | ### POST 69 | 70 | ```ruby 71 | uri = URI('http://www.example.com/search.cgi') 72 | res = Net::HTTP.post_form(uri, 'q' => 'ruby', 'max' => '50') 73 | puts res.body 74 | ``` 75 | 76 | ### POST with Multiple Values 77 | 78 | ```ruby 79 | uri = URI('http://www.example.com/search.cgi') 80 | res = Net::HTTP.post_form(uri, 'q' => ['ruby', 'perl'], 'max' => '50') 81 | puts res.body 82 | ``` 83 | 84 | ## Development 85 | 86 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 87 | 88 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 89 | 90 | ## Contributing 91 | 92 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-http. 93 | 94 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "net/http" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /doc/net-http/examples.rdoc: -------------------------------------------------------------------------------- 1 | Examples here assume that net/http has been required 2 | (which also requires +uri+): 3 | 4 | require 'net/http' 5 | 6 | Many code examples here use these example websites: 7 | 8 | - https://jsonplaceholder.typicode.com. 9 | - http://example.com. 10 | 11 | Some examples also assume these variables: 12 | 13 | uri = URI('https://jsonplaceholder.typicode.com/') 14 | uri.freeze # Examples may not modify. 15 | hostname = uri.hostname # => "jsonplaceholder.typicode.com" 16 | path = uri.path # => "/" 17 | port = uri.port # => 443 18 | 19 | So that example requests may be written as: 20 | 21 | Net::HTTP.get(uri) 22 | Net::HTTP.get(hostname, '/index.html') 23 | Net::HTTP.start(hostname) do |http| 24 | http.get('/todos/1') 25 | http.get('/todos/2') 26 | end 27 | 28 | An example that needs a modified URI first duplicates +uri+, then modifies the duplicate: 29 | 30 | _uri = uri.dup 31 | _uri.path = '/todos/1' 32 | -------------------------------------------------------------------------------- /doc/net-http/included_getters.rdoc: -------------------------------------------------------------------------------- 1 | This class also includes (indirectly) module Net::HTTPHeader, 2 | which gives access to its 3 | {methods for getting headers}[rdoc-ref:Net::HTTPHeader@Getters]. 4 | -------------------------------------------------------------------------------- /lib/net/http/exceptions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Net 3 | # Net::HTTP exception class. 4 | # You cannot use Net::HTTPExceptions directly; instead, you must use 5 | # its subclasses. 6 | module HTTPExceptions 7 | def initialize(msg, res) #:nodoc: 8 | super msg 9 | @response = res 10 | end 11 | attr_reader :response 12 | alias data response #:nodoc: obsolete 13 | end 14 | 15 | class HTTPError < ProtocolError 16 | include HTTPExceptions 17 | end 18 | 19 | class HTTPRetriableError < ProtoRetriableError 20 | include HTTPExceptions 21 | end 22 | 23 | class HTTPClientException < ProtoServerError 24 | include HTTPExceptions 25 | end 26 | 27 | class HTTPFatalError < ProtoFatalError 28 | include HTTPExceptions 29 | end 30 | 31 | # We cannot use the name "HTTPServerError", it is the name of the response. 32 | HTTPServerException = HTTPClientException # :nodoc: 33 | deprecate_constant(:HTTPServerException) 34 | end 35 | -------------------------------------------------------------------------------- /lib/net/http/generic_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # \HTTPGenericRequest is the parent of the Net::HTTPRequest class. 4 | # 5 | # Do not use this directly; instead, use a subclass of Net::HTTPRequest. 6 | # 7 | # == About the Examples 8 | # 9 | # :include: doc/net-http/examples.rdoc 10 | # 11 | class Net::HTTPGenericRequest 12 | 13 | include Net::HTTPHeader 14 | 15 | def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc: 16 | @method = m 17 | @request_has_body = reqbody 18 | @response_has_body = resbody 19 | 20 | if URI === uri_or_path then 21 | raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path 22 | hostname = uri_or_path.hostname 23 | raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) 24 | @uri = uri_or_path.dup 25 | host = @uri.hostname.dup 26 | host << ":" << @uri.port.to_s if @uri.port != @uri.default_port 27 | @path = uri_or_path.request_uri 28 | raise ArgumentError, "no HTTP request path given" unless @path 29 | else 30 | @uri = nil 31 | host = nil 32 | raise ArgumentError, "no HTTP request path given" unless uri_or_path 33 | raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? 34 | @path = uri_or_path.dup 35 | end 36 | 37 | @decode_content = false 38 | 39 | if Net::HTTP::HAVE_ZLIB then 40 | if !initheader || 41 | !initheader.keys.any? { |k| 42 | %w[accept-encoding range].include? k.downcase 43 | } then 44 | @decode_content = true if @response_has_body 45 | initheader = initheader ? initheader.dup : {} 46 | initheader["accept-encoding"] = 47 | "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" 48 | end 49 | end 50 | 51 | initialize_http_header initheader 52 | self['Accept'] ||= '*/*' 53 | self['User-Agent'] ||= 'Ruby' 54 | self['Host'] ||= host if host 55 | @body = nil 56 | @body_stream = nil 57 | @body_data = nil 58 | end 59 | 60 | # Returns the string method name for the request: 61 | # 62 | # Net::HTTP::Get.new(uri).method # => "GET" 63 | # Net::HTTP::Post.new(uri).method # => "POST" 64 | # 65 | attr_reader :method 66 | 67 | # Returns the string path for the request: 68 | # 69 | # Net::HTTP::Get.new(uri).path # => "/" 70 | # Net::HTTP::Post.new('example.com').path # => "example.com" 71 | # 72 | attr_reader :path 73 | 74 | # Returns the URI object for the request, or +nil+ if none: 75 | # 76 | # Net::HTTP::Get.new(uri).uri 77 | # # => # 78 | # Net::HTTP::Get.new('example.com').uri # => nil 79 | # 80 | attr_reader :uri 81 | 82 | # Returns +false+ if the request's header 'Accept-Encoding' 83 | # has been set manually or deleted 84 | # (indicating that the user intends to handle encoding in the response), 85 | # +true+ otherwise: 86 | # 87 | # req = Net::HTTP::Get.new(uri) # => # 88 | # req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" 89 | # req.decode_content # => true 90 | # req['Accept-Encoding'] = 'foo' 91 | # req.decode_content # => false 92 | # req.delete('Accept-Encoding') 93 | # req.decode_content # => false 94 | # 95 | attr_reader :decode_content 96 | 97 | # Returns a string representation of the request: 98 | # 99 | # Net::HTTP::Post.new(uri).inspect # => "#" 100 | # 101 | def inspect 102 | "\#<#{self.class} #{@method}>" 103 | end 104 | 105 | ## 106 | # Don't automatically decode response content-encoding if the user indicates 107 | # they want to handle it. 108 | 109 | def []=(key, val) # :nodoc: 110 | @decode_content = false if key.downcase == 'accept-encoding' 111 | 112 | super key, val 113 | end 114 | 115 | # Returns whether the request may have a body: 116 | # 117 | # Net::HTTP::Post.new(uri).request_body_permitted? # => true 118 | # Net::HTTP::Get.new(uri).request_body_permitted? # => false 119 | # 120 | def request_body_permitted? 121 | @request_has_body 122 | end 123 | 124 | # Returns whether the response may have a body: 125 | # 126 | # Net::HTTP::Post.new(uri).response_body_permitted? # => true 127 | # Net::HTTP::Head.new(uri).response_body_permitted? # => false 128 | # 129 | def response_body_permitted? 130 | @response_has_body 131 | end 132 | 133 | def body_exist? # :nodoc: 134 | warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE 135 | response_body_permitted? 136 | end 137 | 138 | # Returns the string body for the request, or +nil+ if there is none: 139 | # 140 | # req = Net::HTTP::Post.new(uri) 141 | # req.body # => nil 142 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 143 | # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" 144 | # 145 | attr_reader :body 146 | 147 | # Sets the body for the request: 148 | # 149 | # req = Net::HTTP::Post.new(uri) 150 | # req.body # => nil 151 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 152 | # req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}" 153 | # 154 | def body=(str) 155 | @body = str 156 | @body_stream = nil 157 | @body_data = nil 158 | str 159 | end 160 | 161 | # Returns the body stream object for the request, or +nil+ if there is none: 162 | # 163 | # req = Net::HTTP::Post.new(uri) # => # 164 | # req.body_stream # => nil 165 | # require 'stringio' 166 | # req.body_stream = StringIO.new('xyzzy') # => # 167 | # req.body_stream # => # 168 | # 169 | attr_reader :body_stream 170 | 171 | # Sets the body stream for the request: 172 | # 173 | # req = Net::HTTP::Post.new(uri) # => # 174 | # req.body_stream # => nil 175 | # require 'stringio' 176 | # req.body_stream = StringIO.new('xyzzy') # => # 177 | # req.body_stream # => # 178 | # 179 | def body_stream=(input) 180 | @body = nil 181 | @body_stream = input 182 | @body_data = nil 183 | input 184 | end 185 | 186 | def set_body_internal(str) #:nodoc: internal use only 187 | raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream) 188 | self.body = str if str 189 | if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted? 190 | self.body = '' 191 | end 192 | end 193 | 194 | # 195 | # write 196 | # 197 | 198 | def exec(sock, ver, path) #:nodoc: internal use only 199 | if @body 200 | send_request_with_body sock, ver, path, @body 201 | elsif @body_stream 202 | send_request_with_body_stream sock, ver, path, @body_stream 203 | elsif @body_data 204 | send_request_with_body_data sock, ver, path, @body_data 205 | else 206 | write_header sock, ver, path 207 | end 208 | end 209 | 210 | def update_uri(addr, port, ssl) # :nodoc: internal use only 211 | # reflect the connection and @path to @uri 212 | return unless @uri 213 | 214 | if ssl 215 | scheme = 'https' 216 | klass = URI::HTTPS 217 | else 218 | scheme = 'http' 219 | klass = URI::HTTP 220 | end 221 | 222 | if host = self['host'] 223 | host.sub!(/:.*/m, '') 224 | elsif host = @uri.host 225 | else 226 | host = addr 227 | end 228 | # convert the class of the URI 229 | if @uri.is_a?(klass) 230 | @uri.host = host 231 | @uri.port = port 232 | else 233 | @uri = klass.new( 234 | scheme, @uri.userinfo, 235 | host, port, nil, 236 | @uri.path, nil, @uri.query, nil) 237 | end 238 | end 239 | 240 | private 241 | 242 | class Chunker #:nodoc: 243 | def initialize(sock) 244 | @sock = sock 245 | @prev = nil 246 | end 247 | 248 | def write(buf) 249 | # avoid memcpy() of buf, buf can huge and eat memory bandwidth 250 | rv = buf.bytesize 251 | @sock.write("#{rv.to_s(16)}\r\n", buf, "\r\n") 252 | rv 253 | end 254 | 255 | def finish 256 | @sock.write("0\r\n\r\n") 257 | end 258 | end 259 | 260 | def send_request_with_body(sock, ver, path, body) 261 | self.content_length = body.bytesize 262 | delete 'Transfer-Encoding' 263 | supply_default_content_type 264 | write_header sock, ver, path 265 | wait_for_continue sock, ver if sock.continue_timeout 266 | sock.write body 267 | end 268 | 269 | def send_request_with_body_stream(sock, ver, path, f) 270 | unless content_length() or chunked? 271 | raise ArgumentError, 272 | "Content-Length not given and Transfer-Encoding is not `chunked'" 273 | end 274 | supply_default_content_type 275 | write_header sock, ver, path 276 | wait_for_continue sock, ver if sock.continue_timeout 277 | if chunked? 278 | chunker = Chunker.new(sock) 279 | IO.copy_stream(f, chunker) 280 | chunker.finish 281 | else 282 | IO.copy_stream(f, sock) 283 | end 284 | end 285 | 286 | def send_request_with_body_data(sock, ver, path, params) 287 | if /\Amultipart\/form-data\z/i !~ self.content_type 288 | self.content_type = 'application/x-www-form-urlencoded' 289 | return send_request_with_body(sock, ver, path, URI.encode_www_form(params)) 290 | end 291 | 292 | opt = @form_option.dup 293 | require 'securerandom' unless defined?(SecureRandom) 294 | opt[:boundary] ||= SecureRandom.urlsafe_base64(40) 295 | self.set_content_type(self.content_type, boundary: opt[:boundary]) 296 | if chunked? 297 | write_header sock, ver, path 298 | encode_multipart_form_data(sock, params, opt) 299 | else 300 | require 'tempfile' 301 | file = Tempfile.new('multipart') 302 | file.binmode 303 | encode_multipart_form_data(file, params, opt) 304 | file.rewind 305 | self.content_length = file.size 306 | write_header sock, ver, path 307 | IO.copy_stream(file, sock) 308 | file.close(true) 309 | end 310 | end 311 | 312 | def encode_multipart_form_data(out, params, opt) 313 | charset = opt[:charset] 314 | boundary = opt[:boundary] 315 | require 'securerandom' unless defined?(SecureRandom) 316 | boundary ||= SecureRandom.urlsafe_base64(40) 317 | chunked_p = chunked? 318 | 319 | buf = +'' 320 | params.each do |key, value, h={}| 321 | key = quote_string(key, charset) 322 | filename = 323 | h.key?(:filename) ? h[:filename] : 324 | value.respond_to?(:to_path) ? File.basename(value.to_path) : 325 | nil 326 | 327 | buf << "--#{boundary}\r\n" 328 | if filename 329 | filename = quote_string(filename, charset) 330 | type = h[:content_type] || 'application/octet-stream' 331 | buf << "Content-Disposition: form-data; " \ 332 | "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \ 333 | "Content-Type: #{type}\r\n\r\n" 334 | if !out.respond_to?(:write) || !value.respond_to?(:read) 335 | # if +out+ is not an IO or +value+ is not an IO 336 | buf << (value.respond_to?(:read) ? value.read : value) 337 | elsif value.respond_to?(:size) && chunked_p 338 | # if +out+ is an IO and +value+ is a File, use IO.copy_stream 339 | flush_buffer(out, buf, chunked_p) 340 | out << "%x\r\n" % value.size if chunked_p 341 | IO.copy_stream(value, out) 342 | out << "\r\n" if chunked_p 343 | else 344 | # +out+ is an IO, and +value+ is not a File but an IO 345 | flush_buffer(out, buf, chunked_p) 346 | 1 while flush_buffer(out, value.read(4096), chunked_p) 347 | end 348 | else 349 | # non-file field: 350 | # HTML5 says, "The parts of the generated multipart/form-data 351 | # resource that correspond to non-file fields must not have a 352 | # Content-Type header specified." 353 | buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" 354 | buf << (value.respond_to?(:read) ? value.read : value) 355 | end 356 | buf << "\r\n" 357 | end 358 | buf << "--#{boundary}--\r\n" 359 | flush_buffer(out, buf, chunked_p) 360 | out << "0\r\n\r\n" if chunked_p 361 | end 362 | 363 | def quote_string(str, charset) 364 | str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset 365 | str.gsub(/[\\"]/, '\\\\\&') 366 | end 367 | 368 | def flush_buffer(out, buf, chunked_p) 369 | return unless buf 370 | out << "%x\r\n"%buf.bytesize if chunked_p 371 | out << buf 372 | out << "\r\n" if chunked_p 373 | buf.clear 374 | end 375 | 376 | def supply_default_content_type 377 | return if content_type() 378 | warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE 379 | set_content_type 'application/x-www-form-urlencoded' 380 | end 381 | 382 | ## 383 | # Waits up to the continue timeout for a response from the server provided 384 | # we're speaking HTTP 1.1 and are expecting a 100-continue response. 385 | 386 | def wait_for_continue(sock, ver) 387 | if ver >= '1.1' and @header['expect'] and 388 | @header['expect'].include?('100-continue') 389 | if sock.io.to_io.wait_readable(sock.continue_timeout) 390 | res = Net::HTTPResponse.read_new(sock) 391 | unless res.kind_of?(Net::HTTPContinue) 392 | res.decode_content = @decode_content 393 | throw :response, res 394 | end 395 | end 396 | end 397 | end 398 | 399 | def write_header(sock, ver, path) 400 | reqline = "#{@method} #{path} HTTP/#{ver}" 401 | if /[\r\n]/ =~ reqline 402 | raise ArgumentError, "A Request-Line must not contain CR or LF" 403 | end 404 | buf = +'' 405 | buf << reqline << "\r\n" 406 | each_capitalized do |k,v| 407 | buf << "#{k}: #{v}\r\n" 408 | end 409 | buf << "\r\n" 410 | sock.write buf 411 | end 412 | 413 | end 414 | 415 | -------------------------------------------------------------------------------- /lib/net/http/header.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # The \HTTPHeader module provides access to \HTTP headers. 4 | # 5 | # The module is included in: 6 | # 7 | # - Net::HTTPGenericRequest (and therefore Net::HTTPRequest). 8 | # - Net::HTTPResponse. 9 | # 10 | # The headers are a hash-like collection of key/value pairs called _fields_. 11 | # 12 | # == Request and Response Fields 13 | # 14 | # Headers may be included in: 15 | # 16 | # - A Net::HTTPRequest object: 17 | # the object's headers will be sent with the request. 18 | # Any fields may be defined in the request; 19 | # see {Setters}[rdoc-ref:Net::HTTPHeader@Setters]. 20 | # - A Net::HTTPResponse object: 21 | # the objects headers are usually those returned from the host. 22 | # Fields may be retrieved from the object; 23 | # see {Getters}[rdoc-ref:Net::HTTPHeader@Getters] 24 | # and {Iterators}[rdoc-ref:Net::HTTPHeader@Iterators]. 25 | # 26 | # Exactly which fields should be sent or expected depends on the host; 27 | # see: 28 | # 29 | # - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields]. 30 | # - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields]. 31 | # 32 | # == About the Examples 33 | # 34 | # :include: doc/net-http/examples.rdoc 35 | # 36 | # == Fields 37 | # 38 | # A header field is a key/value pair. 39 | # 40 | # === Field Keys 41 | # 42 | # A field key may be: 43 | # 44 | # - A string: Key 'Accept' is treated as if it were 45 | # 'Accept'.downcase; i.e., 'accept'. 46 | # - A symbol: Key :Accept is treated as if it were 47 | # :Accept.to_s.downcase; i.e., 'accept'. 48 | # 49 | # Examples: 50 | # 51 | # req = Net::HTTP::Get.new(uri) 52 | # req[:accept] # => "*/*" 53 | # req['Accept'] # => "*/*" 54 | # req['ACCEPT'] # => "*/*" 55 | # 56 | # req['accept'] = 'text/html' 57 | # req[:accept] = 'text/html' 58 | # req['ACCEPT'] = 'text/html' 59 | # 60 | # === Field Values 61 | # 62 | # A field value may be returned as an array of strings or as a string: 63 | # 64 | # - These methods return field values as arrays: 65 | # 66 | # - #get_fields: Returns the array value for the given key, 67 | # or +nil+ if it does not exist. 68 | # - #to_hash: Returns a hash of all header fields: 69 | # each key is a field name; its value is the array value for the field. 70 | # 71 | # - These methods return field values as string; 72 | # the string value for a field is equivalent to 73 | # self[key.downcase.to_s].join(', ')): 74 | # 75 | # - #[]: Returns the string value for the given key, 76 | # or +nil+ if it does not exist. 77 | # - #fetch: Like #[], but accepts a default value 78 | # to be returned if the key does not exist. 79 | # 80 | # The field value may be set: 81 | # 82 | # - #[]=: Sets the value for the given key; 83 | # the given value may be a string, a symbol, an array, or a hash. 84 | # - #add_field: Adds a given value to a value for the given key 85 | # (not overwriting the existing value). 86 | # - #delete: Deletes the field for the given key. 87 | # 88 | # Example field values: 89 | # 90 | # - \String: 91 | # 92 | # req['Accept'] = 'text/html' # => "text/html" 93 | # req['Accept'] # => "text/html" 94 | # req.get_fields('Accept') # => ["text/html"] 95 | # 96 | # - \Symbol: 97 | # 98 | # req['Accept'] = :text # => :text 99 | # req['Accept'] # => "text" 100 | # req.get_fields('Accept') # => ["text"] 101 | # 102 | # - Simple array: 103 | # 104 | # req[:foo] = %w[bar baz bat] 105 | # req[:foo] # => "bar, baz, bat" 106 | # req.get_fields(:foo) # => ["bar", "baz", "bat"] 107 | # 108 | # - Simple hash: 109 | # 110 | # req[:foo] = {bar: 0, baz: 1, bat: 2} 111 | # req[:foo] # => "bar, 0, baz, 1, bat, 2" 112 | # req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"] 113 | # 114 | # - Nested: 115 | # 116 | # req[:foo] = [%w[bar baz], {bat: 0, bam: 1}] 117 | # req[:foo] # => "bar, baz, bat, 0, bam, 1" 118 | # req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"] 119 | # 120 | # req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}} 121 | # req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1" 122 | # req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"] 123 | # 124 | # == Convenience Methods 125 | # 126 | # Various convenience methods retrieve values, set values, query values, 127 | # set form values, or iterate over fields. 128 | # 129 | # === Setters 130 | # 131 | # \Method #[]= can set any field, but does little to validate the new value; 132 | # some of the other setter methods provide some validation: 133 | # 134 | # - #[]=: Sets the string or array value for the given key. 135 | # - #add_field: Creates or adds to the array value for the given key. 136 | # - #basic_auth: Sets the string authorization header for 'Authorization'. 137 | # - #content_length=: Sets the integer length for field 'Content-Length. 138 | # - #content_type=: Sets the string value for field 'Content-Type'. 139 | # - #proxy_basic_auth: Sets the string authorization header for 'Proxy-Authorization'. 140 | # - #set_range: Sets the value for field 'Range'. 141 | # 142 | # === Form Setters 143 | # 144 | # - #set_form: Sets an HTML form data set. 145 | # - #set_form_data: Sets header fields and a body from HTML form data. 146 | # 147 | # === Getters 148 | # 149 | # \Method #[] can retrieve the value of any field that exists, 150 | # but always as a string; 151 | # some of the other getter methods return something different 152 | # from the simple string value: 153 | # 154 | # - #[]: Returns the string field value for the given key. 155 | # - #content_length: Returns the integer value of field 'Content-Length'. 156 | # - #content_range: Returns the Range value of field 'Content-Range'. 157 | # - #content_type: Returns the string value of field 'Content-Type'. 158 | # - #fetch: Returns the string field value for the given key. 159 | # - #get_fields: Returns the array field value for the given +key+. 160 | # - #main_type: Returns first part of the string value of field 'Content-Type'. 161 | # - #sub_type: Returns second part of the string value of field 'Content-Type'. 162 | # - #range: Returns an array of Range objects of field 'Range', or +nil+. 163 | # - #range_length: Returns the integer length of the range given in field 'Content-Range'. 164 | # - #type_params: Returns the string parameters for 'Content-Type'. 165 | # 166 | # === Queries 167 | # 168 | # - #chunked?: Returns whether field 'Transfer-Encoding' is set to 'chunked'. 169 | # - #connection_close?: Returns whether field 'Connection' is set to 'close'. 170 | # - #connection_keep_alive?: Returns whether field 'Connection' is set to 'keep-alive'. 171 | # - #key?: Returns whether a given key exists. 172 | # 173 | # === Iterators 174 | # 175 | # - #each_capitalized: Passes each field capitalized-name/value pair to the block. 176 | # - #each_capitalized_name: Passes each capitalized field name to the block. 177 | # - #each_header: Passes each field name/value pair to the block. 178 | # - #each_name: Passes each field name to the block. 179 | # - #each_value: Passes each string field value to the block. 180 | # 181 | module Net::HTTPHeader 182 | MAX_KEY_LENGTH = 1024 183 | MAX_FIELD_LENGTH = 65536 184 | 185 | def initialize_http_header(initheader) #:nodoc: 186 | @header = {} 187 | return unless initheader 188 | initheader.each do |key, value| 189 | warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE 190 | if value.nil? 191 | warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE 192 | else 193 | value = value.strip # raise error for invalid byte sequences 194 | if key.to_s.bytesize > MAX_KEY_LENGTH 195 | raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..." 196 | end 197 | if value.to_s.bytesize > MAX_FIELD_LENGTH 198 | raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}" 199 | end 200 | if value.count("\r\n") > 0 201 | raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF" 202 | end 203 | @header[key.downcase.to_s] = [value] 204 | end 205 | end 206 | end 207 | 208 | def size #:nodoc: obsolete 209 | @header.size 210 | end 211 | 212 | alias length size #:nodoc: obsolete 213 | 214 | # Returns the string field value for the case-insensitive field +key+, 215 | # or +nil+ if there is no such key; 216 | # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: 217 | # 218 | # res = Net::HTTP.get_response(hostname, '/todos/1') 219 | # res['Connection'] # => "keep-alive" 220 | # res['Nosuch'] # => nil 221 | # 222 | # Note that some field values may be retrieved via convenience methods; 223 | # see {Getters}[rdoc-ref:Net::HTTPHeader@Getters]. 224 | def [](key) 225 | a = @header[key.downcase.to_s] or return nil 226 | a.join(', ') 227 | end 228 | 229 | # Sets the value for the case-insensitive +key+ to +val+, 230 | # overwriting the previous value if the field exists; 231 | # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: 232 | # 233 | # req = Net::HTTP::Get.new(uri) 234 | # req['Accept'] # => "*/*" 235 | # req['Accept'] = 'text/html' 236 | # req['Accept'] # => "text/html" 237 | # 238 | # Note that some field values may be set via convenience methods; 239 | # see {Setters}[rdoc-ref:Net::HTTPHeader@Setters]. 240 | def []=(key, val) 241 | unless val 242 | @header.delete key.downcase.to_s 243 | return val 244 | end 245 | set_field(key, val) 246 | end 247 | 248 | # Adds value +val+ to the value array for field +key+ if the field exists; 249 | # creates the field with the given +key+ and +val+ if it does not exist. 250 | # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: 251 | # 252 | # req = Net::HTTP::Get.new(uri) 253 | # req.add_field('Foo', 'bar') 254 | # req['Foo'] # => "bar" 255 | # req.add_field('Foo', 'baz') 256 | # req['Foo'] # => "bar, baz" 257 | # req.add_field('Foo', %w[baz bam]) 258 | # req['Foo'] # => "bar, baz, baz, bam" 259 | # req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"] 260 | # 261 | def add_field(key, val) 262 | stringified_downcased_key = key.downcase.to_s 263 | if @header.key?(stringified_downcased_key) 264 | append_field_value(@header[stringified_downcased_key], val) 265 | else 266 | set_field(key, val) 267 | end 268 | end 269 | 270 | private def set_field(key, val) 271 | case val 272 | when Enumerable 273 | ary = [] 274 | append_field_value(ary, val) 275 | @header[key.downcase.to_s] = ary 276 | else 277 | val = val.to_s # for compatibility use to_s instead of to_str 278 | if val.b.count("\r\n") > 0 279 | raise ArgumentError, 'header field value cannot include CR/LF' 280 | end 281 | @header[key.downcase.to_s] = [val] 282 | end 283 | end 284 | 285 | private def append_field_value(ary, val) 286 | case val 287 | when Enumerable 288 | val.each{|x| append_field_value(ary, x)} 289 | else 290 | val = val.to_s 291 | if /[\r\n]/n.match?(val.b) 292 | raise ArgumentError, 'header field value cannot include CR/LF' 293 | end 294 | ary.push val 295 | end 296 | end 297 | 298 | # Returns the array field value for the given +key+, 299 | # or +nil+ if there is no such field; 300 | # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: 301 | # 302 | # res = Net::HTTP.get_response(hostname, '/todos/1') 303 | # res.get_fields('Connection') # => ["keep-alive"] 304 | # res.get_fields('Nosuch') # => nil 305 | # 306 | def get_fields(key) 307 | stringified_downcased_key = key.downcase.to_s 308 | return nil unless @header[stringified_downcased_key] 309 | @header[stringified_downcased_key].dup 310 | end 311 | 312 | # call-seq: 313 | # fetch(key, default_val = nil) {|key| ... } -> object 314 | # fetch(key, default_val = nil) -> value or default_val 315 | # 316 | # With a block, returns the string value for +key+ if it exists; 317 | # otherwise returns the value of the block; 318 | # ignores the +default_val+; 319 | # see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]: 320 | # 321 | # res = Net::HTTP.get_response(hostname, '/todos/1') 322 | # 323 | # # Field exists; block not called. 324 | # res.fetch('Connection') do |value| 325 | # fail 'Cannot happen' 326 | # end # => "keep-alive" 327 | # 328 | # # Field does not exist; block called. 329 | # res.fetch('Nosuch') do |value| 330 | # value.downcase 331 | # end # => "nosuch" 332 | # 333 | # With no block, returns the string value for +key+ if it exists; 334 | # otherwise, returns +default_val+ if it was given; 335 | # otherwise raises an exception: 336 | # 337 | # res.fetch('Connection', 'Foo') # => "keep-alive" 338 | # res.fetch('Nosuch', 'Foo') # => "Foo" 339 | # res.fetch('Nosuch') # Raises KeyError. 340 | # 341 | def fetch(key, *args, &block) #:yield: +key+ 342 | a = @header.fetch(key.downcase.to_s, *args, &block) 343 | a.kind_of?(Array) ? a.join(', ') : a 344 | end 345 | 346 | # Calls the block with each key/value pair: 347 | # 348 | # res = Net::HTTP.get_response(hostname, '/todos/1') 349 | # res.each_header do |key, value| 350 | # p [key, value] if key.start_with?('c') 351 | # end 352 | # 353 | # Output: 354 | # 355 | # ["content-type", "application/json; charset=utf-8"] 356 | # ["connection", "keep-alive"] 357 | # ["cache-control", "max-age=43200"] 358 | # ["cf-cache-status", "HIT"] 359 | # ["cf-ray", "771d17e9bc542cf5-ORD"] 360 | # 361 | # Returns an enumerator if no block is given. 362 | # 363 | # Net::HTTPHeader#each is an alias for Net::HTTPHeader#each_header. 364 | def each_header #:yield: +key+, +value+ 365 | block_given? or return enum_for(__method__) { @header.size } 366 | @header.each do |k,va| 367 | yield k, va.join(', ') 368 | end 369 | end 370 | 371 | alias each each_header 372 | 373 | # Calls the block with each field key: 374 | # 375 | # res = Net::HTTP.get_response(hostname, '/todos/1') 376 | # res.each_key do |key| 377 | # p key if key.start_with?('c') 378 | # end 379 | # 380 | # Output: 381 | # 382 | # "content-type" 383 | # "connection" 384 | # "cache-control" 385 | # "cf-cache-status" 386 | # "cf-ray" 387 | # 388 | # Returns an enumerator if no block is given. 389 | # 390 | # Net::HTTPHeader#each_name is an alias for Net::HTTPHeader#each_key. 391 | def each_name(&block) #:yield: +key+ 392 | block_given? or return enum_for(__method__) { @header.size } 393 | @header.each_key(&block) 394 | end 395 | 396 | alias each_key each_name 397 | 398 | # Calls the block with each capitalized field name: 399 | # 400 | # res = Net::HTTP.get_response(hostname, '/todos/1') 401 | # res.each_capitalized_name do |key| 402 | # p key if key.start_with?('C') 403 | # end 404 | # 405 | # Output: 406 | # 407 | # "Content-Type" 408 | # "Connection" 409 | # "Cache-Control" 410 | # "Cf-Cache-Status" 411 | # "Cf-Ray" 412 | # 413 | # The capitalization is system-dependent; 414 | # see {Case Mapping}[https://docs.ruby-lang.org/en/master/case_mapping_rdoc.html]. 415 | # 416 | # Returns an enumerator if no block is given. 417 | def each_capitalized_name #:yield: +key+ 418 | block_given? or return enum_for(__method__) { @header.size } 419 | @header.each_key do |k| 420 | yield capitalize(k) 421 | end 422 | end 423 | 424 | # Calls the block with each string field value: 425 | # 426 | # res = Net::HTTP.get_response(hostname, '/todos/1') 427 | # res.each_value do |value| 428 | # p value if value.start_with?('c') 429 | # end 430 | # 431 | # Output: 432 | # 433 | # "chunked" 434 | # "cf-q-config;dur=6.0000002122251e-06" 435 | # "cloudflare" 436 | # 437 | # Returns an enumerator if no block is given. 438 | def each_value #:yield: +value+ 439 | block_given? or return enum_for(__method__) { @header.size } 440 | @header.each_value do |va| 441 | yield va.join(', ') 442 | end 443 | end 444 | 445 | # Removes the header for the given case-insensitive +key+ 446 | # (see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]); 447 | # returns the deleted value, or +nil+ if no such field exists: 448 | # 449 | # req = Net::HTTP::Get.new(uri) 450 | # req.delete('Accept') # => ["*/*"] 451 | # req.delete('Nosuch') # => nil 452 | # 453 | def delete(key) 454 | @header.delete(key.downcase.to_s) 455 | end 456 | 457 | # Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise: 458 | # 459 | # req = Net::HTTP::Get.new(uri) 460 | # req.key?('Accept') # => true 461 | # req.key?('Nosuch') # => false 462 | # 463 | def key?(key) 464 | @header.key?(key.downcase.to_s) 465 | end 466 | 467 | # Returns a hash of the key/value pairs: 468 | # 469 | # req = Net::HTTP::Get.new(uri) 470 | # req.to_hash 471 | # # => 472 | # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], 473 | # "accept"=>["*/*"], 474 | # "user-agent"=>["Ruby"], 475 | # "host"=>["jsonplaceholder.typicode.com"]} 476 | # 477 | def to_hash 478 | @header.dup 479 | end 480 | 481 | # Like #each_header, but the keys are returned in capitalized form. 482 | # 483 | # Net::HTTPHeader#canonical_each is an alias for Net::HTTPHeader#each_capitalized. 484 | def each_capitalized 485 | block_given? or return enum_for(__method__) { @header.size } 486 | @header.each do |k,v| 487 | yield capitalize(k), v.join(', ') 488 | end 489 | end 490 | 491 | alias canonical_each each_capitalized 492 | 493 | def capitalize(name) 494 | name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) 495 | end 496 | private :capitalize 497 | 498 | # Returns an array of Range objects that represent 499 | # the value of field 'Range', 500 | # or +nil+ if there is no such field; 501 | # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: 502 | # 503 | # req = Net::HTTP::Get.new(uri) 504 | # req['Range'] = 'bytes=0-99,200-299,400-499' 505 | # req.range # => [0..99, 200..299, 400..499] 506 | # req.delete('Range') 507 | # req.range # # => nil 508 | # 509 | def range 510 | return nil unless @header['range'] 511 | 512 | value = self['Range'] 513 | # byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec ) 514 | # *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] ) 515 | # corrected collected ABNF 516 | # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1 517 | # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C 518 | # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5 519 | unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value 520 | raise Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'" 521 | end 522 | 523 | byte_range_set = $1 524 | result = byte_range_set.split(/,/).map {|spec| 525 | m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or 526 | raise Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'" 527 | d1 = m[1].to_i 528 | d2 = m[2].to_i 529 | if m[1] and m[2] 530 | if d1 > d2 531 | raise Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'" 532 | end 533 | d1..d2 534 | elsif m[1] 535 | d1..-1 536 | elsif m[2] 537 | -d2..-1 538 | else 539 | raise Net::HTTPHeaderSyntaxError, 'range is not specified' 540 | end 541 | } 542 | # if result.empty? 543 | # byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec 544 | # but above regexp already denies it. 545 | if result.size == 1 && result[0].begin == 0 && result[0].end == -1 546 | raise Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length' 547 | end 548 | result 549 | end 550 | 551 | # call-seq: 552 | # set_range(length) -> length 553 | # set_range(offset, length) -> range 554 | # set_range(begin..length) -> range 555 | # 556 | # Sets the value for field 'Range'; 557 | # see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]: 558 | # 559 | # With argument +length+: 560 | # 561 | # req = Net::HTTP::Get.new(uri) 562 | # req.set_range(100) # => 100 563 | # req['Range'] # => "bytes=0-99" 564 | # 565 | # With arguments +offset+ and +length+: 566 | # 567 | # req.set_range(100, 100) # => 100...200 568 | # req['Range'] # => "bytes=100-199" 569 | # 570 | # With argument +range+: 571 | # 572 | # req.set_range(100..199) # => 100..199 573 | # req['Range'] # => "bytes=100-199" 574 | # 575 | # Net::HTTPHeader#range= is an alias for Net::HTTPHeader#set_range. 576 | def set_range(r, e = nil) 577 | unless r 578 | @header.delete 'range' 579 | return r 580 | end 581 | r = (r...r+e) if e 582 | case r 583 | when Numeric 584 | n = r.to_i 585 | rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}") 586 | when Range 587 | first = r.first 588 | last = r.end 589 | last -= 1 if r.exclude_end? 590 | if last == -1 591 | rangestr = (first > 0 ? "#{first}-" : "-#{-first}") 592 | else 593 | raise Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0 594 | raise Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0 595 | raise Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last 596 | rangestr = "#{first}-#{last}" 597 | end 598 | else 599 | raise TypeError, 'Range/Integer is required' 600 | end 601 | @header['range'] = ["bytes=#{rangestr}"] 602 | r 603 | end 604 | 605 | alias range= set_range 606 | 607 | # Returns the value of field 'Content-Length' as an integer, 608 | # or +nil+ if there is no such field; 609 | # see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]: 610 | # 611 | # res = Net::HTTP.get_response(hostname, '/nosuch/1') 612 | # res.content_length # => 2 613 | # res = Net::HTTP.get_response(hostname, '/todos/1') 614 | # res.content_length # => nil 615 | # 616 | def content_length 617 | return nil unless key?('Content-Length') 618 | len = self['Content-Length'].slice(/\d+/) or 619 | raise Net::HTTPHeaderSyntaxError, 'wrong Content-Length format' 620 | len.to_i 621 | end 622 | 623 | # Sets the value of field 'Content-Length' to the given numeric; 624 | # see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]: 625 | # 626 | # _uri = uri.dup 627 | # hostname = _uri.hostname # => "jsonplaceholder.typicode.com" 628 | # _uri.path = '/posts' # => "/posts" 629 | # req = Net::HTTP::Post.new(_uri) # => # 630 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 631 | # req.content_length = req.body.size # => 42 632 | # req.content_type = 'application/json' 633 | # res = Net::HTTP.start(hostname) do |http| 634 | # http.request(req) 635 | # end # => # 636 | # 637 | def content_length=(len) 638 | unless len 639 | @header.delete 'content-length' 640 | return nil 641 | end 642 | @header['content-length'] = [len.to_i.to_s] 643 | end 644 | 645 | # Returns +true+ if field 'Transfer-Encoding' 646 | # exists and has value 'chunked', 647 | # +false+ otherwise; 648 | # see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]: 649 | # 650 | # res = Net::HTTP.get_response(hostname, '/todos/1') 651 | # res['Transfer-Encoding'] # => "chunked" 652 | # res.chunked? # => true 653 | # 654 | def chunked? 655 | return false unless @header['transfer-encoding'] 656 | field = self['Transfer-Encoding'] 657 | (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false 658 | end 659 | 660 | # Returns a Range object representing the value of field 661 | # 'Content-Range', or +nil+ if no such field exists; 662 | # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: 663 | # 664 | # res = Net::HTTP.get_response(hostname, '/todos/1') 665 | # res['Content-Range'] # => nil 666 | # res['Content-Range'] = 'bytes 0-499/1000' 667 | # res['Content-Range'] # => "bytes 0-499/1000" 668 | # res.content_range # => 0..499 669 | # 670 | def content_range 671 | return nil unless @header['content-range'] 672 | m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or 673 | raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format' 674 | return unless m[1] == 'bytes' 675 | m[2].to_i .. m[3].to_i 676 | end 677 | 678 | # Returns the integer representing length of the value of field 679 | # 'Content-Range', or +nil+ if no such field exists; 680 | # see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]: 681 | # 682 | # res = Net::HTTP.get_response(hostname, '/todos/1') 683 | # res['Content-Range'] # => nil 684 | # res['Content-Range'] = 'bytes 0-499/1000' 685 | # res.range_length # => 500 686 | # 687 | def range_length 688 | r = content_range() or return nil 689 | r.end - r.begin + 1 690 | end 691 | 692 | # Returns the {media type}[https://en.wikipedia.org/wiki/Media_type] 693 | # from the value of field 'Content-Type', 694 | # or +nil+ if no such field exists; 695 | # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: 696 | # 697 | # res = Net::HTTP.get_response(hostname, '/todos/1') 698 | # res['content-type'] # => "application/json; charset=utf-8" 699 | # res.content_type # => "application/json" 700 | # 701 | def content_type 702 | main = main_type() 703 | return nil unless main 704 | 705 | sub = sub_type() 706 | if sub 707 | "#{main}/#{sub}" 708 | else 709 | main 710 | end 711 | end 712 | 713 | # Returns the leading ('type') part of the 714 | # {media type}[https://en.wikipedia.org/wiki/Media_type] 715 | # from the value of field 'Content-Type', 716 | # or +nil+ if no such field exists; 717 | # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: 718 | # 719 | # res = Net::HTTP.get_response(hostname, '/todos/1') 720 | # res['content-type'] # => "application/json; charset=utf-8" 721 | # res.main_type # => "application" 722 | # 723 | def main_type 724 | return nil unless @header['content-type'] 725 | self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip 726 | end 727 | 728 | # Returns the trailing ('subtype') part of the 729 | # {media type}[https://en.wikipedia.org/wiki/Media_type] 730 | # from the value of field 'Content-Type', 731 | # or +nil+ if no such field exists; 732 | # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: 733 | # 734 | # res = Net::HTTP.get_response(hostname, '/todos/1') 735 | # res['content-type'] # => "application/json; charset=utf-8" 736 | # res.sub_type # => "json" 737 | # 738 | def sub_type 739 | return nil unless @header['content-type'] 740 | _, sub = *self['Content-Type'].split(';').first.to_s.split('/') 741 | return nil unless sub 742 | sub.strip 743 | end 744 | 745 | # Returns the trailing ('parameters') part of the value of field 'Content-Type', 746 | # or +nil+ if no such field exists; 747 | # see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]: 748 | # 749 | # res = Net::HTTP.get_response(hostname, '/todos/1') 750 | # res['content-type'] # => "application/json; charset=utf-8" 751 | # res.type_params # => {"charset"=>"utf-8"} 752 | # 753 | def type_params 754 | result = {} 755 | list = self['Content-Type'].to_s.split(';') 756 | list.shift 757 | list.each do |param| 758 | k, v = *param.split('=', 2) 759 | result[k.strip] = v.strip 760 | end 761 | result 762 | end 763 | 764 | # Sets the value of field 'Content-Type'; 765 | # returns the new value; 766 | # see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]: 767 | # 768 | # req = Net::HTTP::Get.new(uri) 769 | # req.set_content_type('application/json') # => ["application/json"] 770 | # 771 | # Net::HTTPHeader#content_type= is an alias for Net::HTTPHeader#set_content_type. 772 | def set_content_type(type, params = {}) 773 | @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] 774 | end 775 | 776 | alias content_type= set_content_type 777 | 778 | # Sets the request body to a URL-encoded string derived from argument +params+, 779 | # and sets request header field 'Content-Type' 780 | # to 'application/x-www-form-urlencoded'. 781 | # 782 | # The resulting request is suitable for HTTP request +POST+ or +PUT+. 783 | # 784 | # Argument +params+ must be suitable for use as argument +enum+ to 785 | # {URI.encode_www_form}[https://docs.ruby-lang.org/en/master/URI.html#method-c-encode_www_form]. 786 | # 787 | # With only argument +params+ given, 788 | # sets the body to a URL-encoded string with the default separator '&': 789 | # 790 | # req = Net::HTTP::Post.new('example.com') 791 | # 792 | # req.set_form_data(q: 'ruby', lang: 'en') 793 | # req.body # => "q=ruby&lang=en" 794 | # req['Content-Type'] # => "application/x-www-form-urlencoded" 795 | # 796 | # req.set_form_data([['q', 'ruby'], ['lang', 'en']]) 797 | # req.body # => "q=ruby&lang=en" 798 | # 799 | # req.set_form_data(q: ['ruby', 'perl'], lang: 'en') 800 | # req.body # => "q=ruby&q=perl&lang=en" 801 | # 802 | # req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']]) 803 | # req.body # => "q=ruby&q=perl&lang=en" 804 | # 805 | # With string argument +sep+ also given, 806 | # uses that string as the separator: 807 | # 808 | # req.set_form_data({q: 'ruby', lang: 'en'}, '|') 809 | # req.body # => "q=ruby|lang=en" 810 | # 811 | # Net::HTTPHeader#form_data= is an alias for Net::HTTPHeader#set_form_data. 812 | def set_form_data(params, sep = '&') 813 | query = URI.encode_www_form(params) 814 | query.gsub!(/&/, sep) if sep != '&' 815 | self.body = query 816 | self.content_type = 'application/x-www-form-urlencoded' 817 | end 818 | 819 | alias form_data= set_form_data 820 | 821 | # Stores form data to be used in a +POST+ or +PUT+ request. 822 | # 823 | # The form data given in +params+ consists of zero or more fields; 824 | # each field is: 825 | # 826 | # - A scalar value. 827 | # - A name/value pair. 828 | # - An IO stream opened for reading. 829 | # 830 | # Argument +params+ should be an 831 | # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes] 832 | # (method params.map will be called), 833 | # and is often an array or hash. 834 | # 835 | # First, we set up a request: 836 | # 837 | # _uri = uri.dup 838 | # _uri.path ='/posts' 839 | # req = Net::HTTP::Post.new(_uri) 840 | # 841 | # Argument +params+ As an Array 842 | # 843 | # When +params+ is an array, 844 | # each of its elements is a subarray that defines a field; 845 | # the subarray may contain: 846 | # 847 | # - One string: 848 | # 849 | # req.set_form([['foo'], ['bar'], ['baz']]) 850 | # 851 | # - Two strings: 852 | # 853 | # req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]]) 854 | # 855 | # - When argument +enctype+ (see below) is given as 856 | # 'multipart/form-data': 857 | # 858 | # - A string name and an IO stream opened for reading: 859 | # 860 | # require 'stringio' 861 | # req.set_form([['file', StringIO.new('Ruby is cool.')]]) 862 | # 863 | # - A string name, an IO stream opened for reading, 864 | # and an options hash, which may contain these entries: 865 | # 866 | # - +:filename+: The name of the file to use. 867 | # - +:content_type+: The content type of the uploaded file. 868 | # 869 | # Example: 870 | # 871 | # req.set_form([['file', file, {filename: "other-filename.foo"}]] 872 | # 873 | # The various forms may be mixed: 874 | # 875 | # req.set_form(['foo', %w[bar 1], ['file', file]]) 876 | # 877 | # Argument +params+ As a Hash 878 | # 879 | # When +params+ is a hash, 880 | # each of its entries is a name/value pair that defines a field: 881 | # 882 | # - The name is a string. 883 | # - The value may be: 884 | # 885 | # - +nil+. 886 | # - Another string. 887 | # - An IO stream opened for reading 888 | # (only when argument +enctype+ -- see below -- is given as 889 | # 'multipart/form-data'). 890 | # 891 | # Examples: 892 | # 893 | # # Nil-valued fields. 894 | # req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil}) 895 | # 896 | # # String-valued fields. 897 | # req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2}) 898 | # 899 | # # IO-valued field. 900 | # require 'stringio' 901 | # req.set_form({'file' => StringIO.new('Ruby is cool.')}) 902 | # 903 | # # Mixture of fields. 904 | # req.set_form({'foo' => nil, 'bar' => 1, 'file' => file}) 905 | # 906 | # Optional argument +enctype+ specifies the value to be given 907 | # to field 'Content-Type', and must be one of: 908 | # 909 | # - 'application/x-www-form-urlencoded' (the default). 910 | # - 'multipart/form-data'; 911 | # see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578]. 912 | # 913 | # Optional argument +formopt+ is a hash of options 914 | # (applicable only when argument +enctype+ 915 | # is 'multipart/form-data') 916 | # that may include the following entries: 917 | # 918 | # - +:boundary+: The value is the boundary string for the multipart message. 919 | # If not given, the boundary is a random string. 920 | # See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1]. 921 | # - +:charset+: Value is the character set for the form submission. 922 | # Field names and values of non-file fields should be encoded with this charset. 923 | # 924 | def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) 925 | @body_data = params 926 | @body = nil 927 | @body_stream = nil 928 | @form_option = formopt 929 | case enctype 930 | when /\Aapplication\/x-www-form-urlencoded\z/i, 931 | /\Amultipart\/form-data\z/i 932 | self.content_type = enctype 933 | else 934 | raise ArgumentError, "invalid enctype: #{enctype}" 935 | end 936 | end 937 | 938 | # Sets header 'Authorization' using the given 939 | # +account+ and +password+ strings: 940 | # 941 | # req.basic_auth('my_account', 'my_password') 942 | # req['Authorization'] 943 | # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" 944 | # 945 | def basic_auth(account, password) 946 | @header['authorization'] = [basic_encode(account, password)] 947 | end 948 | 949 | # Sets header 'Proxy-Authorization' using the given 950 | # +account+ and +password+ strings: 951 | # 952 | # req.proxy_basic_auth('my_account', 'my_password') 953 | # req['Proxy-Authorization'] 954 | # # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA==" 955 | # 956 | def proxy_basic_auth(account, password) 957 | @header['proxy-authorization'] = [basic_encode(account, password)] 958 | end 959 | 960 | def basic_encode(account, password) 961 | 'Basic ' + ["#{account}:#{password}"].pack('m0') 962 | end 963 | private :basic_encode 964 | 965 | # Returns whether the HTTP session is to be closed. 966 | def connection_close? 967 | token = /(?:\A|,)\s*close\s*(?:\z|,)/i 968 | @header['connection']&.grep(token) {return true} 969 | @header['proxy-connection']&.grep(token) {return true} 970 | false 971 | end 972 | 973 | # Returns whether the HTTP session is to be kept alive. 974 | def connection_keep_alive? 975 | token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i 976 | @header['connection']&.grep(token) {return true} 977 | @header['proxy-connection']&.grep(token) {return true} 978 | false 979 | end 980 | 981 | end 982 | -------------------------------------------------------------------------------- /lib/net/http/proxy_delta.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Net::HTTP::ProxyDelta #:nodoc: internal use only 3 | private 4 | 5 | def conn_address 6 | proxy_address() 7 | end 8 | 9 | def conn_port 10 | proxy_port() 11 | end 12 | 13 | def edit_path(path) 14 | use_ssl? ? path : "http://#{addr_port()}#{path}" 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/net/http/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This class is the base class for \Net::HTTP request classes. 4 | # The class should not be used directly; 5 | # instead you should use its subclasses, listed below. 6 | # 7 | # == Creating a Request 8 | # 9 | # An request object may be created with either a URI or a string hostname: 10 | # 11 | # require 'net/http' 12 | # uri = URI('https://jsonplaceholder.typicode.com/') 13 | # req = Net::HTTP::Get.new(uri) # => # 14 | # req = Net::HTTP::Get.new(uri.hostname) # => # 15 | # 16 | # And with any of the subclasses: 17 | # 18 | # req = Net::HTTP::Head.new(uri) # => # 19 | # req = Net::HTTP::Post.new(uri) # => # 20 | # req = Net::HTTP::Put.new(uri) # => # 21 | # # ... 22 | # 23 | # The new instance is suitable for use as the argument to Net::HTTP#request. 24 | # 25 | # == Request Headers 26 | # 27 | # A new request object has these header fields by default: 28 | # 29 | # req.to_hash 30 | # # => 31 | # {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], 32 | # "accept"=>["*/*"], 33 | # "user-agent"=>["Ruby"], 34 | # "host"=>["jsonplaceholder.typicode.com"]} 35 | # 36 | # See: 37 | # 38 | # - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding] 39 | # and {Compression and Decompression}[rdoc-ref:Net::HTTP@Compression+and+Decompression]. 40 | # - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header]. 41 | # - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header]. 42 | # - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header]. 43 | # 44 | # You can add headers or override default headers: 45 | # 46 | # # res = Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'}) 47 | # 48 | # This class (and therefore its subclasses) also includes (indirectly) 49 | # module Net::HTTPHeader, which gives access to its 50 | # {methods for setting headers}[rdoc-ref:Net::HTTPHeader@Setters]. 51 | # 52 | # == Request Subclasses 53 | # 54 | # Subclasses for HTTP requests: 55 | # 56 | # - Net::HTTP::Get 57 | # - Net::HTTP::Head 58 | # - Net::HTTP::Post 59 | # - Net::HTTP::Put 60 | # - Net::HTTP::Delete 61 | # - Net::HTTP::Options 62 | # - Net::HTTP::Trace 63 | # - Net::HTTP::Patch 64 | # 65 | # Subclasses for WebDAV requests: 66 | # 67 | # - Net::HTTP::Propfind 68 | # - Net::HTTP::Proppatch 69 | # - Net::HTTP::Mkcol 70 | # - Net::HTTP::Copy 71 | # - Net::HTTP::Move 72 | # - Net::HTTP::Lock 73 | # - Net::HTTP::Unlock 74 | # 75 | class Net::HTTPRequest < Net::HTTPGenericRequest 76 | # Creates an HTTP request object for +path+. 77 | # 78 | # +initheader+ are the default headers to use. Net::HTTP adds 79 | # Accept-Encoding to enable compression of the response body unless 80 | # Accept-Encoding or Range are supplied in +initheader+. 81 | 82 | def initialize(path, initheader = nil) 83 | super self.class::METHOD, 84 | self.class::REQUEST_HAS_BODY, 85 | self.class::RESPONSE_HAS_BODY, 86 | path, initheader 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/net/http/requests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # HTTP/1.1 methods --- RFC2616 4 | 5 | # \Class for representing 6 | # {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]: 7 | # 8 | # require 'net/http' 9 | # uri = URI('http://example.com') 10 | # hostname = uri.hostname # => "example.com" 11 | # req = Net::HTTP::Get.new(uri) # => # 12 | # res = Net::HTTP.start(hostname) do |http| 13 | # http.request(req) 14 | # end 15 | # 16 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 17 | # 18 | # Properties: 19 | # 20 | # - Request body: optional. 21 | # - Response body: yes. 22 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. 23 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 24 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. 25 | # 26 | # Related: 27 | # 28 | # - Net::HTTP.get: sends +GET+ request, returns response body. 29 | # - Net::HTTP#get: sends +GET+ request, returns response object. 30 | # 31 | class Net::HTTP::Get < Net::HTTPRequest 32 | METHOD = 'GET' 33 | REQUEST_HAS_BODY = false 34 | RESPONSE_HAS_BODY = true 35 | end 36 | 37 | # \Class for representing 38 | # {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]: 39 | # 40 | # require 'net/http' 41 | # uri = URI('http://example.com') 42 | # hostname = uri.hostname # => "example.com" 43 | # req = Net::HTTP::Head.new(uri) # => # 44 | # res = Net::HTTP.start(hostname) do |http| 45 | # http.request(req) 46 | # end 47 | # 48 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 49 | # 50 | # Properties: 51 | # 52 | # - Request body: optional. 53 | # - Response body: no. 54 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. 55 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 56 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. 57 | # 58 | # Related: 59 | # 60 | # - Net::HTTP#head: sends +HEAD+ request, returns response object. 61 | # 62 | class Net::HTTP::Head < Net::HTTPRequest 63 | METHOD = 'HEAD' 64 | REQUEST_HAS_BODY = false 65 | RESPONSE_HAS_BODY = false 66 | end 67 | 68 | # \Class for representing 69 | # {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]: 70 | # 71 | # require 'net/http' 72 | # uri = URI('http://example.com') 73 | # hostname = uri.hostname # => "example.com" 74 | # uri.path = '/posts' 75 | # req = Net::HTTP::Post.new(uri) # => # 76 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 77 | # req.content_type = 'application/json' 78 | # res = Net::HTTP.start(hostname) do |http| 79 | # http.request(req) 80 | # end 81 | # 82 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 83 | # 84 | # Properties: 85 | # 86 | # - Request body: yes. 87 | # - Response body: yes. 88 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. 89 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. 90 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes. 91 | # 92 | # Related: 93 | # 94 | # - Net::HTTP.post: sends +POST+ request, returns response object. 95 | # - Net::HTTP#post: sends +POST+ request, returns response object. 96 | # 97 | class Net::HTTP::Post < Net::HTTPRequest 98 | METHOD = 'POST' 99 | REQUEST_HAS_BODY = true 100 | RESPONSE_HAS_BODY = true 101 | end 102 | 103 | # \Class for representing 104 | # {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]: 105 | # 106 | # require 'net/http' 107 | # uri = URI('http://example.com') 108 | # hostname = uri.hostname # => "example.com" 109 | # uri.path = '/posts' 110 | # req = Net::HTTP::Put.new(uri) # => # 111 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 112 | # req.content_type = 'application/json' 113 | # res = Net::HTTP.start(hostname) do |http| 114 | # http.request(req) 115 | # end 116 | # 117 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 118 | # 119 | # Properties: 120 | # 121 | # - Request body: yes. 122 | # - Response body: yes. 123 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. 124 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 125 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. 126 | # 127 | # Related: 128 | # 129 | # - Net::HTTP.put: sends +PUT+ request, returns response object. 130 | # - Net::HTTP#put: sends +PUT+ request, returns response object. 131 | # 132 | class Net::HTTP::Put < Net::HTTPRequest 133 | METHOD = 'PUT' 134 | REQUEST_HAS_BODY = true 135 | RESPONSE_HAS_BODY = true 136 | end 137 | 138 | # \Class for representing 139 | # {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]: 140 | # 141 | # require 'net/http' 142 | # uri = URI('http://example.com') 143 | # hostname = uri.hostname # => "example.com" 144 | # uri.path = '/posts/1' 145 | # req = Net::HTTP::Delete.new(uri) # => # 146 | # res = Net::HTTP.start(hostname) do |http| 147 | # http.request(req) 148 | # end 149 | # 150 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 151 | # 152 | # Properties: 153 | # 154 | # - Request body: optional. 155 | # - Response body: yes. 156 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. 157 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 158 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. 159 | # 160 | # Related: 161 | # 162 | # - Net::HTTP#delete: sends +DELETE+ request, returns response object. 163 | # 164 | class Net::HTTP::Delete < Net::HTTPRequest 165 | METHOD = 'DELETE' 166 | REQUEST_HAS_BODY = false 167 | RESPONSE_HAS_BODY = true 168 | end 169 | 170 | # \Class for representing 171 | # {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]: 172 | # 173 | # require 'net/http' 174 | # uri = URI('http://example.com') 175 | # hostname = uri.hostname # => "example.com" 176 | # req = Net::HTTP::Options.new(uri) # => # 177 | # res = Net::HTTP.start(hostname) do |http| 178 | # http.request(req) 179 | # end 180 | # 181 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 182 | # 183 | # Properties: 184 | # 185 | # - Request body: optional. 186 | # - Response body: yes. 187 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. 188 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 189 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. 190 | # 191 | # Related: 192 | # 193 | # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. 194 | # 195 | class Net::HTTP::Options < Net::HTTPRequest 196 | METHOD = 'OPTIONS' 197 | REQUEST_HAS_BODY = false 198 | RESPONSE_HAS_BODY = true 199 | end 200 | 201 | # \Class for representing 202 | # {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]: 203 | # 204 | # require 'net/http' 205 | # uri = URI('http://example.com') 206 | # hostname = uri.hostname # => "example.com" 207 | # req = Net::HTTP::Trace.new(uri) # => # 208 | # res = Net::HTTP.start(hostname) do |http| 209 | # http.request(req) 210 | # end 211 | # 212 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 213 | # 214 | # Properties: 215 | # 216 | # - Request body: no. 217 | # - Response body: yes. 218 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes. 219 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. 220 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. 221 | # 222 | # Related: 223 | # 224 | # - Net::HTTP#trace: sends +TRACE+ request, returns response object. 225 | # 226 | class Net::HTTP::Trace < Net::HTTPRequest 227 | METHOD = 'TRACE' 228 | REQUEST_HAS_BODY = false 229 | RESPONSE_HAS_BODY = true 230 | end 231 | 232 | # \Class for representing 233 | # {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]: 234 | # 235 | # require 'net/http' 236 | # uri = URI('http://example.com') 237 | # hostname = uri.hostname # => "example.com" 238 | # uri.path = '/posts' 239 | # req = Net::HTTP::Patch.new(uri) # => # 240 | # req.body = '{"title": "foo","body": "bar","userId": 1}' 241 | # req.content_type = 'application/json' 242 | # res = Net::HTTP.start(hostname) do |http| 243 | # http.request(req) 244 | # end 245 | # 246 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 247 | # 248 | # Properties: 249 | # 250 | # - Request body: yes. 251 | # - Response body: yes. 252 | # - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no. 253 | # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no. 254 | # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. 255 | # 256 | # Related: 257 | # 258 | # - Net::HTTP#patch: sends +PATCH+ request, returns response object. 259 | # 260 | class Net::HTTP::Patch < Net::HTTPRequest 261 | METHOD = 'PATCH' 262 | REQUEST_HAS_BODY = true 263 | RESPONSE_HAS_BODY = true 264 | end 265 | 266 | # 267 | # WebDAV methods --- RFC2518 268 | # 269 | 270 | # \Class for representing 271 | # {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]: 272 | # 273 | # require 'net/http' 274 | # uri = URI('http://example.com') 275 | # hostname = uri.hostname # => "example.com" 276 | # req = Net::HTTP::Propfind.new(uri) # => # 277 | # res = Net::HTTP.start(hostname) do |http| 278 | # http.request(req) 279 | # end 280 | # 281 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 282 | # 283 | # Related: 284 | # 285 | # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. 286 | # 287 | class Net::HTTP::Propfind < Net::HTTPRequest 288 | METHOD = 'PROPFIND' 289 | REQUEST_HAS_BODY = true 290 | RESPONSE_HAS_BODY = true 291 | end 292 | 293 | # \Class for representing 294 | # {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]: 295 | # 296 | # require 'net/http' 297 | # uri = URI('http://example.com') 298 | # hostname = uri.hostname # => "example.com" 299 | # req = Net::HTTP::Proppatch.new(uri) # => # 300 | # res = Net::HTTP.start(hostname) do |http| 301 | # http.request(req) 302 | # end 303 | # 304 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 305 | # 306 | # Related: 307 | # 308 | # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. 309 | # 310 | class Net::HTTP::Proppatch < Net::HTTPRequest 311 | METHOD = 'PROPPATCH' 312 | REQUEST_HAS_BODY = true 313 | RESPONSE_HAS_BODY = true 314 | end 315 | 316 | # \Class for representing 317 | # {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]: 318 | # 319 | # require 'net/http' 320 | # uri = URI('http://example.com') 321 | # hostname = uri.hostname # => "example.com" 322 | # req = Net::HTTP::Mkcol.new(uri) # => # 323 | # res = Net::HTTP.start(hostname) do |http| 324 | # http.request(req) 325 | # end 326 | # 327 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 328 | # 329 | # Related: 330 | # 331 | # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. 332 | # 333 | class Net::HTTP::Mkcol < Net::HTTPRequest 334 | METHOD = 'MKCOL' 335 | REQUEST_HAS_BODY = true 336 | RESPONSE_HAS_BODY = true 337 | end 338 | 339 | # \Class for representing 340 | # {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]: 341 | # 342 | # require 'net/http' 343 | # uri = URI('http://example.com') 344 | # hostname = uri.hostname # => "example.com" 345 | # req = Net::HTTP::Copy.new(uri) # => # 346 | # res = Net::HTTP.start(hostname) do |http| 347 | # http.request(req) 348 | # end 349 | # 350 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 351 | # 352 | # Related: 353 | # 354 | # - Net::HTTP#copy: sends +COPY+ request, returns response object. 355 | # 356 | class Net::HTTP::Copy < Net::HTTPRequest 357 | METHOD = 'COPY' 358 | REQUEST_HAS_BODY = false 359 | RESPONSE_HAS_BODY = true 360 | end 361 | 362 | # \Class for representing 363 | # {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]: 364 | # 365 | # require 'net/http' 366 | # uri = URI('http://example.com') 367 | # hostname = uri.hostname # => "example.com" 368 | # req = Net::HTTP::Move.new(uri) # => # 369 | # res = Net::HTTP.start(hostname) do |http| 370 | # http.request(req) 371 | # end 372 | # 373 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 374 | # 375 | # Related: 376 | # 377 | # - Net::HTTP#move: sends +MOVE+ request, returns response object. 378 | # 379 | class Net::HTTP::Move < Net::HTTPRequest 380 | METHOD = 'MOVE' 381 | REQUEST_HAS_BODY = false 382 | RESPONSE_HAS_BODY = true 383 | end 384 | 385 | # \Class for representing 386 | # {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]: 387 | # 388 | # require 'net/http' 389 | # uri = URI('http://example.com') 390 | # hostname = uri.hostname # => "example.com" 391 | # req = Net::HTTP::Lock.new(uri) # => # 392 | # res = Net::HTTP.start(hostname) do |http| 393 | # http.request(req) 394 | # end 395 | # 396 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 397 | # 398 | # Related: 399 | # 400 | # - Net::HTTP#lock: sends +LOCK+ request, returns response object. 401 | # 402 | class Net::HTTP::Lock < Net::HTTPRequest 403 | METHOD = 'LOCK' 404 | REQUEST_HAS_BODY = true 405 | RESPONSE_HAS_BODY = true 406 | end 407 | 408 | # \Class for representing 409 | # {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]: 410 | # 411 | # require 'net/http' 412 | # uri = URI('http://example.com') 413 | # hostname = uri.hostname # => "example.com" 414 | # req = Net::HTTP::Unlock.new(uri) # => # 415 | # res = Net::HTTP.start(hostname) do |http| 416 | # http.request(req) 417 | # end 418 | # 419 | # See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers]. 420 | # 421 | # Related: 422 | # 423 | # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. 424 | # 425 | class Net::HTTP::Unlock < Net::HTTPRequest 426 | METHOD = 'UNLOCK' 427 | REQUEST_HAS_BODY = true 428 | RESPONSE_HAS_BODY = true 429 | end 430 | 431 | -------------------------------------------------------------------------------- /lib/net/http/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This class is the base class for \Net::HTTP response classes. 4 | # 5 | # == About the Examples 6 | # 7 | # :include: doc/net-http/examples.rdoc 8 | # 9 | # == Returned Responses 10 | # 11 | # \Method Net::HTTP.get_response returns 12 | # an instance of one of the subclasses of \Net::HTTPResponse: 13 | # 14 | # Net::HTTP.get_response(uri) 15 | # # => # 16 | # Net::HTTP.get_response(hostname, '/nosuch') 17 | # # => # 18 | # 19 | # As does method Net::HTTP#request: 20 | # 21 | # req = Net::HTTP::Get.new(uri) 22 | # Net::HTTP.start(hostname) do |http| 23 | # http.request(req) 24 | # end # => # 25 | # 26 | # \Class \Net::HTTPResponse includes module Net::HTTPHeader, 27 | # which provides access to response header values via (among others): 28 | # 29 | # - \Hash-like method []. 30 | # - Specific reader methods, such as +content_type+. 31 | # 32 | # Examples: 33 | # 34 | # res = Net::HTTP.get_response(uri) # => # 35 | # res['Content-Type'] # => "text/html; charset=UTF-8" 36 | # res.content_type # => "text/html" 37 | # 38 | # == Response Subclasses 39 | # 40 | # \Class \Net::HTTPResponse has a subclass for each 41 | # {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes]. 42 | # You can look up the response class for a given code: 43 | # 44 | # Net::HTTPResponse::CODE_TO_OBJ['200'] # => Net::HTTPOK 45 | # Net::HTTPResponse::CODE_TO_OBJ['400'] # => Net::HTTPBadRequest 46 | # Net::HTTPResponse::CODE_TO_OBJ['404'] # => Net::HTTPNotFound 47 | # 48 | # And you can retrieve the status code for a response object: 49 | # 50 | # Net::HTTP.get_response(uri).code # => "200" 51 | # Net::HTTP.get_response(hostname, '/nosuch').code # => "404" 52 | # 53 | # The response subclasses (indentation shows class hierarchy): 54 | # 55 | # - Net::HTTPUnknownResponse (for unhandled \HTTP extensions). 56 | # 57 | # - Net::HTTPInformation: 58 | # 59 | # - Net::HTTPContinue (100) 60 | # - Net::HTTPSwitchProtocol (101) 61 | # - Net::HTTPProcessing (102) 62 | # - Net::HTTPEarlyHints (103) 63 | # 64 | # - Net::HTTPSuccess: 65 | # 66 | # - Net::HTTPOK (200) 67 | # - Net::HTTPCreated (201) 68 | # - Net::HTTPAccepted (202) 69 | # - Net::HTTPNonAuthoritativeInformation (203) 70 | # - Net::HTTPNoContent (204) 71 | # - Net::HTTPResetContent (205) 72 | # - Net::HTTPPartialContent (206) 73 | # - Net::HTTPMultiStatus (207) 74 | # - Net::HTTPAlreadyReported (208) 75 | # - Net::HTTPIMUsed (226) 76 | # 77 | # - Net::HTTPRedirection: 78 | # 79 | # - Net::HTTPMultipleChoices (300) 80 | # - Net::HTTPMovedPermanently (301) 81 | # - Net::HTTPFound (302) 82 | # - Net::HTTPSeeOther (303) 83 | # - Net::HTTPNotModified (304) 84 | # - Net::HTTPUseProxy (305) 85 | # - Net::HTTPTemporaryRedirect (307) 86 | # - Net::HTTPPermanentRedirect (308) 87 | # 88 | # - Net::HTTPClientError: 89 | # 90 | # - Net::HTTPBadRequest (400) 91 | # - Net::HTTPUnauthorized (401) 92 | # - Net::HTTPPaymentRequired (402) 93 | # - Net::HTTPForbidden (403) 94 | # - Net::HTTPNotFound (404) 95 | # - Net::HTTPMethodNotAllowed (405) 96 | # - Net::HTTPNotAcceptable (406) 97 | # - Net::HTTPProxyAuthenticationRequired (407) 98 | # - Net::HTTPRequestTimeOut (408) 99 | # - Net::HTTPConflict (409) 100 | # - Net::HTTPGone (410) 101 | # - Net::HTTPLengthRequired (411) 102 | # - Net::HTTPPreconditionFailed (412) 103 | # - Net::HTTPRequestEntityTooLarge (413) 104 | # - Net::HTTPRequestURITooLong (414) 105 | # - Net::HTTPUnsupportedMediaType (415) 106 | # - Net::HTTPRequestedRangeNotSatisfiable (416) 107 | # - Net::HTTPExpectationFailed (417) 108 | # - Net::HTTPMisdirectedRequest (421) 109 | # - Net::HTTPUnprocessableEntity (422) 110 | # - Net::HTTPLocked (423) 111 | # - Net::HTTPFailedDependency (424) 112 | # - Net::HTTPUpgradeRequired (426) 113 | # - Net::HTTPPreconditionRequired (428) 114 | # - Net::HTTPTooManyRequests (429) 115 | # - Net::HTTPRequestHeaderFieldsTooLarge (431) 116 | # - Net::HTTPUnavailableForLegalReasons (451) 117 | # 118 | # - Net::HTTPServerError: 119 | # 120 | # - Net::HTTPInternalServerError (500) 121 | # - Net::HTTPNotImplemented (501) 122 | # - Net::HTTPBadGateway (502) 123 | # - Net::HTTPServiceUnavailable (503) 124 | # - Net::HTTPGatewayTimeOut (504) 125 | # - Net::HTTPVersionNotSupported (505) 126 | # - Net::HTTPVariantAlsoNegotiates (506) 127 | # - Net::HTTPInsufficientStorage (507) 128 | # - Net::HTTPLoopDetected (508) 129 | # - Net::HTTPNotExtended (510) 130 | # - Net::HTTPNetworkAuthenticationRequired (511) 131 | # 132 | # There is also the Net::HTTPBadResponse exception which is raised when 133 | # there is a protocol error. 134 | # 135 | class Net::HTTPResponse 136 | class << self 137 | # true if the response has a body. 138 | def body_permitted? 139 | self::HAS_BODY 140 | end 141 | 142 | def exception_type # :nodoc: internal use only 143 | self::EXCEPTION_TYPE 144 | end 145 | 146 | def read_new(sock) #:nodoc: internal use only 147 | httpv, code, msg = read_status_line(sock) 148 | res = response_class(code).new(httpv, code, msg) 149 | each_response_header(sock) do |k,v| 150 | res.add_field k, v 151 | end 152 | res 153 | end 154 | 155 | private 156 | 157 | def read_status_line(sock) 158 | str = sock.readline 159 | m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or 160 | raise Net::HTTPBadResponse, "wrong status line: #{str.dump}" 161 | m.captures 162 | end 163 | 164 | def response_class(code) 165 | CODE_TO_OBJ[code] or 166 | CODE_CLASS_TO_OBJ[code[0,1]] or 167 | Net::HTTPUnknownResponse 168 | end 169 | 170 | def each_response_header(sock) 171 | key = value = nil 172 | while true 173 | line = sock.readuntil("\n", true).sub(/\s+\z/, '') 174 | break if line.empty? 175 | if line[0] == ?\s or line[0] == ?\t and value 176 | value << ' ' unless value.empty? 177 | value << line.strip 178 | else 179 | yield key, value if key 180 | key, value = line.strip.split(/\s*:\s*/, 2) 181 | raise Net::HTTPBadResponse, 'wrong header line format' if value.nil? 182 | end 183 | end 184 | yield key, value if key 185 | end 186 | end 187 | 188 | # next is to fix bug in RDoc, where the private inside class << self 189 | # spills out. 190 | public 191 | 192 | include Net::HTTPHeader 193 | 194 | def initialize(httpv, code, msg) #:nodoc: internal use only 195 | @http_version = httpv 196 | @code = code 197 | @message = msg 198 | initialize_http_header nil 199 | @body = nil 200 | @read = false 201 | @uri = nil 202 | @decode_content = false 203 | @body_encoding = false 204 | @ignore_eof = true 205 | end 206 | 207 | # The HTTP version supported by the server. 208 | attr_reader :http_version 209 | 210 | # The HTTP result code string. For example, '302'. You can also 211 | # determine the response type by examining which response subclass 212 | # the response object is an instance of. 213 | attr_reader :code 214 | 215 | # The HTTP result message sent by the server. For example, 'Not Found'. 216 | attr_reader :message 217 | alias msg message # :nodoc: obsolete 218 | 219 | # The URI used to fetch this response. The response URI is only available 220 | # if a URI was used to create the request. 221 | attr_reader :uri 222 | 223 | # Set to true automatically when the request did not contain an 224 | # Accept-Encoding header from the user. 225 | attr_accessor :decode_content 226 | 227 | # Returns the value set by body_encoding=, or +false+ if none; 228 | # see #body_encoding=. 229 | attr_reader :body_encoding 230 | 231 | # Sets the encoding that should be used when reading the body: 232 | # 233 | # - If the given value is an Encoding object, that encoding will be used. 234 | # - Otherwise if the value is a string, the value of 235 | # {Encoding#find(value)}[https://docs.ruby-lang.org/en/master/Encoding.html#method-c-find] 236 | # will be used. 237 | # - Otherwise an encoding will be deduced from the body itself. 238 | # 239 | # Examples: 240 | # 241 | # http = Net::HTTP.new(hostname) 242 | # req = Net::HTTP::Get.new('/') 243 | # 244 | # http.request(req) do |res| 245 | # p res.body.encoding # => # 246 | # end 247 | # 248 | # http.request(req) do |res| 249 | # res.body_encoding = "UTF-8" 250 | # p res.body.encoding # => # 251 | # end 252 | # 253 | def body_encoding=(value) 254 | value = Encoding.find(value) if value.is_a?(String) 255 | @body_encoding = value 256 | end 257 | 258 | # Whether to ignore EOF when reading bodies with a specified Content-Length 259 | # header. 260 | attr_accessor :ignore_eof 261 | 262 | def inspect 263 | "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" 264 | end 265 | 266 | # 267 | # response <-> exception relationship 268 | # 269 | 270 | def code_type #:nodoc: 271 | self.class 272 | end 273 | 274 | def error! #:nodoc: 275 | message = @code 276 | message = "#{message} #{@message.dump}" if @message 277 | raise error_type().new(message, self) 278 | end 279 | 280 | def error_type #:nodoc: 281 | self.class::EXCEPTION_TYPE 282 | end 283 | 284 | # Raises an HTTP error if the response is not 2xx (success). 285 | def value 286 | error! unless self.kind_of?(Net::HTTPSuccess) 287 | end 288 | 289 | def uri= uri # :nodoc: 290 | @uri = uri.dup if uri 291 | end 292 | 293 | # 294 | # header (for backward compatibility only; DO NOT USE) 295 | # 296 | 297 | def response #:nodoc: 298 | warn "Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE 299 | self 300 | end 301 | 302 | def header #:nodoc: 303 | warn "Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE 304 | self 305 | end 306 | 307 | def read_header #:nodoc: 308 | warn "Net::HTTPResponse#read_header is obsolete", uplevel: 1 if $VERBOSE 309 | self 310 | end 311 | 312 | # 313 | # body 314 | # 315 | 316 | def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only 317 | @socket = sock 318 | @body_exist = reqmethodallowbody && self.class.body_permitted? 319 | begin 320 | yield 321 | self.body # ensure to read body 322 | ensure 323 | @socket = nil 324 | end 325 | end 326 | 327 | # Gets the entity body returned by the remote HTTP server. 328 | # 329 | # If a block is given, the body is passed to the block, and 330 | # the body is provided in fragments, as it is read in from the socket. 331 | # 332 | # If +dest+ argument is given, response is read into that variable, 333 | # with dest#<< method (it could be String or IO, or any 334 | # other object responding to <<). 335 | # 336 | # Calling this method a second or subsequent time for the same 337 | # HTTPResponse object will return the value already read. 338 | # 339 | # http.request_get('/index.html') {|res| 340 | # puts res.read_body 341 | # } 342 | # 343 | # http.request_get('/index.html') {|res| 344 | # p res.read_body.object_id # 538149362 345 | # p res.read_body.object_id # 538149362 346 | # } 347 | # 348 | # # using iterator 349 | # http.request_get('/index.html') {|res| 350 | # res.read_body do |segment| 351 | # print segment 352 | # end 353 | # } 354 | # 355 | def read_body(dest = nil, &block) 356 | if @read 357 | raise IOError, "#{self.class}\#read_body called twice" if dest or block 358 | return @body 359 | end 360 | to = procdest(dest, block) 361 | stream_check 362 | if @body_exist 363 | read_body_0 to 364 | @body = to 365 | else 366 | @body = nil 367 | end 368 | @read = true 369 | return if @body.nil? 370 | 371 | case enc = @body_encoding 372 | when Encoding, false, nil 373 | # Encoding: force given encoding 374 | # false/nil: do not force encoding 375 | else 376 | # other value: detect encoding from body 377 | enc = detect_encoding(@body) 378 | end 379 | 380 | @body.force_encoding(enc) if enc 381 | 382 | @body 383 | end 384 | 385 | # Returns the string response body; 386 | # note that repeated calls for the unmodified body return a cached string: 387 | # 388 | # path = '/todos/1' 389 | # Net::HTTP.start(hostname) do |http| 390 | # res = http.get(path) 391 | # p res.body 392 | # p http.head(path).body # No body. 393 | # end 394 | # 395 | # Output: 396 | # 397 | # "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}" 398 | # nil 399 | # 400 | def body 401 | read_body() 402 | end 403 | 404 | # Sets the body of the response to the given value. 405 | def body=(value) 406 | @body = value 407 | end 408 | 409 | alias entity body #:nodoc: obsolete 410 | 411 | private 412 | 413 | # :nodoc: 414 | def detect_encoding(str, encoding=nil) 415 | if encoding 416 | elsif encoding = type_params['charset'] 417 | elsif encoding = check_bom(str) 418 | else 419 | encoding = case content_type&.downcase 420 | when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml} 421 | /\A' 508 | ss.getch 509 | return nil 510 | end 511 | name = ss.scan(/[^=\t\n\f\r \/>]*/) 512 | name.downcase! 513 | raise if name.empty? 514 | ss.skip(/[\t\n\f\r ]*/) 515 | if ss.getch != '=' 516 | value = '' 517 | return [name, value] 518 | end 519 | ss.skip(/[\t\n\f\r ]*/) 520 | case ss.peek(1) 521 | when '"' 522 | ss.getch 523 | value = ss.scan(/[^"]+/) 524 | value.downcase! 525 | ss.getch 526 | when "'" 527 | ss.getch 528 | value = ss.scan(/[^']+/) 529 | value.downcase! 530 | ss.getch 531 | when '>' 532 | value = '' 533 | else 534 | value = ss.scan(/[^\t\n\f\r >]+/) 535 | value.downcase! 536 | end 537 | [name, value] 538 | end 539 | 540 | def extracting_encodings_from_meta_elements(value) 541 | # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element 542 | if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value 543 | return $1 || $2 || $3 544 | end 545 | return nil 546 | end 547 | 548 | ## 549 | # Checks for a supported Content-Encoding header and yields an Inflate 550 | # wrapper for this response's socket when zlib is present. If the 551 | # Content-Encoding is not supported or zlib is missing, the plain socket is 552 | # yielded. 553 | # 554 | # If a Content-Range header is present, a plain socket is yielded as the 555 | # bytes in the range may not be a complete deflate block. 556 | 557 | def inflater # :nodoc: 558 | return yield @socket unless Net::HTTP::HAVE_ZLIB 559 | return yield @socket unless @decode_content 560 | return yield @socket if self['content-range'] 561 | 562 | v = self['content-encoding'] 563 | case v&.downcase 564 | when 'deflate', 'gzip', 'x-gzip' then 565 | self.delete 'content-encoding' 566 | 567 | inflate_body_io = Inflater.new(@socket) 568 | 569 | begin 570 | yield inflate_body_io 571 | success = true 572 | ensure 573 | begin 574 | inflate_body_io.finish 575 | if self['content-length'] 576 | self['content-length'] = inflate_body_io.bytes_inflated.to_s 577 | end 578 | rescue => err 579 | # Ignore #finish's error if there is an exception from yield 580 | raise err if success 581 | end 582 | end 583 | when 'none', 'identity' then 584 | self.delete 'content-encoding' 585 | 586 | yield @socket 587 | else 588 | yield @socket 589 | end 590 | end 591 | 592 | def read_body_0(dest) 593 | inflater do |inflate_body_io| 594 | if chunked? 595 | read_chunked dest, inflate_body_io 596 | return 597 | end 598 | 599 | @socket = inflate_body_io 600 | 601 | clen = content_length() 602 | if clen 603 | @socket.read clen, dest, @ignore_eof 604 | return 605 | end 606 | clen = range_length() 607 | if clen 608 | @socket.read clen, dest 609 | return 610 | end 611 | @socket.read_all dest 612 | end 613 | end 614 | 615 | ## 616 | # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF, 617 | # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip 618 | # encoded. 619 | # 620 | # See RFC 2616 section 3.6.1 for definitions 621 | 622 | def read_chunked(dest, chunk_data_io) # :nodoc: 623 | total = 0 624 | while true 625 | line = @socket.readline 626 | hexlen = line.slice(/[0-9a-fA-F]+/) or 627 | raise Net::HTTPBadResponse, "wrong chunk size line: #{line}" 628 | len = hexlen.hex 629 | break if len == 0 630 | begin 631 | chunk_data_io.read len, dest 632 | ensure 633 | total += len 634 | @socket.read 2 # \r\n 635 | end 636 | end 637 | until @socket.readline.empty? 638 | # none 639 | end 640 | end 641 | 642 | def stream_check 643 | raise IOError, 'attempt to read body out of block' if @socket.nil? || @socket.closed? 644 | end 645 | 646 | def procdest(dest, block) 647 | raise ArgumentError, 'both arg and block given for HTTP method' if 648 | dest and block 649 | if block 650 | Net::ReadAdapter.new(block) 651 | else 652 | dest || +'' 653 | end 654 | end 655 | 656 | ## 657 | # Inflater is a wrapper around Net::BufferedIO that transparently inflates 658 | # zlib and gzip streams. 659 | 660 | class Inflater # :nodoc: 661 | 662 | ## 663 | # Creates a new Inflater wrapping +socket+ 664 | 665 | def initialize socket 666 | @socket = socket 667 | # zlib with automatic gzip detection 668 | @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS) 669 | end 670 | 671 | ## 672 | # Finishes the inflate stream. 673 | 674 | def finish 675 | return if @inflate.total_in == 0 676 | @inflate.finish 677 | end 678 | 679 | ## 680 | # The number of bytes inflated, used to update the Content-Length of 681 | # the response. 682 | 683 | def bytes_inflated 684 | @inflate.total_out 685 | end 686 | 687 | ## 688 | # Returns a Net::ReadAdapter that inflates each read chunk into +dest+. 689 | # 690 | # This allows a large response body to be inflated without storing the 691 | # entire body in memory. 692 | 693 | def inflate_adapter(dest) 694 | if dest.respond_to?(:set_encoding) 695 | dest.set_encoding(Encoding::ASCII_8BIT) 696 | elsif dest.respond_to?(:force_encoding) 697 | dest.force_encoding(Encoding::ASCII_8BIT) 698 | end 699 | block = proc do |compressed_chunk| 700 | @inflate.inflate(compressed_chunk) do |chunk| 701 | compressed_chunk.clear 702 | dest << chunk 703 | end 704 | end 705 | 706 | Net::ReadAdapter.new(block) 707 | end 708 | 709 | ## 710 | # Reads +clen+ bytes from the socket, inflates them, then writes them to 711 | # +dest+. +ignore_eof+ is passed down to Net::BufferedIO#read 712 | # 713 | # Unlike Net::BufferedIO#read, this method returns more than +clen+ bytes. 714 | # At this time there is no way for a user of Net::HTTPResponse to read a 715 | # specific number of bytes from the HTTP response body, so this internal 716 | # API does not return the same number of bytes as were requested. 717 | # 718 | # See https://bugs.ruby-lang.org/issues/6492 for further discussion. 719 | 720 | def read clen, dest, ignore_eof = false 721 | temp_dest = inflate_adapter(dest) 722 | 723 | @socket.read clen, temp_dest, ignore_eof 724 | end 725 | 726 | ## 727 | # Reads the rest of the socket, inflates it, then writes it to +dest+. 728 | 729 | def read_all dest 730 | temp_dest = inflate_adapter(dest) 731 | 732 | @socket.read_all temp_dest 733 | end 734 | 735 | end 736 | 737 | end 738 | 739 | -------------------------------------------------------------------------------- /lib/net/http/responses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | #-- 3 | # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 4 | 5 | module Net 6 | 7 | class HTTPUnknownResponse < HTTPResponse 8 | HAS_BODY = true 9 | EXCEPTION_TYPE = HTTPError # 10 | end 11 | 12 | # Parent class for informational (1xx) HTTP response classes. 13 | # 14 | # An informational response indicates that the request was received and understood. 15 | # 16 | # References: 17 | # 18 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx]. 19 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. 20 | # 21 | class HTTPInformation < HTTPResponse 22 | HAS_BODY = false 23 | EXCEPTION_TYPE = HTTPError # 24 | end 25 | 26 | # Parent class for success (2xx) HTTP response classes. 27 | # 28 | # A success response indicates the action requested by the client 29 | # was received, understood, and accepted. 30 | # 31 | # References: 32 | # 33 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx]. 34 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. 35 | # 36 | class HTTPSuccess < HTTPResponse 37 | HAS_BODY = true 38 | EXCEPTION_TYPE = HTTPError # 39 | end 40 | 41 | # Parent class for redirection (3xx) HTTP response classes. 42 | # 43 | # A redirection response indicates the client must take additional action 44 | # to complete the request. 45 | # 46 | # References: 47 | # 48 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx]. 49 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. 50 | # 51 | class HTTPRedirection < HTTPResponse 52 | HAS_BODY = true 53 | EXCEPTION_TYPE = HTTPRetriableError # 54 | end 55 | 56 | # Parent class for client error (4xx) HTTP response classes. 57 | # 58 | # A client error response indicates that the client may have caused an error. 59 | # 60 | # References: 61 | # 62 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx]. 63 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. 64 | # 65 | class HTTPClientError < HTTPResponse 66 | HAS_BODY = true 67 | EXCEPTION_TYPE = HTTPClientException # 68 | end 69 | 70 | # Parent class for server error (5xx) HTTP response classes. 71 | # 72 | # A server error response indicates that the server failed to fulfill a request. 73 | # 74 | # References: 75 | # 76 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx]. 77 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. 78 | # 79 | class HTTPServerError < HTTPResponse 80 | HAS_BODY = true 81 | EXCEPTION_TYPE = HTTPFatalError # 82 | end 83 | 84 | # Response class for +Continue+ responses (status code 100). 85 | # 86 | # A +Continue+ response indicates that the server has received the request headers. 87 | # 88 | # :include: doc/net-http/included_getters.rdoc 89 | # 90 | # References: 91 | # 92 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100]. 93 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue]. 94 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. 95 | # 96 | class HTTPContinue < HTTPInformation 97 | HAS_BODY = false 98 | end 99 | 100 | # Response class for Switching Protocol responses (status code 101). 101 | # 102 | # The Switching Protocol response indicates that the server has received 103 | # a request to switch protocols, and has agreed to do so. 104 | # 105 | # :include: doc/net-http/included_getters.rdoc 106 | # 107 | # References: 108 | # 109 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101]. 110 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols]. 111 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. 112 | # 113 | class HTTPSwitchProtocol < HTTPInformation 114 | HAS_BODY = false 115 | end 116 | 117 | # Response class for +Processing+ responses (status code 102). 118 | # 119 | # The +Processing+ response indicates that the server has received 120 | # and is processing the request, but no response is available yet. 121 | # 122 | # :include: doc/net-http/included_getters.rdoc 123 | # 124 | # References: 125 | # 126 | # - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1]. 127 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. 128 | # 129 | class HTTPProcessing < HTTPInformation 130 | HAS_BODY = false 131 | end 132 | 133 | # Response class for Early Hints responses (status code 103). 134 | # 135 | # The Early Hints indicates that the server has received 136 | # and is processing the request, and contains certain headers; 137 | # the final response is not available yet. 138 | # 139 | # :include: doc/net-http/included_getters.rdoc 140 | # 141 | # References: 142 | # 143 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103]. 144 | # - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2]. 145 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. 146 | # 147 | class HTTPEarlyHints < HTTPInformation 148 | HAS_BODY = false 149 | end 150 | 151 | # Response class for +OK+ responses (status code 200). 152 | # 153 | # The +OK+ response indicates that the server has received 154 | # a request and has responded successfully. 155 | # 156 | # :include: doc/net-http/included_getters.rdoc 157 | # 158 | # References: 159 | # 160 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200]. 161 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok]. 162 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. 163 | # 164 | class HTTPOK < HTTPSuccess 165 | HAS_BODY = true 166 | end 167 | 168 | # Response class for +Created+ responses (status code 201). 169 | # 170 | # The +Created+ response indicates that the server has received 171 | # and has fulfilled a request to create a new resource. 172 | # 173 | # :include: doc/net-http/included_getters.rdoc 174 | # 175 | # References: 176 | # 177 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201]. 178 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created]. 179 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. 180 | # 181 | class HTTPCreated < HTTPSuccess 182 | HAS_BODY = true 183 | end 184 | 185 | # Response class for +Accepted+ responses (status code 202). 186 | # 187 | # The +Accepted+ response indicates that the server has received 188 | # and is processing a request, but the processing has not yet been completed. 189 | # 190 | # :include: doc/net-http/included_getters.rdoc 191 | # 192 | # References: 193 | # 194 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202]. 195 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted]. 196 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. 197 | # 198 | class HTTPAccepted < HTTPSuccess 199 | HAS_BODY = true 200 | end 201 | 202 | # Response class for Non-Authoritative Information responses (status code 203). 203 | # 204 | # The Non-Authoritative Information response indicates that the server 205 | # is a transforming proxy (such as a Web accelerator) 206 | # that received a 200 OK response from its origin, 207 | # and is returning a modified version of the origin's response. 208 | # 209 | # :include: doc/net-http/included_getters.rdoc 210 | # 211 | # References: 212 | # 213 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203]. 214 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor]. 215 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. 216 | # 217 | class HTTPNonAuthoritativeInformation < HTTPSuccess 218 | HAS_BODY = true 219 | end 220 | 221 | # Response class for No Content responses (status code 204). 222 | # 223 | # The No Content response indicates that the server 224 | # successfully processed the request, and is not returning any content. 225 | # 226 | # :include: doc/net-http/included_getters.rdoc 227 | # 228 | # References: 229 | # 230 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204]. 231 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content]. 232 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. 233 | # 234 | class HTTPNoContent < HTTPSuccess 235 | HAS_BODY = false 236 | end 237 | 238 | # Response class for Reset Content responses (status code 205). 239 | # 240 | # The Reset Content response indicates that the server 241 | # successfully processed the request, 242 | # asks that the client reset its document view, and is not returning any content. 243 | # 244 | # :include: doc/net-http/included_getters.rdoc 245 | # 246 | # References: 247 | # 248 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205]. 249 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content]. 250 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. 251 | # 252 | class HTTPResetContent < HTTPSuccess 253 | HAS_BODY = false 254 | end 255 | 256 | # Response class for Partial Content responses (status code 206). 257 | # 258 | # The Partial Content response indicates that the server is delivering 259 | # only part of the resource (byte serving) 260 | # due to a Range header in the request. 261 | # 262 | # :include: doc/net-http/included_getters.rdoc 263 | # 264 | # References: 265 | # 266 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206]. 267 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content]. 268 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. 269 | # 270 | class HTTPPartialContent < HTTPSuccess 271 | HAS_BODY = true 272 | end 273 | 274 | # Response class for Multi-Status (WebDAV) responses (status code 207). 275 | # 276 | # The Multi-Status (WebDAV) response indicates that the server 277 | # has received the request, 278 | # and that the message body can contain a number of separate response codes. 279 | # 280 | # :include: doc/net-http/included_getters.rdoc 281 | # 282 | # References: 283 | # 284 | # - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1]. 285 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. 286 | # 287 | class HTTPMultiStatus < HTTPSuccess 288 | HAS_BODY = true 289 | end 290 | 291 | # Response class for Already Reported (WebDAV) responses (status code 208). 292 | # 293 | # The Already Reported (WebDAV) response indicates that the server 294 | # has received the request, 295 | # and that the members of a DAV binding have already been enumerated 296 | # in a preceding part of the (multi-status) response, 297 | # and are not being included again. 298 | # 299 | # :include: doc/net-http/included_getters.rdoc 300 | # 301 | # References: 302 | # 303 | # - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1]. 304 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. 305 | # 306 | class HTTPAlreadyReported < HTTPSuccess 307 | HAS_BODY = true 308 | end 309 | 310 | # Response class for IM Used responses (status code 226). 311 | # 312 | # The IM Used response indicates that the server has fulfilled a request 313 | # for the resource, and the response is a representation of the result 314 | # of one or more instance-manipulations applied to the current instance. 315 | # 316 | # :include: doc/net-http/included_getters.rdoc 317 | # 318 | # References: 319 | # 320 | # - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1]. 321 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. 322 | # 323 | class HTTPIMUsed < HTTPSuccess 324 | HAS_BODY = true 325 | end 326 | 327 | # Response class for Multiple Choices responses (status code 300). 328 | # 329 | # The Multiple Choices response indicates that the server 330 | # offers multiple options for the resource from which the client may choose. 331 | # 332 | # :include: doc/net-http/included_getters.rdoc 333 | # 334 | # References: 335 | # 336 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300]. 337 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices]. 338 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. 339 | # 340 | class HTTPMultipleChoices < HTTPRedirection 341 | HAS_BODY = true 342 | end 343 | HTTPMultipleChoice = HTTPMultipleChoices 344 | 345 | # Response class for Moved Permanently responses (status code 301). 346 | # 347 | # The Moved Permanently response indicates that links or records 348 | # returning this response should be updated to use the given URL. 349 | # 350 | # :include: doc/net-http/included_getters.rdoc 351 | # 352 | # References: 353 | # 354 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301]. 355 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently]. 356 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. 357 | # 358 | class HTTPMovedPermanently < HTTPRedirection 359 | HAS_BODY = true 360 | end 361 | 362 | # Response class for Found responses (status code 302). 363 | # 364 | # The Found response indicates that the client 365 | # should look at (browse to) another URL. 366 | # 367 | # :include: doc/net-http/included_getters.rdoc 368 | # 369 | # References: 370 | # 371 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302]. 372 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found]. 373 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. 374 | # 375 | class HTTPFound < HTTPRedirection 376 | HAS_BODY = true 377 | end 378 | HTTPMovedTemporarily = HTTPFound 379 | 380 | # Response class for See Other responses (status code 303). 381 | # 382 | # The response to the request can be found under another URI using the GET method. 383 | # 384 | # :include: doc/net-http/included_getters.rdoc 385 | # 386 | # References: 387 | # 388 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303]. 389 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other]. 390 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. 391 | # 392 | class HTTPSeeOther < HTTPRedirection 393 | HAS_BODY = true 394 | end 395 | 396 | # Response class for Not Modified responses (status code 304). 397 | # 398 | # Indicates that the resource has not been modified since the version 399 | # specified by the request headers. 400 | # 401 | # :include: doc/net-http/included_getters.rdoc 402 | # 403 | # References: 404 | # 405 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304]. 406 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified]. 407 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. 408 | # 409 | class HTTPNotModified < HTTPRedirection 410 | HAS_BODY = false 411 | end 412 | 413 | # Response class for Use Proxy responses (status code 305). 414 | # 415 | # The requested resource is available only through a proxy, 416 | # whose address is provided in the response. 417 | # 418 | # :include: doc/net-http/included_getters.rdoc 419 | # 420 | # References: 421 | # 422 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy]. 423 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. 424 | # 425 | class HTTPUseProxy < HTTPRedirection 426 | HAS_BODY = false 427 | end 428 | 429 | # Response class for Temporary Redirect responses (status code 307). 430 | # 431 | # The request should be repeated with another URI; 432 | # however, future requests should still use the original URI. 433 | # 434 | # :include: doc/net-http/included_getters.rdoc 435 | # 436 | # References: 437 | # 438 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307]. 439 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect]. 440 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. 441 | # 442 | class HTTPTemporaryRedirect < HTTPRedirection 443 | HAS_BODY = true 444 | end 445 | 446 | # Response class for Permanent Redirect responses (status code 308). 447 | # 448 | # This and all future requests should be directed to the given URI. 449 | # 450 | # :include: doc/net-http/included_getters.rdoc 451 | # 452 | # References: 453 | # 454 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308]. 455 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect]. 456 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. 457 | # 458 | class HTTPPermanentRedirect < HTTPRedirection 459 | HAS_BODY = true 460 | end 461 | 462 | # Response class for Bad Request responses (status code 400). 463 | # 464 | # The server cannot or will not process the request due to an apparent client error. 465 | # 466 | # :include: doc/net-http/included_getters.rdoc 467 | # 468 | # References: 469 | # 470 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400]. 471 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request]. 472 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. 473 | # 474 | class HTTPBadRequest < HTTPClientError 475 | HAS_BODY = true 476 | end 477 | 478 | # Response class for Unauthorized responses (status code 401). 479 | # 480 | # Authentication is required, but either was not provided or failed. 481 | # 482 | # :include: doc/net-http/included_getters.rdoc 483 | # 484 | # References: 485 | # 486 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401]. 487 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized]. 488 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. 489 | # 490 | class HTTPUnauthorized < HTTPClientError 491 | HAS_BODY = true 492 | end 493 | 494 | # Response class for Payment Required responses (status code 402). 495 | # 496 | # Reserved for future use. 497 | # 498 | # :include: doc/net-http/included_getters.rdoc 499 | # 500 | # References: 501 | # 502 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402]. 503 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required]. 504 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. 505 | # 506 | class HTTPPaymentRequired < HTTPClientError 507 | HAS_BODY = true 508 | end 509 | 510 | # Response class for Forbidden responses (status code 403). 511 | # 512 | # The request contained valid data and was understood by the server, 513 | # but the server is refusing action. 514 | # 515 | # :include: doc/net-http/included_getters.rdoc 516 | # 517 | # References: 518 | # 519 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403]. 520 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden]. 521 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. 522 | # 523 | class HTTPForbidden < HTTPClientError 524 | HAS_BODY = true 525 | end 526 | 527 | # Response class for Not Found responses (status code 404). 528 | # 529 | # The requested resource could not be found but may be available in the future. 530 | # 531 | # :include: doc/net-http/included_getters.rdoc 532 | # 533 | # References: 534 | # 535 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404]. 536 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found]. 537 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. 538 | # 539 | class HTTPNotFound < HTTPClientError 540 | HAS_BODY = true 541 | end 542 | 543 | # Response class for Method Not Allowed responses (status code 405). 544 | # 545 | # The request method is not supported for the requested resource. 546 | # 547 | # :include: doc/net-http/included_getters.rdoc 548 | # 549 | # References: 550 | # 551 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405]. 552 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed]. 553 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. 554 | # 555 | class HTTPMethodNotAllowed < HTTPClientError 556 | HAS_BODY = true 557 | end 558 | 559 | # Response class for Not Acceptable responses (status code 406). 560 | # 561 | # The requested resource is capable of generating only content 562 | # that not acceptable according to the Accept headers sent in the request. 563 | # 564 | # :include: doc/net-http/included_getters.rdoc 565 | # 566 | # References: 567 | # 568 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406]. 569 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable]. 570 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. 571 | # 572 | class HTTPNotAcceptable < HTTPClientError 573 | HAS_BODY = true 574 | end 575 | 576 | # Response class for Proxy Authentication Required responses (status code 407). 577 | # 578 | # The client must first authenticate itself with the proxy. 579 | # 580 | # :include: doc/net-http/included_getters.rdoc 581 | # 582 | # References: 583 | # 584 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407]. 585 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re]. 586 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. 587 | # 588 | class HTTPProxyAuthenticationRequired < HTTPClientError 589 | HAS_BODY = true 590 | end 591 | 592 | # Response class for Request Timeout responses (status code 408). 593 | # 594 | # The server timed out waiting for the request. 595 | # 596 | # :include: doc/net-http/included_getters.rdoc 597 | # 598 | # References: 599 | # 600 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408]. 601 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout]. 602 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. 603 | # 604 | class HTTPRequestTimeout < HTTPClientError 605 | HAS_BODY = true 606 | end 607 | HTTPRequestTimeOut = HTTPRequestTimeout 608 | 609 | # Response class for Conflict responses (status code 409). 610 | # 611 | # The request could not be processed because of conflict in the current state of the resource. 612 | # 613 | # :include: doc/net-http/included_getters.rdoc 614 | # 615 | # References: 616 | # 617 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409]. 618 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict]. 619 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. 620 | # 621 | class HTTPConflict < HTTPClientError 622 | HAS_BODY = true 623 | end 624 | 625 | # Response class for Gone responses (status code 410). 626 | # 627 | # The resource requested was previously in use but is no longer available 628 | # and will not be available again. 629 | # 630 | # :include: doc/net-http/included_getters.rdoc 631 | # 632 | # References: 633 | # 634 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410]. 635 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone]. 636 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. 637 | # 638 | class HTTPGone < HTTPClientError 639 | HAS_BODY = true 640 | end 641 | 642 | # Response class for Length Required responses (status code 411). 643 | # 644 | # The request did not specify the length of its content, 645 | # which is required by the requested resource. 646 | # 647 | # :include: doc/net-http/included_getters.rdoc 648 | # 649 | # References: 650 | # 651 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411]. 652 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required]. 653 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. 654 | # 655 | class HTTPLengthRequired < HTTPClientError 656 | HAS_BODY = true 657 | end 658 | 659 | # Response class for Precondition Failed responses (status code 412). 660 | # 661 | # The server does not meet one of the preconditions 662 | # specified in the request headers. 663 | # 664 | # :include: doc/net-http/included_getters.rdoc 665 | # 666 | # References: 667 | # 668 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412]. 669 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed]. 670 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. 671 | # 672 | class HTTPPreconditionFailed < HTTPClientError 673 | HAS_BODY = true 674 | end 675 | 676 | # Response class for Payload Too Large responses (status code 413). 677 | # 678 | # The request is larger than the server is willing or able to process. 679 | # 680 | # :include: doc/net-http/included_getters.rdoc 681 | # 682 | # References: 683 | # 684 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413]. 685 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large]. 686 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. 687 | # 688 | class HTTPPayloadTooLarge < HTTPClientError 689 | HAS_BODY = true 690 | end 691 | HTTPRequestEntityTooLarge = HTTPPayloadTooLarge 692 | 693 | # Response class for URI Too Long responses (status code 414). 694 | # 695 | # The URI provided was too long for the server to process. 696 | # 697 | # :include: doc/net-http/included_getters.rdoc 698 | # 699 | # References: 700 | # 701 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414]. 702 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long]. 703 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. 704 | # 705 | class HTTPURITooLong < HTTPClientError 706 | HAS_BODY = true 707 | end 708 | HTTPRequestURITooLong = HTTPURITooLong 709 | HTTPRequestURITooLarge = HTTPRequestURITooLong 710 | 711 | # Response class for Unsupported Media Type responses (status code 415). 712 | # 713 | # The request entity has a media type which the server or resource does not support. 714 | # 715 | # :include: doc/net-http/included_getters.rdoc 716 | # 717 | # References: 718 | # 719 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415]. 720 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type]. 721 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. 722 | # 723 | class HTTPUnsupportedMediaType < HTTPClientError 724 | HAS_BODY = true 725 | end 726 | 727 | # Response class for Range Not Satisfiable responses (status code 416). 728 | # 729 | # The request entity has a media type which the server or resource does not support. 730 | # 731 | # :include: doc/net-http/included_getters.rdoc 732 | # 733 | # References: 734 | # 735 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416]. 736 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable]. 737 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. 738 | # 739 | class HTTPRangeNotSatisfiable < HTTPClientError 740 | HAS_BODY = true 741 | end 742 | HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable 743 | 744 | # Response class for Expectation Failed responses (status code 417). 745 | # 746 | # The server cannot meet the requirements of the Expect request-header field. 747 | # 748 | # :include: doc/net-http/included_getters.rdoc 749 | # 750 | # References: 751 | # 752 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417]. 753 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed]. 754 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. 755 | # 756 | class HTTPExpectationFailed < HTTPClientError 757 | HAS_BODY = true 758 | end 759 | 760 | # 418 I'm a teapot - RFC 2324; a joke RFC 761 | # See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418. 762 | 763 | # 420 Enhance Your Calm - Twitter 764 | 765 | # Response class for Misdirected Request responses (status code 421). 766 | # 767 | # The request was directed at a server that is not able to produce a response. 768 | # 769 | # :include: doc/net-http/included_getters.rdoc 770 | # 771 | # References: 772 | # 773 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request]. 774 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. 775 | # 776 | class HTTPMisdirectedRequest < HTTPClientError 777 | HAS_BODY = true 778 | end 779 | 780 | # Response class for Unprocessable Entity responses (status code 422). 781 | # 782 | # The request was well-formed but had semantic errors. 783 | # 784 | # :include: doc/net-http/included_getters.rdoc 785 | # 786 | # References: 787 | # 788 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422]. 789 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content]. 790 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. 791 | # 792 | class HTTPUnprocessableEntity < HTTPClientError 793 | HAS_BODY = true 794 | end 795 | 796 | # Response class for Locked (WebDAV) responses (status code 423). 797 | # 798 | # The requested resource is locked. 799 | # 800 | # :include: doc/net-http/included_getters.rdoc 801 | # 802 | # References: 803 | # 804 | # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3]. 805 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. 806 | # 807 | class HTTPLocked < HTTPClientError 808 | HAS_BODY = true 809 | end 810 | 811 | # Response class for Failed Dependency (WebDAV) responses (status code 424). 812 | # 813 | # The request failed because it depended on another request and that request failed. 814 | # See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. 815 | # 816 | # :include: doc/net-http/included_getters.rdoc 817 | # 818 | # References: 819 | # 820 | # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4]. 821 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. 822 | # 823 | class HTTPFailedDependency < HTTPClientError 824 | HAS_BODY = true 825 | end 826 | 827 | # 425 Too Early 828 | # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425. 829 | 830 | # Response class for Upgrade Required responses (status code 426). 831 | # 832 | # The client should switch to the protocol given in the Upgrade header field. 833 | # 834 | # :include: doc/net-http/included_getters.rdoc 835 | # 836 | # References: 837 | # 838 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426]. 839 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required]. 840 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. 841 | # 842 | class HTTPUpgradeRequired < HTTPClientError 843 | HAS_BODY = true 844 | end 845 | 846 | # Response class for Precondition Required responses (status code 428). 847 | # 848 | # The origin server requires the request to be conditional. 849 | # 850 | # :include: doc/net-http/included_getters.rdoc 851 | # 852 | # References: 853 | # 854 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428]. 855 | # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3]. 856 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. 857 | # 858 | class HTTPPreconditionRequired < HTTPClientError 859 | HAS_BODY = true 860 | end 861 | 862 | # Response class for Too Many Requests responses (status code 429). 863 | # 864 | # The user has sent too many requests in a given amount of time. 865 | # 866 | # :include: doc/net-http/included_getters.rdoc 867 | # 868 | # References: 869 | # 870 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429]. 871 | # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4]. 872 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. 873 | # 874 | class HTTPTooManyRequests < HTTPClientError 875 | HAS_BODY = true 876 | end 877 | 878 | # Response class for Request Header Fields Too Large responses (status code 431). 879 | # 880 | # An individual header field is too large, 881 | # or all the header fields collectively, are too large. 882 | # 883 | # :include: doc/net-http/included_getters.rdoc 884 | # 885 | # References: 886 | # 887 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431]. 888 | # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5]. 889 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. 890 | # 891 | class HTTPRequestHeaderFieldsTooLarge < HTTPClientError 892 | HAS_BODY = true 893 | end 894 | 895 | # Response class for Unavailable For Legal Reasons responses (status code 451). 896 | # 897 | # A server operator has received a legal demand to deny access to a resource or to a set of resources 898 | # that includes the requested resource. 899 | # 900 | # :include: doc/net-http/included_getters.rdoc 901 | # 902 | # References: 903 | # 904 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451]. 905 | # - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3]. 906 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. 907 | # 908 | class HTTPUnavailableForLegalReasons < HTTPClientError 909 | HAS_BODY = true 910 | end 911 | # 444 No Response - Nginx 912 | # 449 Retry With - Microsoft 913 | # 450 Blocked by Windows Parental Controls - Microsoft 914 | # 499 Client Closed Request - Nginx 915 | 916 | # Response class for Internal Server Error responses (status code 500). 917 | # 918 | # An unexpected condition was encountered and no more specific message is suitable. 919 | # 920 | # :include: doc/net-http/included_getters.rdoc 921 | # 922 | # References: 923 | # 924 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500]. 925 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error]. 926 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. 927 | # 928 | class HTTPInternalServerError < HTTPServerError 929 | HAS_BODY = true 930 | end 931 | 932 | # Response class for Not Implemented responses (status code 501). 933 | # 934 | # The server either does not recognize the request method, 935 | # or it lacks the ability to fulfil the request. 936 | # 937 | # :include: doc/net-http/included_getters.rdoc 938 | # 939 | # References: 940 | # 941 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501]. 942 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented]. 943 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. 944 | # 945 | class HTTPNotImplemented < HTTPServerError 946 | HAS_BODY = true 947 | end 948 | 949 | # Response class for Bad Gateway responses (status code 502). 950 | # 951 | # The server was acting as a gateway or proxy 952 | # and received an invalid response from the upstream server. 953 | # 954 | # :include: doc/net-http/included_getters.rdoc 955 | # 956 | # References: 957 | # 958 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502]. 959 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway]. 960 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. 961 | # 962 | class HTTPBadGateway < HTTPServerError 963 | HAS_BODY = true 964 | end 965 | 966 | # Response class for Service Unavailable responses (status code 503). 967 | # 968 | # The server cannot handle the request 969 | # (because it is overloaded or down for maintenance). 970 | # 971 | # :include: doc/net-http/included_getters.rdoc 972 | # 973 | # References: 974 | # 975 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503]. 976 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable]. 977 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. 978 | # 979 | class HTTPServiceUnavailable < HTTPServerError 980 | HAS_BODY = true 981 | end 982 | 983 | # Response class for Gateway Timeout responses (status code 504). 984 | # 985 | # The server was acting as a gateway or proxy 986 | # and did not receive a timely response from the upstream server. 987 | # 988 | # :include: doc/net-http/included_getters.rdoc 989 | # 990 | # References: 991 | # 992 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504]. 993 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout]. 994 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. 995 | # 996 | class HTTPGatewayTimeout < HTTPServerError 997 | HAS_BODY = true 998 | end 999 | HTTPGatewayTimeOut = HTTPGatewayTimeout 1000 | 1001 | # Response class for HTTP Version Not Supported responses (status code 505). 1002 | # 1003 | # The server does not support the HTTP version used in the request. 1004 | # 1005 | # :include: doc/net-http/included_getters.rdoc 1006 | # 1007 | # References: 1008 | # 1009 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505]. 1010 | # - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor]. 1011 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. 1012 | # 1013 | class HTTPVersionNotSupported < HTTPServerError 1014 | HAS_BODY = true 1015 | end 1016 | 1017 | # Response class for Variant Also Negotiates responses (status code 506). 1018 | # 1019 | # Transparent content negotiation for the request results in a circular reference. 1020 | # 1021 | # :include: doc/net-http/included_getters.rdoc 1022 | # 1023 | # References: 1024 | # 1025 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506]. 1026 | # - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1]. 1027 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. 1028 | # 1029 | class HTTPVariantAlsoNegotiates < HTTPServerError 1030 | HAS_BODY = true 1031 | end 1032 | 1033 | # Response class for Insufficient Storage (WebDAV) responses (status code 507). 1034 | # 1035 | # The server is unable to store the representation needed to complete the request. 1036 | # 1037 | # :include: doc/net-http/included_getters.rdoc 1038 | # 1039 | # References: 1040 | # 1041 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507]. 1042 | # - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5]. 1043 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. 1044 | # 1045 | class HTTPInsufficientStorage < HTTPServerError 1046 | HAS_BODY = true 1047 | end 1048 | 1049 | # Response class for Loop Detected (WebDAV) responses (status code 508). 1050 | # 1051 | # The server detected an infinite loop while processing the request. 1052 | # 1053 | # :include: doc/net-http/included_getters.rdoc 1054 | # 1055 | # References: 1056 | # 1057 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508]. 1058 | # - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2]. 1059 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. 1060 | # 1061 | class HTTPLoopDetected < HTTPServerError 1062 | HAS_BODY = true 1063 | end 1064 | # 509 Bandwidth Limit Exceeded - Apache bw/limited extension 1065 | 1066 | # Response class for Not Extended responses (status code 510). 1067 | # 1068 | # Further extensions to the request are required for the server to fulfill it. 1069 | # 1070 | # :include: doc/net-http/included_getters.rdoc 1071 | # 1072 | # References: 1073 | # 1074 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510]. 1075 | # - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7]. 1076 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. 1077 | # 1078 | class HTTPNotExtended < HTTPServerError 1079 | HAS_BODY = true 1080 | end 1081 | 1082 | # Response class for Network Authentication Required responses (status code 511). 1083 | # 1084 | # The client needs to authenticate to gain network access. 1085 | # 1086 | # :include: doc/net-http/included_getters.rdoc 1087 | # 1088 | # References: 1089 | # 1090 | # - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511]. 1091 | # - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6]. 1092 | # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. 1093 | # 1094 | class HTTPNetworkAuthenticationRequired < HTTPServerError 1095 | HAS_BODY = true 1096 | end 1097 | 1098 | end 1099 | 1100 | class Net::HTTPResponse 1101 | CODE_CLASS_TO_OBJ = { 1102 | '1' => Net::HTTPInformation, 1103 | '2' => Net::HTTPSuccess, 1104 | '3' => Net::HTTPRedirection, 1105 | '4' => Net::HTTPClientError, 1106 | '5' => Net::HTTPServerError 1107 | }.freeze 1108 | CODE_TO_OBJ = { 1109 | '100' => Net::HTTPContinue, 1110 | '101' => Net::HTTPSwitchProtocol, 1111 | '102' => Net::HTTPProcessing, 1112 | '103' => Net::HTTPEarlyHints, 1113 | 1114 | '200' => Net::HTTPOK, 1115 | '201' => Net::HTTPCreated, 1116 | '202' => Net::HTTPAccepted, 1117 | '203' => Net::HTTPNonAuthoritativeInformation, 1118 | '204' => Net::HTTPNoContent, 1119 | '205' => Net::HTTPResetContent, 1120 | '206' => Net::HTTPPartialContent, 1121 | '207' => Net::HTTPMultiStatus, 1122 | '208' => Net::HTTPAlreadyReported, 1123 | '226' => Net::HTTPIMUsed, 1124 | 1125 | '300' => Net::HTTPMultipleChoices, 1126 | '301' => Net::HTTPMovedPermanently, 1127 | '302' => Net::HTTPFound, 1128 | '303' => Net::HTTPSeeOther, 1129 | '304' => Net::HTTPNotModified, 1130 | '305' => Net::HTTPUseProxy, 1131 | '307' => Net::HTTPTemporaryRedirect, 1132 | '308' => Net::HTTPPermanentRedirect, 1133 | 1134 | '400' => Net::HTTPBadRequest, 1135 | '401' => Net::HTTPUnauthorized, 1136 | '402' => Net::HTTPPaymentRequired, 1137 | '403' => Net::HTTPForbidden, 1138 | '404' => Net::HTTPNotFound, 1139 | '405' => Net::HTTPMethodNotAllowed, 1140 | '406' => Net::HTTPNotAcceptable, 1141 | '407' => Net::HTTPProxyAuthenticationRequired, 1142 | '408' => Net::HTTPRequestTimeout, 1143 | '409' => Net::HTTPConflict, 1144 | '410' => Net::HTTPGone, 1145 | '411' => Net::HTTPLengthRequired, 1146 | '412' => Net::HTTPPreconditionFailed, 1147 | '413' => Net::HTTPPayloadTooLarge, 1148 | '414' => Net::HTTPURITooLong, 1149 | '415' => Net::HTTPUnsupportedMediaType, 1150 | '416' => Net::HTTPRangeNotSatisfiable, 1151 | '417' => Net::HTTPExpectationFailed, 1152 | '421' => Net::HTTPMisdirectedRequest, 1153 | '422' => Net::HTTPUnprocessableEntity, 1154 | '423' => Net::HTTPLocked, 1155 | '424' => Net::HTTPFailedDependency, 1156 | '426' => Net::HTTPUpgradeRequired, 1157 | '428' => Net::HTTPPreconditionRequired, 1158 | '429' => Net::HTTPTooManyRequests, 1159 | '431' => Net::HTTPRequestHeaderFieldsTooLarge, 1160 | '451' => Net::HTTPUnavailableForLegalReasons, 1161 | 1162 | '500' => Net::HTTPInternalServerError, 1163 | '501' => Net::HTTPNotImplemented, 1164 | '502' => Net::HTTPBadGateway, 1165 | '503' => Net::HTTPServiceUnavailable, 1166 | '504' => Net::HTTPGatewayTimeout, 1167 | '505' => Net::HTTPVersionNotSupported, 1168 | '506' => Net::HTTPVariantAlsoNegotiates, 1169 | '507' => Net::HTTPInsufficientStorage, 1170 | '508' => Net::HTTPLoopDetected, 1171 | '510' => Net::HTTPNotExtended, 1172 | '511' => Net::HTTPNetworkAuthenticationRequired, 1173 | }.freeze 1174 | end 1175 | -------------------------------------------------------------------------------- /lib/net/http/status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../http' 4 | 5 | if $0 == __FILE__ 6 | require 'open-uri' 7 | File.foreach(__FILE__) do |line| 8 | puts line 9 | break if line.start_with?('end') 10 | end 11 | puts 12 | puts "Net::HTTP::STATUS_CODES = {" 13 | url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv" 14 | URI(url).read.each_line do |line| 15 | code, mes, = line.split(',') 16 | next if ['(Unused)', 'Unassigned', 'Description'].include?(mes) 17 | puts " #{code} => '#{mes}'," 18 | end 19 | puts "} # :nodoc:" 20 | end 21 | 22 | Net::HTTP::STATUS_CODES = { 23 | 100 => 'Continue', 24 | 101 => 'Switching Protocols', 25 | 102 => 'Processing', 26 | 103 => 'Early Hints', 27 | 200 => 'OK', 28 | 201 => 'Created', 29 | 202 => 'Accepted', 30 | 203 => 'Non-Authoritative Information', 31 | 204 => 'No Content', 32 | 205 => 'Reset Content', 33 | 206 => 'Partial Content', 34 | 207 => 'Multi-Status', 35 | 208 => 'Already Reported', 36 | 226 => 'IM Used', 37 | 300 => 'Multiple Choices', 38 | 301 => 'Moved Permanently', 39 | 302 => 'Found', 40 | 303 => 'See Other', 41 | 304 => 'Not Modified', 42 | 305 => 'Use Proxy', 43 | 307 => 'Temporary Redirect', 44 | 308 => 'Permanent Redirect', 45 | 400 => 'Bad Request', 46 | 401 => 'Unauthorized', 47 | 402 => 'Payment Required', 48 | 403 => 'Forbidden', 49 | 404 => 'Not Found', 50 | 405 => 'Method Not Allowed', 51 | 406 => 'Not Acceptable', 52 | 407 => 'Proxy Authentication Required', 53 | 408 => 'Request Timeout', 54 | 409 => 'Conflict', 55 | 410 => 'Gone', 56 | 411 => 'Length Required', 57 | 412 => 'Precondition Failed', 58 | 413 => 'Content Too Large', 59 | 414 => 'URI Too Long', 60 | 415 => 'Unsupported Media Type', 61 | 416 => 'Range Not Satisfiable', 62 | 417 => 'Expectation Failed', 63 | 421 => 'Misdirected Request', 64 | 422 => 'Unprocessable Content', 65 | 423 => 'Locked', 66 | 424 => 'Failed Dependency', 67 | 425 => 'Too Early', 68 | 426 => 'Upgrade Required', 69 | 428 => 'Precondition Required', 70 | 429 => 'Too Many Requests', 71 | 431 => 'Request Header Fields Too Large', 72 | 451 => 'Unavailable For Legal Reasons', 73 | 500 => 'Internal Server Error', 74 | 501 => 'Not Implemented', 75 | 502 => 'Bad Gateway', 76 | 503 => 'Service Unavailable', 77 | 504 => 'Gateway Timeout', 78 | 505 => 'HTTP Version Not Supported', 79 | 506 => 'Variant Also Negotiates', 80 | 507 => 'Insufficient Storage', 81 | 508 => 'Loop Detected', 82 | 510 => 'Not Extended (OBSOLETED)', 83 | 511 => 'Network Authentication Required', 84 | } # :nodoc: 85 | -------------------------------------------------------------------------------- /lib/net/https.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | =begin 3 | 4 | = net/https -- SSL/TLS enhancement for Net::HTTP. 5 | 6 | This file has been merged with net/http. There is no longer any need to 7 | require 'net/https' to use HTTPS. 8 | 9 | See Net::HTTP for details on how to make HTTPS connections. 10 | 11 | == Info 12 | 'OpenSSL for Ruby 2' project 13 | Copyright (C) 2001 GOTOU Yuuzou 14 | All rights reserved. 15 | 16 | == Licence 17 | This program is licensed under the same licence as Ruby. 18 | (See the file 'LICENCE'.) 19 | 20 | =end 21 | 22 | require_relative 'http' 23 | require 'openssl' 24 | -------------------------------------------------------------------------------- /net-http.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | name = File.basename(__FILE__, ".gemspec") 4 | version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| 5 | file = File.join(__dir__, dir, "#{name.tr('-', '/')}.rb") 6 | begin 7 | break File.foreach(file, mode: "rb") do |line| 8 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 9 | end 10 | rescue SystemCallError 11 | next 12 | end 13 | end 14 | 15 | Gem::Specification.new do |spec| 16 | spec.name = name 17 | spec.version = version 18 | spec.authors = ["NARUSE, Yui"] 19 | spec.email = ["naruse@airemix.jp"] 20 | 21 | spec.summary = %q{HTTP client api for Ruby.} 22 | spec.description = %q{HTTP client api for Ruby.} 23 | spec.homepage = "https://github.com/ruby/net-http" 24 | spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") 25 | spec.licenses = ["Ruby", "BSD-2-Clause"] 26 | 27 | spec.metadata["changelog_uri"] = spec.homepage + "/releases" 28 | spec.metadata["homepage_uri"] = spec.homepage 29 | spec.metadata["source_code_uri"] = spec.homepage 30 | 31 | # Specify which files should be added to the gem when it is released. 32 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 33 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 34 | `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } 35 | end 36 | spec.bindir = "exe" 37 | spec.require_paths = ["lib"] 38 | 39 | spec.add_dependency "uri" 40 | end 41 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | 6 | module Test 7 | module Unit 8 | class TestCase 9 | def windows? platform = RUBY_PLATFORM 10 | /mswin|mingw/ =~ platform 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/net/fixtures/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | regen_certs: 4 | touch server.key 5 | make server.crt 6 | 7 | cacert.pem: server.key 8 | openssl req -new -x509 -days 3650 -key server.key -out cacert.pem -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" 9 | 10 | server.csr: 11 | openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" 12 | 13 | server.crt: server.csr cacert.pem 14 | openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -out server.crt 15 | rm server.csr 16 | -------------------------------------------------------------------------------- /test/net/fixtures/cacert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID+zCCAuOgAwIBAgIUGMvHl3EhtKPKcgc3NQSAYfFuC+8wDQYJKoZIhvcNAQEL 3 | BQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRQwEgYDVQQHDAtN 4 | YXR6LWUgY2l0eTEXMBUGA1UECgwOUnVieSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1 5 | YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJARYWc2VjdXJpdHlAcnVieS1sYW5nLm9y 6 | ZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEyMjkxMTQ3MjNaMIGMMQswCQYDVQQGEwJK 7 | UDEQMA4GA1UECAwHU2hpbWFuZTEUMBIGA1UEBwwLTWF0ei1lIGNpdHkxFzAVBgNV 8 | BAoMDlJ1YnkgQ29yZSBUZWFtMRUwEwYDVQQDDAxSdWJ5IFRlc3QgQ0ExJTAjBgkq 9 | hkiG9w0BCQEWFnNlY3VyaXR5QHJ1YnktbGFuZy5vcmcwggEiMA0GCSqGSIb3DQEB 10 | AQUAA4IBDwAwggEKAoIBAQCw+egZQ6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI 11 | +1GSqyi1bFBgsRjM0THllIdMbKmJtWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0f 12 | qXmG8UTz0VTWdlAXXmhUs6lSADvAaIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0 13 | yg+801SXzoFTTa+UGIRLE66jH51aa5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIe 14 | NWMF32wHqIOOPvQcWV3M5D2vxJEj702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1 15 | JNPc/n3dVUm+fM6NoDXPoLP7j55G9zKyqGtGAWXAj1MTAgMBAAGjUzBRMB0GA1Ud 16 | DgQWBBSJGVleDvFp9cu9R+E0/OKYzGkwkTAfBgNVHSMEGDAWgBSJGVleDvFp9cu9 17 | R+E0/OKYzGkwkTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBl 18 | 8GLB8skAWlkSw/FwbUmEV3zyqu+p7PNP5YIYoZs0D74e7yVulGQ6PKMZH5hrZmHo 19 | orFSQU+VUUirG8nDGj7Rzce8WeWBxsaDGC8CE2dq6nC6LuUwtbdMnBrH0LRWAz48 20 | jGFF3jHtVz8VsGfoZTZCjukWqNXvU6hETT9GsfU+PZqbqcTVRPH52+XgYayKdIbD 21 | r97RM4X3+aXBHcUW0b76eyyi65RR/Xtvn8ioZt2AdX7T2tZzJyXJN3Hupp77s6Ui 22 | AZR35SToHCZeTZD12YBvLBdaTPLZN7O/Q/aAO9ZiJaZ7SbFOjz813B2hxXab4Fob 23 | 2uJX6eMWTVxYK5D4M9lm 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /test/net/fixtures/dhparams.pem: -------------------------------------------------------------------------------- 1 | DH Parameters: (2048 bit) 2 | prime: 3 | 00:ec:4e:a4:06:b6:22:ca:f9:8a:00:cc:d0:ee:2f: 4 | 16:bf:05:64:f5:8f:fe:7f:c4:bb:b0:24:cd:ef:5d: 5 | 8a:90:ad:dc:a9:dd:63:84:90:d8:25:ba:d8:78:d5: 6 | 77:91:42:0a:84:fc:56:1e:13:9b:1c:aa:43:d5:1f: 7 | 38:52:92:fe:b3:66:f9:e7:e8:8c:77:a1:a6:2f:b3: 8 | 98:98:d2:13:fc:57:1c:2a:14:dc:bd:e6:9b:54:19: 9 | 99:4f:ce:81:64:a6:32:7f:8e:61:50:5f:45:3a:e5: 10 | 0c:f7:13:f3:b8:ad:d5:77:ca:09:42:f7:d8:30:27: 11 | 7b:2c:f0:b4:b5:a0:04:96:34:0b:47:81:1d:7f:c1: 12 | 3a:62:86:8e:7d:f8:13:7f:9a:b1:8b:09:23:9e:55: 13 | 59:41:cd:f0:86:09:c4:b7:d1:69:54:cb:d0:f5:e9: 14 | 27:c9:e1:81:e4:a1:df:6b:20:1c:df:e8:54:02:f2: 15 | 37:fc:2a:f7:d5:b3:6f:79:7e:70:22:78:79:18:3c: 16 | 75:14:68:4a:05:9f:ac:d4:7f:9a:79:db:9d:0a:6e: 17 | ec:0a:04:70:bf:c9:4a:59:81:a2:1f:33:9b:4a:66: 18 | bc:03:ce:8a:1b:e3:03:ec:ba:39:26:ab:90:dc:39: 19 | 41:a1:d8:f7:20:3c:8f:af:12:2f:f7:a9:6f:44:f1: 20 | 6d:03 21 | generator: 2 (0x2) 22 | -----BEGIN DH PARAMETERS----- 23 | MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY 24 | JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab 25 | VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 26 | YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 27 | 1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD 28 | 7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== 29 | -----END DH PARAMETERS----- 30 | -------------------------------------------------------------------------------- /test/net/fixtures/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDYTCCAkkCAQAwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYD 3 | VQQIDAdTaGltYW5lMRQwEgYDVQQHDAtNYXR6LWUgY2l0eTEXMBUGA1UECgwOUnVi 4 | eSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJ 5 | ARYWc2VjdXJpdHlAcnVieS1sYW5nLm9yZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEy 6 | MjkxMTQ3MjNaMGAxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRcwFQYD 7 | VQQKDA5SdWJ5IENvcmUgVGVhbTESMBAGA1UECwwJUnVieSBUZXN0MRIwEAYDVQQD 8 | DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw+egZ 9 | Q6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI+1GSqyi1bFBgsRjM0THllIdMbKmJ 10 | tWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0fqXmG8UTz0VTWdlAXXmhUs6lSADvA 11 | aIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0yg+801SXzoFTTa+UGIRLE66jH51a 12 | a5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIeNWMF32wHqIOOPvQcWV3M5D2vxJEj 13 | 702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1JNPc/n3dVUm+fM6NoDXPoLP7j55G 14 | 9zKyqGtGAWXAj1MTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACtGNdj5TEtnJBYp 15 | M+LhBeU3oNteldfycEm993gJp6ghWZFg23oX8fVmyEeJr/3Ca9bAgDqg0t9a0npN 16 | oWKEY6wVKqcHgu3gSvThF5c9KhGbeDDmlTSVVNQmXWX0K2d4lS2cwZHH8mCm2mrY 17 | PDqlEkSc7k4qSiqigdS8i80Yk+lDXWsm8CjsiC93qaRM7DnS0WPQR0c16S95oM6G 18 | VklFKUSDAuFjw9aVWA/nahOucjn0w5fVW6lyIlkBslC1ChlaDgJmvhz+Ol3iMsE0 19 | kAmFNu2KKPVrpMWaBID49QwQTDyhetNLaVVFM88iUdA9JDoVMEuP1mm39JqyzHTu 20 | uBrdP4Q= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/net/fixtures/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso 3 | tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE 4 | 89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU 5 | l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s 6 | B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 7 | 3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ 8 | dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI 9 | FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J 10 | aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 11 | BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx 12 | IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ 13 | fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u 14 | pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT 15 | Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl 16 | u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD 17 | fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X 18 | Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE 19 | k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo 20 | qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS 21 | CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ 22 | XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw 23 | AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r 24 | UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 25 | 2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 26 | 7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/net/http/test_buffered_io.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'net/http' 4 | require 'stringio' 5 | 6 | require_relative 'utils' 7 | 8 | module Net 9 | class TestBufferedIO < Test::Unit::TestCase 10 | def test_eof? 11 | s = StringIO.new 12 | assert s.eof? 13 | bio = BufferedIO.new(s) 14 | assert_equal s, bio.io 15 | assert_equal s.eof?, bio.eof? 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/net/http/test_http_request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'net/http' 3 | require 'test/unit' 4 | 5 | class HTTPRequestTest < Test::Unit::TestCase 6 | 7 | def test_initialize_GET 8 | req = Net::HTTP::Get.new '/' 9 | 10 | assert_equal 'GET', req.method 11 | assert_not_predicate req, :request_body_permitted? 12 | assert_predicate req, :response_body_permitted? 13 | 14 | expected = { 15 | 'accept' => %w[*/*], 16 | 'user-agent' => %w[Ruby], 17 | } 18 | 19 | expected['accept-encoding'] = %w[gzip;q=1.0,deflate;q=0.6,identity;q=0.3] if 20 | Net::HTTP::HAVE_ZLIB 21 | 22 | assert_equal expected, req.to_hash 23 | end 24 | 25 | def test_initialize_GET_range 26 | req = Net::HTTP::Get.new '/', 'Range' => 'bytes=0-9' 27 | 28 | assert_equal 'GET', req.method 29 | assert_not_predicate req, :request_body_permitted? 30 | assert_predicate req, :response_body_permitted? 31 | 32 | expected = { 33 | 'accept' => %w[*/*], 34 | 'user-agent' => %w[Ruby], 35 | 'range' => %w[bytes=0-9], 36 | } 37 | 38 | assert_equal expected, req.to_hash 39 | end 40 | 41 | def test_initialize_HEAD 42 | req = Net::HTTP::Head.new '/' 43 | 44 | assert_equal 'HEAD', req.method 45 | assert_not_predicate req, :request_body_permitted? 46 | assert_not_predicate req, :response_body_permitted? 47 | 48 | expected = { 49 | 'accept' => %w[*/*], 50 | "accept-encoding" => %w[gzip;q=1.0,deflate;q=0.6,identity;q=0.3], 51 | 'user-agent' => %w[Ruby], 52 | } 53 | 54 | assert_equal expected, req.to_hash 55 | end 56 | 57 | def test_initialize_accept_encoding 58 | req1 = Net::HTTP::Get.new '/' 59 | 60 | assert req1.decode_content, 'Bug #7831 - automatically decode content' 61 | 62 | req2 = Net::HTTP::Get.new '/', 'accept-encoding' => 'identity' 63 | 64 | assert_not_predicate req2, :decode_content, 65 | 'Bug #7381 - do not decode content if the user overrides' 66 | end if Net::HTTP::HAVE_ZLIB 67 | 68 | def test_initialize_GET_uri 69 | req = Net::HTTP::Get.new(URI("http://example.com/foo")) 70 | assert_equal "/foo", req.path 71 | assert_equal "example.com", req['Host'] 72 | 73 | req = Net::HTTP::Get.new(URI("https://example.com/foo")) 74 | assert_equal "/foo", req.path 75 | assert_equal "example.com", req['Host'] 76 | 77 | assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("urn:ietf:rfc:7231")) } 78 | assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("http://")) } 79 | end 80 | 81 | def test_header_set 82 | req = Net::HTTP::Get.new '/' 83 | 84 | assert req.decode_content, 'Bug #7831 - automatically decode content' 85 | 86 | req['accept-encoding'] = 'identity' 87 | 88 | assert_not_predicate req, :decode_content, 89 | 'Bug #7831 - do not decode content if the user overrides' 90 | end if Net::HTTP::HAVE_ZLIB 91 | 92 | end 93 | 94 | -------------------------------------------------------------------------------- /test/net/http/test_httpheader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'net/http' 3 | require 'test/unit' 4 | 5 | class HTTPHeaderTest < Test::Unit::TestCase 6 | 7 | class C 8 | include Net::HTTPHeader 9 | def initialize 10 | initialize_http_header({}) 11 | end 12 | attr_accessor :body 13 | end 14 | 15 | def setup 16 | @c = C.new 17 | end 18 | 19 | def test_initialize 20 | @c.initialize_http_header("foo"=>"abc") 21 | assert_equal "abc", @c["foo"] 22 | @c.initialize_http_header("foo"=>"abc", "bar"=>"xyz") 23 | assert_equal "xyz", @c["bar"] 24 | @c.initialize_http_header([["foo", "abc"]]) 25 | assert_equal "abc", @c["foo"] 26 | @c.initialize_http_header([["foo", "abc"], ["bar","xyz"]]) 27 | assert_equal "xyz", @c["bar"] 28 | assert_raise(NoMethodError){ @c.initialize_http_header("foo"=>[]) } 29 | assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\nb") } 30 | assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\rb") } 31 | end 32 | 33 | def test_initialize_with_broken_coderange 34 | error = RUBY_VERSION >= "3.2" ? Encoding::CompatibilityError : ArgumentError 35 | assert_raise(error){ @c.initialize_http_header("foo"=>"a\xff") } 36 | end 37 | 38 | def test_initialize_with_symbol 39 | @c.initialize_http_header(foo: "abc") 40 | assert_equal "abc", @c["foo"] 41 | end 42 | 43 | def test_size 44 | assert_equal 0, @c.size 45 | @c['a'] = 'a' 46 | assert_equal 1, @c.size 47 | @c['b'] = 'b' 48 | assert_equal 2, @c.size 49 | @c['b'] = 'b' 50 | assert_equal 2, @c.size 51 | @c['c'] = 'c' 52 | assert_equal 3, @c.size 53 | end 54 | 55 | def test_ASET 56 | @c['My-Header'] = 'test string' 57 | @c['my-Header'] = 'test string' 58 | @c['My-header'] = 'test string' 59 | @c['my-header'] = 'test string' 60 | @c['MY-HEADER'] = 'test string' 61 | assert_equal 1, @c.size 62 | 63 | @c['AaA'] = 'aaa' 64 | @c['aaA'] = 'aaa' 65 | @c['AAa'] = 'aaa' 66 | assert_equal 2, @c.length 67 | 68 | @c['aaa'] = ['aaa', ['bbb', [3]]] 69 | assert_equal 2, @c.length 70 | assert_equal ['aaa', 'bbb', '3'], @c.get_fields('aaa') 71 | 72 | @c['aaa'] = "aaa\xff" 73 | assert_equal 2, @c.length 74 | 75 | assert_raise(ArgumentError){ @c['foo'] = "a\nb" } 76 | assert_raise(ArgumentError){ @c['foo'] = ["a\nb"] } 77 | end 78 | 79 | def test_AREF 80 | @c['My-Header'] = 'test string' 81 | assert_equal 'test string', @c['my-header'] 82 | assert_equal 'test string', @c['MY-header'] 83 | assert_equal 'test string', @c['my-HEADER'] 84 | 85 | @c['Next-Header'] = 'next string' 86 | assert_equal 'next string', @c['next-header'] 87 | end 88 | 89 | def test_add_field 90 | @c.add_field 'My-Header', 'a' 91 | assert_equal 'a', @c['My-Header'] 92 | assert_equal ['a'], @c.get_fields('My-Header') 93 | @c.add_field 'My-Header', 'b' 94 | assert_equal 'a, b', @c['My-Header'] 95 | assert_equal ['a', 'b'], @c.get_fields('My-Header') 96 | @c.add_field 'My-Header', 'c' 97 | assert_equal 'a, b, c', @c['My-Header'] 98 | assert_equal ['a', 'b', 'c'], @c.get_fields('My-Header') 99 | @c.add_field 'My-Header', 'd, d' 100 | assert_equal 'a, b, c, d, d', @c['My-Header'] 101 | assert_equal ['a', 'b', 'c', 'd, d'], @c.get_fields('My-Header') 102 | assert_raise(ArgumentError){ @c.add_field 'My-Header', "d\nd" } 103 | @c.add_field 'My-Header', ['e', ["\xff", 7]] 104 | assert_equal "a, b, c, d, d, e, \xff, 7", @c['My-Header'] 105 | assert_equal ['a', 'b', 'c', 'd, d', 'e', "\xff", '7'], @c.get_fields('My-Header') 106 | end 107 | 108 | def test_get_fields 109 | @c['My-Header'] = 'test string' 110 | assert_equal ['test string'], @c.get_fields('my-header') 111 | assert_equal ['test string'], @c.get_fields('My-header') 112 | assert_equal ['test string'], @c.get_fields('my-Header') 113 | 114 | assert_nil @c.get_fields('not-found') 115 | assert_nil @c.get_fields('Not-Found') 116 | 117 | @c.get_fields('my-header').push 'junk' 118 | assert_equal ['test string'], @c.get_fields('my-header') 119 | @c.get_fields('my-header').clear 120 | assert_equal ['test string'], @c.get_fields('my-header') 121 | end 122 | 123 | class D; include Net::HTTPHeader; end 124 | 125 | def test_nil_variable_header 126 | assert_nothing_raised do 127 | assert_warning("#{__FILE__}:#{__LINE__+1}: warning: net/http: nil HTTP header: Authorization\n") do 128 | D.new.initialize_http_header({Authorization: nil}) 129 | end 130 | end 131 | end 132 | 133 | def test_duplicated_variable_header 134 | assert_nothing_raised do 135 | assert_warning("#{__FILE__}:#{__LINE__+1}: warning: net/http: duplicated HTTP header: Authorization\n") do 136 | D.new.initialize_http_header({"AUTHORIZATION": "yes", "Authorization": "no"}) 137 | end 138 | end 139 | end 140 | 141 | def test_delete 142 | @c['My-Header'] = 'test' 143 | assert_equal 'test', @c['My-Header'] 144 | assert_nil @c['not-found'] 145 | @c.delete 'My-Header' 146 | assert_nil @c['My-Header'] 147 | assert_nil @c['not-found'] 148 | @c.delete 'My-Header' 149 | @c.delete 'My-Header' 150 | assert_nil @c['My-Header'] 151 | assert_nil @c['not-found'] 152 | end 153 | 154 | def test_each 155 | @c['My-Header'] = 'test' 156 | @c.each do |k, v| 157 | assert_equal 'my-header', k 158 | assert_equal 'test', v 159 | end 160 | @c.each do |k, v| 161 | assert_equal 'my-header', k 162 | assert_equal 'test', v 163 | end 164 | e = @c.each 165 | assert_equal 1, e.size 166 | e.each do |k, v| 167 | assert_equal 'my-header', k 168 | assert_equal 'test', v 169 | end 170 | end 171 | 172 | def test_each_key 173 | @c['My-Header'] = 'test' 174 | @c.each_key do |k| 175 | assert_equal 'my-header', k 176 | end 177 | @c.each_key do |k| 178 | assert_equal 'my-header', k 179 | end 180 | e = @c.each_key 181 | assert_equal 1, e.size 182 | e.each do |k| 183 | assert_equal 'my-header', k 184 | end 185 | end 186 | 187 | def test_each_capitalized_name 188 | @c['my-header'] = 'test' 189 | @c.each_capitalized_name do |k| 190 | assert_equal 'My-Header', k 191 | end 192 | @c.each_capitalized_name do |k| 193 | assert_equal 'My-Header', k 194 | end 195 | e = @c.each_capitalized_name 196 | assert_equal 1, e.size 197 | e.each do |k| 198 | assert_equal 'My-Header', k 199 | end 200 | end 201 | 202 | def test_each_value 203 | @c['My-Header'] = 'test' 204 | @c.each_value do |v| 205 | assert_equal 'test', v 206 | end 207 | @c.each_value do |v| 208 | assert_equal 'test', v 209 | end 210 | e = @c.each_value 211 | assert_equal 1, e.size 212 | e.each do |v| 213 | assert_equal 'test', v 214 | end 215 | end 216 | 217 | def test_canonical_each 218 | @c['my-header'] = ['a', 'b'] 219 | @c.canonical_each do |k,v| 220 | assert_equal 'My-Header', k 221 | assert_equal 'a, b', v 222 | end 223 | e = @c.canonical_each 224 | assert_equal 1, e.size 225 | e.each do |k,v| 226 | assert_equal 'My-Header', k 227 | assert_equal 'a, b', v 228 | end 229 | end 230 | 231 | def test_each_capitalized 232 | @c['my-header'] = ['a', 'b'] 233 | @c.each_capitalized do |k,v| 234 | assert_equal 'My-Header', k 235 | assert_equal 'a, b', v 236 | end 237 | e = @c.each_capitalized 238 | assert_equal 1, e.size 239 | e.each do |k,v| 240 | assert_equal 'My-Header', k 241 | assert_equal 'a, b', v 242 | end 243 | end 244 | 245 | def test_each_capitalized_with_symbol 246 | @c[:my_header] = ['a', 'b'] 247 | @c.each_capitalized do |k,v| 248 | assert_equal "My_header", k 249 | assert_equal 'a, b', v 250 | end 251 | e = @c.each_capitalized 252 | assert_equal 1, e.size 253 | e.each do |k,v| 254 | assert_equal 'My_header', k 255 | assert_equal 'a, b', v 256 | end 257 | end 258 | 259 | def test_key? 260 | @c['My-Header'] = 'test' 261 | assert_equal true, @c.key?('My-Header') 262 | assert_equal true, @c.key?('my-header') 263 | assert_equal false, @c.key?('Not-Found') 264 | assert_equal false, @c.key?('not-found') 265 | assert_equal false, @c.key?('') 266 | assert_equal false, @c.key?('x' * 1024) 267 | end 268 | 269 | def test_to_hash 270 | end 271 | 272 | def test_range 273 | try_range([1..5], '1-5') 274 | try_invalid_range('5-1') 275 | try_range([234..567], '234-567') 276 | try_range([-5..-1], '-5') 277 | try_invalid_range('-0') 278 | try_range([1..-1], '1-') 279 | try_range([0..0,-1..-1], '0-0,-1') 280 | try_range([1..2, 3..4], '1-2,3-4') 281 | try_range([1..2, 3..4], '1-2 , 3-4') 282 | try_range([1..2, 1..4], '1-2,1-4') 283 | 284 | try_invalid_range('invalid') 285 | try_invalid_range(' 12-') 286 | try_invalid_range('12- ') 287 | try_invalid_range('123-abc') 288 | try_invalid_range('abc-123') 289 | end 290 | 291 | def try_range(r, s) 292 | @c['range'] = "bytes=#{s}" 293 | assert_equal r, @c.range 294 | end 295 | 296 | def try_invalid_range(s) 297 | @c['range'] = "bytes=#{s}" 298 | assert_raise(Net::HTTPHeaderSyntaxError, s){ @c.range } 299 | end 300 | 301 | def test_range= 302 | @c.range = 0..499 303 | assert_equal 'bytes=0-499', @c['range'] 304 | @c.range = 0...500 305 | assert_equal 'bytes=0-499', @c['range'] 306 | @c.range = 300 307 | assert_equal 'bytes=0-299', @c['range'] 308 | @c.range = -400 309 | assert_equal 'bytes=-400', @c['range'] 310 | @c.set_range 0, 500 311 | assert_equal 'bytes=0-499', @c['range'] 312 | end 313 | 314 | def test_content_range 315 | @c['Content-Range'] = "bytes 0-499/1000" 316 | assert_equal 0..499, @c.content_range 317 | @c['Content-Range'] = "bytes 1-500/1000" 318 | assert_equal 1..500, @c.content_range 319 | @c['Content-Range'] = "bytes 1-1/1000" 320 | assert_equal 1..1, @c.content_range 321 | @c['Content-Range'] = "tokens 1-1/1000" 322 | assert_equal nil, @c.content_range 323 | 324 | try_invalid_content_range "invalid" 325 | try_invalid_content_range "bytes 123-abc" 326 | try_invalid_content_range "bytes abc-123" 327 | end 328 | 329 | def test_range_length 330 | @c['Content-Range'] = "bytes 0-499/1000" 331 | assert_equal 500, @c.range_length 332 | @c['Content-Range'] = "bytes 1-500/1000" 333 | assert_equal 500, @c.range_length 334 | @c['Content-Range'] = "bytes 1-1/1000" 335 | assert_equal 1, @c.range_length 336 | @c['Content-Range'] = "tokens 1-1/1000" 337 | assert_equal nil, @c.range_length 338 | 339 | try_invalid_content_range "bytes 1-1/abc" 340 | end 341 | 342 | def try_invalid_content_range(s) 343 | @c['Content-Range'] = "#{s}" 344 | assert_raise(Net::HTTPHeaderSyntaxError, s){ @c.content_range } 345 | end 346 | 347 | def test_chunked? 348 | try_chunked true, 'chunked' 349 | try_chunked true, ' chunked ' 350 | try_chunked true, '(OK)chunked' 351 | 352 | try_chunked false, 'not-chunked' 353 | try_chunked false, 'chunked-but-not-chunked' 354 | end 355 | 356 | def try_chunked(bool, str) 357 | @c['transfer-encoding'] = str 358 | assert_equal bool, @c.chunked? 359 | end 360 | 361 | def test_content_length 362 | @c.delete('content-length') 363 | assert_nil @c['content-length'] 364 | 365 | try_content_length 500, '500' 366 | try_content_length 10000_0000_0000, '1000000000000' 367 | try_content_length 123, ' 123' 368 | try_content_length 1, '1 23' 369 | try_content_length 500, '(OK)500' 370 | assert_raise(Net::HTTPHeaderSyntaxError, 'here is no digit, but') { 371 | @c['content-length'] = 'no digit' 372 | @c.content_length 373 | } 374 | end 375 | 376 | def try_content_length(len, str) 377 | @c['content-length'] = str 378 | assert_equal len, @c.content_length 379 | end 380 | 381 | def test_content_length= 382 | @c.content_length = 0 383 | assert_equal 0, @c.content_length 384 | @c.content_length = 1 385 | assert_equal 1, @c.content_length 386 | @c.content_length = 999 387 | assert_equal 999, @c.content_length 388 | @c.content_length = 10000000000000 389 | assert_equal 10000000000000, @c.content_length 390 | end 391 | 392 | def test_content_type 393 | assert_nil @c.content_type 394 | @c.content_type = 'text/html' 395 | assert_equal 'text/html', @c.content_type 396 | @c.content_type = 'application/pdf' 397 | assert_equal 'application/pdf', @c.content_type 398 | @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'} 399 | assert_equal 'text/html', @c.content_type 400 | @c.content_type = 'text' 401 | assert_equal 'text', @c.content_type 402 | end 403 | 404 | def test_main_type 405 | assert_nil @c.main_type 406 | @c.content_type = 'text/html' 407 | assert_equal 'text', @c.main_type 408 | @c.content_type = 'application/pdf' 409 | assert_equal 'application', @c.main_type 410 | @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'} 411 | assert_equal 'text', @c.main_type 412 | @c.content_type = 'text' 413 | assert_equal 'text', @c.main_type 414 | end 415 | 416 | def test_sub_type 417 | assert_nil @c.sub_type 418 | @c.content_type = 'text/html' 419 | assert_equal 'html', @c.sub_type 420 | @c.content_type = 'application/pdf' 421 | assert_equal 'pdf', @c.sub_type 422 | @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'} 423 | assert_equal 'html', @c.sub_type 424 | @c.content_type = 'text' 425 | assert_nil @c.sub_type 426 | end 427 | 428 | def test_type_params 429 | assert_equal({}, @c.type_params) 430 | @c.content_type = 'text/html' 431 | assert_equal({}, @c.type_params) 432 | @c.content_type = 'application/pdf' 433 | assert_equal({}, @c.type_params) 434 | @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'} 435 | assert_equal({'charset' => 'iso-2022-jp'}, @c.type_params) 436 | @c.content_type = 'text' 437 | assert_equal({}, @c.type_params) 438 | end 439 | 440 | def test_set_content_type 441 | end 442 | 443 | def test_form_data= 444 | @c.form_data = {"cmd"=>"search", "q"=>"ruby", "max"=>"50"} 445 | assert_equal 'application/x-www-form-urlencoded', @c.content_type 446 | assert_equal %w( cmd=search max=50 q=ruby ), @c.body.split('&').sort 447 | end 448 | 449 | def test_set_form_data 450 | @c.set_form_data "cmd"=>"search", "q"=>"ruby", "max"=>"50" 451 | assert_equal 'application/x-www-form-urlencoded', @c.content_type 452 | assert_equal %w( cmd=search max=50 q=ruby ), @c.body.split('&').sort 453 | 454 | @c.set_form_data "cmd"=>"search", "q"=>"ruby", "max"=>50 455 | assert_equal 'application/x-www-form-urlencoded', @c.content_type 456 | assert_equal %w( cmd=search max=50 q=ruby ), @c.body.split('&').sort 457 | 458 | @c.set_form_data({"cmd"=>"search", "q"=>"ruby", "max"=>"50"}, ';') 459 | assert_equal 'application/x-www-form-urlencoded', @c.content_type 460 | assert_equal %w( cmd=search max=50 q=ruby ), @c.body.split(';').sort 461 | end 462 | 463 | def test_basic_auth 464 | end 465 | 466 | def test_proxy_basic_auth 467 | end 468 | 469 | end 470 | -------------------------------------------------------------------------------- /test/net/http/test_httpresponse.rb: -------------------------------------------------------------------------------- 1 | # coding: US-ASCII 2 | # frozen_string_literal: false 3 | require 'net/http' 4 | require 'test/unit' 5 | require 'stringio' 6 | 7 | class HTTPResponseTest < Test::Unit::TestCase 8 | def test_singleline_header 9 | io = dummy_io(<hello\u1234" 198 | io = dummy_io(<hello\u1234" 222 | io = dummy_io(<', res.inspect 740 | 741 | res = Net::HTTPUnknownResponse.new('1.0', '???', 'test response') 742 | socket = Net::BufferedIO.new(StringIO.new('test body')) 743 | res.reading_body(socket, true) {} 744 | assert_equal '#', res.inspect 745 | end 746 | 747 | private 748 | 749 | def dummy_io(str) 750 | str = str.gsub(/\n/, "\r\n") 751 | 752 | Net::BufferedIO.new(StringIO.new(str)) 753 | end 754 | end 755 | -------------------------------------------------------------------------------- /test/net/http/test_httpresponses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'net/http' 3 | require 'test/unit' 4 | 5 | class HTTPResponsesTest < Test::Unit::TestCase 6 | def test_status_code_classes 7 | Net::HTTPResponse::CODE_TO_OBJ.each_pair { |code, klass| 8 | case code 9 | when /\A1\d\d\z/ 10 | group = Net::HTTPInformation 11 | when /\A2\d\d\z/ 12 | group = Net::HTTPSuccess 13 | when /\A3\d\d\z/ 14 | group = Net::HTTPRedirection 15 | when /\A4\d\d\z/ 16 | group = Net::HTTPClientError 17 | when /\A5\d\d\z/ 18 | group = Net::HTTPServerError 19 | else 20 | flunk "Unknown HTTP status code: #{code} => #{klass.name}" 21 | end 22 | assert(klass < group, "#{klass.name} (#{code}) must inherit from #{group.name}") 23 | } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/net/http/test_https.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require "test/unit" 3 | require_relative "utils" 4 | begin 5 | require 'net/https' 6 | rescue LoadError 7 | # should skip this test 8 | end 9 | 10 | class TestNetHTTPS < Test::Unit::TestCase 11 | include TestNetHTTPUtils 12 | 13 | def self.read_fixture(key) 14 | File.read(File.expand_path("../fixtures/#{key}", __dir__)) 15 | end 16 | 17 | HOST = 'localhost' 18 | HOST_IP = '127.0.0.1' 19 | CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) 20 | SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) 21 | SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) 22 | DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem")) 23 | TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } 24 | 25 | CONFIG = { 26 | 'host' => HOST, 27 | 'proxy_host' => nil, 28 | 'proxy_port' => nil, 29 | 'ssl_enable' => true, 30 | 'ssl_certificate' => SERVER_CERT, 31 | 'ssl_private_key' => SERVER_KEY, 32 | 'ssl_tmp_dh_callback' => proc { DHPARAMS }, 33 | } 34 | 35 | def test_get 36 | http = Net::HTTP.new(HOST, config("port")) 37 | http.use_ssl = true 38 | http.cert_store = TEST_STORE 39 | certs = [] 40 | http.verify_callback = Proc.new do |preverify_ok, store_ctx| 41 | certs << store_ctx.current_cert 42 | preverify_ok 43 | end 44 | http.request_get("/") {|res| 45 | assert_equal($test_net_http_data, res.body) 46 | } 47 | # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility 48 | certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected| 49 | assert_equal(expected.to_der, actual.to_der) 50 | end 51 | end 52 | 53 | def test_get_SNI 54 | http = Net::HTTP.new(HOST, config("port")) 55 | http.ipaddr = config('host') 56 | http.use_ssl = true 57 | http.cert_store = TEST_STORE 58 | certs = [] 59 | http.verify_callback = Proc.new do |preverify_ok, store_ctx| 60 | certs << store_ctx.current_cert 61 | preverify_ok 62 | end 63 | http.request_get("/") {|res| 64 | assert_equal($test_net_http_data, res.body) 65 | } 66 | # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility 67 | certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected| 68 | assert_equal(expected.to_der, actual.to_der) 69 | end 70 | end 71 | 72 | def test_get_SNI_proxy 73 | TCPServer.open(HOST_IP, 0) {|serv| 74 | _, port, _, _ = serv.addr 75 | client_thread = Thread.new { 76 | proxy = Net::HTTP.Proxy(HOST_IP, port, 'user', 'password') 77 | http = proxy.new("foo.example.org", 8000) 78 | http.ipaddr = "192.0.2.1" 79 | http.use_ssl = true 80 | http.cert_store = TEST_STORE 81 | certs = [] 82 | http.verify_callback = Proc.new do |preverify_ok, store_ctx| 83 | certs << store_ctx.current_cert 84 | preverify_ok 85 | end 86 | begin 87 | http.start 88 | rescue EOFError 89 | end 90 | } 91 | server_thread = Thread.new { 92 | sock = serv.accept 93 | begin 94 | proxy_request = sock.gets("\r\n\r\n") 95 | assert_equal( 96 | "CONNECT 192.0.2.1:8000 HTTP/1.1\r\n" + 97 | "Host: foo.example.org:8000\r\n" + 98 | "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" + 99 | "\r\n", 100 | proxy_request, 101 | "[ruby-dev:25673]") 102 | ensure 103 | sock.close 104 | end 105 | } 106 | assert_join_threads([client_thread, server_thread]) 107 | } 108 | 109 | end 110 | 111 | def test_get_SNI_failure 112 | TestNetHTTPUtils.clean_http_proxy_env do 113 | http = Net::HTTP.new("invalidservername", config("port")) 114 | http.ipaddr = config('host') 115 | http.use_ssl = true 116 | http.cert_store = TEST_STORE 117 | certs = [] 118 | http.verify_callback = Proc.new do |preverify_ok, store_ctx| 119 | certs << store_ctx.current_cert 120 | preverify_ok 121 | end 122 | @log_tester = lambda {|_| } 123 | assert_raise(OpenSSL::SSL::SSLError){ http.start } 124 | end 125 | end 126 | 127 | def test_post 128 | http = Net::HTTP.new(HOST, config("port")) 129 | http.use_ssl = true 130 | http.cert_store = TEST_STORE 131 | data = config('ssl_private_key').to_der 132 | http.request_post("/", data, {'content-type' => 'application/x-www-form-urlencoded'}) {|res| 133 | assert_equal(data, res.body) 134 | } 135 | end 136 | 137 | def test_session_reuse 138 | # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. 139 | # See https://github.com/openssl/openssl/pull/5967 for details. 140 | omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h') 141 | 142 | http = Net::HTTP.new(HOST, config("port")) 143 | http.use_ssl = true 144 | http.cert_store = TEST_STORE 145 | 146 | if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /LibreSSL (\d+\.\d+)/ && $1.to_f > 3.19 147 | # LibreSSL 3.2 defaults to TLSv1.3 in server and client, which doesn't currently 148 | # support session resuse. Limiting the version to the TLSv1.2 stack allows 149 | # this test to continue to work on LibreSSL 3.2+. LibreSSL may eventually 150 | # support session reuse, but there are no current plans to do so. 151 | http.ssl_version = :TLSv1_2 152 | end 153 | 154 | http.start 155 | session_reused = http.instance_variable_get(:@socket).io.session_reused? 156 | assert_false session_reused unless session_reused.nil? # can not detect re-use under JRuby 157 | http.get("/") 158 | http.finish 159 | 160 | http.start 161 | session_reused = http.instance_variable_get(:@socket).io.session_reused? 162 | assert_true session_reused unless session_reused.nil? # can not detect re-use under JRuby 163 | assert_equal $test_net_http_data, http.get("/").body 164 | http.finish 165 | end 166 | 167 | def test_session_reuse_but_expire 168 | # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. 169 | omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h') 170 | 171 | http = Net::HTTP.new(HOST, config("port")) 172 | http.use_ssl = true 173 | http.cert_store = TEST_STORE 174 | 175 | http.ssl_timeout = 1 176 | http.start 177 | http.get("/") 178 | http.finish 179 | sleep 1.25 180 | http.start 181 | http.get("/") 182 | 183 | socket = http.instance_variable_get(:@socket).io 184 | assert_equal false, socket.session_reused?, "NOTE: OpenSSL library version is #{OpenSSL::OPENSSL_LIBRARY_VERSION}" 185 | 186 | http.finish 187 | end 188 | 189 | if ENV["RUBY_OPENSSL_TEST_ALL"] 190 | def test_verify 191 | http = Net::HTTP.new("ssl.netlab.jp", 443) 192 | http.use_ssl = true 193 | assert( 194 | (http.request_head("/"){|res| } rescue false), 195 | "The system may not have default CA certificate store." 196 | ) 197 | end 198 | end 199 | 200 | def test_verify_none 201 | http = Net::HTTP.new(HOST, config("port")) 202 | http.use_ssl = true 203 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 204 | http.request_get("/") {|res| 205 | assert_equal($test_net_http_data, res.body) 206 | } 207 | end 208 | 209 | def test_skip_hostname_verification 210 | TestNetHTTPUtils.clean_http_proxy_env do 211 | http = Net::HTTP.new('invalidservername', config('port')) 212 | http.ipaddr = config('host') 213 | http.use_ssl = true 214 | http.cert_store = TEST_STORE 215 | http.verify_hostname = false 216 | assert_nothing_raised { http.start } 217 | ensure 218 | http.finish if http&.started? 219 | end 220 | end 221 | 222 | def test_fail_if_verify_hostname_is_true 223 | TestNetHTTPUtils.clean_http_proxy_env do 224 | http = Net::HTTP.new('invalidservername', config('port')) 225 | http.ipaddr = config('host') 226 | http.use_ssl = true 227 | http.cert_store = TEST_STORE 228 | http.verify_hostname = true 229 | @log_tester = lambda { |_| } 230 | assert_raise(OpenSSL::SSL::SSLError) { http.start } 231 | end 232 | end 233 | 234 | def test_certificate_verify_failure 235 | http = Net::HTTP.new(HOST, config("port")) 236 | http.use_ssl = true 237 | ex = assert_raise(OpenSSL::SSL::SSLError){ 238 | http.request_get("/") {|res| } 239 | } 240 | assert_match(/certificate verify failed/, ex.message) 241 | end 242 | 243 | def test_timeout_during_SSL_handshake 244 | bug4246 = "expected the SSL connection to have timed out but have not. [ruby-core:34203]" 245 | 246 | # listen for connections... but deliberately do not complete SSL handshake 247 | TCPServer.open(HOST, 0) {|server| 248 | port = server.addr[1] 249 | 250 | conn = Net::HTTP.new(HOST, port) 251 | conn.use_ssl = true 252 | conn.read_timeout = 0.01 253 | conn.open_timeout = 0.01 254 | 255 | th = Thread.new do 256 | assert_raise(Net::OpenTimeout) { 257 | conn.get('/') 258 | } 259 | end 260 | assert th.join(10), bug4246 261 | } 262 | end 263 | 264 | def test_min_version 265 | http = Net::HTTP.new(HOST, config("port")) 266 | http.use_ssl = true 267 | http.min_version = :TLS1 268 | http.cert_store = TEST_STORE 269 | http.request_get("/") {|res| 270 | assert_equal($test_net_http_data, res.body) 271 | } 272 | end 273 | 274 | def test_max_version 275 | http = Net::HTTP.new(HOST, config("port")) 276 | http.use_ssl = true 277 | http.max_version = :SSL2 278 | http.verify_callback = Proc.new do |preverify_ok, store_ctx| 279 | true 280 | end 281 | @log_tester = lambda {|_| } 282 | ex = assert_raise(OpenSSL::SSL::SSLError){ 283 | http.request_get("/") {|res| } 284 | } 285 | re_msg = /\ASSL_connect returned=1 errno=0 |SSL_CTX_set_max_proto_version|No appropriate protocol/ 286 | assert_match(re_msg, ex.message) 287 | end 288 | 289 | end if defined?(OpenSSL::SSL) 290 | 291 | class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase 292 | include TestNetHTTPUtils 293 | 294 | def self.read_fixture(key) 295 | File.read(File.expand_path("../fixtures/#{key}", __dir__)) 296 | end 297 | 298 | HOST = 'localhost' 299 | HOST_IP = '127.0.0.1' 300 | CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) 301 | SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) 302 | SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) 303 | DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem")) 304 | TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } 305 | 306 | CONFIG = { 307 | 'host' => HOST_IP, 308 | 'proxy_host' => nil, 309 | 'proxy_port' => nil, 310 | 'ssl_enable' => true, 311 | 'ssl_certificate' => SERVER_CERT, 312 | 'ssl_private_key' => SERVER_KEY, 313 | 'ssl_tmp_dh_callback' => proc { DHPARAMS }, 314 | } 315 | 316 | def test_identity_verify_failure 317 | # the certificate's subject has CN=localhost 318 | http = Net::HTTP.new(HOST_IP, config("port")) 319 | http.use_ssl = true 320 | http.cert_store = TEST_STORE 321 | @log_tester = lambda {|_| } 322 | ex = assert_raise(OpenSSL::SSL::SSLError){ 323 | http.request_get("/") {|res| } 324 | sleep 0.5 325 | } 326 | re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/ 327 | assert_match(re_msg, ex.message) 328 | end 329 | end if defined?(OpenSSL::SSL) 330 | -------------------------------------------------------------------------------- /test/net/http/test_https_proxy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | begin 3 | require 'net/https' 4 | rescue LoadError 5 | end 6 | require 'test/unit' 7 | 8 | class HTTPSProxyTest < Test::Unit::TestCase 9 | def test_https_proxy_authentication 10 | begin 11 | OpenSSL 12 | rescue LoadError 13 | omit 'autoload problem. see [ruby-dev:45021][Bug #5786]' 14 | end 15 | 16 | TCPServer.open("127.0.0.1", 0) {|serv| 17 | _, port, _, _ = serv.addr 18 | client_thread = Thread.new { 19 | proxy = Net::HTTP.Proxy("127.0.0.1", port, 'user', 'password') 20 | http = proxy.new("foo.example.org", 8000) 21 | http.use_ssl = true 22 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 23 | begin 24 | http.start 25 | rescue EOFError 26 | end 27 | } 28 | server_thread = Thread.new { 29 | sock = serv.accept 30 | begin 31 | proxy_request = sock.gets("\r\n\r\n") 32 | assert_equal( 33 | "CONNECT foo.example.org:8000 HTTP/1.1\r\n" + 34 | "Host: foo.example.org:8000\r\n" + 35 | "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" + 36 | "\r\n", 37 | proxy_request, 38 | "[ruby-dev:25673]") 39 | ensure 40 | sock.close 41 | end 42 | } 43 | assert_join_threads([client_thread, server_thread]) 44 | } 45 | end 46 | 47 | 48 | def read_fixture(key) 49 | File.read(File.expand_path("../fixtures/#{key}", __dir__)) 50 | end 51 | 52 | def test_https_proxy_ssl_connection 53 | begin 54 | OpenSSL 55 | rescue LoadError 56 | omit 'autoload problem. see [ruby-dev:45021][Bug #5786]' 57 | end 58 | 59 | TCPServer.open("127.0.0.1", 0) {|tcpserver| 60 | ctx = OpenSSL::SSL::SSLContext.new 61 | ctx.key = OpenSSL::PKey.read(read_fixture("server.key")) 62 | ctx.cert = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) 63 | serv = OpenSSL::SSL::SSLServer.new(tcpserver, ctx) 64 | 65 | _, port, _, _ = serv.addr 66 | client_thread = Thread.new { 67 | proxy = Net::HTTP.Proxy("127.0.0.1", port, 'user', 'password', true) 68 | http = proxy.new("foo.example.org", 8000) 69 | http.use_ssl = true 70 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 71 | begin 72 | http.start 73 | rescue EOFError 74 | end 75 | } 76 | server_thread = Thread.new { 77 | sock = serv.accept 78 | begin 79 | proxy_request = sock.gets("\r\n\r\n") 80 | assert_equal( 81 | "CONNECT foo.example.org:8000 HTTP/1.1\r\n" + 82 | "Host: foo.example.org:8000\r\n" + 83 | "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" + 84 | "\r\n", 85 | proxy_request, 86 | "[ruby-core:96672]") 87 | ensure 88 | sock.close 89 | end 90 | } 91 | assert_join_threads([client_thread, server_thread]) 92 | } 93 | end 94 | end if defined?(OpenSSL) 95 | -------------------------------------------------------------------------------- /test/net/http/utils.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'socket' 3 | require 'openssl' 4 | 5 | module TestNetHTTPUtils 6 | 7 | class Forbidden < StandardError; end 8 | 9 | class HTTPServer 10 | def initialize(config, &block) 11 | @config = config 12 | @server = TCPServer.new(@config['host'], 0) 13 | @port = @server.addr[1] 14 | @procs = {} 15 | 16 | if @config['ssl_enable'] 17 | context = OpenSSL::SSL::SSLContext.new 18 | context.cert = @config['ssl_certificate'] 19 | context.key = @config['ssl_private_key'] 20 | context.tmp_dh_callback = @config['ssl_tmp_dh_callback'] 21 | @ssl_server = OpenSSL::SSL::SSLServer.new(@server, context) 22 | end 23 | 24 | @block = block 25 | end 26 | 27 | def start 28 | @thread = Thread.new do 29 | loop do 30 | socket = (@ssl_server || @server).accept 31 | run(socket) 32 | rescue 33 | ensure 34 | socket&.close 35 | end 36 | ensure 37 | (@ssl_server || @server).close 38 | end 39 | end 40 | 41 | def run(socket) 42 | handle_request(socket) 43 | end 44 | 45 | def shutdown 46 | @thread&.kill 47 | @thread&.join 48 | end 49 | 50 | def mount(path, proc) 51 | @procs[path] = proc 52 | end 53 | 54 | def mount_proc(path, &block) 55 | mount(path, block.to_proc) 56 | end 57 | 58 | def handle_request(socket) 59 | request_line = socket.gets 60 | return if request_line.nil? || request_line.strip.empty? 61 | 62 | method, path, _version = request_line.split 63 | headers = {} 64 | while (line = socket.gets) 65 | break if line.strip.empty? 66 | key, value = line.split(': ', 2) 67 | headers[key] = value.strip 68 | end 69 | 70 | if headers['Expect'] == '100-continue' 71 | socket.write "HTTP/1.1 100 Continue\r\n\r\n" 72 | end 73 | 74 | req = Request.new(method, path, headers, socket) 75 | if @procs.key?(req.path) || @procs.key?("#{req.path}/") 76 | proc = @procs[req.path] || @procs["#{req.path}/"] 77 | res = Response.new(socket) 78 | begin 79 | proc.call(req, res) 80 | rescue Forbidden 81 | res.status = 403 82 | end 83 | res.finish 84 | else 85 | @block.call(method, path, headers, socket) 86 | end 87 | end 88 | 89 | def port 90 | @port 91 | end 92 | 93 | class Request 94 | attr_reader :method, :path, :headers, :query, :body 95 | def initialize(method, path, headers, socket) 96 | @method = method 97 | @path, @query = parse_path_and_query(path) 98 | @headers = headers 99 | @socket = socket 100 | if method == 'POST' && (@path == '/continue' || @headers['Content-Type'].include?('multipart/form-data')) 101 | if @headers['Transfer-Encoding'] == 'chunked' 102 | @body = read_chunked_body 103 | else 104 | @body = read_body 105 | end 106 | @query = @body.split('&').each_with_object({}) do |pair, hash| 107 | key, value = pair.split('=') 108 | hash[key] = value 109 | end if @body && @body.include?('=') 110 | end 111 | end 112 | 113 | def [](key) 114 | @headers[key.downcase] 115 | end 116 | 117 | def []=(key, value) 118 | @headers[key.downcase] = value 119 | end 120 | 121 | def continue 122 | @socket.write "HTTP\/1.1 100 continue\r\n\r\n" 123 | end 124 | 125 | def remote_ip 126 | @socket.peeraddr[3] 127 | end 128 | 129 | def peeraddr 130 | @socket.peeraddr 131 | end 132 | 133 | private 134 | 135 | def parse_path_and_query(path) 136 | path, query_string = path.split('?', 2) 137 | query = {} 138 | if query_string 139 | query_string.split('&').each do |pair| 140 | key, value = pair.split('=', 2) 141 | query[key] = value 142 | end 143 | end 144 | [path, query] 145 | end 146 | 147 | def read_body 148 | content_length = @headers['Content-Length']&.to_i 149 | return unless content_length && content_length > 0 150 | @socket.read(content_length) 151 | end 152 | 153 | def read_chunked_body 154 | body = "" 155 | while (chunk_size = @socket.gets.strip.to_i(16)) > 0 156 | body << @socket.read(chunk_size) 157 | @socket.read(2) # read \r\n after each chunk 158 | end 159 | body 160 | end 161 | end 162 | 163 | class Response 164 | attr_accessor :body, :headers, :status, :chunked, :cookies 165 | def initialize(client) 166 | @client = client 167 | @body = "" 168 | @headers = {} 169 | @status = 200 170 | @chunked = false 171 | @cookies = [] 172 | end 173 | 174 | def [](key) 175 | @headers[key.downcase] 176 | end 177 | 178 | def []=(key, value) 179 | @headers[key.downcase] = value 180 | end 181 | 182 | def write_chunk(chunk) 183 | return unless @chunked 184 | @client.write("#{chunk.bytesize.to_s(16)}\r\n") 185 | @client.write("#{chunk}\r\n") 186 | end 187 | 188 | def finish 189 | @client.write build_response_headers 190 | if @chunked 191 | write_chunk(@body) 192 | @client.write "0\r\n\r\n" 193 | else 194 | @client.write @body 195 | end 196 | end 197 | 198 | private 199 | 200 | def build_response_headers 201 | response = "HTTP/1.1 #{@status} #{status_message(@status)}\r\n" 202 | if @chunked 203 | @headers['Transfer-Encoding'] = 'chunked' 204 | else 205 | @headers['Content-Length'] = @body.bytesize.to_s 206 | end 207 | @headers.each do |key, value| 208 | response << "#{key}: #{value}\r\n" 209 | end 210 | @cookies.each do |cookie| 211 | response << "Set-Cookie: #{cookie}\r\n" 212 | end 213 | response << "\r\n" 214 | response 215 | end 216 | 217 | def status_message(code) 218 | case code 219 | when 200 then 'OK' 220 | when 301 then 'Moved Permanently' 221 | when 403 then 'Forbidden' 222 | else 'Unknown' 223 | end 224 | end 225 | end 226 | end 227 | 228 | def start(&block) 229 | new().start(&block) 230 | end 231 | 232 | def new 233 | klass = Net::HTTP::Proxy(config('proxy_host'), config('proxy_port')) 234 | http = klass.new(config('host'), config('port')) 235 | http.set_debug_output logfile 236 | http 237 | end 238 | 239 | def config(key) 240 | @config ||= self.class::CONFIG 241 | @config[key] 242 | end 243 | 244 | def logfile 245 | $stderr if $DEBUG 246 | end 247 | 248 | def setup 249 | spawn_server 250 | end 251 | 252 | def teardown 253 | sleep 0.5 if @config['ssl_enable'] 254 | if @server 255 | @server.shutdown 256 | end 257 | @log_tester.call(@log) if @log_tester 258 | Net::HTTP.version_1_2 259 | end 260 | 261 | def spawn_server 262 | @log = [] 263 | @log_tester = lambda {|log| assert_equal([], log) } 264 | @config = self.class::CONFIG 265 | @server = HTTPServer.new(@config) do |method, path, headers, socket| 266 | @log << "DEBUG accept: #{@config['host']}:#{socket.addr[1]}" if @logger_level == :debug 267 | case method 268 | when 'HEAD' 269 | handle_head(path, headers, socket) 270 | when 'GET' 271 | handle_get(path, headers, socket) 272 | when 'POST' 273 | handle_post(path, headers, socket) 274 | when 'PATCH' 275 | handle_patch(path, headers, socket) 276 | else 277 | socket.print "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n" 278 | end 279 | end 280 | @server.start 281 | @config['port'] = @server.port 282 | end 283 | 284 | def handle_head(path, headers, socket) 285 | if headers['Accept'] != '*/*' 286 | content_type = headers['Accept'] 287 | else 288 | content_type = $test_net_http_data_type 289 | end 290 | response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{$test_net_http_data.bytesize}" 291 | socket.print(response) 292 | end 293 | 294 | def handle_get(path, headers, socket) 295 | if headers['Accept'] != '*/*' 296 | content_type = headers['Accept'] 297 | else 298 | content_type = $test_net_http_data_type 299 | end 300 | response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{$test_net_http_data.bytesize}\r\n\r\n#{$test_net_http_data}" 301 | socket.print(response) 302 | end 303 | 304 | def handle_post(path, headers, socket) 305 | body = socket.read(headers['Content-Length'].to_i) 306 | scheme = headers['X-Request-Scheme'] || 'http' 307 | host = @config['host'] 308 | port = socket.addr[1] 309 | charset = parse_content_type(headers['Content-Type'])[1] 310 | path = "#{scheme}://#{host}:#{port}#{path}" 311 | path = path.encode(charset) if charset 312 | response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}" 313 | socket.print(response) 314 | end 315 | 316 | def handle_patch(path, headers, socket) 317 | body = socket.read(headers['Content-Length'].to_i) 318 | response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" 319 | socket.print(response) 320 | end 321 | 322 | def parse_content_type(content_type) 323 | return [nil, nil] unless content_type 324 | type, *params = content_type.split(';').map(&:strip) 325 | charset = params.find { |param| param.start_with?('charset=') } 326 | charset = charset.split('=', 2).last if charset 327 | [type, charset] 328 | end 329 | 330 | $test_net_http = nil 331 | $test_net_http_data = (0...256).to_a.map { |i| i.chr }.join('') * 64 332 | $test_net_http_data.force_encoding("ASCII-8BIT") 333 | $test_net_http_data_type = 'application/octet-stream' 334 | 335 | def self.clean_http_proxy_env 336 | orig = { 337 | 'http_proxy' => ENV['http_proxy'], 338 | 'http_proxy_user' => ENV['http_proxy_user'], 339 | 'http_proxy_pass' => ENV['http_proxy_pass'], 340 | 'no_proxy' => ENV['no_proxy'], 341 | } 342 | 343 | orig.each_key do |key| 344 | ENV.delete key 345 | end 346 | 347 | yield 348 | ensure 349 | orig.each do |key, value| 350 | ENV[key] = value 351 | end 352 | end 353 | end 354 | --------------------------------------------------------------------------------