httparty 0.18.1 → 0.21.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: 21274d1c09f3460352bc2aaf0cc00e6c0f9fb76eae45bc6dd0a65748c8d29463
4
- data.tar.gz: c6900248181afef6fdf36381d4e33365345073a5a00f73498ac3e89259838f14
3
+ metadata.gz: 9dccd9b1a1fa3f98ca96da312ccdf19371418815a556b1443acac322a8ae4e15
4
+ data.tar.gz: 61f75235d1dcf4e1e38cbab62869aeeff28de9d0dc27fad30c7ca11d6355cd0d
5
5
  SHA512:
6
- metadata.gz: 116978b76182751905d115a80ac0e350637564cad3c8d2bf8938ffcac99ff0e782bd810aa9889db3218ad147d38632c75fd85521682d22d7bb654de8cf1d0352
7
- data.tar.gz: 2048e308a2d88288f9ab373f1472acd9955aeb7870c2a8cbfc128d0b71ace183bc7011a20f2f20ce7c2e37e400b4c9c6a3b695fc6dc36144e583a7151fc4a5e1
6
+ metadata.gz: e47fce0ce8b6c4fe97ee0335b4f0544bc039755f12f0c2aac9f47e901739fd172390a3dc01f070d6799b1af335293b5d09a66514deb0810dfafc37f25d2f3c81
7
+ data.tar.gz: 9aa37800410a58a60ca751de44eed1026514b26c408a1f890b48a8f0049c814052a70a55343d3798c3d4d59ca9b157257c1fa83240200d283ba374a9b62db2ef
@@ -0,0 +1,26 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ build:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ ruby:
9
+ - 2.3
10
+ - 2.4
11
+ - 2.5
12
+ - 2.6
13
+ - 2.7
14
+ - '3.0' # Quoted, to avoid YAML float 3.0 interplated to "3"
15
+ - 3.1
16
+ - 3.2
17
+ steps:
18
+ - name: Check out repository code
19
+ uses: actions/checkout@v3
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ bundler-cache: true # Run "bundle install", and cache the result automatically.
25
+ - name: Run Rake
26
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -10,4 +10,5 @@ pkg/
10
10
  .rvmrc
11
11
  coverage
12
12
  *.gem
