httparty 0.13.7 → 0.24.2

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 (108) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.github/workflows/ci.yml +24 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop_todo.yml +1 -1
  7. data/Changelog.md +624 -0
  8. data/Gemfile +11 -3
  9. data/Guardfile +3 -2
  10. data/README.md +18 -17
  11. data/bin/httparty +3 -1
  12. data/docs/README.md +223 -0
  13. data/examples/README.md +35 -12
  14. data/examples/aaws.rb +7 -3
  15. data/examples/body_stream.rb +14 -0
  16. data/examples/crack.rb +1 -1
  17. data/examples/custom_parsers.rb +5 -1
  18. data/examples/delicious.rb +4 -4
  19. data/examples/headers_and_user_agents.rb +7 -3
  20. data/examples/idn.rb +10 -0
  21. data/examples/logging.rb +4 -4
  22. data/examples/microsoft_graph.rb +52 -0
  23. data/examples/multipart.rb +35 -0
  24. data/examples/party_foul_mode.rb +90 -0
  25. data/examples/peer_cert.rb +9 -0
  26. data/examples/stackexchange.rb +1 -1
  27. data/examples/stream_download.rb +26 -0
  28. data/examples/tripit_sign_in.rb +17 -6
  29. data/examples/twitter.rb +2 -2
  30. data/examples/whoismyrep.rb +1 -1
  31. data/httparty.gemspec +9 -5
  32. data/lib/httparty/connection_adapter.rb +71 -24
  33. data/lib/httparty/cookie_hash.rb +10 -8
  34. data/lib/httparty/decompressor.rb +102 -0
  35. data/lib/httparty/exceptions.rb +42 -5
  36. data/lib/httparty/hash_conversions.rb +30 -8
  37. data/lib/httparty/headers_processor.rb +32 -0
  38. data/lib/httparty/logger/apache_formatter.rb +31 -6
  39. data/lib/httparty/logger/curl_formatter.rb +68 -23
  40. data/lib/httparty/logger/logger.rb +5 -1
  41. data/lib/httparty/logger/logstash_formatter.rb +62 -0
  42. data/lib/httparty/module_inheritable_attributes.rb +9 -9
  43. data/lib/httparty/net_digest_auth.rb +23 -21
  44. data/lib/httparty/parser.rb +28 -14
  45. data/lib/httparty/request/body.rb +125 -0
  46. data/lib/httparty/request/multipart_boundary.rb +13 -0
  47. data/lib/httparty/request/streaming_multipart_body.rb +190 -0
  48. data/lib/httparty/request.rb +224 -122
  49. data/lib/httparty/response/headers.rb +23 -19
  50. data/lib/httparty/response.rb +92 -13
  51. data/lib/httparty/response_fragment.rb +21 -0
  52. data/lib/httparty/text_encoder.rb +72 -0
  53. data/lib/httparty/utils.rb +13 -0
  54. data/lib/httparty/version.rb +3 -1
  55. data/lib/httparty.rb +118 -42
  56. data/script/release +4 -4
  57. data/website/css/common.css +1 -1
  58. metadata +50 -112
  59. data/.simplecov +0 -1
  60. data/.travis.yml +0 -7
  61. data/History +0 -390
  62. data/features/basic_authentication.feature +0 -20
  63. data/features/command_line.feature +0 -95
  64. data/features/deals_with_http_error_codes.feature +0 -26
  65. data/features/digest_authentication.feature +0 -30
  66. data/features/handles_compressed_responses.feature +0 -27
  67. data/features/handles_multiple_formats.feature +0 -57
  68. data/features/steps/env.rb +0 -27
  69. data/features/steps/httparty_response_steps.rb +0 -52
  70. data/features/steps/httparty_steps.rb +0 -43
  71. data/features/steps/mongrel_helper.rb +0 -127
  72. data/features/steps/remote_service_steps.rb +0 -90
  73. data/features/supports_read_timeout_option.feature +0 -13
  74. data/features/supports_redirection.feature +0 -22
  75. data/features/supports_timeout_option.feature +0 -13
  76. data/spec/fixtures/delicious.xml +0 -23
  77. data/spec/fixtures/empty.xml +0 -0
  78. data/spec/fixtures/google.html +0 -3
  79. data/spec/fixtures/ssl/generate.sh +0 -29
  80. data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
  81. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  82. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  83. data/spec/fixtures/ssl/generated/ca.key +0 -15
  84. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  85. data/spec/fixtures/ssl/generated/server.crt +0 -13
  86. data/spec/fixtures/ssl/generated/server.key +0 -15
  87. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  88. data/spec/fixtures/twitter.csv +0 -2
  89. data/spec/fixtures/twitter.json +0 -1
  90. data/spec/fixtures/twitter.xml +0 -403
  91. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  92. data/spec/httparty/connection_adapter_spec.rb +0 -468
  93. data/spec/httparty/cookie_hash_spec.rb +0 -83
  94. data/spec/httparty/exception_spec.rb +0 -38
  95. data/spec/httparty/hash_conversions_spec.rb +0 -41
  96. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  97. data/spec/httparty/logger/curl_formatter_spec.rb +0 -18
  98. data/spec/httparty/logger/logger_spec.rb +0 -38
  99. data/spec/httparty/net_digest_auth_spec.rb +0 -230
  100. data/spec/httparty/parser_spec.rb +0 -173
  101. data/spec/httparty/request_spec.rb +0 -1073
  102. data/spec/httparty/response_spec.rb +0 -241
  103. data/spec/httparty/ssl_spec.rb +0 -74
  104. data/spec/httparty_spec.rb +0 -850
  105. data/spec/spec_helper.rb +0 -59
  106. data/spec/support/ssl_test_helper.rb +0 -47
  107. data/spec/support/ssl_test_server.rb +0 -80
  108. data/spec/support/stub_response.rb +0 -49
