http 5.0.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe455ddc7caea6475216135d2748825078dfe40aa5d474e219e054a330d54967
4
- data.tar.gz: cdc04d4ccd9e7a8d45ad5db093c63bd2172720aa0c22f6e095fe93c6892e4ea2
3
+ metadata.gz: 3826a20981b5ed6a7e5f29fd99af814a8409dea3d556d8d4f1c1faeee0060211
4
+ data.tar.gz: 5f1a624d39059a53df7535df9d8094ec2eb17330ce113ee5f8201f028cb08a01
5
5
  SHA512:
6
- metadata.gz: eff3f4e56087fd798ecc435ddc662b9e3b8e248c4a39448323643238cc7f8855a98ecfbe392e26f9e77b9129f18fcab13c1a3c588c819453a2f5199a24683c28
7
- data.tar.gz: fe8fbd07014f69631414e2644aa48198e34cac0d085a4bce8f056a5284088a2414557a5bb83456f953ce2fdaa6b69621746a31430446664f56eca2a2b454117c
6
+ metadata.gz: f34965016796ff2864a48d86c45a95db18a5bd118921e9f5ab653b042d53f13332d155238b1b26d28be643b6dffea6f0bf9ac20bbc71bcab05e73e7358ca9b7d
7
+ data.tar.gz: dfcf6ad46848750d86cb9139b7b0ab9d4ae28624bc7c0d779450f3cfbd8de11dd6ba242855fee759210ffade294743746059157cd1eab4eb69125f644653f5e5
@@ -16,11 +16,11 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- ruby: [ ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0 ]
19
+ ruby: [ ruby-2.6, ruby-2.7, ruby-3.0, ruby-3.1 ]
20
20
  os: [ ubuntu-latest ]
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v2
23
+ - uses: actions/checkout@v3
24
24
 
25
25
  - uses: ruby/setup-ruby@v1
26
26
  with:
@@ -52,11 +52,11 @@ jobs:
52
52
  runs-on: ubuntu-latest
53
53
 
54
54
  steps:
55
- - uses: actions/checkout@v2
55
+ - uses: actions/checkout@v3
56
56
 
57
57
  - uses: ruby/setup-ruby@v1
58
58
  with:
59
- ruby-version: 2.5
59
+ ruby-version: 2.6
60
60
  bundler-cache: true
61
61
 
62
62
  - name: bundle exec rubocop
data/.rubocop.yml CHANGED
@@ -7,4 +7,4 @@ AllCops:
7
7
  DefaultFormatter: fuubar
8
8
  DisplayCopNames: true
9
9
  NewCops: enable
10
- TargetRubyVersion: 2.5
10
+ TargetRubyVersion: 2.6
data/.rubocop_todo.yml CHANGED
@@ -1,13 +1,21 @@
1
1
  # This configuration was generated by