13
- .idea
13
+ .idea
14
+ .tool-versions
data/Changelog.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## 0.21.0
2
+
3
+ * [escape filename in the multipart/form-data Content-Disposition header](https://github.com/jnunemaker/httparty/commit/cdb45a678c43e44570b4e73f84b1abeb5ec22b8e)
4
+ * [Fix request marshaling](https://github.com/jnunemaker/httparty/pull/767)
5
+ * [Replace `mime-types` with `mini_mime`](https://github.com/jnunemaker/httparty/pull/769)
6
+
7
+ ## 0.20.0
8
+
9
+ Breaking changes
10
+
11
+ * Require Ruby >= 2.3.0
12
+
13
+ Fixes
14
+
15
+ * [`Marshal.dump` fails on response objects when request option `:logger` is set or `:parser` is a proc](https://github.com/jnunemaker/httparty/pull/714)
16
+ * [Switch `:pem` option to to `OpenSSL::PKey.read` to support other algorithms](https://github.com/jnunemaker/httparty/pull/720)
17
+
18
+ ## 0.19.1
19
+
20
+ * [Remove use of unary + method for creating non-frozen string to increase compatibility with older versions of ruby](https://github.com/jnunemaker/httparty/commit/4416141d37fd71bdba4f37589ec265f55aa446ce)
21
+
22
+ ## 0.19.0
23
+
24
+ * [Multipart/Form-Data: rewind files after read](https://github.com/jnunemaker/httparty/pull/709)
25
+ * [add frozen_string_literal pragma to all files](https://github.com/jnunemaker/httparty/pull/711)
26
+ * [Better handling of Accept-Encoding / Content-Encoding decompression (fixes #562)](https://github.com/jnunemaker/httparty/pull/729)
27
+
1
28
  ## 0.18.1
2
29
 
3
30
  * [Rename cop Lint/HandleExceptions to Lint/SuppressedException](https://github.com/jnunemaker/httparty/pull/699).
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec
3
3
 
4
4
  gem 'rake'
5
5
  gem 'mongrel', '1.2.0.pre2'
6
+ gem 'json'
6
7
 
7
8
  group :development do
8
9
  gem 'guard'
@@ -11,6 +12,7 @@ group :development do
11
12
  end
12
13
 
13
14
  group :test do
15
+ gem 'rexml'
14
16
  gem 'rspec', '~> 3.4'
15
17
  gem 'simplecov', require: false
16
18
  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,6 +1,6 @@
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
5
  Makes http fun again! Ain't no party like a httparty, because a httparty don't stop.
6
6
 
@@ -12,7 +12,7 @@ gem install httparty
12
12
 
13
13
  ## Requirements
14
14
 
15
- * Ruby 2.0.0 or higher
15
+ * Ruby 2.3.0 or higher
16
16
  * multi_xml
17
17
  * You like to party!
18
18
 
@@ -48,7 +48,6 @@ puts stack_exchange.users
48
48
  ```
49
49
 
50
50
  See the [examples directory](http://github.com/jnunemaker/httparty/tree/master/examples) for even more goodies.
51
-
52
51
  ## Command Line Interface
53
52
 
54
53
  httparty also includes the executable `httparty` which can be
@@ -65,9 +64,8 @@ httparty "https://api.stackexchange.com/2.2/questions?site=stackoverflow"
65
64
  ## Help and Docs
66
65
 
67
66
  * [Docs](https://github.com/jnunemaker/httparty/tree/master/docs)
68
- * https://groups.google.com/forum/#!forum/httparty-gem
67
+ * https://github.com/jnunemaker/httparty/discussions
69
68
  * https://www.rubydoc.info/github/jnunemaker/httparty
70
- * http://stackoverflow.com/questions/tagged/httparty
71
69
 
72
70
  ## Contributing
73
71
 
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.
@@ -70,7 +84,7 @@ class Client
70
84
  end
71
85
  ```
72
86
 
73
- You can also include this options with the call:
87
+ You can also include all of these options with the call:
74
88
 
75
89
  ```ruby
76
90
  class Client
@@ -86,7 +100,7 @@ end
86
100
 
87
101
  ### Avoid SSL verification
88
102
 
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:
103
+ 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
104
 
91
105
  ```ruby
92
106
  # Skips SSL certificate verification
@@ -104,3 +118,74 @@ class Client
104
118
  end
105
119
  end
106
120
  ```
121
+
122
+ ### HTTP Compression
123
+
124
+ The `Accept-Encoding` request header and `Content-Encoding` response header
125
+ are used to control compression (gzip, etc.) over the wire. Refer to
126
+ [RFC-2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) for details.
127
+ (For clarity: these headers are **not** used for character encoding i.e. `utf-8`
128
+ which is specified in the `Accept` and `Content-Type` headers.)
129
+
130
+ Unless you have specific requirements otherwise, we recommend to **not** set
131
+ set the `Accept-Encoding` header on HTTParty requests. In this case, `Net::HTTP`
132
+ will set a sensible default compression scheme and automatically decompress the response.
133
+
134
+ If you explicitly set `Accept-Encoding`, there be dragons:
135
+
136
+ * If the HTTP response `Content-Encoding` received on the wire is `gzip` or `deflate`,
137
+ `Net::HTTP` will automatically decompress it, and will omit `Content-Encoding`
138
+ from your `HTTParty::Response` headers.
139
+
140
+ * For the following encodings, HTTParty will automatically decompress them if you include
141
+ the required gem into your project. Similar to above, if decompression succeeds,
142
+ `Content-Encoding` will be omitted from your `HTTParty::Response` headers.
143
+ **Warning:** Support for these encodings is experimental and not fully battle-tested.
144
+
145
+ | Content-Encoding | Required Gem |
146
+ | --- | --- |
147
+ | `br` (Brotli) | [brotli](https://rubygems.org/gems/brotli) |
148
+ | `compress` (LZW) | [ruby-lzws](https://rubygems.org/gems/ruby-lzws) |
149
+ | `zstd` (Zstandard) | [zstd-ruby](https://rubygems.org/gems/zstd-ruby) |
150
+
151
+ * For other encodings, `HTTParty::Response#body` will return the raw uncompressed byte string,
152
+ and you'll need to inspect the `Content-Encoding` response header and decompress it yourself.
153
+ In this case, `HTTParty::Response#parsed_response` will be `nil`.
154
+
155
+ * Lastly, you may use the `skip_decompression` option to disable all automatic decompression
156
+ and always get `HTTParty::Response#body` in its raw form along with the `Content-Encoding` header.
157
+
158
+ ```ruby
159
+ # Accept-Encoding=gzip,deflate can be safely assumed to be auto-decompressed
160
+
161
+ res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'gzip,deflate,identity' })
162
+ JSON.parse(res.body) # safe
163
+
164
+
165
+ # Accept-Encoding=br,compress requires third-party gems
166
+
167
+ require 'brotli'
168
+ require 'lzws'
169
+ require 'zstd-ruby'
170
+ res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'br,compress,zstd' })
171
+ JSON.parse(res.body)
172
+
173
+
174
+ # Accept-Encoding=* may return unhandled Content-Encoding
175
+
176
+ res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => '*' })
177
+ encoding = res.headers['Content-Encoding']
178
+ if encoding
179
+ JSON.parse(your_decompression_handling(res.body, encoding))
180
+ else
181
+ # Content-Encoding not present implies decompressed
182
+ JSON.parse(res.body)
183
+ end
184
+
185
+
186
+ # Gimme the raw data!
187
+
188
+ res = HTTParty.get('https://example.com/test.json', skip_decompression: true)
189
+ encoding = res.headers['Content-Encoding']
190
+ JSON.parse(your_decompression_handling(res.body, encoding))
191
+ ```
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")
data/httparty.gemspec CHANGED
@@ -13,10 +13,10 @@ Gem::Specification.new do |s|
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
15
 
16
- s.required_ruby_version = '>= 2.0.0'
16
+ s.required_ruby_version = '>= 2.3.0'
17
17
 
18
18
  s.add_dependency 'multi_xml', ">= 0.5.2"
19
- s.add_dependency('mime-types', "~> 3.0")
19
+ s.add_dependency 'mini_mime', ">= 1.0.0"
20
20
 
21
21
  # If this line is removed, all hard partying will cease.
22
22
  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
  #
@@ -221,7 +223,7 @@ module HTTParty
221
223
  # Note: options[:pem] must contain the content of a PEM file having the private key appended
222
224
  if options[:pem]
223
225
  http.cert = OpenSSL::X509::Certificate.new(options[:pem])
224
- http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
226
+ http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password])
225
227
  http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
226
228
  end
227
229
 
@@ -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,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
- # @abstact Exceptions raised by HTTParty inherit from Error
4
+ # @abstract Exceptions raised by HTTParty inherit from Error
3
5
  class Error < StandardError; end
4
6
 
5
7
  # Exception raised when you attempt to set a non-existent format
@@ -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
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module Logger
3
5
  class CurlFormatter #:nodoc:
4
6
  TAG_NAME = HTTParty.name
5
- OUT = '>'.freeze
6
- IN = '<'.freeze
7
+ OUT = '>'
8
+ IN = '<'
7
9
 
8
10
  attr_accessor :level, :logger
9
11
 
@@ -44,7 +46,7 @@ module HTTParty
44
46
  end
45
47
 
46
48
  def log_url
47
- http_method = request.http_method.name.split("::").last.upcase
49
+ http_method = request.http_method.name.split('::').last.upcase
48
50
  uri = if request.options[:base_uri]
49
51
  request.options[:base_uri] + request.path.path
50
52
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'httparty/logger/apache_formatter'
2
4
  require 'httparty/logger/curl_formatter'
3
5
  require 'httparty/logger/logstash_formatter'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module Logger
3
5
  class LogstashFormatter #:nodoc:
@@ -40,11 +42,11 @@ module HTTParty
40
42
  end
41
43
 
42
44
  def current_time
43
- Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
45
+ Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
44
46
  end
45
47
 
46
48
  def http_method
47
- @http_method ||= request.http_method.name.split("::").last.upcase
49
+ @http_method ||= request.http_method.name.split('::').last.upcase
48
50
  end
49
51
 
50
52
  def path
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module ModuleInheritableAttributes #:nodoc:
3
5
  def self.included(base)
@@ -36,7 +38,7 @@ module HTTParty
36
38
  def inherited(subclass)
37
39
  super
38
40
  @mattr_inheritable_attrs.each do |inheritable_attribute|
39
- ivar = "@#{inheritable_attribute}"
41
+ ivar = :"@#{inheritable_attribute}"
40
42
  subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
41
43
 
42
44
  if instance_variable_get(ivar).respond_to?(:merge)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'digest/md5'
2
4
  require 'net/http'
3
5
 
@@ -44,12 +46,9 @@ module Net
44
46
  header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
45
47
 
46
48
  if qop_present?
47
- fields = [
48
- %(cnonce="#{@cnonce}"),
49
- %(qop="#{@response['qop']}"),
50
- "nc=00000001"
51
- ]
52
- fields.each { |field| header << field }
49
+ header << %(cnonce="#{@cnonce}")
50
+ header << %(qop="#{@response['qop']}")
51
+ header << 'nc=00000001'
53
52
  end
54
53
 
55
54
  header << %(opaque="#{@response['opaque']}") if opaque_present?
@@ -98,13 +97,13 @@ module Net
98
97
  end
99
98
 
100
99
  def random
101
- format "%x", (Time.now.to_i + rand(65535))
100
+ format '%x', (Time.now.to_i + rand(65535))
102
101
  end
103
102
 
104
103
  def request_digest
105
104
  a = [md5(a1), @response['nonce'], md5(a2)]
106
- a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
107
- md5(a.join(":"))
105
+ a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
106
+ md5(a.join(':'))
108
107
  end
109
108
 
110
109
  def md5(str)
@@ -129,7 +128,7 @@ module Net
129
128
  end
130
129
 
131
130
  def a2
132
- [@method, @path].join(":")
131
+ [@method, @path].join(':')
133
132
  end
134
133
  end
135
134
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  # The default parser used by HTTParty, supports xml, json, html, csv and
3
5
  # plain text.
@@ -101,7 +103,7 @@ module HTTParty
101
103
  # @return [nil] when the response body is nil, an empty string, spaces only or "null"
102
104
  def parse
103
105
  return nil if body.nil?
104
- return nil if body == "null"
106
+ return nil if body == 'null'
105
107
  return nil if body.valid_encoding? && body.strip.empty?
106
108
  if body.valid_encoding? && body.encoding == Encoding::UTF_8
107
109
  @body = body.gsub(/\A#{UTF8_BOM}/, '')
@@ -119,7 +121,7 @@ module HTTParty
119
121
  MultiXml.parse(body)
120
122
  end
121
123
 
122
- UTF8_BOM = "\xEF\xBB\xBF".freeze
124
+ UTF8_BOM = "\xEF\xBB\xBF"
123
125
 
124
126
  def json
125
127
  JSON.parse(body, :quirks_mode => true, :allow_nan => true)
@@ -142,9 +144,11 @@ module HTTParty
142
144
  end
143
145
 
144
146
  def parse_supported_format
145
- send(format)
146
- rescue NoMethodError => e
147
- raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
147
+ if respond_to?(format, true)
148
+ send(format)
149
+ else
150
+ raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
151
+ end
148
152
  end
149
153
  end
150
154
  end
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'multipart_boundary'
2
4
 
3
5
  module HTTParty
4
6
  class Request
5
7
  class Body
8
+ NEWLINE = "\r\n"
9
+ private_constant :NEWLINE
10
+
6
11
  def initialize(params, query_string_normalizer: nil, force_multipart: false)
7
12
  @params = params
8
13
  @query_string_normalizer = query_string_normalizer
@@ -27,23 +32,30 @@ module HTTParty
27
32
 
28
33
  private
29
34
 
35
+ # https://html.spec.whatwg.org/#multipart-form-data
36
+ MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
37
+ '"' => '%22',
38
+ "\r" => '%0D',
39
+ "\n" => '%0A'
40
+ }.freeze
41
+
30
42
  def generate_multipart
31
43
  normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
32
44
 
33
- multipart = normalized_params.inject('') do |memo, (key, value)|
34
- memo += "--#{boundary}\r\n"
35
- memo += %(Content-Disposition: form-data; name="#{key}")
45
+ multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
46
+ memo << "--#{boundary}#{NEWLINE}"
47
+ memo << %(Content-Disposition: form-data; name="#{key}")
36
48
  # value.path is used to support ActionDispatch::Http::UploadedFile
37
49
  # https://github.com/jnunemaker/httparty/pull/585
38
- memo += %(; filename="#{file_name(value)}") if file?(value)
39
- memo += "\r\n"
40
- memo += "Content-Type: #{content_type(value)}\r\n" if file?(value)
41
- memo += "\r\n"
42
- memo += file?(value) ? value.read : value.to_s
43
- memo += "\r\n"
50
+ memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value)
51
+ memo << NEWLINE
52
+ memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
53
+ memo << NEWLINE
54
+ memo << content_body(value)
55
+ memo << NEWLINE
44
56
  end
45
57
 
46
- multipart += "--#{boundary}--\r\n"
58
+ multipart << "--#{boundary}--#{NEWLINE}"
47
59
  end
48
60
 
49
61
  def has_file?(value)
@@ -68,10 +80,19 @@ module HTTParty
68
80
  end
69
81
  end
70
82
 
83
+ def content_body(object)
84
+ if file?(object)
85
+ object = (file = object).read
86
+ file.rewind if file.respond_to?(:rewind)
87
+ end
88
+
89
+ object.to_s
90
+ end
91
+
71
92
  def content_type(object)
72
93
  return object.content_type if object.respond_to?(:content_type)
73
- mime = MIME::Types.type_for(object.path)
74
- mime.empty? ? 'application/octet-stream' : mime[0].content_type
94
+ mime = MiniMime.lookup_by_filename(object.path)
95
+ mime ? mime.content_type : 'application/octet-stream'
75
96
  end
76
97
 
77
98
  def file_name(object)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
2
4
 
3
5
  module HTTParty
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module HTTParty
@@ -44,6 +46,15 @@ module HTTParty
44
46
  end.flatten.join('&')
45
47
  end
46
48
 
49
+ def self._load(data)
50
+ http_method, path, options, last_response, last_uri, raw_request = Marshal.load(data)
51
+ instance = new(http_method, path, options)
52
+ instance.last_response = last_response
53
+ instance.last_uri = last_uri
54
+ instance.instance_variable_set("@raw_request", raw_request)
55
+ instance
56
+ end
57
+
47
58
  attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
48
59
  attr_reader :path
49
60
 
@@ -87,11 +98,11 @@ module HTTParty
87
98
  end
88
99
 
89
100
  def uri
90
- if redirect && path.relative? && path.path[0] != "/"
91
- last_uri_host = @last_uri.path.gsub(/[^\/]+$/, "")
101
+ if redirect && path.relative? && path.path[0] != '/'
102
+ last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
92
103
 
93
- path.path = "/#{path.path}" if last_uri_host[-1] != "/"
94
- path.path = last_uri_host + path.path
104
+ path.path = "/#{path.path}" if last_uri_host[-1] != '/'
105
+ path.path = "#{last_uri_host}#{path.path}"
95
106
  end
96
107
 
97
108
  if path.relative? && path.host
@@ -117,7 +128,7 @@ module HTTParty
117
128
  def base_uri
118
129
  if redirect
119
130
  base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
120
- base_uri += ":#{@last_uri.port}" if @last_uri.port != 80
131
+ base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
121
132
  base_uri
122
133
  else
123
134
  options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
@@ -173,6 +184,13 @@ module HTTParty
173
184
  @raw_request.body
174
185
  end
175
186
 
187
+ def _dump(_level)
188
+ opts = options.dup
189
+ opts.delete(:logger)
190
+ opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc)
191
+ Marshal.dump([http_method, path, opts, last_response, @last_uri, @raw_request])
192
+ end
193
+
176
194
  private
177
195
 
178
196
  def http
@@ -227,6 +245,8 @@ module HTTParty
227
245
  @raw_request.body = body.call
228
246
  end
229
247
 
248
+ @raw_request.instance_variable_set(:@decode_content, decompress_content?)
249
+
230
250
  if options[:basic_auth] && send_authorization_header?
231
251
  @raw_request.basic_auth(username, password)
232
252
  @credentials_sent = true
@@ -238,6 +258,10 @@ module HTTParty
238
258
  !!options[:digest_auth]
239
259
  end
240
260
 
261
+ def decompress_content?
262
+ !options[:skip_decompression]
263
+ end
264
+
241
265
  def response_unauthorized?
242
266
  !!last_response && last_response.code == '401'
243
267
  end
@@ -261,7 +285,7 @@ module HTTParty
261
285
  query_string_parts << options[:query] unless options[:query].nil?
262
286
  end
263
287
 
264
- query_string_parts.reject!(&:empty?) unless query_string_parts == [""]
288
+ query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
265
289
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
266
290
  end
267
291
 
@@ -269,7 +293,7 @@ module HTTParty
269
293
  options[:assume_utf16_is_big_endian]
270
294
  end
271
295
 
272
- def handle_response(body, &block)
296
+ def handle_response(raw_body, &block)
273
297
  if response_redirects?
274
298
  options[:limit] -= 1
275
299
  if options[:logger]
@@ -290,9 +314,20 @@ module HTTParty
290
314
  capture_cookies(last_response)
291
315
  perform(&block)
292
316
  else
293
- body ||= last_response.body
294
- body = body.nil? ? body : encode_text(body, last_response['content-type'])
295
- Response.new(self, last_response, lambda { parse_response(body) }, body: body)
317
+ raw_body ||= last_response.body
318
+
319
+ body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
320
+
321
+ unless body.nil?
322
+ body = encode_text(body, last_response['content-type'])
323
+
324
+ if decompress_content?
325
+ last_response.delete('content-encoding')
326
+ raw_body = body
327
+ end
328
+ end
329
+
330
+ Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
296
331
  end
297
332
  end
298
333
 
@@ -368,6 +403,10 @@ module HTTParty
368
403
  end
369
404
  end
370
405
 
406
+ def decompress(body, encoding)
407
+ Decompressor.new(body, encoding).decompress
408
+ end
409
+
371
410
  def encode_text(text, content_type)
372
411
  TextEncoder.new(
373
412
  text,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  class Response < Object
3
5
  def self.underscore(string)
@@ -49,14 +51,14 @@ module HTTParty
49
51
  end
50
52
 
51
53
  def inspect
52
- inspect_id = ::Kernel::format "%x", (object_id * 2)
54
+ inspect_id = ::Kernel::format '%x', (object_id * 2)
53
55
  %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
54
56
  end
55
57
 
56
58
  CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
57
59
 
58
60
  CODES_TO_OBJ.each do |response_code, klass|
59
- name = klass.name.sub("Net::HTTP", '')
61
+ name = klass.name.sub('Net::HTTP', '')
60
62
  name = "#{underscore(name)}?".to_sym
61
63
 
62
64
  define_method(name) do
@@ -65,12 +67,12 @@ module HTTParty
65
67
  end
66
68
 
67
69
  # Support old multiple_choice? method from pre 2.0.0 era.
68
- if ::RUBY_VERSION >= "2.0.0" && ::RUBY_PLATFORM != "java"
70
+ if ::RUBY_VERSION >= '2.0.0' && ::RUBY_PLATFORM != 'java'
69
71
  alias_method :multiple_choice?, :multiple_choices?
70
72
  end
71
73
 
72
74
  # Support old status codes method from pre 2.6.0 era.
73
- if ::RUBY_VERSION >= "2.6.0" && ::RUBY_PLATFORM != "java"
75
+ if ::RUBY_VERSION >= '2.6.0' && ::RUBY_PLATFORM != 'java'
74
76
  alias_method :gateway_time_out?, :gateway_timeout?
75
77
  alias_method :request_entity_too_large?, :payload_too_large?
76
78
  alias_method :request_time_out?, :request_timeout?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  module HTTParty
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  class TextEncoder
3
5
  attr_reader :text, :content_type, :assume_utf16_is_big_endian
4
6
 
5
7
  def initialize(text, assume_utf16_is_big_endian: true, content_type: nil)
6
- @text = text.dup
8
+ @text = +text
7
9
  @content_type = content_type
8
10
  @assume_utf16_is_big_endian = assume_utf16_is_big_endian
9
11
  end
@@ -33,16 +35,16 @@ module HTTParty
33
35
  def encode_utf_16
34
36
  if text.bytesize >= 2
35
37
  if text.getbyte(0) == 0xFF && text.getbyte(1) == 0xFE
36
- return text.force_encoding("UTF-16LE")
38
+ return text.force_encoding('UTF-16LE')
37
39
  elsif text.getbyte(0) == 0xFE && text.getbyte(1) == 0xFF
38
- return text.force_encoding("UTF-16BE")
40
+ return text.force_encoding('UTF-16BE')
39
41
  end
40
42
  end
41
43
 
42
44
  if assume_utf16_is_big_endian # option
43
- text.force_encoding("UTF-16BE")
45
+ text.force_encoding('UTF-16BE')
44
46
  else
45
- text.force_encoding("UTF-16LE")
47
+ text.force_encoding('UTF-16LE')
46
48
  end
47
49
  end
48
50
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module Utils
3
5
  def self.stringify_keys(hash)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
- VERSION = "0.18.1"
4
+ VERSION = '0.21.0'
3
5
  end
data/lib/httparty.rb CHANGED
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'net/http'
3
- require 'net/https'
4
5
  require 'uri'
5
6
  require 'zlib'
6
7
  require 'multi_xml'
7
- require 'mime/types'
8
+ require 'mini_mime'
8
9
  require 'json'
9
10
  require 'csv'
10
11
 
@@ -16,6 +17,7 @@ require 'httparty/connection_adapter'
16
17
  require 'httparty/logger/logger'
17
18
  require 'httparty/request/body'
18
19
  require 'httparty/response_fragment'
20
+ require 'httparty/decompressor'
19
21
  require 'httparty/text_encoder'
20
22
  require 'httparty/headers_processor'
21
23
 
@@ -26,8 +28,8 @@ module HTTParty
26
28
  base.send :include, ModuleInheritableAttributes
27
29
  base.send(:mattr_inheritable, :default_options)
28
30
  base.send(:mattr_inheritable, :default_cookies)
29
- base.instance_variable_set("@default_options", {})
30
- base.instance_variable_set("@default_cookies", CookieHash.new)
31
+ base.instance_variable_set(:@default_options, {})
32
+ base.instance_variable_set(:@default_cookies, CookieHash.new)
31
33
  end
32
34
 
33
35
  # == Common Request Options
@@ -399,6 +401,22 @@ module HTTParty
399
401
  default_options[:ssl_version] = version
400
402
  end
401
403
 
404
+ # Deactivate automatic decompression of the response body.
405
+ # This will require you to explicitly handle body decompression
406
+ # by inspecting the Content-Encoding response header.
407
+ #
408
+ # Refer to docs/README.md "HTTP Compression" section for
409
+ # further details.
410
+ #
411
+ # @example
412
+ # class Foo
413
+ # include HTTParty
414
+ # skip_decompression
415
+ # end
416
+ def skip_decompression(value = true)
417
+ default_options[:skip_decompression] = !!value
418
+ end
419
+
402
420
  # Allows setting of SSL ciphers to use. This only works in Ruby 1.9+.
403
421
  # You can get a list of valid specific ciphers from OpenSSL::Cipher.ciphers.
404
422
  # You also can specify a cipher suite here, listed here at openssl.org:
@@ -597,7 +615,7 @@ module HTTParty
597
615
  def process_cookies(options) #:nodoc:
598
616
  return unless options[:cookies] || default_cookies.any?
599
617
  options[:headers] ||= headers.dup
600
- options[:headers]["cookie"] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string
618
+ options[:headers]['cookie'] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string
601
619
  end
602
620
 
603
621
  def validate_format
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httparty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.1
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  - Sandro Turriate
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-06-10 00:00:00.000000000 Z
12
+ date: 2022-12-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_xml
@@ -26,19 +26,19 @@ dependencies:
26
26
  - !ruby/object:Gem::Version
27
27
  version: 0.5.2
28
28
  - !ruby/object:Gem::Dependency
29
- name: mime-types
29
+ name: mini_mime
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '3.0'
34
+ version: 1.0.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - "~>"
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '3.0'
41
+ version: 1.0.0
42
42
  description: Makes http fun! Also, makes consuming restful web services dead easy.
43
43
  email:
44
44
  - nunemaker@gmail.com
@@ -48,11 +48,10 @@ extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
50
  - ".editorconfig"
51
+ - ".github/workflows/ci.yml"
51
52
  - ".gitignore"
52
53
  - ".rubocop.yml"
53
54
  - ".rubocop_todo.yml"
54
- - ".simplecov"
55
- - ".travis.yml"
56
55
  - CONTRIBUTING.md
57
56
  - Changelog.md
58
57
  - Gemfile
@@ -72,6 +71,7 @@ files:
72
71
  - examples/delicious.rb
73
72
  - examples/google.rb
74
73
  - examples/headers_and_user_agents.rb
74
+ - examples/idn.rb
75
75
  - examples/logging.rb
76
76
  - examples/microsoft_graph.rb
77
77
  - examples/multipart.rb
@@ -88,6 +88,7 @@ files:
88
88
  - lib/httparty.rb
89
89
  - lib/httparty/connection_adapter.rb
90
90
  - lib/httparty/cookie_hash.rb
91
+ - lib/httparty/decompressor.rb
91
92
  - lib/httparty/exceptions.rb
92
93
  - lib/httparty/hash_conversions.rb
93
94
  - lib/httparty/headers_processor.rb
@@ -122,15 +123,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
123
  requirements:
123
124
  - - ">="
124
125
  - !ruby/object:Gem::Version
125
- version: 2.0.0
126
+ version: 2.3.0
126
127
  required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  requirements:
128
129
  - - ">="
129
130
  - !ruby/object:Gem::Version
130
131
  version: '0'
131
132
  requirements: []
132
- rubygems_version: 3.1.2
133
- signing_key:
133
+ rubygems_version: 3.3.7
134
+ signing_key:
134
135
  specification_version: 4
135
136
  summary: Makes http fun! Also, makes consuming restful web services dead easy.
136
137
  test_files: []
data/.simplecov DELETED
@@ -1 +0,0 @@
1
- SimpleCov.start "test_frameworks"
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0.0
4
- - 2.1.10
5
- - 2.2.10
6
- - 2.3.8
7
- - 2.4.5
8
- - 2.5.3
9
- - 2.6.1
10
- bundler_args: --without development
11
- before_install: gem install bundler -v '< 2'