@@ -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
@@ -0,0 +1,9 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+
4
+ peer_cert = nil
5
+ HTTParty.get("https://www.example.com") do |fragment|
6
+ peer_cert ||= fragment.connection.peer_cert
7
+ end
8
+
9
+ puts "The server's certificate expires #{peer_cert.not_after}"
@@ -7,7 +7,7 @@ class StackExchange
7
7
  base_uri 'api.stackexchange.com'
8
8
 
9
9
  def initialize(service, page)
10
- @options = { query: {site: service, page: page} }
10
+ @options = { query: { site: service, page: page } }
11
11
  end
12
12
 
13
13
  def questions
@@ -0,0 +1,26 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+
5
+ # download file linux-4.6.4.tar.xz without using the memory
6
+ response = nil
7
+ filename = "linux-4.6.4.tar.xz"
8
+ url = "https://cdn.kernel.org/pub/linux/kernel/v4.x/#{filename}"
9
+
10
+ File.open(filename, "w") do |file|
11
+ response = HTTParty.get(url, stream_body: true) do |fragment|
12
+ if [301, 302].include?(fragment.code)
13
+ print "skip writing for redirect"
14
+ elsif fragment.code == 200
15
+ print "."
16
+ file.write(fragment)
17
+ else
18
+ raise StandardError, "Non-success status code while streaming #{fragment.code}"
19
+ end
20
+ end
21
+ end
22
+ puts
23
+
24
+ pp "Success: #{response.success?}"
25
+ pp File.stat(filename).inspect
26
+ File.unlink(filename)
@@ -3,30 +3,41 @@ require File.join(dir, 'httparty')
3
3
 
4
4
  class TripIt
5
5
  include HTTParty
6
- base_uri 'http://www.tripit.com'
6
+ base_uri 'https://www.tripit.com'
7
7
  debug_output
8
8
 
9
9
  def initialize(email, password)
10
10
  @email = email