2
- # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 42`
3
- # on 2021-04-10 09:49:03 UTC using RuboCop version 1.12.1.
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 100`
3
+ # on 2022-06-16 14:35:44 UTC using RuboCop version 1.30.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 1
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: Include.
12
+ # Include: **/*.gemspec
13
+ Gemspec/DeprecatedAttributeAssignment:
14
+ Exclude:
15
+ - 'http.gemspec'
16
+
9
17
  # Offense count: 53
10
- # Cop supports --auto-correct.
18
+ # This cop supports safe autocorrection (--autocorrect).
11
19
  # Configuration parameters: EnforcedStyle.
12
20
  # SupportedStyles: leading, trailing
13
21
  Layout/DotPosition:
@@ -22,8 +30,8 @@ Layout/DotPosition:
22
30
  - 'spec/lib/http_spec.rb'
23
31
  - 'spec/support/http_handling_shared.rb'
24
32
 
25
- # Offense count: 174
26
- # Cop supports --auto-correct.
33
+ # Offense count: 176
34
+ # This cop supports safe autocorrection (--autocorrect).
27
35
  # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
28
36
  # SupportedStyles: space, no_space, compact
29
37
  # SupportedStylesForEmptyBraces: space, no_space
@@ -66,7 +74,7 @@ Metrics/AbcSize:
66
74
  - 'lib/http/request.rb'
67
75
  - 'lib/http/response.rb'
68
76
 
69
- # Offense count: 66
77
+ # Offense count: 69
70
78
  # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
71
79
  # IgnoredMethods: refine
72
80
  Metrics/BlockLength:
@@ -109,7 +117,7 @@ Metrics/CyclomaticComplexity:
109
117
  - 'lib/http/chainable.rb'
110
118
  - 'lib/http/client.rb'
111
119
 
112
- # Offense count: 19
120
+ # Offense count: 18
113
121
  # Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
114
122
  Metrics/MethodLength:
115
123
  Exclude:
@@ -132,8 +140,13 @@ Metrics/ModuleLength:
132
140
  Exclude:
133
141
  - 'lib/http/chainable.rb'
134
142
 
143
+ # Offense count: 1
144
+ Security/CompoundHash:
145
+ Exclude:
146
+ - 'lib/http/uri.rb'
147
+
135
148
  # Offense count: 2
136
- # Cop supports --auto-correct.
149
+ # This cop supports safe autocorrection (--autocorrect).
137
150
  # Configuration parameters: EnforcedStyle.
138
151
  # SupportedStyles: separated, grouped
139
152
  Style/AccessorGrouping:
@@ -141,7 +154,7 @@ Style/AccessorGrouping:
141
154
  - 'lib/http/request.rb'
142
155
 
143
156
  # Offense count: 4
144
- # Cop supports --auto-correct.
157
+ # This cop supports safe autocorrection (--autocorrect).
145
158
  Style/EmptyCaseCondition:
146
159
  Exclude:
147
160
  - 'lib/http/client.rb'
@@ -150,7 +163,7 @@ Style/EmptyCaseCondition:
150
163
  - 'lib/http/response/status.rb'
151
164
 
152
165
  # Offense count: 5
153
- # Cop supports --auto-correct.
166
+ # This cop supports safe autocorrection (--autocorrect).
154
167
  Style/Encoding:
155
168
  Exclude:
156
169
  - 'spec/lib/http/client_spec.rb'
@@ -160,7 +173,7 @@ Style/Encoding:
160
173
  - 'spec/support/dummy_server/servlet.rb'
161
174
 
162
175
  # Offense count: 17
163
- # Configuration parameters: SuspiciousParamNames.
176
+ # Configuration parameters: SuspiciousParamNames, Allowlist.
164
177
  # SuspiciousParamNames: options, opts, args, params, parameters
165
178
  Style/OptionHash:
166
179
  Exclude:
@@ -183,8 +196,8 @@ Style/OptionalBooleanParameter:
183
196
  - 'lib/http/uri.rb'
184
197
 
185
198
  # Offense count: 3
186
- # Cop supports --auto-correct.
187
- # Configuration parameters: AutoCorrect, Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
199
+ # This cop supports safe autocorrection (--autocorrect).
200
+ # Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns.
188
201
  # URISchemes: http, https
189
202
  Layout/LineLength:
190
203
  Exclude:
data/CHANGES.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## 5.1.0 (2022-06-17)
2
+
3
+ * Drop ruby-2.5 support.
4
+
5
+ * [#715](https://github.com/httprb/http/pull/715)
6
+ Set default encoding to UTF-8 for `application/json`.
7
+ ([@drwl])
8
+
9
+ * [#712](https://github.com/httprb/http/pull/712)
10
+ Recognize cookies set by redirect.
11
+ ([@tkellogg])
12
+
13
+ * [#707](https://github.com/httprb/http/pull/707)
14
+ Distinguish connection timeouts.
15
+ ([@YuLeven])
16
+
17
+ ## 5.0.4 (2021-10-07)
18
+
19
+ * [#698](https://github.com/httprb/http/pull/698)
20
+ Fix `HTTP::Timeout::Global#connect_ssl`.
21
+ ([@tarcieri])
22
+
23
+ ## 5.0.3 (2021-10-06)
24
+
25
+ * [#695](https://github.com/httprb/http/pull/695)
26
+ Revert DNS resolving feature.
27
+ ([@PhilCoggins])
28
+
29
+ * [#694](https://github.com/httprb/http/pull/694)
30
+ Fix cookies extraction.
31
+ ([@flosacca])
32
+
1
33
  ## 5.0.2 (2021-09-10)
2
34
 
3
35
  * [#686](https://github.com/httprb/http/pull/686)
@@ -26,7 +58,8 @@
26
58
 
27
59
  * [#638](https://github.com/httprb/http/pull/638)
28
60
  DNS failover handling.
29
- ([@midnight-wonderer])
61
+ ([@midnight-wonderer])
62
+
30
63
 
31
64
  ## 5.0.1 (2021-06-26)
32
65
 
@@ -42,6 +75,7 @@
42
75
  Bump llhttp-ffi to 0.3.0.
43
76
  ([@bryanp])
44
77
 
78
+
45
79
  ## 5.0.0 (2021-05-12)
46
80
 
47
81
  * [#656](https://github.com/httprb/http/pull/656)
@@ -94,6 +128,7 @@
94
128
 
95
129
  * [#576](https://github.com/httprb/http/pull/576)
96
130
  [#524](https://github.com/httprb/http/issues/524)
131
+ **BREAKING CHANGE**
97
132
  Preserve header names casing.
98
133
  ([@joshuaflanagan])
99
134
 
@@ -948,3 +983,8 @@ end
948
983
  [@midnight-wonderer]: https://github.com/midnight-wonderer
949
984
  [@schwern]: https://github.com/schwern
950
985
  [@matheussilvasantos]: https://github.com/matheussilvasantos
986
+ [@PhilCoggins]: https://github.com/PhilCoggins
987
+ [@flosacca]: https://github.com/flosacca
988
+ [@YuLeven]: https://github.com/YuLeven
989
+ [@drwl]: https://github.com/drwl
990
+ [@tkellogg]: https://github.com/tkellogg
data/Gemfile CHANGED
@@ -27,7 +27,7 @@ group :test do
27
27
 
28
28
  gem "backports"
29
29
 
30
- gem "rubocop"
30
+ gem "rubocop", "~> 1.30.0"
31
31
  gem "rubocop-performance"
32
32
  gem "rubocop-rake"
33
33
  gem "rubocop-rspec"
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2021 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
1
+ Copyright (c) 2011-2022 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,16 +1,12 @@
1
- # ![http.rb](https://raw.github.com/httprb/http.rb/master/logo.png)
1
+ # ![http.rb](https://raw.github.com/httprb/http.rb/main/logo.png)
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/http?logo=ruby)](https://rubygems.org/gems/http)
4
- [![Build Status](https://github.com/httprb/http/workflows/CI/badge.svg)](https://github.com/httprb/http/actions?query=workflow:CI)
5
- [![Code Climate](https://codeclimate.com/github/httprb/http.svg?branch=master)](https://codeclimate.com/github/httprb/http)
6
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/httprb/http/blob/master/LICENSE.txt)
3
+ [![Gem Version][gem-image]][gem-link]
4
+ [![MIT licensed][license-image]][license-link]
5
+ [![Build Status][build-image]][build-link]
6
+ [![Code Climate][codeclimate-image]][codeclimate-link]
7
7
 
8
8
  [Documentation]
9
9
 
10
- _NOTE: This is the 5.x **development** branch. For the 4.x **stable** branch, please see:_
11
-
12
- https://github.com/httprb/http/tree/4-x-stable
13
-
14
10
  ## About
15
11
 
16
12
  HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making requests
@@ -21,63 +17,16 @@ Under the hood, http.rb uses the [llhttp] parser, a fast HTTP parsing native ext
21
17
  This library isn't just yet another wrapper around `Net::HTTP`. It implements the HTTP
22
18
  protocol natively and outsources the parsing to native extensions.
23
19
 
24
- [requests]: http://docs.python-requests.org/en/latest/
25
- [llhttp]: https://llhttp.org/
26
-
27
-
28
- ## Another Ruby HTTP library? Why should I care?
29
-
30
- There are a lot of HTTP libraries to choose from in the Ruby ecosystem.
31
- So why would you choose this one?
20
+ ### Why http.rb?
32
21
 
33
- Top three reasons:
34
-
35
- 1. **Clean API**: http.rb offers an easy-to-use API that should be a
22
+ - **Clean API**: http.rb offers an easy-to-use API that should be a
36
23
  breath of fresh air after using something like Net::HTTP.
37
24
 
38
- 2. **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
25
+ - **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
39
26
  features like persistent connections and fine-grained timeouts.
40
27
 
41
- 3. **Performance**: using native parsers and a clean, lightweight implementation,
42
- http.rb achieves the best performance of any Ruby HTTP library which
43
- implements the HTTP protocol in Ruby instead of C:
44
-
45
- | HTTP client | Time | Implementation |
46
- |--------------------------|--------|-----------------------|
47
- | curb (persistent) | 2.519 | libcurl wrapper |
48
- | em-http-request | 2.731 | EM + http_parser.rb |
49
- | Typhoeus | 2.851 | libcurl wrapper |
50
- | StreamlyFFI (persistent) | 2.853 | libcurl wrapper |
51
- | http.rb (persistent) | 2.970 | Ruby + http_parser.rb |
52
- | http.rb | 3.588 | Ruby + http_parser.rb |
53
- | HTTParty | 3.931 | Net::HTTP wrapper |
54
- | Net::HTTP | 3.959 | Pure Ruby |
55
- | Net::HTTP (persistent) | 4.043 | Pure Ruby |
56
- | open-uri | 4.479 | Net::HTTP wrapper |
57
- | Excon (persistent) | 4.618 | Pure Ruby |
58
- | Excon | 4.701 | Pure Ruby |
59
- | RestClient | 26.838 | Net::HTTP wrapper |
60
-
61
- Benchmarks performed using excon's benchmarking tool
62
-
63
- DISCLAIMER: Most benchmarks you find in READMEs are crap,
64
- including this one. These are out-of-date. If you care about
65
- performance, benchmark for yourself for your own use cases!
66
-
67
- ## Help and Discussion
68
-
69
- If you need help or just want to talk about the http.rb,
70
- visit the http.rb Google Group:
71
-
72
- https://groups.google.com/forum/#!forum/httprb
73
-
74
- You can join by email by sending a message to:
75
-
76
- [httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
77
-
78
- If you believe you've found a bug, please report it at:
79
-
80
- https://github.com/httprb/http/issues
28
+ - **Performance**: using native parsers and a clean, lightweight implementation,
29
+ http.rb achieves high performance while implementing HTTP in Ruby instead of C.
81
30
 
82
31
 
83
32
  ## Installation
@@ -112,10 +61,9 @@ for more detailed documentation and usage notes.
112
61
 
113
62
  The following API documentation is also available:
114
63
 
115
- * [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
116
- * [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
64
+ - [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
65
+ - [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
117
66
 
118
- [documentation]: https://github.com/httprb/http/wiki
119
67
 
120
68
  ### Basic Usage
121
69
 
@@ -142,7 +90,7 @@ We can also obtain an `HTTP::Response::Body` object for this response:
142
90
  ```
143
91
 
144
92
  The response body can be streamed with `HTTP::Response::Body#readpartial`.
145
- In practice, you'll want to bind the HTTP::Response::Body to a local variable
93
+ In practice, you'll want to bind the `HTTP::Response::Body` to a local variable
146
94
  and call `#readpartial` on it repeatedly until it returns `nil`:
147
95
 
148
96
  ```ruby
@@ -159,13 +107,13 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
159
107
 
160
108
  ## Supported Ruby Versions
161
109
 
162
- This library aims to support and is [tested against][travis] the following Ruby
163
- versions:
110
+ This library aims to support and is [tested against][build-link]
111
+ the following Ruby versions:
164
112
 
165
- * Ruby 2.6
166
- * Ruby 2.7
167
- * Ruby 3.0
168
- * JRuby 9.2
113
+ - Ruby 2.6
114
+ - Ruby 2.7
115
+ - Ruby 3.0
116
+ - JRuby 9.2
169
117
 
170
118
  If something doesn't work on one of these versions, it's a bug.
171
119
 
@@ -180,20 +128,36 @@ patches in a timely fashion. If critical issues for a particular implementation
180
128
  exist at the time of a major release, support for that Ruby version may be
181
129
  dropped.
182
130
 
183
- [travis]: http://travis-ci.org/httprb/http
184
-
185
131
 
186
132
  ## Contributing to http.rb
187
133
 
188
- * Fork http.rb on GitHub
189
- * Make your changes
190
- * Ensure all tests pass (`bundle exec rake`)
191
- * Send a pull request
192
- * If we like them we'll merge them
193
- * If we've accepted a patch, feel free to ask for commit access!
134
+ - Fork http.rb on GitHub
135
+ - Make your changes
136
+ - Ensure all tests pass (`bundle exec rake`)
137
+ - Send a pull request
138
+ - If we like them we'll merge them
139
+ - If we've accepted a patch, feel free to ask for commit access!
194
140
 
195
141
 
196
142
  ## Copyright
197
143
 
198
- Copyright (c) 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
144
+ Copyright © 2011-2022 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
199
145
  See LICENSE.txt for further details.
146
+
147
+
148
+ [//]: # (badges)
149
+
150
+ [gem-image]: https://img.shields.io/gem/v/http?logo=ruby
151
+ [gem-link]: https://rubygems.org/gems/http
152
+ [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
153
+ [license-link]: https://github.com/httprb/http/blob/main/LICENSE.txt
154
+ [build-image]: https://github.com/httprb/http/workflows/CI/badge.svg
155
+ [build-link]: https://github.com/httprb/http/actions/workflows/ci.yml
156
+ [codeclimate-image]: https://codeclimate.com/github/httprb/http.svg?branch=main
157
+ [codeclimate-link]: https://codeclimate.com/github/httprb/http
158
+
159
+ [//]: # (links)
160
+
161
+ [documentation]: https://github.com/httprb/http/wiki
162
+ [requests]: http://docs.python-requests.org/en/latest/
163
+ [llhttp]: https://llhttp.org/
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Please report security issues to `bascule@gmail.com`
data/http.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
25
25
  gem.require_paths = ["lib"]
26
26
  gem.version = HTTP::VERSION
27
27
 
28
- gem.required_ruby_version = ">= 2.5"
28
+ gem.required_ruby_version = ">= 2.6"
29
29
 
30
30
  gem.add_runtime_dependency "addressable", "~> 2.8"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
@@ -35,9 +35,10 @@ Gem::Specification.new do |gem|
35
35
  gem.add_development_dependency "bundler", "~> 2.0"
36
36
 
37
37
  gem.metadata = {
38
- "source_code_uri" => "https://github.com/httprb/http",
39
- "wiki_uri" => "https://github.com/httprb/http/wiki",
40
- "bug_tracker_uri" => "https://github.com/httprb/http/issues",
41
- "changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md"
38
+ "source_code_uri" => "https://github.com/httprb/http",
39
+ "wiki_uri" => "https://github.com/httprb/http/wiki",
40
+ "bug_tracker_uri" => "https://github.com/httprb/http/issues",
41
+ "changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md",
42
+ "rubygems_mfa_required" => "true"
42
43
  }
43
44
  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/headers.rb CHANGED
@@ -111,7 +111,7 @@ module HTTP
111
111
  #
112
112
  # @return [Hash]
113
113
  def to_h
114
- keys.map { |k| [k, self[k]] }.to_h
114
+ keys.to_h { |k| [k, self[k]] }
115
115
  end
116
116
  alias to_hash to_h
117
117
 
@@ -49,6 +49,8 @@ module HTTP
49
49
  @request = request
50
50
  @response = response
51
51
  @visited = []
52
+ collect_cookies_from_request
53
+ collect_cookies_from_response
52
54
 
53
55
  while REDIRECT_CODES.include? @response.status.code
54
56
  @visited << "#{@request.verb} #{@request.uri}"
@@ -59,8 +61,12 @@ module HTTP
59
61
  @response.flush
60
62
 
61
63
  # XXX(ixti): using `Array#inject` to return `nil` if no Location header.
62
- @request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
64
+ @request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
65
+ unless cookie_jar.empty?
66
+ @request.headers.set(Headers::COOKIE, cookie_jar.cookies.map { |c| "#{c.name}=#{c.value}" }.join("; "))
67
+ end
63
68
  @response = yield @request
69
+ collect_cookies_from_response
64
70
  end
65
71
 
66
72
  @response
@@ -68,6 +74,48 @@ module HTTP
68
74
 
69
75
  private
70
76
 
77
+ # All known cookies. On the original request, this is only the original cookies, but after that,
78
+ # Set-Cookie headers can add, set or delete cookies.
79
+ def cookie_jar
80
+ # it seems that @response.cookies instance is reused between responses, so we have to "clone"
81
+ @cookie_jar ||= HTTP::CookieJar.new
82
+ end
83
+
84
+ def collect_cookies_from_request
85
+ request_cookie_header = @request.headers["Cookie"]
86
+ cookies =
87
+ if request_cookie_header
88
+ HTTP::Cookie.cookie_value_to_hash(request_cookie_header)
89
+ else
90
+ {}
91
+ end
92
+
93
+ cookies.each do |key, value|
94
+ cookie_jar.add(HTTP::Cookie.new(key, value, :path => @request.uri.path, :domain => @request.host))
95
+ end
96
+ end
97
+
98
+ # Carry cookies from one response to the next. Carrying cookies to the next response ends up
99
+ # carrying them to the next request as well.
100
+ #
101
+ # Note that this isn't part of the IETF standard, but all major browsers support setting cookies
102
+ # on redirect: https://blog.dubbelboer.com/2012/11/25/302-cookie.html
103
+ def collect_cookies_from_response
104
+ # Overwrite previous cookies
105
+ @response.cookies.each do |cookie|
106
+ if cookie.value == ""
107
+ cookie_jar.delete(cookie)
108
+ else
109
+ cookie_jar.add(cookie)
110
+ end
111
+ end
112
+
113
+ # I wish we could just do @response.cookes = cookie_jar
114
+ cookie_jar.each do |cookie|
115
+ @response.cookies.add(cookie)
116
+ end
117
+ end
118
+
71
119
  # Check if we reached max amount of redirect hops
72
120
  # @return [Boolean]
73
121
  def too_many_hops?
@@ -61,7 +61,7 @@ module HTTP
61
61
  def join_headers
62
62
  # join the headers array with crlfs, stick two on the end because
63
63
  # that ends the request header
64
- @request_header.join(CRLF) + CRLF * 2
64
+ @request_header.join(CRLF) + (CRLF * 2)
65
65
  end
66
66
 
67
67
  # Writes HTTP request data into the socket.
@@ -69,7 +69,7 @@ module HTTP
69
69
  # SYMBOL_CODES[:im_a_teapot] # => 418
70
70
  #
71
71
  # @return [Hash<Symbol => Fixnum>]
72
- SYMBOL_CODES = SYMBOLS.map { |k, v| [v, k] }.to_h.freeze
72
+ SYMBOL_CODES = SYMBOLS.to_h { |k, v| [v, k] }.freeze
73
73
 
74
74
  # @return [Fixnum] status code
75
75
  attr_reader :code
data/lib/http/response.rb CHANGED
@@ -53,7 +53,7 @@ module HTTP
53
53
  @body = opts.fetch(:body)
54
54
  else
55
55
  connection = opts.fetch(:connection)
56
- encoding = opts[:encoding] || charset || Encoding::BINARY
56
+ encoding = opts[:encoding] || charset || default_encoding
57
57
 
58
58
  @body = Response::Body.new(connection, :encoding => encoding)
59
59
  end
@@ -138,8 +138,8 @@ module HTTP
138
138
  def_delegator :content_type, :charset
139
139
 
140
140
  def cookies
141
- @cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
142
- jar.parse(v, uri) if k == Headers::SET_COOKIE
141
+ @cookies ||= headers.get(Headers::SET_COOKIE).each_with_object CookieJar.new do |v, jar|
142
+ jar.parse(v, uri)
143
143
  end
144
144
  end
145
145
 
@@ -168,6 +168,12 @@ module HTTP
168
168
 
169
169
  private
170
170
 
171
+ def default_encoding
172
+ return Encoding::UTF_8 if mime_type == "application/json"
173
+
174
+ Encoding::BINARY
175
+ end
176
+
171
177
  # Initialize an HTTP::Request from options.
172
178
  #
173
179
  # @return [HTTP::Request]
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "io/wait"
4
- require "resolv"
5
3
  require "timeout"
4
+ require "io/wait"
6
5
 
7
6
  require "http/timeout/null"
8
7
 
@@ -13,9 +12,6 @@ module HTTP
13
12
  super
14
13
 
15
14
  @timeout = @time_left = options.fetch(:global_timeout)
16
- @dns_resolver = options.fetch(:dns_resolver) do
17
- ::Resolv.method(:getaddresses)
18
- end
19
15
  end
20
16
 
21
17
  # To future me: Don't remove this again, past you was smarter.
@@ -23,28 +19,14 @@ module HTTP
23
19
  @time_left = @timeout
24
20
  end
25
21
 
26
- def connect(socket_class, host_name, *args)
27
- connect_operation = lambda do |host_address|
28
- ::Timeout.timeout(@time_left, TimeoutError) do
29
- super(socket_class, host_address, *args)
30
- end
31
- end
32
- host_addresses = @dns_resolver.call(host_name)
33
- # ensure something to iterates
34
- trying_targets = host_addresses.empty? ? [host_name] : host_addresses
22
+ def connect(socket_class, host, port, nodelay = false)
35
23
  reset_timer
36
- trying_iterator = trying_targets.lazy
37
- error = nil
38
- begin
39
- connect_operation.call(trying_iterator.next).tap do
40
- log_time
41
- end
42
- rescue TimeoutError => e
43
- error = e
44
- retry
45
- rescue ::StopIteration
46
- raise error
24
+ ::Timeout.timeout(@time_left, ConnectTimeoutError) do
25
+ @socket = socket_class.open(host, port)
26
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
47
27
  end
28
+
29
+ log_time
48
30
  end
49
31
 
50
32
  def connect_ssl
@@ -53,12 +35,10 @@ module HTTP
53
35
  begin
54
36
  @socket.connect_nonblock
55
37
  rescue IO::WaitReadable
56
- IO.select([@socket], nil, nil, @time_left)
57
- log_time
38
+ wait_readable_or_timeout
58
39
  retry
59
40
  rescue IO::WaitWritable
60
- IO.select(nil, [@socket], nil, @time_left)
61
- log_time
41
+ wait_writable_or_timeout
62
42
  retry
63
43
  end
64
44
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "resolv"
4
3
  require "timeout"
5
4
 
6
5
  require "http/timeout/null"
@@ -18,34 +17,14 @@ module HTTP
18
17
  @read_timeout = options.fetch(:read_timeout, READ_TIMEOUT)
19
18
  @write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT)
20
19
  @connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
21
- @dns_resolver = options.fetch(:dns_resolver) do
22
- ::Resolv.method(:getaddresses)
23
- end
24
20
  end
25
21
 
26
- # TODO: refactor
27
- # rubocop:disable Metrics/MethodLength
28
- def connect(socket_class, host_name, *args)
29
- connect_operation = lambda do |host_address|
30
- ::Timeout.timeout(@connect_timeout, TimeoutError) do
31
- super(socket_class, host_address, *args)
32
- end
33
- end
34
- host_addresses = @dns_resolver.call(host_name)
35
- # ensure something to iterates
36
- trying_targets = host_addresses.empty? ? [host_name] : host_addresses
37
- trying_iterator = trying_targets.lazy
38
- error = nil
39
- begin
40
- connect_operation.call(trying_iterator.next)
41
- rescue TimeoutError => e
42
- error = e
43
- retry
44
- rescue ::StopIteration
45
- raise error
22
+ def connect(socket_class, host, port, nodelay = false)
23
+ ::Timeout.timeout(@connect_timeout, ConnectTimeoutError) do
24
+ @socket = socket_class.open(host, port)
25
+ @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
46
26
  end
47
27
  end
48
- # rubocop:enable Metrics/MethodLength
49
28
 
50
29
  def connect_ssl
51
30
  rescue_readable(@connect_timeout) do
data/lib/http/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- VERSION = "5.0.2"
4
+ VERSION = "5.1.0"
5
5
  end
@@ -389,8 +389,8 @@ RSpec.describe HTTP::Client do
389
389
  client.use(:test_feature => feature_instance).
390
390
  timeout(0.001).
391
391
  request(:post, sleep_url)
392
- end.to raise_error(HTTP::TimeoutError)
393
- expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
392
+ end.to raise_error(HTTP::ConnectTimeoutError)
393
+ expect(feature_instance.captured_error).to be_a(HTTP::ConnectTimeoutError)
394
394
  end
395
395
  end
396
396
  end
@@ -11,8 +11,12 @@ RSpec.describe HTTP::Redirector do
11
11
  )
12
12
  end
13
13
 
14
- def redirect_response(status, location)
15
- simple_response status, "", "Location" => location
14
+ def redirect_response(status, location, set_cookie = {})
15
+ res = simple_response status, "", "Location" => location
16
+ set_cookie.each do |name, value|
17
+ res.headers.add("Set-Cookie", "#{name}=#{value}; path=/; httponly; secure; SameSite=none; Secure")
18
+ end
19
+ res
16
20
  end
17
21
 
18
22
  describe "#strict" do
@@ -89,6 +93,61 @@ RSpec.describe HTTP::Redirector do
89
93
  expect(res.to_s).to eq "http://example.com/123"
90
94
  end
91
95
 
96
+ it "returns cookies in response" do
97
+ req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
98
+ hops = [
99
+ redirect_response(301, "http://example.com/1", {"foo" => "42"}),
100
+ redirect_response(301, "http://example.com/2", {"bar" => "53", "deleted" => "foo"}),
101
+ redirect_response(301, "http://example.com/3", {"baz" => "64", "deleted" => ""}),
102
+ redirect_response(301, "http://example.com/4", {"baz" => "65"}),
103
+ simple_response(200, "bar")
104
+ ]
105
+
106
+ request_cookies = [
107
+ {"foo" => "42"},
108
+ {"foo" => "42", "bar" => "53", "deleted" => "foo"},
109
+ {"foo" => "42", "bar" => "53", "baz" => "64"},
110
+ {"foo" => "42", "bar" => "53", "baz" => "65"}
111
+ ]
112
+
113
+ res = redirector.perform(req, hops.shift) do |request|
114
+ req_cookie = HTTP::Cookie.cookie_value_to_hash(request.headers["Cookie"] || "")
115
+ expect(req_cookie).to eq request_cookies.shift
116
+ hops.shift
117
+ end
118
+ expect(res.to_s).to eq "bar"
119
+ cookies = res.cookies.cookies.to_h { |c| [c.name, c.value] }
120
+ expect(cookies["foo"]).to eq "42"
121
+ expect(cookies["bar"]).to eq "53"
122
+ expect(cookies["baz"]).to eq "65"
123
+ expect(cookies["deleted"]).to eq nil
124
+ end
125
+
126
+ it "returns original cookies in response" do
127
+ req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
128
+ req.headers.set("Cookie", "foo=42; deleted=baz")
129
+ hops = [
130
+ redirect_response(301, "http://example.com/1", {"bar" => "64", "deleted" => ""}),
131
+ simple_response(200, "bar")
132
+ ]
133
+
134
+ request_cookies = [
135
+ {"foo" => "42", "bar" => "64"},
136
+ {"foo" => "42", "bar" => "64"}
137
+ ]
138
+
139
+ res = redirector.perform(req, hops.shift) do |request|
140
+ req_cookie = HTTP::Cookie.cookie_value_to_hash(request.headers["Cookie"] || "")
141
+ expect(req_cookie).to eq request_cookies.shift
142
+ hops.shift
143
+ end
144
+ expect(res.to_s).to eq "bar"
145
+ cookies = res.cookies.cookies.to_h { |c| [c.name, c.value] }
146
+ expect(cookies["foo"]).to eq "42"
147
+ expect(cookies["bar"]).to eq "64"
148
+ expect(cookies["deleted"]).to eq nil
149
+ end
150
+
92
151
  context "following 300 redirect" do
93
152
  context "with strict mode" do
94
153
  let(:options) { {:strict => true} }
@@ -400,7 +459,7 @@ RSpec.describe HTTP::Redirector do
400
459
  describe "changing verbs during redirects" do
401
460
  let(:options) { {:strict => false} }
402
461
  let(:post_body) { HTTP::Request::Body.new("i might be way longer in real life") }
403
- let(:cookie) { "dont eat my cookies" }
462
+ let(:cookie) { "dont=eat my cookies" }
404
463
 
405
464
  def a_dangerous_request(verb)
406
465
  HTTP::Request.new(
@@ -118,10 +118,10 @@ RSpec.describe HTTP::Request::Body do
118
118
  end
119
119
 
120
120
  context "when body is a non-Enumerable IO" do
121
- let(:body) { FakeIO.new("a" * 16 * 1024 + "b" * 10 * 1024) }
121
+ let(:body) { FakeIO.new(("a" * 16 * 1024) + ("b" * 10 * 1024)) }
122
122
 
123
123
  it "yields chunks of content" do
124
- expect(chunks.inject("", :+)).to eq "a" * 16 * 1024 + "b" * 10 * 1024
124
+ expect(chunks.inject("", :+)).to eq ("a" * 16 * 1024) + ("b" * 10 * 1024)
125
125
  end
126
126
  end
127
127
 
@@ -148,7 +148,7 @@ RSpec.describe HTTP::Request::Body do
148
148
  end
149
149
 
150
150
  context "when body is an Enumerable IO" do
151
- let(:data) { "a" * 16 * 1024 + "b" * 10 * 1024 }
151
+ let(:data) { ("a" * 16 * 1024) + ("b" * 10 * 1024) }
152
152
  let(:body) { StringIO.new data }
153
153
 
154
154
  it "yields chunks of content" do
@@ -59,7 +59,7 @@ RSpec.describe HTTP::Response::Body do
59
59
  let(:chunks) do
60
60
  body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
61
61
  len = body.length
62
- [body[0, len / 2], body[(len / 2)..-1]]
62
+ [body[0, len / 2], body[(len / 2)..]]
63
63
  end
64
64
  subject(:body) do
65
65
  inflater = HTTP::Response::Inflater.new(connection)
@@ -223,4 +223,40 @@ RSpec.describe HTTP::Response do
223
223
  end
224
224
  end
225
225
  end
226
+
227
+ describe "#body" do
228
+ let(:connection) { double(:sequence_id => 0) }
229
+ let(:chunks) { ["Hello, ", "World!"] }
230
+
231
+ subject(:response) do
232
+ HTTP::Response.new(
233
+ :status => 200,
234
+ :version => "1.1",
235
+ :headers => headers,
236
+ :request => request,
237
+ :connection => connection
238
+ )
239
+ end
240
+
241
+ before do
242
+ allow(connection).to receive(:readpartial) { chunks.shift }
243
+ allow(connection).to receive(:body_completed?) { chunks.empty? }
244
+ end
245
+
246
+ context "with no Content-Type" do
247
+ let(:headers) { {} }
248
+
249
+ it "returns a body with default binary encoding" do
250
+ expect(response.body.to_s.encoding).to eq Encoding::BINARY
251
+ end
252
+ end
253
+
254
+ context "with Content-Type: application/json" do
255
+ let(:headers) { {"Content-Type" => "application/json"} }
256
+
257
+ it "returns a body with a default UTF_8 encoding" do
258
+ expect(response.body.to_s.encoding).to eq Encoding::UTF_8
259
+ end
260
+ end
261
+ end
226
262
  end
@@ -77,7 +77,7 @@ RSpec.shared_context "HTTP handling" do
77
77
  sleep 1.25
78
78
  end
79
79
 
80
- expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
80
+ expect { response }.to raise_error(HTTP::ConnectTimeoutError, /execution/)
81
81
  end
82
82
 
83
83
  it "errors if reading takes too long" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.2
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2021-09-10 00:00:00.000000000 Z
14
+ date: 2022-06-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: addressable
@@ -106,6 +106,7 @@ files:
106
106
  - LICENSE.txt
107
107
  - README.md
108
108
  - Rakefile
109
+ - SECURITY.md
109
110
  - http.gemspec
110
111
  - lib/http.rb
111
112
  - lib/http/chainable.rb
@@ -191,7 +192,8 @@ metadata:
191
192
  source_code_uri: https://github.com/httprb/http
192
193
  wiki_uri: https://github.com/httprb/http/wiki
193
194
  bug_tracker_uri: https://github.com/httprb/http/issues
194
- changelog_uri: https://github.com/httprb/http/blob/v5.0.2/CHANGES.md
195
+ changelog_uri: https://github.com/httprb/http/blob/v5.1.0/CHANGES.md
196
+ rubygems_mfa_required: 'true'
195
197
  post_install_message:
196
198
  rdoc_options: []
197
199
  require_paths:
@@ -200,14 +202,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
202
  requirements:
201
203
  - - ">="
202
204
  - !ruby/object:Gem::Version
203
- version: '2.5'
205
+ version: '2.6'
204
206
  required_rubygems_version: !ruby/object:Gem::Requirement
205
207
  requirements:
206
208
  - - ">="
207
209
  - !ruby/object:Gem::Version
208
210
  version: '0'
209
211
  requirements: []
210
- rubygems_version: 3.0.3
212
+ rubygems_version: 3.1.6
211
213
  signing_key:
212
214
  specification_version: 4
213
215
  summary: HTTP should be easy