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.
- checksums.yaml +5 -5
- data/.editorconfig +18 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +1 -1
- data/Changelog.md +624 -0
- data/Gemfile +11 -3
- data/Guardfile +3 -2
- data/README.md +18 -17
- data/bin/httparty +3 -1
- data/docs/README.md +223 -0
- data/examples/README.md +35 -12
- data/examples/aaws.rb +7 -3
- data/examples/body_stream.rb +14 -0
- data/examples/crack.rb +1 -1
- data/examples/custom_parsers.rb +5 -1
- data/examples/delicious.rb +4 -4
- data/examples/headers_and_user_agents.rb +7 -3
- data/examples/idn.rb +10 -0
- data/examples/logging.rb +4 -4
- data/examples/microsoft_graph.rb +52 -0
- data/examples/multipart.rb +35 -0
- data/examples/party_foul_mode.rb +90 -0
- data/examples/peer_cert.rb +9 -0
- data/examples/stackexchange.rb +1 -1
- data/examples/stream_download.rb +26 -0
- data/examples/tripit_sign_in.rb +17 -6
- data/examples/twitter.rb +2 -2
- data/examples/whoismyrep.rb +1 -1
- data/httparty.gemspec +9 -5
- data/lib/httparty/connection_adapter.rb +71 -24
- data/lib/httparty/cookie_hash.rb +10 -8
- data/lib/httparty/decompressor.rb +102 -0
- data/lib/httparty/exceptions.rb +42 -5
- data/lib/httparty/hash_conversions.rb +30 -8
- data/lib/httparty/headers_processor.rb +32 -0
- data/lib/httparty/logger/apache_formatter.rb +31 -6
- data/lib/httparty/logger/curl_formatter.rb +68 -23
- data/lib/httparty/logger/logger.rb +5 -1
- data/lib/httparty/logger/logstash_formatter.rb +62 -0
- data/lib/httparty/module_inheritable_attributes.rb +9 -9
- data/lib/httparty/net_digest_auth.rb +23 -21
- data/lib/httparty/parser.rb +28 -14
- data/lib/httparty/request/body.rb +125 -0
- data/lib/httparty/request/multipart_boundary.rb +13 -0
- data/lib/httparty/request/streaming_multipart_body.rb +190 -0
- data/lib/httparty/request.rb +224 -122
- data/lib/httparty/response/headers.rb +23 -19
- data/lib/httparty/response.rb +92 -13
- data/lib/httparty/response_fragment.rb +21 -0
- data/lib/httparty/text_encoder.rb +72 -0
- data/lib/httparty/utils.rb +13 -0
- data/lib/httparty/version.rb +3 -1
- data/lib/httparty.rb +118 -42
- data/script/release +4 -4
- data/website/css/common.css +1 -1
- metadata +50 -112
- data/.simplecov +0 -1
- data/.travis.yml +0 -7
- data/History +0 -390
- data/features/basic_authentication.feature +0 -20
- data/features/command_line.feature +0 -95
- data/features/deals_with_http_error_codes.feature +0 -26
- data/features/digest_authentication.feature +0 -30
- data/features/handles_compressed_responses.feature +0 -27
- data/features/handles_multiple_formats.feature +0 -57
- data/features/steps/env.rb +0 -27
- data/features/steps/httparty_response_steps.rb +0 -52
- data/features/steps/httparty_steps.rb +0 -43
- data/features/steps/mongrel_helper.rb +0 -127
- data/features/steps/remote_service_steps.rb +0 -90
- data/features/supports_read_timeout_option.feature +0 -13
- data/features/supports_redirection.feature +0 -22
- data/features/supports_timeout_option.feature +0 -13
- data/spec/fixtures/delicious.xml +0 -23
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +0 -3
- data/spec/fixtures/ssl/generate.sh +0 -29
- data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
- data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
- data/spec/fixtures/ssl/generated/ca.crt +0 -16
- data/spec/fixtures/ssl/generated/ca.key +0 -15
- data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
- data/spec/fixtures/ssl/generated/server.crt +0 -13
- data/spec/fixtures/ssl/generated/server.key +0 -15
- data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
- data/spec/fixtures/twitter.csv +0 -2
- data/spec/fixtures/twitter.json +0 -1
- data/spec/fixtures/twitter.xml +0 -403
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
- data/spec/httparty/connection_adapter_spec.rb +0 -468
- data/spec/httparty/cookie_hash_spec.rb +0 -83
- data/spec/httparty/exception_spec.rb +0 -38
- data/spec/httparty/hash_conversions_spec.rb +0 -41
- data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
- data/spec/httparty/logger/curl_formatter_spec.rb +0 -18
- data/spec/httparty/logger/logger_spec.rb +0 -38
- data/spec/httparty/net_digest_auth_spec.rb +0 -230
- data/spec/httparty/parser_spec.rb +0 -173
- data/spec/httparty/request_spec.rb +0 -1073
- data/spec/httparty/response_spec.rb +0 -241
- data/spec/httparty/ssl_spec.rb +0 -74
- data/spec/httparty_spec.rb +0 -850
- data/spec/spec_helper.rb +0 -59
- data/spec/support/ssl_test_helper.rb +0 -47
- data/spec/support/ssl_test_server.rb +0 -80
- 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}"
|
data/examples/stackexchange.rb
CHANGED
|
@@ -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)
|
data/examples/tripit_sign_in.rb
CHANGED
|
@@ -3,30 +3,41 @@ require File.join(dir, 'httparty')
|
|
|
3
3
|
|
|
4
4
|
class TripIt
|
|
5
5
|
include HTTParty
|
|
6
|
-
base_uri '
|
|
6
|
+
base_uri 'https://www.tripit.com'
|
|
7
7
|
debug_output
|
|
8
8
|
|
|
9
9
|
def initialize(email, password)
|
|
10
10
|
@email = email
|
|
11
|
-
|
|
12
|
-
|
|
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' =>
|
|
20
|
+
headers: {'Cookie' => get_response_cookie.to_cookie_string }
|
|
19
21
|
)
|
|
20
|
-
|
|
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
|
data/examples/whoismyrep.rb
CHANGED
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 = "
|
|
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 = '>=
|
|
17
|
+
s.required_ruby_version = '>= 2.7.0'
|
|
17
18
|
|
|
18
|
-
s.add_dependency '
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
# * :+
|
|
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
|
|
75
|
-
http = Net::HTTP.new(
|
|
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
|
|
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
|
|
125
|
+
if add_timeout?(options[:read_timeout])
|
|
90
126
|
http.read_timeout = options[:read_timeout]
|
|
91
127
|
end
|
|
92
128
|
|
|
93
|
-
if
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
161
|
-
http.verify_mode =
|
|
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 =
|
|
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
|
data/lib/httparty/cookie_hash.rb
CHANGED
|
@@ -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(
|
|
5
|
-
case
|
|
6
|
+
def add_cookies(data)
|
|
7
|
+
case data
|
|
6
8
|
when Hash
|
|
7
|
-
merge!(
|
|
9
|
+
merge!(data)
|
|
8
10
|
when String
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
self[
|
|
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
|
-
|
|
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
|
|
|
5
|
-
#
|
|
6
|
-
|
|
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 <
|
|
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
|
|
@@ -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
|