11
- response = self.class.get('/account/login')
12
- response = self.class.post(
11
+ get_response = self.class.get('/account/login')
12
+ get_response_cookie = parse_cookie(get_response.headers['Set-Cookie'])
13
+
14
+ post_response = self.class.post(
13
15
  '/account/login',
14
16
  body: {
15
17
  login_email_address: email,
16
18
  login_password: password
17
19
  },
18
- headers: {'Cookie' => response.headers['Set-Cookie']}
20
+ headers: {'Cookie' => get_response_cookie.to_cookie_string }
19
21
  )
20
- @cookie = response.request.options[:headers]['Cookie']
22
+
23
+ @cookie = parse_cookie(post_response.headers['Set-Cookie'])
21
24
  end
22
25
 
23
26
  def account_settings
24
- self.class.get('/account/edit', headers: {'Cookie' => @cookie})
27
+ self.class.get('/account/edit', headers: { 'Cookie' => @cookie.to_cookie_string })
25
28
  end
26
29
 
27
30
  def logged_in?
28
31
  account_settings.include? "You're logged in as #{@email}"
29
32
  end
33
+
34
+ private
35
+
36
+ def parse_cookie(resp)
37
+ cookie_hash = CookieHash.new
38
+ resp.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
39
+ cookie_hash
40
+ end
30
41
  end
31
42
 
32
43
  tripit = TripIt.new('email', 'password')
data/examples/twitter.rb CHANGED
@@ -14,12 +14,12 @@ class Twitter
14
14
  # which can be :friends, :user or :public
15
15
  # options[:query] can be things like since, since_id, count, etc.
16
16
  def timeline(which = :friends, options = {})
17
- options.merge!({basic_auth: @auth})
17
+ options.merge!({ basic_auth: @auth })
18
18
  self.class.get("/statuses/#{which}_timeline.json", options)
19
19
  end
20
20
 
21
21
  def post(text)
22
- options = { query: {status: text}, basic_auth: @auth }
22
+ options = { query: { status: text }, basic_auth: @auth }
23
23
  self.class.post('/statuses/update.json', options)
24
24
  end
25
25
  end
@@ -7,4 +7,4 @@ class Rep
7
7
  end
8
8
 
9
9
  pp Rep.get('http://whoismyrepresentative.com/getall_mems.php?zip=46544')
10
- pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: {zip: 46544})
10
+ pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: { zip: 46544 })
data/httparty.gemspec CHANGED
@@ -9,20 +9,24 @@ Gem::Specification.new do |s|
9
9
  s.licenses = ['MIT']
10
10
  s.authors = ["John Nunemaker", "Sandro Turriate"]
11
11
  s.email = ["nunemaker@gmail.com"]
12
- s.homepage = "http://jnunemaker.github.com/httparty"
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 = '>= 1.9.3'
17
+ s.required_ruby_version = '>= 2.7.0'
17
18
 
18
- s.add_dependency 'json', "~> 1.8"
19
+ s.add_dependency 'csv'
19
20
  s.add_dependency 'multi_xml', ">= 0.5.2"
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!"
23
25
 
24
- s.files = `git ls-files`.split("\n")
25
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ all_files = `git ls-files`.split("\n")
27
+ test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+
29
+ s.files = all_files - test_files
26
30
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
27
31
  s.require_paths = ["lib"]
28
32
  end
@@ -1,10 +1,12 @@
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
  #
4
6
  # == Custom Connection Factories
5
7
  #
6
8
  # If you like to implement your own connection adapter, subclassing
7
- # HTTPParty::ConnectionAdapter will make it easier. Just override
9
+ # HTTParty::ConnectionAdapter will make it easier. Just override
8
10
  # the #connection method. The uri and options attributes will have
9
11
  # all the info you need to construct your http connection. Whatever
10
12
  # you return from your connection method needs to adhere to the
@@ -38,26 +40,52 @@ module HTTParty
38
40
  # in the #options attribute. It is up to you to interpret them within your
39
41
  # connection adapter. Take a look at the implementation of
40
42
  # HTTParty::ConnectionAdapter#connection for examples of how they are used.
41
- # Some things that are probably interesting are as follows:
43
+ # The keys used in options are
42
44
  # * :+timeout+: timeout in seconds
43
45
  # * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
44
46
  # * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
47
+ # * :+write_timeout+: http connection write_timeout in seconds, overrides timeout if set (Ruby >= 2.6.0 required)
45
48
  # * :+debug_output+: see HTTParty::ClassMethods.debug_output.
46
- # * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
49
+ # * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
50
+ # * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates'
51
+ # * :+p12+: contains PKCS12 client client certificate data. see method 'attach_ssl_certificates'
47
52
  # * :+verify+: verify the server’s certificate against the ca certificate.
