micro-max-tool 0.0.1
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 +7 -0
- data/httparty-0.24.2/CONTRIBUTING.md +23 -0
- data/httparty-0.24.2/Changelog.md +624 -0
- data/httparty-0.24.2/Gemfile +27 -0
- data/httparty-0.24.2/Guardfile +17 -0
- data/httparty-0.24.2/MIT-LICENSE +20 -0
- data/httparty-0.24.2/README.md +79 -0
- data/httparty-0.24.2/Rakefile +10 -0
- data/httparty-0.24.2/bin/httparty +123 -0
- data/httparty-0.24.2/cucumber.yml +1 -0
- data/httparty-0.24.2/docs/README.md +223 -0
- data/httparty-0.24.2/examples/README.md +90 -0
- data/httparty-0.24.2/examples/aaws.rb +36 -0
- data/httparty-0.24.2/examples/basic.rb +28 -0
- data/httparty-0.24.2/examples/body_stream.rb +14 -0
- data/httparty-0.24.2/examples/crack.rb +19 -0
- data/httparty-0.24.2/examples/custom_parsers.rb +68 -0
- data/httparty-0.24.2/examples/delicious.rb +37 -0
- data/httparty-0.24.2/examples/google.rb +16 -0
- data/httparty-0.24.2/examples/headers_and_user_agents.rb +10 -0
- data/httparty-0.24.2/examples/idn.rb +10 -0
- data/httparty-0.24.2/examples/logging.rb +36 -0
- data/httparty-0.24.2/examples/microsoft_graph.rb +52 -0
- data/httparty-0.24.2/examples/multipart.rb +35 -0
- data/httparty-0.24.2/examples/nokogiri_html_parser.rb +19 -0
- data/httparty-0.24.2/examples/party_foul_mode.rb +90 -0
- data/httparty-0.24.2/examples/peer_cert.rb +9 -0
- data/httparty-0.24.2/examples/rescue_json.rb +17 -0
- data/httparty-0.24.2/examples/rubyurl.rb +14 -0
- data/httparty-0.24.2/examples/stackexchange.rb +24 -0
- data/httparty-0.24.2/examples/stream_download.rb +26 -0
- data/httparty-0.24.2/examples/tripit_sign_in.rb +44 -0
- data/httparty-0.24.2/examples/twitter.rb +31 -0
- data/httparty-0.24.2/examples/whoismyrep.rb +10 -0
- data/httparty-0.24.2/httparty.gemspec +32 -0
- data/httparty-0.24.2/lib/httparty/connection_adapter.rb +237 -0
- data/httparty-0.24.2/lib/httparty/cookie_hash.rb +23 -0
- data/httparty-0.24.2/lib/httparty/decompressor.rb +102 -0
- data/httparty-0.24.2/lib/httparty/exceptions.rb +66 -0
- data/httparty-0.24.2/lib/httparty/hash_conversions.rb +71 -0
- data/httparty-0.24.2/lib/httparty/headers_processor.rb +32 -0
- data/httparty-0.24.2/lib/httparty/logger/apache_formatter.rb +47 -0
- data/httparty-0.24.2/lib/httparty/logger/curl_formatter.rb +93 -0
- data/httparty-0.24.2/lib/httparty/logger/logger.rb +30 -0
- data/httparty-0.24.2/lib/httparty/logger/logstash_formatter.rb +62 -0
- data/httparty-0.24.2/lib/httparty/module_inheritable_attributes.rb +56 -0
- data/httparty-0.24.2/lib/httparty/net_digest_auth.rb +135 -0
- data/httparty-0.24.2/lib/httparty/parser.rb +157 -0
- data/httparty-0.24.2/lib/httparty/request/body.rb +125 -0
- data/httparty-0.24.2/lib/httparty/request/multipart_boundary.rb +13 -0
- data/httparty-0.24.2/lib/httparty/request/streaming_multipart_body.rb +190 -0
- data/httparty-0.24.2/lib/httparty/request.rb +466 -0
- data/httparty-0.24.2/lib/httparty/response/headers.rb +35 -0
- data/httparty-0.24.2/lib/httparty/response.rb +156 -0
- data/httparty-0.24.2/lib/httparty/response_fragment.rb +21 -0
- data/httparty-0.24.2/lib/httparty/text_encoder.rb +72 -0
- data/httparty-0.24.2/lib/httparty/utils.rb +13 -0
- data/httparty-0.24.2/lib/httparty/version.rb +5 -0
- data/httparty-0.24.2/lib/httparty.rb +699 -0
- data/httparty-0.24.2/script/release +42 -0
- data/httparty-0.24.2/website/css/common.css +47 -0
- data/httparty-0.24.2/website/index.html +73 -0
- data/micro-max-tool.gemspec +11 -0
- metadata +102 -0
|
@@ -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
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
module ModuleInheritableAttributes #:nodoc:
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# borrowed from Rails 3.2 ActiveSupport
|
|
10
|
+
def self.hash_deep_dup(hash)
|
|
11
|
+
duplicate = hash.dup
|
|
12
|
+
|
|
13
|
+
duplicate.each_pair do |key, value|
|
|
14
|
+
if value.is_a?(Hash)
|
|
15
|
+
duplicate[key] = hash_deep_dup(value)
|
|
16
|
+
elsif value.is_a?(Proc)
|
|
17
|
+
duplicate[key] = value.dup
|
|
18
|
+
else
|
|
19
|
+
duplicate[key] = value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
duplicate
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module ClassMethods #:nodoc:
|
|
27
|
+
def mattr_inheritable(*args)
|
|
28
|
+
@mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
|
|
29
|
+
@mattr_inheritable_attrs += args
|
|
30
|
+
|
|
31
|
+
args.each do |arg|
|
|
32
|
+
singleton_class.attr_accessor(arg)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@mattr_inheritable_attrs
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inherited(subclass)
|
|
39
|
+
super
|
|
40
|
+
@mattr_inheritable_attrs.each do |inheritable_attribute|
|
|
41
|
+
ivar = :"@#{inheritable_attribute}"
|
|
42
|
+
subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
|
|
43
|
+
|
|
44
|
+
if instance_variable_get(ivar).respond_to?(:merge)
|
|
45
|
+
subclass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
46
|
+
def self.#{inheritable_attribute}
|
|
47
|
+
duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar})
|
|
48
|
+
#{ivar} = superclass.#{inheritable_attribute}.merge(duplicate)
|
|
49
|
+
end
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
|
|
6
|
+
module Net
|
|
7
|
+
module HTTPHeader
|
|
8
|
+
def digest_auth(username, password, response)
|
|
9
|
+
authenticator = DigestAuthenticator.new(
|
|
10
|
+
username,
|
|
11
|
+
password,
|
|
12
|
+
@method,
|
|
13
|
+
@path,
|
|
14
|
+
response
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
authenticator.authorization_header.each do |v|
|
|
18
|
+
add_field('Authorization', v)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
authenticator.cookie_header.each do |v|
|
|
22
|
+
add_field('Cookie', v)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class DigestAuthenticator
|
|
27
|
+
def initialize(username, password, method, path, response_header)
|
|
28
|
+
@username = username
|
|
29
|
+
@password = password
|
|
30
|
+
@method = method
|
|
31
|
+
@path = path
|
|
32
|
+
@response = parse(response_header)
|
|
33
|
+
@cookies = parse_cookies(response_header)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def authorization_header
|
|
37
|
+
@cnonce = md5(random)
|
|
38
|
+
header = [
|
|
39
|
+
%(Digest username="#{@username}"),
|
|
40
|
+
%(realm="#{@response['realm']}"),
|
|
41
|
+
%(nonce="#{@response['nonce']}"),
|
|
42
|
+
%(uri="#{@path}"),
|
|
43
|
+
%(response="#{request_digest}")
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
|
|
47
|
+
|
|
48
|
+
if qop_present?
|
|
49
|
+
header << %(cnonce="#{@cnonce}")
|
|
50
|
+
header << %(qop="#{@response['qop']}")
|
|
51
|
+
header << 'nc=00000001'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
header << %(opaque="#{@response['opaque']}") if opaque_present?
|
|
55
|
+
header
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cookie_header
|
|
59
|
+
@cookies
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def parse(response_header)
|
|
65
|
+
header = response_header['www-authenticate']
|
|
66
|
+
|
|
67
|
+
header = header.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
|
|
68
|
+
|
|
69
|
+
header =~ /Digest (.*)/
|
|
70
|
+
params = {}
|
|
71
|
+
if $1
|
|
72
|
+
non_quoted = $1.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
|
73
|
+
non_quoted.gsub(/(\w+)=([^,]*)/) { params[$1] = $2 }
|
|
74
|
+
end
|
|
75
|
+
params
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_cookies(response_header)
|
|
79
|
+
return [] unless response_header['Set-Cookie']
|
|
80
|
+
|
|
81
|
+
cookies = response_header['Set-Cookie'].split('; ')
|
|
82
|
+
|
|
83
|
+
cookies.reduce([]) do |ret, cookie|
|
|
84
|
+
ret << cookie
|
|
85
|
+
ret
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
cookies
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def opaque_present?
|
|
92
|
+
@response.key?('opaque') && !@response['opaque'].empty?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def qop_present?
|
|
96
|
+
@response.key?('qop') && !@response['qop'].empty?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def random
|
|
100
|
+
format '%x', (Time.now.to_i + rand(65535))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def request_digest
|
|
104
|
+
a = [md5(a1), @response['nonce'], md5(a2)]
|
|
105
|
+
a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
|
|
106
|
+
md5(a.join(':'))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def md5(str)
|
|
110
|
+
Digest::MD5.hexdigest(str)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def algorithm_present?
|
|
114
|
+
@response.key?('algorithm') && !@response['algorithm'].empty?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def use_md5_sess?
|
|
118
|
+
algorithm_present? && @response['algorithm'] == 'MD5-sess'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def a1
|
|
122
|
+
a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
|
|
123
|
+
if use_md5_sess?
|
|
124
|
+
[ md5(a1_user_realm_pwd), @response['nonce'], @cnonce ].join(':')
|
|
125
|
+
else
|
|
126
|
+
a1_user_realm_pwd
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def a2
|
|
131
|
+
[@method, @path].join(':')
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
# The default parser used by HTTParty, supports xml, json, html, csv and
|
|
5
|
+
# plain text.
|
|
6
|
+
#
|
|
7
|
+
# == Custom Parsers
|
|
8
|
+
#
|
|
9
|
+
# If you'd like to do your own custom parsing, subclassing HTTParty::Parser
|
|
10
|
+
# will make that process much easier. There are a few different ways you can
|
|
11
|
+
# utilize HTTParty::Parser as a superclass.
|
|
12
|
+
#
|
|
13
|
+
# @example Intercept the parsing for all formats
|
|
14
|
+
# class SimpleParser < HTTParty::Parser
|
|
15
|
+
# def parse
|
|
16
|
+
# perform_parsing
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Add the atom format and parsing method to the default parser
|
|
21
|
+
# class AtomParsingIncluded < HTTParty::Parser
|
|
22
|
+
# SupportedFormats.merge!(
|
|
23
|
+
# {"application/atom+xml" => :atom}
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# def atom
|
|
27
|
+
# perform_atom_parsing
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Only support the atom format
|
|
32
|
+
# class ParseOnlyAtom < HTTParty::Parser
|
|
33
|
+
# SupportedFormats = {"application/atom+xml" => :atom}
|
|
34
|
+
#
|
|
35
|
+
# def atom
|
|
36
|
+
# perform_atom_parsing
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @abstract Read the Custom Parsers section for more information.
|
|
41
|
+
class Parser
|
|
42
|
+
SupportedFormats = {
|
|
43
|
+
'text/xml' => :xml,
|
|
44
|
+
'application/xml' => :xml,
|
|
45
|
+
'application/json' => :json,
|
|
46
|
+
'application/vnd.api+json' => :json,
|
|
47
|
+
'application/hal+json' => :json,
|
|
48
|
+
'text/json' => :json,
|
|
49
|
+
'application/javascript' => :plain,
|
|
50
|
+
'text/javascript' => :plain,
|
|
51
|
+
'text/html' => :html,
|
|
52
|
+
'text/plain' => :plain,
|
|
53
|
+
'text/csv' => :csv,
|
|
54
|
+
'application/csv' => :csv,
|
|
55
|
+
'text/comma-separated-values' => :csv
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# The response body of the request
|
|
59
|
+
# @return [String]
|
|
60
|
+
attr_reader :body
|
|
61
|
+
|
|
62
|
+
# The intended parsing format for the request
|
|
63
|
+
# @return [Symbol] e.g. :json
|
|
64
|
+
attr_reader :format
|
|
65
|
+
|
|
66
|
+
# Instantiate the parser and call {#parse}.
|
|
67
|
+
# @param [String] body the response body
|
|
68
|
+
# @param [Symbol] format the response format
|
|
69
|
+
# @return parsed response
|
|
70
|
+
def self.call(body, format)
|
|
71
|
+
new(body, format).parse
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Hash] the SupportedFormats hash
|
|
75
|
+
def self.formats
|
|
76
|
+
const_get(:SupportedFormats)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param [String] mimetype response MIME type
|
|
80
|
+
# @return [Symbol]
|
|
81
|
+
# @return [nil] mime type not supported
|
|
82
|
+
def self.format_from_mimetype(mimetype)
|
|
83
|
+
formats[formats.keys.detect {|k| mimetype.include?(k)}]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Array<Symbol>] list of supported formats
|
|
87
|
+
def self.supported_formats
|
|
88
|
+
formats.values.uniq
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param [Symbol] format e.g. :json, :xml
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def self.supports_format?(format)
|
|
94
|
+
supported_formats.include?(format)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def initialize(body, format)
|
|
98
|
+
@body = body
|
|
99
|
+
@format = format
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Object] the parsed body
|
|
103
|
+
# @return [nil] when the response body is nil, an empty string, spaces only or "null"
|
|
104
|
+
def parse
|
|
105
|
+
return nil if body.nil?
|
|
106
|
+
return nil if body == 'null'
|
|
107
|
+
return nil if body.valid_encoding? && body.strip.empty?
|
|
108
|
+
if body.valid_encoding? && body.encoding == Encoding::UTF_8
|
|
109
|
+
@body = body.gsub(/\A#{UTF8_BOM}/, '')
|
|
110
|
+
end
|
|
111
|
+
if supports_format?
|
|
112
|
+
parse_supported_format
|
|
113
|
+
else
|
|
114
|
+
body
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
protected
|
|
119
|
+
|
|
120
|
+
def xml
|
|
121
|
+
require 'multi_xml'
|
|
122
|
+
MultiXml.parse(body)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
UTF8_BOM = "\xEF\xBB\xBF"
|
|
126
|
+
|
|
127
|
+
def json
|
|
128
|
+
require 'json'
|
|
129
|
+
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def csv
|
|
133
|
+
require 'csv'
|
|
134
|
+
CSV.parse(body)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def html
|
|
138
|
+
body
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def plain
|
|
142
|
+
body
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def supports_format?
|
|
146
|
+
self.class.supports_format?(format)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parse_supported_format
|
|
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
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'multipart_boundary'
|
|
4
|
+
require_relative 'streaming_multipart_body'
|
|
5
|
+
|
|
6
|
+
module HTTParty
|
|
7
|
+
class Request
|
|
8
|
+
class Body
|
|
9
|
+
NEWLINE = "\r\n"
|
|
10
|
+
private_constant :NEWLINE
|
|
11
|
+
|
|
12
|
+
def initialize(params, query_string_normalizer: nil, force_multipart: false)
|
|
13
|
+
@params = params
|
|
14
|
+
@query_string_normalizer = query_string_normalizer
|
|
15
|
+
@force_multipart = force_multipart
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
if params.respond_to?(:to_hash)
|
|
20
|
+
multipart? ? generate_multipart : normalize_query(params)
|
|
21
|
+
else
|
|
22
|
+
params
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def boundary
|
|
27
|
+
@boundary ||= MultipartBoundary.generate
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def multipart?
|
|
31
|
+
params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def streaming?
|
|
35
|
+
multipart? && has_file?(params)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_stream
|
|
39
|
+
return nil unless streaming?
|
|
40
|
+
StreamingMultipartBody.new(prepared_parts, boundary)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prepared_parts
|
|
44
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
45
|
+
normalized_params.map do |key, value|
|
|
46
|
+
[key, value, file?(value)]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# https://html.spec.whatwg.org/#multipart-form-data
|
|
53
|
+
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
|
54
|
+
'"' => '%22',
|
|
55
|
+
"\r" => '%0D',
|
|
56
|
+
"\n" => '%0A'
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
def generate_multipart
|
|
60
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
|
61
|
+
|
|
62
|
+
multipart = normalized_params.inject(''.b) do |memo, (key, value)|
|
|
63
|
+
memo << "--#{boundary}#{NEWLINE}".b
|
|
64
|
+
memo << %(Content-Disposition: form-data; name="#{key}").b
|
|
65
|
+
# value.path is used to support ActionDispatch::Http::UploadedFile
|
|
66
|
+
# https://github.com/jnunemaker/httparty/pull/585
|
|
67
|
+
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}").b if file?(value)
|
|
68
|
+
memo << NEWLINE.b
|
|
69
|
+
memo << "Content-Type: #{content_type(value)}#{NEWLINE}".b if file?(value)
|
|
70
|
+
memo << NEWLINE.b
|
|
71
|
+
memo << content_body(value)
|
|
72
|
+
memo << NEWLINE.b
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
multipart << "--#{boundary}--#{NEWLINE}".b
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def has_file?(value)
|
|
79
|
+
if value.respond_to?(:to_hash)
|
|
80
|
+
value.to_hash.any? { |_, v| has_file?(v) }
|
|
81
|
+
elsif value.respond_to?(:to_ary)
|
|
82
|
+
value.to_ary.any? { |v| has_file?(v) }
|
|
83
|
+
else
|
|
84
|
+
file?(value)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def file?(object)
|
|
89
|
+
object.respond_to?(:path) && object.respond_to?(:read)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize_query(query)
|
|
93
|
+
if query_string_normalizer
|
|
94
|
+
query_string_normalizer.call(query)
|
|
95
|
+
else
|
|
96
|
+
HashConversions.to_params(query)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def content_body(object)
|
|
101
|
+
if file?(object)
|
|
102
|
+
object = (file = object).read
|
|
103
|
+
object.force_encoding(Encoding::BINARY) if object.respond_to?(:force_encoding)
|
|
104
|
+
file.rewind if file.respond_to?(:rewind)
|
|
105
|
+
object.to_s
|
|
106
|
+
else
|
|
107
|
+
object.to_s.b
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def content_type(object)
|
|
112
|
+
return object.content_type if object.respond_to?(:content_type)
|
|
113
|
+
require 'mini_mime'
|
|
114
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
|
115
|
+
mime ? mime.content_type : 'application/octet-stream'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def file_name(object)
|
|
119
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
attr_reader :params, :query_string_normalizer, :force_multipart
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|