httparty 0.16.2 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.editorconfig +18 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +23 -0
- data/.gitignore +2 -0
- data/.rubocop_todo.yml +1 -1
- data/Changelog.md +425 -280
- data/Gemfile +7 -0
- data/Guardfile +3 -2
- data/README.md +5 -5
- data/docs/README.md +90 -5
- data/examples/README.md +28 -11
- data/examples/aaws.rb +6 -2
- data/examples/body_stream.rb +14 -0
- data/examples/idn.rb +10 -0
- data/examples/microsoft_graph.rb +52 -0
- data/examples/multipart.rb +22 -0
- data/examples/peer_cert.rb +9 -0
- data/examples/stream_download.rb +8 -2
- data/httparty.gemspec +4 -3
- data/lib/httparty/connection_adapter.rb +44 -20
- data/lib/httparty/cookie_hash.rb +10 -8
- data/lib/httparty/decompressor.rb +102 -0
- data/lib/httparty/exceptions.rb +3 -1
- data/lib/httparty/hash_conversions.rb +10 -4
- data/lib/httparty/headers_processor.rb +32 -0
- data/lib/httparty/logger/apache_formatter.rb +31 -6
- data/lib/httparty/logger/curl_formatter.rb +9 -7
- 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 +15 -15
- data/lib/httparty/parser.rb +12 -5
- data/lib/httparty/request/body.rb +54 -27
- data/lib/httparty/request/multipart_boundary.rb +2 -0
- data/lib/httparty/request.rb +105 -107
- data/lib/httparty/response/headers.rb +4 -2
- data/lib/httparty/response.rb +52 -9
- 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 +81 -33
- data/script/release +4 -4
- data/website/css/common.css +1 -1
- metadata +50 -107
- data/.simplecov +0 -1
- data/.travis.yml +0 -10
- 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 -56
- data/features/steps/httparty_steps.rb +0 -43
- data/features/steps/mongrel_helper.rb +0 -127
- data/features/steps/remote_service_steps.rb +0 -92
- 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/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/tiny.gif +0 -0
- 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 -498
- data/spec/httparty/cookie_hash_spec.rb +0 -100
- data/spec/httparty/exception_spec.rb +0 -45
- data/spec/httparty/hash_conversions_spec.rb +0 -56
- data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
- data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
- data/spec/httparty/logger/logger_spec.rb +0 -38
- data/spec/httparty/net_digest_auth_spec.rb +0 -270
- data/spec/httparty/parser_spec.rb +0 -190
- data/spec/httparty/request/body_spec.rb +0 -60
- data/spec/httparty/request_spec.rb +0 -1312
- data/spec/httparty/response_spec.rb +0 -347
- data/spec/httparty/ssl_spec.rb +0 -74
- data/spec/httparty_spec.rb +0 -896
- data/spec/spec_helper.rb +0 -51
- 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,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,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
|
-
# @
|
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
|
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
|
|
@@ -39,7 +41,9 @@ module HTTParty
|
|
39
41
|
if value.empty?
|
40
42
|
normalized_keys << ["#{key}[]", '']
|
41
43
|
else
|
42
|
-
normalized_keys = value.to_ary.flat_map
|
44
|
+
normalized_keys = value.to_ary.flat_map do |element|
|
45
|
+
normalize_keys("#{key}[]", element)
|
46
|
+
end
|
43
47
|
end
|
44
48
|
elsif value.respond_to?(:to_hash)
|
45
49
|
stack << [key, value.to_hash]
|
@@ -52,7 +56,9 @@ module HTTParty
|
|
52
56
|
if child_value.respond_to?(:to_hash)
|
53
57
|
stack << ["#{parent}[#{child_key}]", child_value.to_hash]
|
54
58
|
elsif child_value.respond_to?(:to_ary)
|
55
|
-
child_value.to_ary.each
|
59
|
+
child_value.to_ary.each do |v|
|
60
|
+
normalized_keys << normalize_keys("#{parent}[#{child_key}][]", v).flatten
|
61
|
+
end
|
56
62
|
else
|
57
63
|
normalized_keys << normalize_keys("#{parent}[#{child_key}]", child_value).flatten
|
58
64
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTParty
|
4
|
+
class HeadersProcessor
|
5
|
+
attr_reader :headers, :options
|
6
|
+
|
7
|
+
def initialize(headers, options)
|
8
|
+
@headers = headers
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
return unless options[:headers]
|
14
|
+
|
15
|
+
options[:headers] = headers.merge(options[:headers]) if headers.any?
|
16
|
+
options[:headers] = Utils.stringify_keys(process_dynamic_headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def process_dynamic_headers
|
22
|
+
options[:headers].each_with_object({}) do |header, processed_headers|
|
23
|
+
key, value = header
|
24
|
+
processed_headers[key] = if value.respond_to?(:call)
|
25
|
+
value.arity == 0 ? value.call : value.call(options)
|
26
|
+
else
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
module Logger
|
3
5
|
class ApacheFormatter #:nodoc:
|
4
6
|
TAG_NAME = HTTParty.name
|
5
7
|
|
6
|
-
attr_accessor :level, :logger
|
8
|
+
attr_accessor :level, :logger
|
7
9
|
|
8
10
|
def initialize(logger, level)
|
9
11
|
@logger = logger
|
@@ -11,11 +13,34 @@ module HTTParty
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def format(request, response)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
@request = request
|
17
|
+
@response = response
|
18
|
+
|
19
|
+
logger.public_send level, message
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :request, :response
|
25
|
+
|
26
|
+
def message
|
27
|
+
"[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_time
|
31
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
32
|
+
end
|
33
|
+
|
34
|
+
def http_method
|
35
|
+
request.http_method.name.split('::').last.upcase
|
36
|
+
end
|
37
|
+
|
38
|
+
def path
|
39
|
+
request.path.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def content_length
|
43
|
+
response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
|
19
44
|
end
|
20
45
|
end
|
21
46
|
end
|
@@ -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 = '>'
|
6
|
-
IN = '<'
|
7
|
+
OUT = '>'
|
8
|
+
IN = '<'
|
7
9
|
|
8
10
|
attr_accessor :level, :logger
|
9
11
|
|
@@ -20,7 +22,7 @@ module HTTParty
|
|
20
22
|
log_request
|
21
23
|
log_response
|
22
24
|
|
23
|
-
logger.
|
25
|
+
logger.public_send level, messages.join("\n")
|
24
26
|
end
|
25
27
|
|
26
28
|
private
|
@@ -44,7 +46,7 @@ module HTTParty
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def log_url
|
47
|
-
http_method = request.http_method.name.split(
|
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
|
@@ -80,11 +82,11 @@ module HTTParty
|
|
80
82
|
end
|
81
83
|
|
82
84
|
def log(direction, line = '')
|
83
|
-
messages << "[#{TAG_NAME}] [#{
|
85
|
+
messages << "[#{TAG_NAME}] [#{current_time}] #{direction} #{line}"
|
84
86
|
end
|
85
87
|
|
86
|
-
def
|
87
|
-
|
88
|
+
def current_time
|
89
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'httparty/logger/apache_formatter'
|
2
4
|
require 'httparty/logger/curl_formatter'
|
5
|
+
require 'httparty/logger/logstash_formatter'
|
3
6
|
|
4
7
|
module HTTParty
|
5
8
|
module Logger
|
6
9
|
def self.formatters
|
7
10
|
@formatters ||= {
|
8
11
|
:curl => Logger::CurlFormatter,
|
9
|
-
:apache => Logger::ApacheFormatter
|
12
|
+
:apache => Logger::ApacheFormatter,
|
13
|
+
:logstash => Logger::LogstashFormatter,
|
10
14
|
}
|
11
15
|
end
|
12
16
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTParty
|
4
|
+
module Logger
|
5
|
+
class LogstashFormatter #:nodoc:
|
6
|
+
TAG_NAME = HTTParty.name
|
7
|
+
|
8
|
+
attr_accessor :level, :logger
|
9
|
+
|
10
|
+
def initialize(logger, level)
|
11
|
+
@logger = logger
|
12
|
+
@level = level.to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
def format(request, response)
|
16
|
+
@request = request
|
17
|
+
@response = response
|
18
|
+
|
19
|
+
logger.public_send level, logstash_message
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :request, :response
|
25
|
+
|
26
|
+
def logstash_message
|
27
|
+
require 'json'
|
28
|
+
{
|
29
|
+
'@timestamp' => current_time,
|
30
|
+
'@version' => 1,
|
31
|
+
'content_length' => content_length || '-',
|
32
|
+
'http_method' => http_method,
|
33
|
+
'message' => message,
|
34
|
+
'path' => path,
|
35
|
+
'response_code' => response.code,
|
36
|
+
'severity' => level,
|
37
|
+
'tags' => [TAG_NAME],
|
38
|
+
}.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
def message
|
42
|
+
"[#{TAG_NAME}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_time
|
46
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
47
|
+
end
|
48
|
+
|
49
|
+
def http_method
|
50
|
+
@http_method ||= request.http_method.name.split('::').last.upcase
|
51
|
+
end
|
52
|
+
|
53
|
+
def path
|
54
|
+
@path ||= request.path.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def content_length
|
58
|
+
@content_length ||= response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -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)
|
@@ -9,12 +11,12 @@ module HTTParty
|
|
9
11
|
duplicate = hash.dup
|
10
12
|
|
11
13
|
duplicate.each_pair do |key, value|
|
12
|
-
|
13
|
-
|
14
|
+
if value.is_a?(Hash)
|
15
|
+
duplicate[key] = hash_deep_dup(value)
|
14
16
|
elsif value.is_a?(Proc)
|
15
17
|
duplicate[key] = value.dup
|
16
18
|
else
|
17
|
-
value
|
19
|
+
duplicate[key] = value
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -27,7 +29,7 @@ module HTTParty
|
|
27
29
|
@mattr_inheritable_attrs += args
|
28
30
|
|
29
31
|
args.each do |arg|
|
30
|
-
|
32
|
+
singleton_class.attr_accessor(arg)
|
31
33
|
end
|
32
34
|
|
33
35
|
@mattr_inheritable_attrs
|
@@ -36,18 +38,16 @@ 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)
|
43
|
-
|
45
|
+
subclass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
44
46
|
def self.#{inheritable_attribute}
|
45
47
|
duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar})
|
46
48
|
#{ivar} = superclass.#{inheritable_attribute}.merge(duplicate)
|
47
49
|
end
|
48
|
-
|
49
|
-
|
50
|
-
subclass.class_eval method
|
50
|
+
RUBY
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest/md5'
|
2
4
|
require 'net/http'
|
3
5
|
|
@@ -14,11 +16,11 @@ module Net
|
|
14
16
|
|
15
17
|
authenticator.authorization_header.each do |v|
|
16
18
|
add_field('Authorization', v)
|
17
|
-
end
|
19
|
+
end
|
18
20
|
|
19
21
|
authenticator.cookie_header.each do |v|
|
20
22
|
add_field('Cookie', v)
|
21
|
-
end
|
23
|
+
end
|
22
24
|
end
|
23
25
|
|
24
26
|
class DigestAuthenticator
|
@@ -44,12 +46,9 @@ module Net
|
|
44
46
|
header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
|
45
47
|
|
46
48
|
if qop_present?
|
47
|
-
|
48
|
-
|
49
|
-
|
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?
|
@@ -64,7 +63,8 @@ module Net
|
|
64
63
|
|
65
64
|
def parse(response_header)
|
66
65
|
header = response_header['www-authenticate']
|
67
|
-
|
66
|
+
|
67
|
+
header = header.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
|
68
68
|
|
69
69
|
header =~ /Digest (.*)/
|
70
70
|
params = {}
|
@@ -97,13 +97,13 @@ module Net
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def random
|
100
|
-
format
|
100
|
+
format '%x', (Time.now.to_i + rand(65535))
|
101
101
|
end
|
102
102
|
|
103
103
|
def request_digest
|
104
104
|
a = [md5(a1), @response['nonce'], md5(a2)]
|
105
|
-
a.insert(2,
|
106
|
-
md5(a.join(
|
105
|
+
a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
|
106
|
+
md5(a.join(':'))
|
107
107
|
end
|
108
108
|
|
109
109
|
def md5(str)
|
@@ -113,11 +113,11 @@ module Net
|
|
113
113
|
def algorithm_present?
|
114
114
|
@response.key?('algorithm') && !@response['algorithm'].empty?
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
def use_md5_sess?
|
118
118
|
algorithm_present? && @response['algorithm'] == 'MD5-sess'
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
def a1
|
122
122
|
a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
|
123
123
|
if use_md5_sess?
|
@@ -128,7 +128,7 @@ module Net
|
|
128
128
|
end
|
129
129
|
|
130
130
|
def a2
|
131
|
-
[@method, @path].join(
|
131
|
+
[@method, @path].join(':')
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
data/lib/httparty/parser.rb
CHANGED
@@ -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 ==
|
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}/, '')
|
@@ -116,16 +118,19 @@ module HTTParty
|
|
116
118
|
protected
|
117
119
|
|
118
120
|
def xml
|
121
|
+
require 'multi_xml'
|
119
122
|
MultiXml.parse(body)
|
120
123
|
end
|
121
124
|
|
122
|
-
UTF8_BOM = "\xEF\xBB\xBF"
|
125
|
+
UTF8_BOM = "\xEF\xBB\xBF"
|
123
126
|
|
124
127
|
def json
|
128
|
+
require 'json'
|
125
129
|
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
|
126
130
|
end
|
127
131
|
|
128
132
|
def csv
|
133
|
+
require 'csv'
|
129
134
|
CSV.parse(body)
|
130
135
|
end
|
131
136
|
|
@@ -142,9 +147,11 @@ module HTTParty
|
|
142
147
|
end
|
143
148
|
|
144
149
|
def parse_supported_format
|
145
|
-
|
146
|
-
|
147
|
-
|
150
|
+
if respond_to?(format, true)
|
151
|
+
send(format)
|
152
|
+
else
|
153
|
+
raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
|
154
|
+
end
|
148
155
|
end
|
149
156
|
end
|
150
157
|
end
|
@@ -1,11 +1,17 @@
|
|
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
|
6
|
-
|
8
|
+
NEWLINE = "\r\n"
|
9
|
+
private_constant :NEWLINE
|
10
|
+
|
11
|
+
def initialize(params, query_string_normalizer: nil, force_multipart: false)
|
7
12
|
@params = params
|
8
13
|
@query_string_normalizer = query_string_normalizer
|
14
|
+
@force_multipart = force_multipart
|
9
15
|
end
|
10
16
|
|
11
17
|
def call
|
@@ -21,48 +27,49 @@ module HTTParty
|
|
21
27
|
end
|
22
28
|
|
23
29
|
def multipart?
|
24
|
-
params.respond_to?(:to_hash) && has_file?(params
|
30
|
+
params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
|
25
31
|
end
|
26
32
|
|
27
33
|
private
|
28
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
|
+
|
29
42
|
def generate_multipart
|
30
43
|
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
31
44
|
|
32
|
-
multipart = normalized_params.inject('') do |memo, (key, value)|
|
33
|
-
memo
|
34
|
-
memo
|
45
|
+
multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
|
46
|
+
memo << "--#{boundary}#{NEWLINE}"
|
47
|
+
memo << %(Content-Disposition: form-data; name="#{key}")
|
35
48
|
# value.path is used to support ActionDispatch::Http::UploadedFile
|
36
49
|
# https://github.com/jnunemaker/httparty/pull/585
|
37
|
-
memo
|
38
|
-
memo
|
39
|
-
memo
|
40
|
-
memo
|
41
|
-
memo
|
42
|
-
memo
|
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
|
43
56
|
end
|
44
57
|
|
45
|
-
multipart
|
58
|
+
multipart << "--#{boundary}--#{NEWLINE}"
|
46
59
|
end
|
47
60
|
|
48
|
-
def has_file?(
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
file?(value)
|
56
|
-
end
|
61
|
+
def has_file?(value)
|
62
|
+
if value.respond_to?(:to_hash)
|
63
|
+
value.to_hash.any? { |_, v| has_file?(v) }
|
64
|
+
elsif value.respond_to?(:to_ary)
|
65
|
+
value.to_ary.any? { |v| has_file?(v) }
|
66
|
+
else
|
67
|
+
file?(value)
|
57
68
|
end
|
58
69
|
end
|
59
70
|
|
60
71
|
def file?(object)
|
61
|
-
object.respond_to?(:path) && object.respond_to?(:read)
|
62
|
-
end
|
63
|
-
|
64
|
-
def includes_hash?(object)
|
65
|
-
object.respond_to?(:to_ary) && object.any? { |e| e.respond_to?(:hash) }
|
72
|
+
object.respond_to?(:path) && object.respond_to?(:read)
|
66
73
|
end
|
67
74
|
|
68
75
|
def normalize_query(query)
|
@@ -73,7 +80,27 @@ module HTTParty
|
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
76
|
-
|
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
|
+
|
92
|
+
def content_type(object)
|
93
|
+
return object.content_type if object.respond_to?(:content_type)
|
94
|
+
require 'mini_mime'
|
95
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
96
|
+
mime ? mime.content_type : 'application/octet-stream'
|
97
|
+
end
|
98
|
+
|
99
|
+
def file_name(object)
|
100
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
101
|
+
end
|
102
|
+
|
103
|
+
attr_reader :params, :query_string_normalizer, :force_multipart
|
77
104
|
end
|
78
105
|
end
|
79
106
|
end
|