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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ci.yml +24 -0
  4. data/.gitignore +2 -1
  5. data/Changelog.md +402 -308
  6. data/Gemfile +3 -0
  7. data/Guardfile +3 -2
  8. data/README.md +16 -18
  9. data/docs/README.md +89 -2
  10. data/examples/README.md +3 -0
  11. data/examples/aaws.rb +6 -2
  12. data/examples/idn.rb +10 -0
  13. data/examples/party_foul_mode.rb +90 -0
  14. data/httparty.gemspec +4 -2
  15. data/lib/httparty/connection_adapter.rb +8 -25
  16. data/lib/httparty/cookie_hash.rb +3 -1
  17. data/lib/httparty/decompressor.rb +102 -0
  18. data/lib/httparty/exceptions.rb +37 -4
  19. data/lib/httparty/hash_conversions.rb +4 -2
  20. data/lib/httparty/headers_processor.rb +2 -0
  21. data/lib/httparty/logger/apache_formatter.rb +4 -2
  22. data/lib/httparty/logger/curl_formatter.rb +5 -3
  23. data/lib/httparty/logger/logger.rb +2 -0
  24. data/lib/httparty/logger/logstash_formatter.rb +5 -2
  25. data/lib/httparty/module_inheritable_attributes.rb +6 -6
  26. data/lib/httparty/net_digest_auth.rb +9 -10
  27. data/lib/httparty/parser.rb +12 -5
  28. data/lib/httparty/request/body.rb +53 -12
  29. data/lib/httparty/request/multipart_boundary.rb +2 -0
  30. data/lib/httparty/request/streaming_multipart_body.rb +188 -0
  31. data/lib/httparty/request.rb +130 -43
  32. data/lib/httparty/response/headers.rb +2 -0
  33. data/lib/httparty/response.rb +7 -5
  34. data/lib/httparty/response_fragment.rb +2 -0
  35. data/lib/httparty/text_encoder.rb +7 -5
  36. data/lib/httparty/utils.rb +2 -0
  37. data/lib/httparty/version.rb +3 -1
  38. data/lib/httparty.rb +45 -14
  39. data/script/release +4 -4
  40. metadata +33 -14
  41. data/.simplecov +0 -1
  42. 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
@@ -1,7 +1,8 @@
1
1
  rspec_options = {
2
- version: 1,
3
2
  all_after_pass: false,
4
- all_on_start: false
3
+ all_on_start: false,
4
+ failed_mode: :keep,
5
+ cmd: 'bundle exec rspec',
5
6
  }
6
7
 
7
8
  guard 'rspec', rspec_options do
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # httparty
2
2
 
3
- [![Build Status](https://travis-ci.org/jnunemaker/httparty.svg?branch=master)](https://travis-ci.org/jnunemaker/httparty)
3
+ [![CI](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml/badge.svg)](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml)
4
4
 
5
- Makes http fun again! Ain't no party like a httparty, because a httparty don't stop.
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
- * Ruby 2.0.0 or higher
16
- * multi_xml
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/master/examples) for even more goodies.
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
- * [Docs](https://github.com/jnunemaker/httparty/tree/master/docs)
68
- * https://groups.google.com/forum/#!forum/httparty-gem
69
- * https://www.rubydoc.info/github/jnunemaker/httparty
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
- * Fork the project.
75
- * Run `bundle`
76
- * Run `bundle exec rake`
77
- * Make your feature addition or bug fix.
78
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
79
- * Run `bundle exec rake` (No, REALLY :))
80
- * 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)
81
- * Send me a pull request. Bonus points for topic branches.
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 this options with the call:
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 issue the certificate is not a valid one, but you still want to work with it. You can achieve this through:
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
- self.class.default_params AWSAccessKeyId: key
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].inject({}) { |h, q| h[q[0].to_s.camelize] = q[1]; h }
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,10 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+
5
+ class Idn
6
+ include HTTParty
7
+ uri_adapter Addressable::URI
8
+ end
9
+
10
+ pp Idn.get("https://i❤️.ws/emojidomain/💎?format=json")
@@ -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.0.0'
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('mime-types', "~> 3.0")
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
- from_ruby_version('2.6.0', option: :write_timeout) do
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
- from_ruby_version('2.5.0', option: :max_retries) do
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
- from_ruby_version('2.0.0', option: :local_host) do
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
- from_ruby_version('2.0.0', option: :local_port) do
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::RSA.new(options[:pem], options[:pem_password])
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
 
@@ -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
@@ -1,16 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
- # @abstact Exceptions raised by HTTParty inherit from Error
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 < Error; end
32
+ class UnsupportedFormat < Foul; end
7
33
 
8
34
  # Exception raised when using a URI scheme other than HTTP or HTTPS
9
- class UnsupportedURIScheme < Error; end
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 < Error
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 + "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v.to_s)}&"
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
  class HeadersProcessor
3
5
  attr_reader :headers, :options
@@ -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("%Y-%m-%d %H:%M:%S %z")
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("::").last.upcase
35
+ request.http_method.name.split('::').last.upcase
34
36
  end
35
37
 
36
38
  def path