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,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTParty
|
|
4
|
+
class Request
|
|
5
|
+
class StreamingMultipartBody
|
|
6
|
+
NEWLINE = "\r\n"
|
|
7
|
+
CHUNK_SIZE = 64 * 1024 # 64 KB chunks
|
|
8
|
+
|
|
9
|
+
def initialize(parts, boundary)
|
|
10
|
+
@parts = parts
|
|
11
|
+
@boundary = boundary
|
|
12
|
+
@part_index = 0
|
|
13
|
+
@state = :header
|
|
14
|
+
@current_file = nil
|
|
15
|
+
@header_buffer = nil
|
|
16
|
+
@header_offset = 0
|
|
17
|
+
@footer_sent = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def size
|
|
21
|
+
@size ||= calculate_size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read(length = nil, outbuf = nil)
|
|
25
|
+
outbuf = outbuf ? outbuf.replace(''.b) : ''.b
|
|
26
|
+
|
|
27
|
+
return read_all(outbuf) if length.nil?
|
|
28
|
+
|
|
29
|
+
while outbuf.bytesize < length
|
|
30
|
+
chunk = read_chunk(length - outbuf.bytesize)
|
|
31
|
+
break if chunk.nil?
|
|
32
|
+
outbuf << chunk
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
outbuf.empty? ? nil : outbuf
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def rewind
|
|
39
|
+
@part_index = 0
|
|
40
|
+
@state = :header
|
|
41
|
+
@current_file = nil
|
|
42
|
+
@header_buffer = nil
|
|
43
|
+
@header_offset = 0
|
|
44
|
+
@footer_sent = false
|
|
45
|
+
@parts.each do |_key, value, _is_file|
|
|
46
|
+
value.rewind if value.respond_to?(:rewind)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def read_all(outbuf)
|
|
53
|
+
while (chunk = read_chunk(CHUNK_SIZE))
|
|
54
|
+
outbuf << chunk
|
|
55
|
+
end
|
|
56
|
+
outbuf.empty? ? nil : outbuf
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def read_chunk(max_length)
|
|
60
|
+
loop do
|
|
61
|
+
return nil if @part_index >= @parts.size && @footer_sent
|
|
62
|
+
|
|
63
|
+
if @part_index >= @parts.size
|
|
64
|
+
@footer_sent = true
|
|
65
|
+
return "--#{@boundary}--#{NEWLINE}".b
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
key, value, is_file = @parts[@part_index]
|
|
69
|
+
|
|
70
|
+
case @state
|
|
71
|
+
when :header
|
|
72
|
+
chunk = read_header_chunk(key, value, is_file, max_length)
|
|
73
|
+
return chunk if chunk
|
|
74
|
+
|
|
75
|
+
when :body
|
|
76
|
+
chunk = read_body_chunk(value, is_file, max_length)
|
|
77
|
+
return chunk if chunk
|
|
78
|
+
|
|
79
|
+
when :newline
|
|
80
|
+
@state = :header
|
|
81
|
+
@part_index += 1
|
|
82
|
+
return NEWLINE.b
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def read_header_chunk(key, value, is_file, max_length)
|
|
88
|
+
if @header_buffer.nil?
|
|
89
|
+
@header_buffer = build_part_header(key, value, is_file)
|
|
90
|
+
@header_offset = 0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
remaining = @header_buffer.bytesize - @header_offset
|
|
94
|
+
if remaining > 0
|
|
95
|
+
chunk_size = [remaining, max_length].min
|
|
96
|
+
chunk = @header_buffer.byteslice(@header_offset, chunk_size)
|
|
97
|
+
@header_offset += chunk_size
|
|
98
|
+
return chunk
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@header_buffer = nil
|
|
102
|
+
@header_offset = 0
|
|
103
|
+
@state = :body
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read_body_chunk(value, is_file, max_length)
|
|
108
|
+
if is_file
|
|
109
|
+
chunk = read_file_chunk(value, max_length)
|
|
110
|
+
if chunk
|
|
111
|
+
return chunk
|
|
112
|
+
else
|
|
113
|
+
@current_file = nil
|
|
114
|
+
@state = :newline
|
|
115
|
+
return nil
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
@state = :newline
|
|
119
|
+
return value.to_s.b
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def read_file_chunk(file, max_length)
|
|
124
|
+
chunk_size = [max_length, CHUNK_SIZE].min
|
|
125
|
+
chunk = file.read(chunk_size)
|
|
126
|
+
return nil if chunk.nil?
|
|
127
|
+
chunk.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
|
|
128
|
+
chunk
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_part_header(key, value, is_file)
|
|
132
|
+
header = "--#{@boundary}#{NEWLINE}".b
|
|
133
|
+
header << %(Content-Disposition: form-data; name="#{key}").b
|
|
134
|
+
if is_file
|
|
135
|
+
header << %(; filename="#{file_name(value).gsub(/["\r\n]/, replacement_table)}").b
|
|
136
|
+
header << NEWLINE.b
|
|
137
|
+
header << "Content-Type: #{content_type(value)}#{NEWLINE}".b
|
|
138
|
+
else
|
|
139
|
+
header << NEWLINE.b
|
|
140
|
+
end
|
|
141
|
+
header << NEWLINE.b
|
|
142
|
+
header
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def calculate_size
|
|
146
|
+
total = 0
|
|
147
|
+
@parts.each do |key, value, is_file|
|
|
148
|
+
total += build_part_header(key, value, is_file).bytesize
|
|
149
|
+
total += content_size(value, is_file)
|
|
150
|
+
total += NEWLINE.bytesize
|
|
151
|
+
end
|
|
152
|
+
total += "--#{@boundary}--#{NEWLINE}".bytesize
|
|
153
|
+
total
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def content_size(value, is_file)
|
|
157
|
+
if is_file
|
|
158
|
+
if value.respond_to?(:size)
|
|
159
|
+
value.size
|
|
160
|
+
elsif value.respond_to?(:stat)
|
|
161
|
+
value.stat.size
|
|
162
|
+
else
|
|
163
|
+
value.read.bytesize.tap { value.rewind }
|
|
164
|
+
end
|
|
165
|
+
else
|
|
166
|
+
value.to_s.b.bytesize
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def content_type(object)
|
|
171
|
+
return object.content_type if object.respond_to?(:content_type)
|
|
172
|
+
require 'mini_mime'
|
|
173
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
|
174
|
+
mime ? mime.content_type : 'application/octet-stream'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def file_name(object)
|
|
178
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def replacement_table
|
|
182
|
+
@replacement_table ||= {
|
|
183
|
+
'"' => '%22',
|
|
184
|
+
"\r" => '%0D',
|
|
185
|
+
"\n" => '%0A'
|
|
186
|
+
}.freeze
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
5
|
+
module HTTParty
|
|
6
|
+
class Request #:nodoc:
|
|
7
|
+
SupportedHTTPMethods = [
|
|
8
|
+
Net::HTTP::Get,
|
|
9
|
+
Net::HTTP::Post,
|
|
10
|
+
Net::HTTP::Patch,
|
|
11
|
+
Net::HTTP::Put,
|
|
12
|
+
Net::HTTP::Delete,
|
|
13
|
+
Net::HTTP::Head,
|
|
14
|
+
Net::HTTP::Options,
|
|
15
|
+
Net::HTTP::Move,
|
|
16
|
+
Net::HTTP::Copy,
|
|
17
|
+
Net::HTTP::Mkcol,
|
|
18
|
+
Net::HTTP::Lock,
|
|
19
|
+
Net::HTTP::Unlock,
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
SupportedURISchemes = ['http', 'https', 'webcal', nil]
|
|
23
|
+
|
|
24
|
+
NON_RAILS_QUERY_STRING_NORMALIZER = proc do |query|
|
|
25
|
+
Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
|
|
26
|
+
if value.nil?
|
|
27
|
+
key.to_s
|
|
28
|
+
elsif value.respond_to?(:to_ary)
|
|
29
|
+
value.to_ary.map {|v| "#{key}=#{ERB::Util.url_encode(v.to_s)}"}
|
|
30
|
+
else
|
|
31
|
+
HashConversions.to_params(key => value)
|
|
32
|
+
end
|
|
33
|
+
end.flatten.join('&')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
JSON_API_QUERY_STRING_NORMALIZER = proc do |query|
|
|
37
|
+
Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
|
|
38
|
+
if value.nil?
|
|
39
|
+
key.to_s
|
|
40
|
+
elsif value.respond_to?(:to_ary)
|
|
41
|
+
values = value.to_ary.map{|v| ERB::Util.url_encode(v.to_s)}
|
|
42
|
+
"#{key}=#{values.join(',')}"
|
|
43
|
+
else
|
|
44
|
+
HashConversions.to_params(key => value)
|
|
45
|
+
end
|
|
46
|
+
end.flatten.join('&')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self._load(data)
|
|
50
|
+
http_method, path, options, last_response, last_uri, raw_request = Marshal.load(data)
|
|
51
|
+
instance = new(http_method, path, options)
|
|
52
|
+
instance.last_response = last_response
|
|
53
|
+
instance.last_uri = last_uri
|
|
54
|
+
instance.instance_variable_set("@raw_request", raw_request)
|
|
55
|
+
instance
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
|
|
59
|
+
attr_reader :path
|
|
60
|
+
|
|
61
|
+
def initialize(http_method, path, o = {})
|
|
62
|
+
@changed_hosts = false
|
|
63
|
+
@credentials_sent = false
|
|
64
|
+
|
|
65
|
+
self.http_method = http_method
|
|
66
|
+
self.options = {
|
|
67
|
+
limit: o.delete(:no_follow) ? 1 : 5,
|
|
68
|
+
assume_utf16_is_big_endian: true,
|
|
69
|
+
default_params: {},
|
|
70
|
+
follow_redirects: true,
|
|
71
|
+
parser: Parser,
|
|
72
|
+
uri_adapter: URI,
|
|
73
|
+
connection_adapter: ConnectionAdapter
|
|
74
|
+
}.merge(o)
|
|
75
|
+
self.path = path
|
|
76
|
+
set_basic_auth_from_uri
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def path=(uri)
|
|
80
|
+
uri_adapter = options[:uri_adapter]
|
|
81
|
+
|
|
82
|
+
@path = if uri.is_a?(uri_adapter)
|
|
83
|
+
uri
|
|
84
|
+
elsif String.try_convert(uri)
|
|
85
|
+
uri_adapter.parse(uri).normalize
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError,
|
|
88
|
+
"bad argument (expected #{uri_adapter} object or URI string)"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def request_uri(uri)
|
|
93
|
+
if uri.respond_to? :request_uri
|
|
94
|
+
uri.request_uri
|
|
95
|
+
else
|
|
96
|
+
uri.path
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def uri
|
|
101
|
+
if redirect && path.relative? && path.path[0] != '/'
|
|
102
|
+
last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '')
|
|
103
|
+
|
|
104
|
+
path.path = "/#{path.path}" if last_uri_host[-1] != '/'
|
|
105
|
+
path.path = "#{last_uri_host}#{path.path}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if path.relative? && path.host
|
|
109
|
+
new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize
|
|
110
|
+
elsif path.relative?
|
|
111
|
+
new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize
|
|
112
|
+
else
|
|
113
|
+
new_uri = path.clone
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
validate_uri_safety!(new_uri) unless redirect
|
|
117
|
+
|
|
118
|
+
# avoid double query string on redirects [#12]
|
|
119
|
+
unless redirect
|
|
120
|
+
new_uri.query = query_string(new_uri)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unless SupportedURISchemes.include? new_uri.scheme
|
|
124
|
+
raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP, HTTPS or Generic"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
@last_uri = new_uri
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def base_uri
|
|
131
|
+
if redirect
|
|
132
|
+
base_uri = "#{@last_uri.scheme}://#{@last_uri.host}"
|
|
133
|
+
base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80
|
|
134
|
+
base_uri
|
|
135
|
+
else
|
|
136
|
+
options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def format
|
|
141
|
+
options[:format] || (format_from_mimetype(last_response['content-type']) if last_response)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def parser
|
|
145
|
+
options[:parser]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def connection_adapter
|
|
149
|
+
options[:connection_adapter]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def perform(&block)
|
|
153
|
+
validate
|
|
154
|
+
setup_raw_request
|
|
155
|
+
chunked_body = nil
|
|
156
|
+
current_http = http
|
|
157
|
+
|
|
158
|
+
begin
|
|
159
|
+
self.last_response = current_http.request(@raw_request) do |http_response|
|
|
160
|
+
if block
|
|
161
|
+
chunks = []
|
|
162
|
+
|
|
163
|
+
http_response.read_body do |fragment|
|
|
164
|
+
encoded_fragment = encode_text(fragment, http_response['content-type'])
|
|
165
|
+
chunks << encoded_fragment if !options[:stream_body]
|
|
166
|
+
block.call ResponseFragment.new(encoded_fragment, http_response, current_http)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
chunked_body = chunks.join
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
handle_host_redirection if response_redirects?
|
|
174
|
+
result = handle_unauthorized
|
|
175
|
+
result ||= handle_response(chunked_body, &block)
|
|
176
|
+
result
|
|
177
|
+
rescue *COMMON_NETWORK_ERRORS => e
|
|
178
|
+
raise options[:foul] ? HTTParty::NetworkError.new("#{e.class}: #{e.message}") : e
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def handle_unauthorized(&block)
|
|
183
|
+
return unless digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
|
184
|
+
return if @credentials_sent
|
|
185
|
+
@credentials_sent = true
|
|
186
|
+
perform(&block)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def raw_body
|
|
190
|
+
@raw_request.body
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def _dump(_level)
|
|
194
|
+
opts = options.dup
|
|
195
|
+
opts.delete(:logger)
|
|
196
|
+
opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc)
|
|
197
|
+
Marshal.dump([http_method, path, opts, last_response, @last_uri, @raw_request])
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def http
|
|
203
|
+
connection_adapter.call(uri, options)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def credentials
|
|
207
|
+
(options[:basic_auth] || options[:digest_auth]).to_hash
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def username
|
|
211
|
+
credentials[:username]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def password
|
|
215
|
+
credentials[:password]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def normalize_query(query)
|
|
219
|
+
if query_string_normalizer
|
|
220
|
+
query_string_normalizer.call(query)
|
|
221
|
+
else
|
|
222
|
+
HashConversions.to_params(query)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def query_string_normalizer
|
|
227
|
+
options[:query_string_normalizer]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def setup_raw_request
|
|
231
|
+
if options[:headers].respond_to?(:to_hash)
|
|
232
|
+
headers_hash = options[:headers].to_hash
|
|
233
|
+
else
|
|
234
|
+
headers_hash = nil
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
@raw_request = http_method.new(request_uri(uri), headers_hash)
|
|
238
|
+
@raw_request.body_stream = options[:body_stream] if options[:body_stream]
|
|
239
|
+
|
|
240
|
+
if options[:body]
|
|
241
|
+
body = Body.new(
|
|
242
|
+
options[:body],
|
|
243
|
+
query_string_normalizer: query_string_normalizer,
|
|
244
|
+
force_multipart: options[:multipart]
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if body.multipart?
|
|
248
|
+
content_type = "multipart/form-data; boundary=#{body.boundary}"
|
|
249
|
+
@raw_request['Content-Type'] = content_type
|
|
250
|
+
elsif options[:body].respond_to?(:to_hash) && !@raw_request['Content-Type']
|
|
251
|
+
@raw_request['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if body.streaming? && options[:stream_body] == true
|
|
255
|
+
stream = body.to_stream
|
|
256
|
+
@raw_request.body_stream = stream
|
|
257
|
+
@raw_request['Content-Length'] = stream.size.to_s
|
|
258
|
+
else
|
|
259
|
+
@raw_request.body = body.call
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
@raw_request.instance_variable_set(:@decode_content, decompress_content?)
|
|
264
|
+
|
|
265
|
+
if options[:basic_auth] && send_authorization_header?
|
|
266
|
+
@raw_request.basic_auth(username, password)
|
|
267
|
+
@credentials_sent = true
|
|
268
|
+
end
|
|
269
|
+
setup_digest_auth if digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def digest_auth?
|
|
273
|
+
!!options[:digest_auth]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def decompress_content?
|
|
277
|
+
!options[:skip_decompression]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def response_unauthorized?
|
|
281
|
+
!!last_response && last_response.code == '401'
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def response_has_digest_auth_challenge?
|
|
285
|
+
!last_response['www-authenticate'].nil? && last_response['www-authenticate'].length > 0
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def setup_digest_auth
|
|
289
|
+
@raw_request.digest_auth(username, password, last_response)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def query_string(uri)
|
|
293
|
+
query_string_parts = []
|
|
294
|
+
query_string_parts << uri.query unless uri.query.nil?
|
|
295
|
+
|
|
296
|
+
if options[:query].respond_to?(:to_hash)
|
|
297
|
+
query_string_parts << normalize_query(options[:default_params].merge(options[:query].to_hash))
|
|
298
|
+
else
|
|
299
|
+
query_string_parts << normalize_query(options[:default_params]) unless options[:default_params].empty?
|
|
300
|
+
query_string_parts << options[:query] unless options[:query].nil?
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
query_string_parts.reject!(&:empty?) unless query_string_parts == ['']
|
|
304
|
+
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def assume_utf16_is_big_endian
|
|
308
|
+
options[:assume_utf16_is_big_endian]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def handle_response(raw_body, &block)
|
|
312
|
+
if response_redirects?
|
|
313
|
+
handle_redirection(&block)
|
|
314
|
+
else
|
|
315
|
+
raw_body ||= last_response.body
|
|
316
|
+
|
|
317
|
+
body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil?
|
|
318
|
+
|
|
319
|
+
unless body.nil?
|
|
320
|
+
body = encode_text(body, last_response['content-type'])
|
|
321
|
+
|
|
322
|
+
if decompress_content?
|
|
323
|
+
last_response.delete('content-encoding')
|
|
324
|
+
raw_body = body
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def handle_redirection(&block)
|
|
333
|
+
options[:limit] -= 1
|
|
334
|
+
if options[:logger]
|
|
335
|
+
logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format])
|
|
336
|
+
logger.format(self, last_response)
|
|
337
|
+
end
|
|
338
|
+
self.path = last_response['location']
|
|
339
|
+
self.redirect = true
|
|
340
|
+
if last_response.class == Net::HTTPSeeOther
|
|
341
|
+
unless options[:maintain_method_across_redirects] && options[:resend_on_redirect]
|
|
342
|
+
self.http_method = Net::HTTP::Get
|
|
343
|
+
end
|
|
344
|
+
elsif last_response.code != '307' && last_response.code != '308'
|
|
345
|
+
unless options[:maintain_method_across_redirects]
|
|
346
|
+
self.http_method = Net::HTTP::Get
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
if http_method == Net::HTTP::Get
|
|
350
|
+
clear_body
|
|
351
|
+
end
|
|
352
|
+
capture_cookies(last_response)
|
|
353
|
+
perform(&block)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def handle_host_redirection
|
|
357
|
+
check_duplicate_location_header
|
|
358
|
+
redirect_path = options[:uri_adapter].parse(last_response['location']).normalize
|
|
359
|
+
return if redirect_path.relative? || path.host == redirect_path.host || uri.host == redirect_path.host
|
|
360
|
+
@changed_hosts = true
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def check_duplicate_location_header
|
|
364
|
+
location = last_response.get_fields('location')
|
|
365
|
+
if location.is_a?(Array) && location.count > 1
|
|
366
|
+
raise DuplicateLocationHeader.new(last_response)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def send_authorization_header?
|
|
371
|
+
!@changed_hosts
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def response_redirects?
|
|
375
|
+
case last_response
|
|
376
|
+
when Net::HTTPNotModified # 304
|
|
377
|
+
false
|
|
378
|
+
when Net::HTTPRedirection
|
|
379
|
+
options[:follow_redirects] && last_response.key?('location')
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def parse_response(body)
|
|
384
|
+
parser.call(body, format)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Some Web Application Firewalls reject incoming GET requests that have a body
|
|
388
|
+
# if we redirect, and the resulting verb is GET then we will clear the body that
|
|
389
|
+
# may be left behind from the initiating request
|
|
390
|
+
def clear_body
|
|
391
|
+
options[:body] = nil
|
|
392
|
+
@raw_request.body = nil
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def capture_cookies(response)
|
|
396
|
+
return unless response['Set-Cookie']
|
|
397
|
+
cookies_hash = HTTParty::CookieHash.new
|
|
398
|
+
cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
|
|
399
|
+
response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
|
|
400
|
+
|
|
401
|
+
options[:headers] ||= {}
|
|
402
|
+
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Uses the HTTP Content-Type header to determine the format of the
|
|
406
|
+
# response It compares the MIME type returned to the types stored in the
|
|
407
|
+
# SupportedFormats hash
|
|
408
|
+
def format_from_mimetype(mimetype)
|
|
409
|
+
if mimetype && parser.respond_to?(:format_from_mimetype)
|
|
410
|
+
parser.format_from_mimetype(mimetype)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def validate
|
|
415
|
+
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
|
|
416
|
+
raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
|
|
417
|
+
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash)
|
|
418
|
+
raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
|
|
419
|
+
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash)
|
|
420
|
+
raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash)
|
|
421
|
+
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def post?
|
|
425
|
+
Net::HTTP::Post == http_method
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def set_basic_auth_from_uri
|
|
429
|
+
if path.userinfo
|
|
430
|
+
username, password = path.userinfo.split(':')
|
|
431
|
+
options[:basic_auth] = {username: username, password: password}
|
|
432
|
+
@credentials_sent = true
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def decompress(body, encoding)
|
|
437
|
+
Decompressor.new(body, encoding).decompress
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def encode_text(text, content_type)
|
|
441
|
+
TextEncoder.new(
|
|
442
|
+
text,
|
|
443
|
+
content_type: content_type,
|
|
444
|
+
assume_utf16_is_big_endian: assume_utf16_is_big_endian
|
|
445
|
+
).call
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def validate_uri_safety!(new_uri)
|
|
449
|
+
return if options[:skip_uri_validation]
|
|
450
|
+
|
|
451
|
+
configured_base_uri = options[:base_uri]
|
|
452
|
+
return unless configured_base_uri
|
|
453
|
+
|
|
454
|
+
normalized_base = options[:uri_adapter].parse(
|
|
455
|
+
HTTParty.normalize_base_uri(configured_base_uri)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
return if new_uri.host == normalized_base.host
|
|
459
|
+
|
|
460
|
+
raise UnsafeURIError,
|
|
461
|
+
"Requested URI '#{new_uri}' has host '#{new_uri.host}' but the " \
|
|
462
|
+
"configured base_uri '#{normalized_base}' has host '#{normalized_base.host}'. " \
|
|
463
|
+
"This request could send credentials to an unintended server."
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'delegate'
|
|
4
|
+
|
|
5
|
+
module HTTParty
|
|
6
|
+
class Response #:nodoc:
|
|
7
|
+
class Headers < ::SimpleDelegator
|
|
8
|
+
include ::Net::HTTPHeader
|
|
9
|
+
|
|
10
|
+
def initialize(header_values = nil)
|
|
11
|
+
@header = {}
|
|
12
|
+
if header_values
|
|
13
|
+
header_values.each_pair do |k,v|
|
|
14
|
+
if v.is_a?(Array)
|
|
15
|
+
v.each do |sub_v|
|
|
16
|
+
add_field(k, sub_v)
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
add_field(k, v)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
super(@header)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
if other.is_a?(::Net::HTTPHeader)
|
|
28
|
+
@header == other.instance_variable_get(:@header)
|
|
29
|
+
elsif other.is_a?(Hash)
|
|
30
|
+
@header == other || @header == Headers.new(other).instance_variable_get(:@header)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|