48
53
  # * :+verify_peer+: set to false to turn off server verification but still send client certificate
49
54
  # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
50
55
  # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
56
+ # * :+ssl_version+: SSL versions to allow. see method 'attach_ssl_certificates'
57
+ # * :+ciphers+: The list of SSL ciphers to support
51
58
  # * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter
59
+ # * :+local_host+: The local address to bind to
60
+ # * :+local_port+: The local port to bind to
61
+ # * :+http_proxyaddr+: HTTP Proxy address
62
+ # * :+http_proxyport+: HTTP Proxy port
63
+ # * :+http_proxyuser+: HTTP Proxy user
64
+ # * :+http_proxypass+: HTTP Proxy password
65
+ #
66
+ # === Inherited methods
67
+ # * :+clean_host+: Method used to sanitize host names
68
+
52
69
  class ConnectionAdapter
53
70
  # Private: Regex used to strip brackets from IPv6 URIs.
54
71
  StripIpv6BracketsRegex = /\A\[(.*)\]\z/
55
72
 
73
+ OPTION_DEFAULTS = {
74
+ verify: true,
75
+ verify_peer: true
76
+ }
77
+
56
78
  # Public
57
79
  def self.call(uri, options)
58
80
  new(uri, options).connection
59
81
  end
60
82
 
83
+ def self.default_cert_store
84
+ @default_cert_store ||= OpenSSL::X509::Store.new.tap do |cert_store|
85
+ cert_store.set_default_paths
86
+ end
87
+ end
88
+
61
89
  attr_reader :uri, :options
62
90
 
63
91
  def initialize(uri, options = {})
@@ -65,14 +93,21 @@ module HTTParty
65
93
  raise ArgumentError, "uri must be a #{uri_adapter}, not a #{uri.class}" unless uri.is_a? uri_adapter
66
94
 
67
95
  @uri = uri
68
- @options = options
96
+ @options = OPTION_DEFAULTS.merge(options)
69
97
  end
70
98
 
71
99
  def connection
72
100
  host = clean_host(uri.host)
73
101
  port = uri.port || (uri.scheme == 'https' ? 443 : 80)
