http 4.4.1 → 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +85 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop.yml +8 -110
- data/.rubocop_todo.yml +206 -0
- data/.yardopts +1 -1
- data/CHANGES.md +200 -3
- data/Gemfile +18 -10
- data/LICENSE.txt +1 -1
- data/README.md +48 -86
- data/Rakefile +2 -10
- data/SECURITY.md +5 -0
- data/http.gemspec +9 -8
- data/lib/http/chainable.rb +23 -17
- data/lib/http/client.rb +44 -34
- data/lib/http/connection.rb +11 -7
- data/lib/http/content_type.rb +12 -7
- data/lib/http/errors.rb +3 -0
- data/lib/http/feature.rb +3 -1
- data/lib/http/features/auto_deflate.rb +6 -6
- data/lib/http/features/auto_inflate.rb +6 -7
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +50 -13
- data/lib/http/mime_type/adapter.rb +3 -1
- data/lib/http/mime_type/json.rb +1 -0
- data/lib/http/options.rb +5 -8
- data/lib/http/redirector.rb +55 -4
- data/lib/http/request/body.rb +1 -0
- data/lib/http/request/writer.rb +9 -4
- data/lib/http/request.rb +28 -11
- data/lib/http/response/body.rb +6 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +74 -62
- data/lib/http/response/status.rb +4 -3
- data/lib/http/response.rb +44 -18
- data/lib/http/timeout/global.rb +20 -36
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +32 -55
- data/lib/http/uri.rb +51 -6
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +155 -30
- data/spec/lib/http/connection_spec.rb +8 -5
- data/spec/lib/http/features/auto_inflate_spec.rb +3 -2
- data/spec/lib/http/features/instrumentation_spec.rb +27 -21
- data/spec/lib/http/features/logging_spec.rb +8 -10
- data/spec/lib/http/headers_spec.rb +53 -18
- data/spec/lib/http/options/headers_spec.rb +1 -1
- data/spec/lib/http/options/merge_spec.rb +16 -16
- data/spec/lib/http/redirector_spec.rb +133 -3
- data/spec/lib/http/request/body_spec.rb +3 -3
- data/spec/lib/http/request/writer_spec.rb +25 -2
- data/spec/lib/http/request_spec.rb +5 -5
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +33 -4
- data/spec/lib/http/response/status_spec.rb +3 -3
- data/spec/lib/http/response_spec.rb +80 -3
- data/spec/lib/http/uri_spec.rb +39 -0
- data/spec/lib/http_spec.rb +30 -3
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +19 -6
- data/spec/support/dummy_server.rb +7 -7
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/http_handling_shared.rb +5 -5
- data/spec/support/simplecov.rb +19 -0
- data/spec/support/ssl_helper.rb +4 -4
- metadata +22 -14
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -39
data/lib/http/client.rb
CHANGED
@@ -16,7 +16,7 @@ module HTTP
|
|
16
16
|
extend Forwardable
|
17
17
|
include Chainable
|
18
18
|
|
19
|
-
HTTP_OR_HTTPS_RE = %r{^https?://}i
|
19
|
+
HTTP_OR_HTTPS_RE = %r{^https?://}i.freeze
|
20
20
|
|
21
21
|
def initialize(default_options = {})
|
22
22
|
@default_options = HTTP::Options.new(default_options)
|
@@ -25,19 +25,19 @@ module HTTP
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# Make an HTTP request
|
28
|
-
def request(verb, uri, opts = {})
|
28
|
+
def request(verb, uri, opts = {})
|
29
29
|
opts = @default_options.merge(opts)
|
30
30
|
req = build_request(verb, uri, opts)
|
31
31
|
res = perform(req, opts)
|
32
32
|
return res unless opts.follow
|
33
33
|
|
34
34
|
Redirector.new(opts.follow).perform(req, res) do |request|
|
35
|
-
perform(request, opts)
|
35
|
+
perform(wrap_request(request, opts), opts)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
# Prepare an HTTP request
|
40
|
-
def build_request(verb, uri, opts = {})
|
40
|
+
def build_request(verb, uri, opts = {})
|
41
41
|
opts = @default_options.merge(opts)
|
42
42
|
uri = make_request_uri(uri, opts)
|
43
43
|
headers = make_request_headers(opts)
|
@@ -52,9 +52,7 @@ module HTTP
|
|
52
52
|
:body => body
|
53
53
|
)
|
54
54
|
|
55
|
-
|
56
|
-
feature.wrap_request(request)
|
57
|
-
end
|
55
|
+
wrap_request(req, opts)
|
58
56
|
end
|
59
57
|
|
60
58
|
# @!method persistent?
|
@@ -68,22 +66,20 @@ module HTTP
|
|
68
66
|
|
69
67
|
@state = :dirty
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@connection.
|
75
|
-
|
69
|
+
begin
|
70
|
+
@connection ||= HTTP::Connection.new(req, options)
|
71
|
+
|
72
|
+
unless @connection.failed_proxy_connect?
|
73
|
+
@connection.send_request(req)
|
74
|
+
@connection.read_headers!
|
75
|
+
end
|
76
|
+
rescue Error => e
|
77
|
+
options.features.each_value do |feature|
|
78
|
+
feature.on_error(req, e)
|
79
|
+
end
|
80
|
+
raise
|
76
81
|
end
|
77
|
-
|
78
|
-
res = Response.new(
|
79
|
-
:status => @connection.status_code,
|
80
|
-
:version => @connection.http_version,
|
81
|
-
:headers => @connection.headers,
|
82
|
-
:proxy_headers => @connection.proxy_response_headers,
|
83
|
-
:connection => @connection,
|
84
|
-
:encoding => options.encoding,
|
85
|
-
:uri => req.uri
|
86
|
-
)
|
82
|
+
res = build_response(req, options)
|
87
83
|
|
88
84
|
res = options.features.inject(res) do |response, (_name, feature)|
|
89
85
|
feature.wrap_response(response)
|
@@ -99,26 +95,44 @@ module HTTP
|
|
99
95
|
end
|
100
96
|
|
101
97
|
def close
|
102
|
-
@connection
|
98
|
+
@connection&.close
|
103
99
|
@connection = nil
|
104
100
|
@state = :clean
|
105
101
|
end
|
106
102
|
|
107
103
|
private
|
108
104
|
|
105
|
+
def wrap_request(req, opts)
|
106
|
+
opts.features.inject(req) do |request, (_name, feature)|
|
107
|
+
feature.wrap_request(request)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_response(req, options)
|
112
|
+
Response.new(
|
113
|
+
:status => @connection.status_code,
|
114
|
+
:version => @connection.http_version,
|
115
|
+
:headers => @connection.headers,
|
116
|
+
:proxy_headers => @connection.proxy_response_headers,
|
117
|
+
:connection => @connection,
|
118
|
+
:encoding => options.encoding,
|
119
|
+
:request => req
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
109
123
|
# Verify our request isn't going to be made against another URI
|
110
124
|
def verify_connection!(uri)
|
111
125
|
if default_options.persistent? && uri.origin != default_options.persistent
|
112
126
|
raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
|
127
|
+
end
|
128
|
+
|
113
129
|
# We re-create the connection object because we want to let prior requests
|
114
130
|
# lazily load the body as long as possible, and this mimics prior functionality.
|
115
|
-
|
116
|
-
|
131
|
+
return close if @connection && (!@connection.keep_alive? || @connection.expired?)
|
132
|
+
|
117
133
|
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
|
118
134
|
# close the connection to prevent potential for mixed responses.
|
119
|
-
|
120
|
-
close
|
121
|
-
end
|
135
|
+
return close if @state == :dirty
|
122
136
|
end
|
123
137
|
|
124
138
|
# Merges query params if needed
|
@@ -128,15 +142,11 @@ module HTTP
|
|
128
142
|
def make_request_uri(uri, opts)
|
129
143
|
uri = uri.to_s
|
130
144
|
|
131
|
-
if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
|
132
|
-
uri = "#{default_options.persistent}#{uri}"
|
133
|
-
end
|
145
|
+
uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
|
134
146
|
|
135
147
|
uri = HTTP::URI.parse uri
|
136
148
|
|
137
|
-
if opts.params && !opts.params.empty?
|
138
|
-
uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a)
|
139
|
-
end
|
149
|
+
uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
|
140
150
|
|
141
151
|
# Some proxies (seen on WEBRick) fail if URL has
|
142
152
|
# empty path (e.g. `http://example.com`) while it's RFC-complaint:
|
data/lib/http/connection.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require "forwardable"
|
4
4
|
|
5
5
|
require "http/headers"
|
6
|
-
require "http/response/parser"
|
7
6
|
|
8
7
|
module HTTP
|
9
8
|
# A connection to the HTTP server
|
@@ -45,8 +44,8 @@ module HTTP
|
|
45
44
|
send_proxy_connect_request(req)
|
46
45
|
start_tls(req, options)
|
47
46
|
reset_timer
|
48
|
-
rescue IOError, SocketError, SystemCallError =>
|
49
|
-
raise ConnectionError, "failed to connect: #{
|
47
|
+
rescue IOError, SocketError, SystemCallError => e
|
48
|
+
raise ConnectionError, "failed to connect: #{e}", e.backtrace
|
50
49
|
end
|
51
50
|
|
52
51
|
# @see (HTTP::Response::Parser#status_code)
|
@@ -68,8 +67,13 @@ module HTTP
|
|
68
67
|
# @param [Request] req Request to send to the server
|
69
68
|
# @return [nil]
|
70
69
|
def send_request(req)
|
71
|
-
|
72
|
-
|
70
|
+
if @pending_response
|
71
|
+
raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body."
|
72
|
+
end
|
73
|
+
|
74
|
+
if @pending_request
|
75
|
+
raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body."
|
76
|
+
end
|
73
77
|
|
74
78
|
@pending_request = true
|
75
79
|
|
@@ -216,8 +220,8 @@ module HTTP
|
|
216
220
|
elsif value
|
217
221
|
@parser << value
|
218
222
|
end
|
219
|
-
rescue IOError, SocketError, SystemCallError =>
|
220
|
-
raise ConnectionError, "error reading from socket: #{
|
223
|
+
rescue IOError, SocketError, SystemCallError => e
|
224
|
+
raise ConnectionError, "error reading from socket: #{e}", e.backtrace
|
221
225
|
end
|
222
226
|
end
|
223
227
|
end
|
data/lib/http/content_type.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTP
|
4
|
-
ContentType
|
5
|
-
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
|
6
|
-
CHARSET_RE = /;\s*charset=([^;]+)/i
|
4
|
+
class ContentType
|
5
|
+
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
6
|
+
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
7
|
+
|
8
|
+
attr_accessor :mime_type, :charset
|
7
9
|
|
8
10
|
class << self
|
9
11
|
# Parse string and return ContentType struct
|
@@ -15,15 +17,18 @@ module HTTP
|
|
15
17
|
|
16
18
|
# :nodoc:
|
17
19
|
def mime_type(str)
|
18
|
-
|
19
|
-
m && m.strip.downcase
|
20
|
+
str.to_s[MIME_TYPE_RE, 1]&.strip&.downcase
|
20
21
|
end
|
21
22
|
|
22
23
|
# :nodoc:
|
23
24
|
def charset(str)
|
24
|
-
|
25
|
-
m && m.strip.delete('"')
|
25
|
+
str.to_s[CHARSET_RE, 1]&.strip&.delete('"')
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
def initialize(mime_type = nil, charset = nil)
|
30
|
+
@mime_type = mime_type
|
31
|
+
@charset = charset
|
32
|
+
end
|
28
33
|
end
|
29
34
|
end
|
data/lib/http/errors.rb
CHANGED
@@ -19,6 +19,9 @@ module HTTP
|
|
19
19
|
# Generic Timeout error
|
20
20
|
class TimeoutError < Error; end
|
21
21
|
|
22
|
+
# Timeout when first establishing the conncetion
|
23
|
+
class ConnectTimeoutError < TimeoutError; end
|
24
|
+
|
22
25
|
# Header value is of unexpected format (similar to Net::HTTPHeaderSyntaxError)
|
23
26
|
class HeaderError < Error; end
|
24
27
|
end
|
data/lib/http/feature.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module HTTP
|
4
4
|
class Feature
|
5
|
-
def initialize(opts = {})
|
5
|
+
def initialize(opts = {})
|
6
6
|
@opts = opts
|
7
7
|
end
|
8
8
|
|
@@ -13,6 +13,8 @@ module HTTP
|
|
13
13
|
def wrap_response(response)
|
14
14
|
response
|
15
15
|
end
|
16
|
+
|
17
|
+
def on_error(request, error); end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
@@ -27,12 +27,12 @@ module HTTP
|
|
27
27
|
request.headers[Headers::CONTENT_ENCODING] = method
|
28
28
|
|
29
29
|
Request.new(
|
30
|
-
:version
|
31
|
-
:verb
|
32
|
-
:uri
|
33
|
-
:headers
|
34
|
-
:proxy
|
35
|
-
:body
|
30
|
+
:version => request.version,
|
31
|
+
:verb => request.verb,
|
32
|
+
:uri => request.uri,
|
33
|
+
:headers => request.headers,
|
34
|
+
:proxy => request.proxy,
|
35
|
+
:body => deflated_body(request.body),
|
36
36
|
:uri_normalizer => request.uri_normalizer
|
37
37
|
)
|
38
38
|
end
|
@@ -12,16 +12,15 @@ module HTTP
|
|
12
12
|
return response unless supported_encoding?(response)
|
13
13
|
|
14
14
|
options = {
|
15
|
-
:status
|
16
|
-
:version
|
17
|
-
:headers
|
15
|
+
:status => response.status,
|
16
|
+
:version => response.version,
|
17
|
+
:headers => response.headers,
|
18
18
|
:proxy_headers => response.proxy_headers,
|
19
|
-
:connection
|
20
|
-
:body
|
19
|
+
:connection => response.connection,
|
20
|
+
:body => stream_for(response.connection),
|
21
|
+
:request => response.request
|
21
22
|
}
|
22
23
|
|
23
|
-
options[:uri] = response.uri if response.uri
|
24
|
-
|
25
24
|
Response.new(options)
|
26
25
|
end
|
27
26
|
|
@@ -29,7 +29,7 @@ module HTTP
|
|
29
29
|
def wrap_request(request)
|
30
30
|
# Emit a separate "start" event, so a logger can print the request
|
31
31
|
# being run without waiting for a response
|
32
|
-
instrumenter.instrument("start_#{name}", :request => request)
|
32
|
+
instrumenter.instrument("start_#{name}", :request => request)
|
33
33
|
instrumenter.start(name, :request => request)
|
34
34
|
request
|
35
35
|
end
|
@@ -9,6 +9,20 @@ module HTTP
|
|
9
9
|
# HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")
|
10
10
|
#
|
11
11
|
class Logging < Feature
|
12
|
+
HTTP::Options.register_feature(:logging, self)
|
13
|
+
|
14
|
+
class NullLogger
|
15
|
+
%w[fatal error warn info debug].each do |level|
|
16
|
+
define_method(level.to_sym) do |*_args|
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method(:"#{level}?") do
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
12
26
|
attr_reader :logger
|
13
27
|
|
14
28
|
def initialize(logger: NullLogger.new)
|
@@ -17,39 +31,23 @@ module HTTP
|
|
17
31
|
|
18
32
|
def wrap_request(request)
|
19
33
|
logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" }
|
20
|
-
logger.debug
|
21
|
-
headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
22
|
-
body = request.body.source
|
34
|
+
logger.debug { "#{stringify_headers(request.headers)}\n\n#{request.body.source}" }
|
23
35
|
|
24
|
-
headers + "\n\n" + body.to_s
|
25
|
-
end
|
26
36
|
request
|
27
37
|
end
|
28
38
|
|
29
39
|
def wrap_response(response)
|
30
40
|
logger.info { "< #{response.status}" }
|
31
|
-
logger.debug
|
32
|
-
headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
33
|
-
body = response.body.to_s
|
41
|
+
logger.debug { "#{stringify_headers(response.headers)}\n\n#{response.body}" }
|
34
42
|
|
35
|
-
headers + "\n\n" + body
|
36
|
-
end
|
37
43
|
response
|
38
44
|
end
|
39
45
|
|
40
|
-
|
41
|
-
%w[fatal error warn info debug].each do |level|
|
42
|
-
define_method(level.to_sym) do |*_args|
|
43
|
-
nil
|
44
|
-
end
|
46
|
+
private
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
48
|
+
def stringify_headers(headers)
|
49
|
+
headers.map { |name, value| "#{name}: #{value}" }.join("\n")
|
50
50
|
end
|
51
|
-
|
52
|
-
HTTP::Options.register_feature(:logging, self)
|
53
51
|
end
|
54
52
|
end
|
55
53
|
end
|
data/lib/http/headers.rb
CHANGED
@@ -13,14 +13,18 @@ module HTTP
|
|
13
13
|
include Enumerable
|
14
14
|
|
15
15
|
# Matches HTTP header names when in "Canonical-Http-Format"
|
16
|
-
CANONICAL_NAME_RE =
|
16
|
+
CANONICAL_NAME_RE = /\A[A-Z][a-z]*(?:-[A-Z][a-z]*)*\z/.freeze
|
17
17
|
|
18
18
|
# Matches valid header field name according to RFC.
|
19
19
|
# @see http://tools.ietf.org/html/rfc7230#section-3.2
|
20
|
-
COMPLIANT_NAME_RE =
|
20
|
+
COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/.freeze
|
21
21
|
|
22
22
|
# Class constructor.
|
23
23
|
def initialize
|
24
|
+
# The @pile stores each header value using a three element array:
|
25
|
+
# 0 - the normalized header key, used for lookup
|
26
|
+
# 1 - the header key as it will be sent with a request
|
27
|
+
# 2 - the value
|
24
28
|
@pile = []
|
25
29
|
end
|
26
30
|
|
@@ -45,12 +49,31 @@ module HTTP
|
|
45
49
|
|
46
50
|
# Appends header.
|
47
51
|
#
|
48
|
-
# @param [
|
52
|
+
# @param [String, Symbol] name header name. When specified as a string, the
|
53
|
+
# name is sent as-is. When specified as a symbol, the name is converted
|
54
|
+
# to a string of capitalized words separated by a dash. Word boundaries
|
55
|
+
# are determined by an underscore (`_`) or a dash (`-`).
|
56
|
+
# Ex: `:content_type` is sent as `"Content-Type"`, and `"auth_key"` (string)
|
57
|
+
# is sent as `"auth_key"`.
|
49
58
|
# @param [Array<#to_s>, #to_s] value header value(s) to be appended
|
50
59
|
# @return [void]
|
51
60
|
def add(name, value)
|
52
|
-
|
53
|
-
|
61
|
+
lookup_name = normalize_header(name.to_s)
|
62
|
+
wire_name = case name
|
63
|
+
when String
|
64
|
+
name
|
65
|
+
when Symbol
|
66
|
+
lookup_name
|
67
|
+
else
|
68
|
+
raise HTTP::HeaderError, "HTTP header must be a String or Symbol: #{name.inspect}"
|
69
|
+
end
|
70
|
+
Array(value).each do |v|
|
71
|
+
@pile << [
|
72
|
+
lookup_name,
|
73
|
+
wire_name,
|
74
|
+
validate_value(v)
|
75
|
+
]
|
76
|
+
end
|
54
77
|
end
|
55
78
|
|
56
79
|
# Returns list of header values if any.
|
@@ -58,7 +81,7 @@ module HTTP
|
|
58
81
|
# @return [Array<String>]
|
59
82
|
def get(name)
|
60
83
|
name = normalize_header name.to_s
|
61
|
-
@pile.select { |k, _| k == name }.map { |_, v| v }
|
84
|
+
@pile.select { |k, _| k == name }.map { |_, _, v| v }
|
62
85
|
end
|
63
86
|
|
64
87
|
# Smart version of {#get}.
|
@@ -88,7 +111,7 @@ module HTTP
|
|
88
111
|
#
|
89
112
|
# @return [Hash]
|
90
113
|
def to_h
|
91
|
-
|
114
|
+
keys.to_h { |k| [k, self[k]] }
|
92
115
|
end
|
93
116
|
alias to_hash to_h
|
94
117
|
|
@@ -96,7 +119,7 @@ module HTTP
|
|
96
119
|
#
|
97
120
|
# @return [Array<[String, String]>]
|
98
121
|
def to_a
|
99
|
-
@pile.map { |
|
122
|
+
@pile.map { |item| item[1..2] }
|
100
123
|
end
|
101
124
|
|
102
125
|
# Returns human-readable representation of `self` instance.
|
@@ -110,7 +133,7 @@ module HTTP
|
|
110
133
|
#
|
111
134
|
# @return [Array<String>]
|
112
135
|
def keys
|
113
|
-
@pile.map { |k, _| k }.uniq
|
136
|
+
@pile.map { |_, k, _| k }.uniq
|
114
137
|
end
|
115
138
|
|
116
139
|
# Compares headers to another Headers or Array of key/value pairs
|
@@ -118,7 +141,8 @@ module HTTP
|
|
118
141
|
# @return [Boolean]
|
119
142
|
def ==(other)
|
120
143
|
return false unless other.respond_to? :to_a
|
121
|
-
|
144
|
+
|
145
|
+
to_a == other.to_a
|
122
146
|
end
|
123
147
|
|
124
148
|
# Calls the given block once for each key/value pair in headers container.
|
@@ -127,7 +151,8 @@ module HTTP
|
|
127
151
|
# @return [Headers] self-reference
|
128
152
|
def each
|
129
153
|
return to_enum(__method__) unless block_given?
|
130
|
-
|
154
|
+
|
155
|
+
@pile.each { |item| yield(item[1..2]) }
|
131
156
|
self
|
132
157
|
end
|
133
158
|
|
@@ -139,7 +164,7 @@ module HTTP
|
|
139
164
|
|
140
165
|
# @!method hash
|
141
166
|
# Compute a hash-code for this headers container.
|
142
|
-
# Two
|
167
|
+
# Two containers with the same content will have the same hash code.
|
143
168
|
#
|
144
169
|
# @see http://www.ruby-doc.org/core/Object.html#method-i-hash
|
145
170
|
# @return [Fixnum]
|
@@ -150,7 +175,7 @@ module HTTP
|
|
150
175
|
# @api private
|
151
176
|
def initialize_copy(orig)
|
152
177
|
super
|
153
|
-
@pile =
|
178
|
+
@pile = @pile.map(&:dup)
|
154
179
|
end
|
155
180
|
|
156
181
|
# Merges `other` headers into `self`.
|
@@ -209,5 +234,17 @@ module HTTP
|
|
209
234
|
|
210
235
|
raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
|
211
236
|
end
|
237
|
+
|
238
|
+
# Ensures there is no new line character in the header value
|
239
|
+
#
|
240
|
+
# @param [String] value
|
241
|
+
# @raise [HeaderError] if value includes new line character
|
242
|
+
# @return [String] stringified header value
|
243
|
+
def validate_value(value)
|
244
|
+
v = value.to_s
|
245
|
+
return v unless v.include?("\n")
|
246
|
+
|
247
|
+
raise HeaderError, "Invalid HTTP header field value: #{v.inspect}"
|
248
|
+
end
|
212
249
|
end
|
213
250
|
end
|
@@ -14,13 +14,15 @@ module HTTP
|
|
14
14
|
def_delegators :instance, :encode, :decode
|
15
15
|
end
|
16
16
|
|
17
|
+
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
17
18
|
%w[encode decode].each do |operation|
|
18
|
-
class_eval <<-RUBY, __FILE__, __LINE__
|
19
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
20
|
def #{operation}(*)
|
20
21
|
fail Error, "\#{self.class} does not supports ##{operation}"
|
21
22
|
end
|
22
23
|
RUBY
|
23
24
|
end
|
25
|
+
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
data/lib/http/mime_type/json.rb
CHANGED
data/lib/http/options.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/ClassLength
|
4
|
-
|
5
3
|
require "http/headers"
|
6
4
|
require "openssl"
|
7
5
|
require "socket"
|
8
6
|
require "http/uri"
|
9
7
|
|
10
8
|
module HTTP
|
11
|
-
class Options
|
9
|
+
class Options # rubocop:disable Metrics/ClassLength
|
12
10
|
@default_socket_class = TCPSocket
|
13
11
|
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
|
14
12
|
@default_timeout_class = HTTP::Timeout::Null
|
@@ -18,9 +16,8 @@ module HTTP
|
|
18
16
|
attr_accessor :default_socket_class, :default_ssl_socket_class, :default_timeout_class
|
19
17
|
attr_reader :available_features
|
20
18
|
|
21
|
-
def new(options = {})
|
22
|
-
|
23
|
-
super
|
19
|
+
def new(options = {})
|
20
|
+
options.is_a?(self) ? options : super
|
24
21
|
end
|
25
22
|
|
26
23
|
def defined_options
|
@@ -35,7 +32,7 @@ module HTTP
|
|
35
32
|
|
36
33
|
def def_option(name, reader_only: false, &interpreter)
|
37
34
|
defined_options << name.to_sym
|
38
|
-
interpreter ||=
|
35
|
+
interpreter ||= ->(v) { v }
|
39
36
|
|
40
37
|
if reader_only
|
41
38
|
attr_reader name
|
@@ -50,7 +47,7 @@ module HTTP
|
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
53
|
-
def initialize(options = {})
|
50
|
+
def initialize(options = {})
|
54
51
|
defaults = {
|
55
52
|
:response => :auto,
|
56
53
|
:proxy => {},
|