httparty 0.18.1 → 0.24.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 +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +2 -1
- data/Changelog.md +402 -308
- data/Gemfile +3 -0
- data/Guardfile +3 -2
- data/README.md +16 -18
- data/docs/README.md +89 -2
- data/examples/README.md +3 -0
- data/examples/aaws.rb +6 -2
- data/examples/idn.rb +10 -0
- data/examples/party_foul_mode.rb +90 -0
- data/httparty.gemspec +4 -2
- data/lib/httparty/connection_adapter.rb +8 -25
- data/lib/httparty/cookie_hash.rb +3 -1
- data/lib/httparty/decompressor.rb +102 -0
- data/lib/httparty/exceptions.rb +37 -4
- data/lib/httparty/hash_conversions.rb +4 -2
- data/lib/httparty/headers_processor.rb +2 -0
- data/lib/httparty/logger/apache_formatter.rb +4 -2
- data/lib/httparty/logger/curl_formatter.rb +5 -3
- data/lib/httparty/logger/logger.rb +2 -0
- data/lib/httparty/logger/logstash_formatter.rb +5 -2
- data/lib/httparty/module_inheritable_attributes.rb +6 -6
- data/lib/httparty/net_digest_auth.rb +9 -10
- data/lib/httparty/parser.rb +12 -5
- data/lib/httparty/request/body.rb +53 -12
- data/lib/httparty/request/multipart_boundary.rb +2 -0
- data/lib/httparty/request/streaming_multipart_body.rb +188 -0
- data/lib/httparty/request.rb +130 -43
- data/lib/httparty/response/headers.rb +2 -0
- data/lib/httparty/response.rb +7 -5
- data/lib/httparty/response_fragment.rb +2 -0
- data/lib/httparty/text_encoder.rb +7 -5
- data/lib/httparty/utils.rb +2 -0
- data/lib/httparty/version.rb +3 -1
- data/lib/httparty.rb +45 -14
- data/script/release +4 -4
- metadata +33 -14
- data/.simplecov +0 -1
- data/.travis.yml +0 -11
data/Gemfile
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
gemspec
|
|
3
3
|
|
|
4
|
+
gem 'base64'
|
|
4
5
|
gem 'rake'
|
|
5
6
|
gem 'mongrel', '1.2.0.pre2'
|
|
7
|
+
gem 'json'
|
|
6
8
|
|
|
7
9
|
group :development do
|
|
8
10
|
gem 'guard'
|
|
@@ -11,6 +13,7 @@ group :development do
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
group :test do
|
|
16
|
+
gem 'rexml'
|
|
14
17
|
gem 'rspec', '~> 3.4'
|
|
15
18
|
gem 'simplecov', require: false
|
|
16
19
|
gem 'aruba'
|
data/Guardfile
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# httparty
|
|
2
2
|
|
|
3
|
-
[](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
Makes http fun again!
|
|
5
|
+
Makes http fun again! Ain't no party like a httparty, because a httparty don't stop.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,9 +12,8 @@ gem install httparty
|
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* You like to party!
|
|
15
|
+
- Ruby 2.7.0 or higher
|
|
16
|
+
- You like to party!
|
|
18
17
|
|
|
19
18
|
## Examples
|
|
20
19
|
|
|
@@ -47,7 +46,7 @@ puts stack_exchange.questions
|
|
|
47
46
|
puts stack_exchange.users
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
See the [examples directory](http://github.com/jnunemaker/httparty/tree/
|
|
49
|
+
See the [examples directory](http://github.com/jnunemaker/httparty/tree/main/examples) for even more goodies.
|
|
51
50
|
|
|
52
51
|
## Command Line Interface
|
|
53
52
|
|
|
@@ -64,18 +63,17 @@ httparty "https://api.stackexchange.com/2.2/questions?site=stackoverflow"
|
|
|
64
63
|
|
|
65
64
|
## Help and Docs
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* http://stackoverflow.com/questions/tagged/httparty
|
|
66
|
+
- [Docs](https://github.com/jnunemaker/httparty/tree/main/docs)
|
|
67
|
+
- https://github.com/jnunemaker/httparty/discussions
|
|
68
|
+
- https://www.rubydoc.info/github/jnunemaker/httparty
|
|
71
69
|
|
|
72
70
|
## Contributing
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
- Fork the project.
|
|
73
|
+
- Run `bundle`
|
|
74
|
+
- Run `bundle exec rake`
|
|
75
|
+
- Make your feature addition or bug fix.
|
|
76
|
+
- Add tests for it. This is important so I don't break it in a future version unintentionally.
|
|
77
|
+
- Run `bundle exec rake` (No, REALLY :))
|
|
78
|
+
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
|
|
79
|
+
- Send me a pull request. Bonus points for topic branches.
|
data/docs/README.md
CHANGED
|
@@ -14,6 +14,20 @@ response = HTTParty.get('http://example.com', format: :plain)
|
|
|
14
14
|
JSON.parse response, symbolize_names: true
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Posting JSON
|
|
18
|
+
When using Content Type `application/json` with `POST`, `PUT` or `PATCH` requests, the body should be a string of valid JSON:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# With written JSON
|
|
22
|
+
HTTParty.post('http://example.com', body: "{\"foo\":\"bar\"}", headers: { 'Content-Type' => 'application/json' })
|
|
23
|
+
|
|
24
|
+
# Using JSON.generate
|
|
25
|
+
HTTParty.post('http://example.com', body: JSON.generate({ foo: 'bar' }), headers: { 'Content-Type' => 'application/json' })
|
|
26
|
+
|
|
27
|
+
# Using object.to_json
|
|
28
|
+
HTTParty.post('http://example.com', body: { foo: 'bar' }.to_json, headers: { 'Content-Type' => 'application/json' })
|
|
29
|
+
```
|
|
30
|
+
|
|
17
31
|
## Working with SSL
|
|
18
32
|
|
|
19
33
|
You can use this guide to work with SSL certificates.
|
|
@@ -22,6 +36,8 @@ You can use this guide to work with SSL certificates.
|
|
|
22
36
|
|
|
23
37
|
```ruby
|
|
24
38
|
# Use this example if you are using a pem file
|
|
39
|
+
# - cert.pem must contain the content of a PEM file having the private key appended (separated from the cert by a newline \n)
|
|
40
|
+
# - Use an empty string for the password if the cert is not password protected
|
|
25
41
|
|
|
26
42
|
class Client
|
|
27
43
|
include HTTParty
|
|
@@ -70,7 +86,7 @@ class Client
|
|
|
70
86
|
end
|
|
71
87
|
```
|
|
72
88
|
|
|
73
|
-
You can also include
|
|
89
|
+
You can also include all of these options with the call:
|
|
74
90
|
|
|
75
91
|
```ruby
|
|
76
92
|
class Client
|
|
@@ -86,7 +102,7 @@ end
|
|
|
86
102
|
|
|
87
103
|
### Avoid SSL verification
|
|
88
104
|
|
|
89
|
-
In some cases you may want to skip SSL verification, because the entity that
|
|
105
|
+
In some cases you may want to skip SSL verification, because the entity that issued the certificate is not a valid one, but you still want to work with it. You can achieve this through:
|
|
90
106
|
|
|
91
107
|
```ruby
|
|
92
108
|
# Skips SSL certificate verification
|
|
@@ -104,3 +120,74 @@ class Client
|
|
|
104
120
|
end
|
|
105
121
|
end
|
|
106
122
|
```
|
|
123
|
+
|
|
124
|
+
### HTTP Compression
|
|
125
|
+
|
|
126
|
+
The `Accept-Encoding` request header and `Content-Encoding` response header
|
|
127
|
+
are used to control compression (gzip, etc.) over the wire. Refer to
|
|
128
|
+
[RFC-2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) for details.
|
|
129
|
+
(For clarity: these headers are **not** used for character encoding i.e. `utf-8`
|
|
130
|
+
which is specified in the `Accept` and `Content-Type` headers.)
|
|
131
|
+
|
|
132
|
+
Unless you have specific requirements otherwise, we recommend to **not** set
|
|
133
|
+
set the `Accept-Encoding` header on HTTParty requests. In this case, `Net::HTTP`
|
|
134
|
+
will set a sensible default compression scheme and automatically decompress the response.
|
|
135
|
+
|
|
136
|
+
If you explicitly set `Accept-Encoding`, there be dragons:
|
|
137
|
+
|
|
138
|
+
* If the HTTP response `Content-Encoding` received on the wire is `gzip` or `deflate`,
|
|
139
|
+
`Net::HTTP` will automatically decompress it, and will omit `Content-Encoding`
|
|
140
|
+
from your `HTTParty::Response` headers.
|
|
141
|
+
|
|
142
|
+
* For the following encodings, HTTParty will automatically decompress them if you include
|
|
143
|
+
the required gem into your project. Similar to above, if decompression succeeds,
|
|
144
|
+
`Content-Encoding` will be omitted from your `HTTParty::Response` headers.
|
|
145
|
+
**Warning:** Support for these encodings is experimental and not fully battle-tested.
|
|
146
|
+
|
|
147
|
+
| Content-Encoding | Required Gem |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `br` (Brotli) | [brotli](https://rubygems.org/gems/brotli) |
|
|
150
|
+
| `compress` (LZW) | [ruby-lzws](https://rubygems.org/gems/ruby-lzws) |
|
|
151
|
+
| `zstd` (Zstandard) | [zstd-ruby](https://rubygems.org/gems/zstd-ruby) |
|
|
152
|
+
|
|
153
|
+
* For other encodings, `HTTParty::Response#body` will return the raw uncompressed byte string,
|
|
154
|
+
and you'll need to inspect the `Content-Encoding` response header and decompress it yourself.
|
|
155
|
+
In this case, `HTTParty::Response#parsed_response` will be `nil`.
|
|
156
|
+
|
|
157
|
+
* Lastly, you may use the `skip_decompression` option to disable all automatic decompression
|
|
158
|
+
and always get `HTTParty::Response#body` in its raw form along with the `Content-Encoding` header.
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Accept-Encoding=gzip,deflate can be safely assumed to be auto-decompressed
|
|
162
|
+
|
|
163
|
+
res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'gzip,deflate,identity' })
|
|
164
|
+
JSON.parse(res.body) # safe
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Accept-Encoding=br,compress requires third-party gems
|
|
168
|
+
|
|
169
|
+
require 'brotli'
|
|
170
|
+
require 'lzws'
|
|
171
|
+
require 'zstd-ruby'
|
|
172
|
+
res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'br,compress,zstd' })
|
|
173
|
+
JSON.parse(res.body)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Accept-Encoding=* may return unhandled Content-Encoding
|
|
177
|
+
|
|
178
|
+
res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => '*' })
|
|
179
|
+
encoding = res.headers['Content-Encoding']
|
|
180
|
+
if encoding
|
|
181
|
+
JSON.parse(your_decompression_handling(res.body, encoding))
|
|
182
|
+
else
|
|
183
|
+
# Content-Encoding not present implies decompressed
|
|
184
|
+
JSON.parse(res.body)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Gimme the raw data!
|
|
189
|
+
|
|
190
|
+
res = HTTParty.get('https://example.com/test.json', skip_decompression: true)
|
|
191
|
+
encoding = res.headers['Content-Encoding']
|
|
192
|
+
JSON.parse(your_decompression_handling(res.body, encoding))
|
|
193
|
+
```
|
data/examples/README.md
CHANGED
|
@@ -84,3 +84,6 @@
|
|
|
84
84
|
|
|
85
85
|
* [Accessing x509 Peer Certificate](peer_cert.rb)
|
|
86
86
|
* Provides access to the server's TLS certificate
|
|
87
|
+
|
|
88
|
+
* [Accessing IDNs](idn.rb)
|
|
89
|
+
* Uses a `get` request with an International domain names, which are Urls with emojis and non-ASCII characters such as accented letters.
|
data/examples/aaws.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'active_support'
|
|
3
|
+
require 'active_support/core_ext/hash'
|
|
4
|
+
require 'active_support/core_ext/string'
|
|
3
5
|
|
|
4
6
|
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
5
7
|
require File.join(dir, 'httparty')
|
|
@@ -13,14 +15,16 @@ module AAWS
|
|
|
13
15
|
default_params Service: 'AWSECommerceService', Operation: 'ItemSearch', SearchIndex: 'Books'
|
|
14
16
|
|
|
15
17
|
def initialize(key)
|
|
16
|
-
|
|
18
|
+
@auth = { AWSAccessKeyId: key }
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def search(options = {})
|
|
20
22
|
raise ArgumentError, 'You must search for something' if options[:query].blank?
|
|
21
23
|
|
|
22
24
|
# amazon uses nasty camelized query params
|
|
23
|
-
options[:query] = options[:query]
|
|
25
|
+
options[:query] = options[:query]
|
|
26
|
+
.reverse_merge(@auth)
|
|
27
|
+
.transform_keys { |k| k.to_s.camelize }
|
|
24
28
|
|
|
25
29
|
# make a request and return the items (NOTE: this doesn't handle errors at this point)
|
|
26
30
|
self.class.get('/onca/xml', options)['ItemSearchResponse']['Items']
|
data/examples/idn.rb
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
class APIClient
|
|
4
|
+
include HTTParty
|
|
5
|
+
base_uri 'api.example.com'
|
|
6
|
+
|
|
7
|
+
def self.fetch_user(id)
|
|
8
|
+
begin
|
|
9
|
+
get("/users/#{id}", foul: true)
|
|
10
|
+
rescue HTTParty::NetworkError => e
|
|
11
|
+
handle_network_error(e)
|
|
12
|
+
rescue HTTParty::ResponseError => e
|
|
13
|
+
handle_api_error(e)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def self.handle_network_error(error)
|
|
20
|
+
case error.cause
|
|
21
|
+
when Errno::ECONNREFUSED
|
|
22
|
+
{
|
|
23
|
+
error: :server_down,
|
|
24
|
+
message: "The API server appears to be down",
|
|
25
|
+
details: error.message
|
|
26
|
+
}
|
|
27
|
+
when Net::OpenTimeout, Timeout::Error
|
|
28
|
+
{
|
|
29
|
+
error: :timeout,
|
|
30
|
+
message: "The request timed out",
|
|
31
|
+
details: error.message
|
|
32
|
+
}
|
|
33
|
+
when SocketError
|
|
34
|
+
{
|
|
35
|
+
error: :network_error,
|
|
36
|
+
message: "Could not connect to the API server",
|
|
37
|
+
details: error.message
|
|
38
|
+
}
|
|
39
|
+
when OpenSSL::SSL::SSLError
|
|
40
|
+
{
|
|
41
|
+
error: :ssl_error,
|
|
42
|
+
message: "SSL certificate verification failed",
|
|
43
|
+
details: error.message
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
{
|
|
47
|
+
error: :unknown_network_error,
|
|
48
|
+
message: "An unexpected network error occurred",
|
|
49
|
+
details: error.message
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.handle_api_error(error)
|
|
55
|
+
{
|
|
56
|
+
error: :api_error,
|
|
57
|
+
message: "API returned error #{error.response.code}",
|
|
58
|
+
details: error.response.body
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Example usage:
|
|
64
|
+
|
|
65
|
+
# 1. When server is down
|
|
66
|
+
result = APIClient.fetch_user(123)
|
|
67
|
+
puts "Server down example:"
|
|
68
|
+
puts result.inspect
|
|
69
|
+
puts
|
|
70
|
+
|
|
71
|
+
# 2. When request times out
|
|
72
|
+
result = APIClient.fetch_user(456)
|
|
73
|
+
puts "Timeout example:"
|
|
74
|
+
puts result.inspect
|
|
75
|
+
puts
|
|
76
|
+
|
|
77
|
+
# 3. When SSL error occurs
|
|
78
|
+
result = APIClient.fetch_user(789)
|
|
79
|
+
puts "SSL error example:"
|
|
80
|
+
puts result.inspect
|
|
81
|
+
puts
|
|
82
|
+
|
|
83
|
+
# 4. Simple example without a wrapper class
|
|
84
|
+
begin
|
|
85
|
+
HTTParty.get('https://api.example.com/users', foul: true)
|
|
86
|
+
rescue HTTParty::Foul => e
|
|
87
|
+
puts "Direct usage example:"
|
|
88
|
+
puts "Error type: #{e.cause.class}"
|
|
89
|
+
puts "Error message: #{e.message}"
|
|
90
|
+
end
|
data/httparty.gemspec
CHANGED
|
@@ -12,11 +12,13 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.homepage = "https://github.com/jnunemaker/httparty"
|
|
13
13
|
s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
|
14
14
|
s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
|
15
|
+
s.metadata["changelog_uri"] = 'https://github.com/jnunemaker/httparty/releases'
|
|
15
16
|
|
|
16
|
-
s.required_ruby_version = '>= 2.
|
|
17
|
+
s.required_ruby_version = '>= 2.7.0'
|
|
17
18
|
|
|
19
|
+
s.add_dependency 'csv'
|
|
18
20
|
s.add_dependency 'multi_xml', ">= 0.5.2"
|
|
19
|
-
s.add_dependency
|
|
21
|
+
s.add_dependency 'mini_mime', ">= 1.0.0"
|
|
20
22
|
|
|
21
23
|
# If this line is removed, all hard partying will cease.
|
|
22
24
|
s.post_install_message = "When you HTTParty, you must party hard!"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module HTTParty
|
|
2
4
|
# Default connection adapter that returns a new Net::HTTP each time
|
|
3
5
|
#
|
|
@@ -117,10 +119,7 @@ module HTTParty
|
|
|
117
119
|
if add_timeout?(options[:timeout])
|
|
118
120
|
http.open_timeout = options[:timeout]
|
|
119
121
|
http.read_timeout = options[:timeout]
|
|
120
|
-
|
|
121
|
-
from_ruby_version('2.6.0', option: :write_timeout, warn: false) do
|
|
122
|
-
http.write_timeout = options[:timeout]
|
|
123
|
-
end
|
|
122
|
+
http.write_timeout = options[:timeout]
|
|
124
123
|
end
|
|
125
124
|
|
|
126
125
|
if add_timeout?(options[:read_timeout])
|
|
@@ -132,15 +131,11 @@ module HTTParty
|
|
|
132
131
|
end
|
|
133
132
|
|
|
134
133
|
if add_timeout?(options[:write_timeout])
|
|
135
|
-
|
|
136
|
-
http.write_timeout = options[:write_timeout]
|
|
137
|
-
end
|
|
134
|
+
http.write_timeout = options[:write_timeout]
|
|
138
135
|
end
|
|
139
136
|
|
|
140
137
|
if add_max_retries?(options[:max_retries])
|
|
141
|
-
|
|
142
|
-
http.max_retries = options[:max_retries]
|
|
143
|
-
end
|
|
138
|
+
http.max_retries = options[:max_retries]
|
|
144
139
|
end
|
|
145
140
|
|
|
146
141
|
if options[:debug_output]
|
|
@@ -155,15 +150,11 @@ module HTTParty
|
|
|
155
150
|
#
|
|
156
151
|
# @see https://bugs.ruby-lang.org/issues/6617
|
|
157
152
|
if options[:local_host]
|
|
158
|
-
|
|
159
|
-
http.local_host = options[:local_host]
|
|
160
|
-
end
|
|
153
|
+
http.local_host = options[:local_host]
|
|
161
154
|
end
|
|
162
155
|
|
|
163
156
|
if options[:local_port]
|
|
164
|
-
|
|
165
|
-
http.local_port = options[:local_port]
|
|
166
|
-
end
|
|
157
|
+
http.local_port = options[:local_port]
|
|
167
158
|
end
|
|
168
159
|
|
|
169
160
|
http
|
|
@@ -171,14 +162,6 @@ module HTTParty
|
|
|
171
162
|
|
|
172
163
|
private
|
|
173
164
|
|
|
174
|
-
def from_ruby_version(ruby_version, option: nil, warn: true)
|
|
175
|
-
if RUBY_VERSION >= ruby_version
|
|
176
|
-
yield
|
|
177
|
-
elsif warn
|
|
178
|
-
Kernel.warn("Warning: option #{ option } requires Ruby version #{ ruby_version } or later")
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
165
|
def add_timeout?(timeout)
|
|
183
166
|
timeout && (timeout.is_a?(Integer) || timeout.is_a?(Float))
|
|
184
167
|
end
|
|
@@ -221,7 +204,7 @@ module HTTParty
|
|
|
221
204
|
# Note: options[:pem] must contain the content of a PEM file having the private key appended
|
|
222
205
|
if options[:pem]
|
|
223
206
|
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
|
224
|
-
http.key = OpenSSL::PKey
|
|
207
|
+
http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password])
|
|
225
208
|
http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
226
209
|
end
|
|
227
210
|
|
data/lib/httparty/cookie_hash.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class HTTParty::CookieHash < Hash #:nodoc:
|
|
2
4
|
CLIENT_COOKIES = %w(path expires domain path secure httponly samesite)
|
|
3
5
|
|
|
@@ -16,6 +18,6 @@ class HTTParty::CookieHash < Hash #:nodoc:
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def to_cookie_string
|
|
19
|
-
select { |k, v| !CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join(
|
|
21
|
+
select { |k, v| !CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join('; ')
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
# Decompresses the response body based on the Content-Encoding header.
|
|
5
|
+
#
|
|
6
|
+
# Net::HTTP automatically decompresses Content-Encoding values "gzip" and "deflate".
|
|
7
|
+
# This class will handle "br" (Brotli) and "compress" (LZW) if the requisite
|
|
8
|
+
# gems are installed. Otherwise, it returns nil if the body data cannot be
|
|
9
|
+
# decompressed.
|
|
10
|
+
#
|
|
11
|
+
# @abstract Read the HTTP Compression section for more information.
|
|
12
|
+
class Decompressor
|
|
13
|
+
|
|
14
|
+
# "gzip" and "deflate" are handled by Net::HTTP
|
|
15
|
+
# hence they do not need to be handled by HTTParty
|
|
16
|
+
SupportedEncodings = {
|
|
17
|
+
'none' => :none,
|
|
18
|
+
'identity' => :none,
|
|
19
|
+
'br' => :brotli,
|
|
20
|
+
'compress' => :lzw,
|
|
21
|
+
'zstd' => :zstd
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# The response body of the request
|
|
25
|
+
# @return [String]
|
|
26
|
+
attr_reader :body
|
|
27
|
+
|
|
28
|
+
# The Content-Encoding algorithm used to encode the body
|
|
29
|
+
# @return [Symbol] e.g. :gzip
|
|
30
|
+
attr_reader :encoding
|
|
31
|
+
|
|
32
|
+
# @param [String] body - the response body of the request
|
|
33
|
+
# @param [Symbol] encoding - the Content-Encoding algorithm used to encode the body
|
|
34
|
+
def initialize(body, encoding)
|
|
35
|
+
@body = body
|
|
36
|
+
@encoding = encoding
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Perform decompression on the response body
|
|
40
|
+
# @return [String] the decompressed body
|
|
41
|
+
# @return [nil] when the response body is nil or cannot decompressed
|
|
42
|
+
def decompress
|
|
43
|
+
return nil if body.nil?
|
|
44
|
+
return body if encoding.nil? || encoding.strip.empty?
|
|
45
|
+
|
|
46
|
+
if supports_encoding?
|
|
47
|
+
decompress_supported_encoding
|
|
48
|
+
else
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
def supports_encoding?
|
|
56
|
+
SupportedEncodings.keys.include?(encoding)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def decompress_supported_encoding
|
|
60
|
+
method = SupportedEncodings[encoding]
|
|
61
|
+
if respond_to?(method, true)
|
|
62
|
+
send(method)
|
|
63
|
+
else
|
|
64
|
+
raise NotImplementedError, "#{self.class.name} has not implemented a decompression method for #{encoding.inspect} encoding."
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def none
|
|
69
|
+
body
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def brotli
|
|
73
|
+
return nil unless defined?(::Brotli)
|
|
74
|
+
begin
|
|
75
|
+
::Brotli.inflate(body)
|
|
76
|
+
rescue StandardError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def lzw
|
|
82
|
+
begin
|
|
83
|
+
if defined?(::LZWS::String)
|
|
84
|
+
::LZWS::String.decompress(body)
|
|
85
|
+
elsif defined?(::LZW::Simple)
|
|
86
|
+
::LZW::Simple.new.decompress(body)
|
|
87
|
+
end
|
|
88
|
+
rescue StandardError
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def zstd
|
|
94
|
+
return nil unless defined?(::Zstd)
|
|
95
|
+
begin
|
|
96
|
+
::Zstd.decompress(body)
|
|
97
|
+
rescue StandardError
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/httparty/exceptions.rb
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module HTTParty
|
|
2
|
-
|
|
4
|
+
COMMON_NETWORK_ERRORS = [
|
|
5
|
+
EOFError,
|
|
6
|
+
Errno::ECONNABORTED,
|
|
7
|
+
Errno::ECONNREFUSED,
|
|
8
|
+
Errno::ECONNRESET,
|
|
9
|
+
Errno::EHOSTUNREACH,
|
|
10
|
+
Errno::EINVAL,
|
|
11
|
+
Errno::ENETUNREACH,
|
|
12
|
+
Errno::ENOTSOCK,
|
|
13
|
+
Errno::EPIPE,
|
|
14
|
+
Errno::ETIMEDOUT,
|
|
15
|
+
Net::HTTPBadResponse,
|
|
16
|
+
Net::HTTPHeaderSyntaxError,
|
|
17
|
+
Net::ProtocolError,
|
|
18
|
+
Net::ReadTimeout,
|
|
19
|
+
OpenSSL::SSL::SSLError,
|
|
20
|
+
SocketError,
|
|
21
|
+
Timeout::Error # Also covers subclasses like Net::OpenTimeout
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
# @abstract Exceptions raised by HTTParty inherit from Error
|
|
3
25
|
class Error < StandardError; end
|
|
4
26
|
|
|
27
|
+
# @abstract Exceptions raised by HTTParty inherit from this because it is funny
|
|
28
|
+
# and if you don't like fun you should be using a different library.
|
|
29
|
+
class Foul < Error; end
|
|
30
|
+
|
|
5
31
|
# Exception raised when you attempt to set a non-existent format
|
|
6
|
-
class UnsupportedFormat <
|
|
32
|
+
class UnsupportedFormat < Foul; end
|
|
7
33
|
|
|
8
34
|
# Exception raised when using a URI scheme other than HTTP or HTTPS
|
|
9
|
-
class UnsupportedURIScheme <
|
|
35
|
+
class UnsupportedURIScheme < Foul; end
|
|
10
36
|
|
|
11
37
|
# @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
|
|
12
38
|
# response object accessible via the {#response} method.
|
|
13
|
-
class ResponseError <
|
|
39
|
+
class ResponseError < Foul
|
|
14
40
|
# Returns the response of the last request
|
|
15
41
|
# @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
|
|
16
42
|
# Net::HTTPOK
|
|
@@ -30,4 +56,11 @@ module HTTParty
|
|
|
30
56
|
|
|
31
57
|
# Exception that is raised when request redirects and location header is present more than once
|
|
32
58
|
class DuplicateLocationHeader < ResponseError; end
|
|
59
|
+
|
|
60
|
+
# Exception that is raised when common network errors occur.
|
|
61
|
+
class NetworkError < Foul; end
|
|
62
|
+
|
|
63
|
+
# Exception that is raised when an absolute URI is used that doesn't match
|
|
64
|
+
# the configured base_uri, which could indicate an SSRF attempt.
|
|
65
|
+
class UnsafeURIError < Foul; end
|
|
33
66
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'erb'
|
|
2
4
|
|
|
3
5
|
module HTTParty
|
|
@@ -26,8 +28,8 @@ module HTTParty
|
|
|
26
28
|
def self.normalize_param(key, value)
|
|
27
29
|
normalized_keys = normalize_keys(key, value)
|
|
28
30
|
|
|
29
|
-
normalized_keys.flatten.each_slice(2).inject('') do |string, (k, v)|
|
|
30
|
-
string
|
|
31
|
+
normalized_keys.flatten.each_slice(2).inject(''.dup) do |string, (k, v)|
|
|
32
|
+
string << "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v.to_s)}&"
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module HTTParty
|
|
2
4
|
module Logger
|
|
3
5
|
class ApacheFormatter #:nodoc:
|
|
@@ -26,11 +28,11 @@ module HTTParty
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def current_time
|
|
29
|
-
Time.now.strftime(
|
|
31
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def http_method
|
|
33
|
-
request.http_method.name.split(
|
|
35
|
+
request.http_method.name.split('::').last.upcase
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def path
|