74
- if options[:http_proxyaddr]
75
- http = Net::HTTP.new(host, port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
102
+ if options.key?(:http_proxyaddr)
103
+ http = Net::HTTP.new(
104
+ host,
105
+ port,
106
+ options[:http_proxyaddr],
107
+ options[:http_proxyport],
108
+ options[:http_proxyuser],
109
+ options[:http_proxypass]
110
+ )
76
111
  else
77
112
  http = Net::HTTP.new(host, port)
78
113
  end
@@ -81,19 +116,28 @@ module HTTParty
81
116
 
82
117
  attach_ssl_certificates(http, options)
83
118
 
84
- if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
119
+ if add_timeout?(options[:timeout])
85
120
  http.open_timeout = options[:timeout]
86
121
  http.read_timeout = options[:timeout]
122
+ http.write_timeout = options[:timeout]
87
123
  end
88
124
 
89
- if options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
125
+ if add_timeout?(options[:read_timeout])
90
126
  http.read_timeout = options[:read_timeout]
91
127
  end
92
128
 
93
- if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
129
+ if add_timeout?(options[:open_timeout])
94
130
  http.open_timeout = options[:open_timeout]
95
131
  end
96
132
 
133
+ if add_timeout?(options[:write_timeout])
134
+ http.write_timeout = options[:write_timeout]
135
+ end
136
+
137
+ if add_max_retries?(options[:max_retries])
138
+ http.max_retries = options[:max_retries]
139
+ end
140
+
97
141
  if options[:debug_output]
98
142
  http.set_debug_output(options[:debug_output])
99
143
  end
@@ -106,19 +150,11 @@ module HTTParty
106
150
  #
107
151
  # @see https://bugs.ruby-lang.org/issues/6617
108
152
  if options[:local_host]
109
- if RUBY_VERSION >= "2.0.0"
110
- http.local_host = options[:local_host]
111
- else
112
- Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
113
- end
153
+ http.local_host = options[:local_host]
114
154
  end
115
155
 
116
156
  if options[:local_port]
117
- if RUBY_VERSION >= "2.0.0"
118
- http.local_port = options[:local_port]
119
- else
120
- Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
121
- end
157
+ http.local_port = options[:local_port]
122
158
  end
123
159
 
124
160
  http
@@ -126,6 +162,14 @@ module HTTParty
126
162
 
127
163
  private
128
164
 
165
+ def add_timeout?(timeout)
166
+ timeout && (timeout.is_a?(Integer) || timeout.is_a?(Float))
167
+ end
168
+
169
+ def add_max_retries?(max_retries)
170
+ max_retries && max_retries.is_a?(Integer) && max_retries >= 0
171
+ end
172
+
129
173
  def clean_host(host)
130
174
  strip_ipv6_brackets(host)
131
175
  end
@@ -138,6 +182,10 @@ module HTTParty
138
182
  uri.port == 443 || uri.scheme == 'https'
139
183
  end
140
184
 
185
+ def verify_ssl_certificate?
186
+ !(options[:verify] == false || options[:verify_peer] == false)
187
+ end
188
+
141
189
  def attach_ssl_certificates(http, options)
142
190
  if http.use_ssl?
143
191
  if options.fetch(:verify, true)
@@ -146,8 +194,7 @@ module HTTParty
146
194
  http.cert_store = options[:cert_store]
147
195
  else
148
196
  # Use the default cert store by default, i.e. system ca certs
149
- http.cert_store = OpenSSL::X509::Store.new
150
- http.cert_store.set_default_paths
197
+ http.cert_store = self.class.default_cert_store
151
198
  end
152
199
  else
153
200
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@@ -157,8 +204,8 @@ module HTTParty
157
204
  # Note: options[:pem] must contain the content of a PEM file having the private key appended
158
205
  if options[:pem]
159
206
  http.cert = OpenSSL::X509::Certificate.new(options[:pem])
160
- http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
161
- http.verify_mode = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
207
+ http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password])
208
+ http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
162
209
  end
163
210
 
164
211
  # PKCS12 client certificate authentication
@@ -166,7 +213,7 @@ module HTTParty
166
213
  p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password])
167
214
  http.cert = p12.certificate
168
215
  http.key = p12.key
169
- http.verify_mode = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
216
+ http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
170
217
  end
171
218
 
172
219
  # SSL certificate authority file and/or directory
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class HTTParty::CookieHash < Hash #:nodoc:
2
- CLIENT_COOKIES = %w(path expires domain path secure httponly)
4
+ CLIENT_COOKIES = %w(path expires domain path secure httponly samesite)
3
5
 
4
- def add_cookies(value)
5
- case value
6
+ def add_cookies(data)
7
+ case data
6
8
  when Hash
7
- merge!(value)
9
+ merge!(data)
8
10
  when String
9
- value.split('; ').each do |cookie|
10
- array = cookie.split('=', 2)
11
- self[array[0].to_sym] = array[1]
11
+ data.split('; ').each do |cookie|
12
+ key, value = cookie.split('=', 2)
13
+ self[key.to_sym] = value if key
12
14
  end
13
15
  else
14
16
  raise "add_cookies only takes a Hash or a String"
@@ -16,6 +18,6 @@ class HTTParty::CookieHash < Hash #:nodoc:
16
18
  end
17
19
 
18
20
  def to_cookie_string
19
- delete_if { |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
 
5
- # Exception raised when you attempt to set a non-existant format
6
- class UnsupportedFormat < Error; end
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
+
31
+ # Exception raised when you attempt to set a non-existent format
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
@@ -20,10 +46,21 @@ module HTTParty
20
46
  # @param [Net::HTTPResponse]
21
47
  def initialize(response)
22
48
  @response = response
49
+ super(response)
23
50
  end
24
51
  end
25
52
 
26
53
  # Exception that is raised when request has redirected too many times.
27
54
  # Calling {#response} returns the Net:HTTP response object.
28
55
  class RedirectionTooDeep < ResponseError; end
56
+
57
+ # Exception that is raised when request redirects and location header is present more than once
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
29
66
  end