esank-rest-client 1.6.7

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.
@@ -0,0 +1,55 @@
1
+ module Net
2
+ class HTTP
3
+
4
+ # Adding the patch method if it doesn't exist (rest-client issue: https://github.com/archiloque/rest-client/issues/79)
5
+ if !defined?(Net::HTTP::Patch)
6
+ # Code taken from this commit: https://github.com/ruby/ruby/commit/ab70e53ac3b5102d4ecbe8f38d4f76afad29d37d#lib/net/http.rb
7
+ class Protocol
8
+ # Sends a PATCH request to the +path+ and gets a response,
9
+ # as an HTTPResponse object.
10
+ def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
11
+ send_entity(path, data, initheader, dest, Patch, &block)
12
+ end
13
+
14
+ # Executes a request which uses a representation
15
+ # and returns its body.
16
+ def send_entity(path, data, initheader, dest, type, &block)
17
+ res = nil
18
+ request(type.new(path, initheader), data) {|r|
19
+ r.read_body dest, &block
20
+ res = r
21
+ }
22
+ unless @newimpl
23
+ res.value
24
+ return res, res.body
25
+ end
26
+ res
27
+ end
28
+ end
29
+
30
+ class Patch < HTTPRequest
31
+ METHOD = 'PATCH'
32
+ REQUEST_HAS_BODY = true
33
+ RESPONSE_HAS_BODY = true
34
+ end
35
+ end
36
+
37
+ #
38
+ # Replace the request method in Net::HTTP to sniff the body type
39
+ # and set the stream if appropriate
40
+ #
41
+ # Taken from:
42
+ # http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
43
+
44
+ alias __request__ request
45
+
46
+ def request(req, body=nil, &block)
47
+ if body != nil && body.respond_to?(:read)
48
+ req.body_stream = body
49
+ return __request__(req, nil, &block)
50
+ else
51
+ return __request__(req, body, &block)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,235 @@
1
+ require 'tempfile'
2
+ require 'stringio'
3
+ require 'mime/types'
4
+
5
+ module RestClient
6
+ module Payload
7
+ extend self
8
+
9
+ def generate(params)
10
+ if params.is_a?(String)
11
+ Base.new(params)
12
+ elsif params.respond_to?(:read)
13
+ Streamed.new(params)
14
+ elsif params
15
+ if params.delete(:multipart) == true || has_file?(params)
16
+ Multipart.new(params)
17
+ else
18
+ UrlEncoded.new(params)
19
+ end
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def has_file?(params)
26
+ params.any? do |_, v|
27
+ case v
28
+ when Hash
29
+ has_file?(v)
30
+ when Array
31
+ has_file_array?(v)
32
+ else
33
+ v.respond_to?(:path) && v.respond_to?(:read)
34
+ end
35
+ end
36
+ end
37
+
38
+ def has_file_array?(params)
39
+ params.any? do |v|
40
+ case v
41
+ when Hash
42
+ has_file?(v)
43
+ when Array
44
+ has_file_array?(v)
45
+ else
46
+ v.respond_to?(:path) && v.respond_to?(:read)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Base
52
+ def initialize(params)
53
+ build_stream(params)
54
+ end
55
+
56
+ def build_stream(params)
57
+ @stream = StringIO.new(params)
58
+ @stream.seek(0)
59
+ end
60
+
61
+ def read(bytes=nil)
62
+ @stream.read(bytes)
63
+ end
64
+
65
+ alias :to_s :read
66
+
67
+ # Flatten parameters by converting hashes of hashes to flat hashes
68
+ # {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value]
69
+ def flatten_params(params, parent_key = nil)
70
+ result = []
71
+ params.each do |key, value|
72
+ calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key)
73
+ if value.is_a? Hash
74
+ result += flatten_params(value, calculated_key)
75
+ elsif value.is_a? Array
76
+ result += flatten_params_array(value, calculated_key)
77
+ else
78
+ result << [calculated_key, value]
79
+ end
80
+ end
81
+ result
82
+ end
83
+
84
+ def flatten_params_array value, calculated_key
85
+ result = []
86
+ value.each do |elem|
87
+ if elem.is_a? Hash
88
+ result += flatten_params(elem, calculated_key)
89
+ elsif elem.is_a? Array
90
+ result += flatten_params_array(elem, calculated_key)
91
+ else
92
+ result << ["#{calculated_key}[]", elem]
93
+ end
94
+ end
95
+ result
96
+ end
97
+
98
+ def headers
99
+ {'Content-Length' => size.to_s}
100
+ end
101
+
102
+ def size
103
+ @stream.size
104
+ end
105
+
106
+ alias :length :size
107
+
108
+ def close
109
+ @stream.close unless @stream.closed?
110
+ end
111
+
112
+ def inspect
113
+ result = to_s.inspect
114
+ @stream.seek(0)
115
+ result
116
+ end
117
+
118
+ def short_inspect
119
+ (size > 500 ? "#{size} byte(s) length" : inspect)
120
+ end
121
+
122
+ end
123
+
124
+ class Streamed < Base
125
+ def build_stream(params = nil)
126
+ @stream = params
127
+ end
128
+
129
+ def size
130
+ if @stream.respond_to?(:size)
131
+ @stream.size
132
+ elsif @stream.is_a?(IO)
133
+ @stream.stat.size
134
+ end
135
+ end
136
+
137
+ alias :length :size
138
+ end
139
+
140
+ class UrlEncoded < Base
141
+ def build_stream(params = nil)
142
+ @stream = StringIO.new(flatten_params(params).collect do |entry|
143
+ "#{entry[0]}=#{handle_key(entry[1])}"
144
+ end.join("&"))
145
+ @stream.seek(0)
146
+ end
147
+
148
+ # for UrlEncoded escape the keys
149
+ def handle_key key
150
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
151
+ end
152
+
153
+ def headers
154
+ super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
155
+ end
156
+ end
157
+
158
+ class Multipart < Base
159
+ EOL = "\r\n"
160
+
161
+ def build_stream(params)
162
+ b = "--#{boundary}"
163
+
164
+ @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
165
+ @stream.binmode
166
+ @stream.write(b + EOL)
167
+
168
+ if params.is_a? Hash
169
+ x = flatten_params(params)
170
+ else
171
+ x = params
172
+ end
173
+
174
+ last_index = x.length - 1
175
+ x.each_with_index do |a, index|
176
+ k, v = * a
177
+ if v.respond_to?(:read) && v.respond_to?(:path)
178
+ create_file_field(@stream, k, v)
179
+ else
180
+ create_regular_field(@stream, k, v)
181
+ end
182
+ @stream.write(EOL + b)
183
+ @stream.write(EOL) unless last_index == index
184
+ end
185
+ @stream.write('--')
186
+ @stream.write(EOL)
187
+ @stream.seek(0)
188
+ end
189
+
190
+ def create_regular_field(s, k, v)
191
+ s.write("Content-Disposition: form-data; name=\"#{k}\"")
192
+ s.write(EOL)
193
+ s.write(EOL)
194
+ s.write(v)
195
+ end
196
+
197
+ def create_file_field(s, k, v)
198
+ begin
199
+ s.write("Content-Disposition: form-data;")
200
+ s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
201
+ s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
202
+ s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
203
+ s.write(EOL)
204
+ while data = v.read(8124)
205
+ s.write(data)
206
+ end
207
+ ensure
208
+ v.close if v.respond_to?(:close)
209
+ end
210
+ end
211
+
212
+ def mime_for(path)
213
+ mime = MIME::Types.type_for path
214
+ mime.empty? ? 'text/plain' : mime[0].content_type
215
+ end
216
+
217
+ def boundary
218
+ @boundary ||= rand(1_000_000).to_s
219
+ end
220
+
221
+ # for Multipart do not escape the keys
222
+ def handle_key key
223
+ key
224
+ end
225
+
226
+ def headers
227
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
228
+ end
229
+
230
+ def close
231
+ @stream.close!
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,34 @@
1
+ module RestClient
2
+ # The response from RestClient on a raw request looks like a string, but is
3
+ # actually one of these. 99% of the time you're making a rest call all you
4
+ # care about is the body, but on the occassion you want to fetch the
5
+ # headers you can:
6
+ #
7
+ # RestClient.get('http://example.com').headers[:content_type]
8
+ #
9
+ # In addition, if you do not use the response as a string, you can access
10
+ # a Tempfile object at res.file, which contains the path to the raw
11
+ # downloaded request body.
12
+ class RawResponse
13
+
14
+ include AbstractResponse
15
+
16
+ attr_reader :file
17
+
18
+ def initialize tempfile, net_http_res, args
19
+ @net_http_res = net_http_res
20
+ @args = args
21
+ @file = tempfile
22
+ end
23
+
24
+ def to_s
25
+ @file.open
26
+ @file.read
27
+ end
28
+
29
+ def size
30
+ File.size file
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,320 @@
1
+ require 'tempfile'
2
+ require 'mime/types'
3
+ require 'cgi'
4
+ require 'netrc'
5
+
6
+ module RestClient
7
+ # This class is used internally by RestClient to send the request, but you can also
8
+ # call it directly if you'd like to use a method not supported by the
9
+ # main API. For example:
10
+ #
11
+ # RestClient::Request.execute(:method => :head, :url => 'http://example.com')
12
+ #
13
+ # Mandatory parameters:
14
+ # * :method
15
+ # * :url
16
+ # Optional parameters (have a look at ssl and/or uri for some explanations):
17
+ # * :headers a hash containing the request headers
18
+ # * :cookies will replace possible cookies in the :headers
19
+ # * :user and :password for basic auth, will be replaced by a user/password available in the :url
20
+ # * :block_response call the provided block with the HTTPResponse as parameter
21
+ # * :raw_response return a low-level RawResponse instead of a Response
22
+ # * :max_redirects maximum number of redirections (default to 10)
23
+ # * :verify_ssl enable ssl verification, possible values are constants from OpenSSL::SSL
24
+ # * :timeout and :open_timeout passing in -1 will disable the timeout by setting the corresponding net timeout values to nil
25
+ # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file
26
+ class Request
27
+
28
+ attr_reader :method, :url, :headers, :cookies,
29
+ :payload, :user, :password, :timeout, :max_redirects,
30
+ :open_timeout, :raw_response, :verify_ssl, :ssl_client_cert,
31
+ :ssl_client_key, :ssl_ca_file, :processed_headers, :args
32
+
33
+ def self.execute(args, & block)
34
+ new(args).execute(& block)
35
+ end
36
+
37
+ def initialize args
38
+ @method = args[:method] or raise ArgumentError, "must pass :method"
39
+ @headers = args[:headers] || {}
40
+ if args[:url]
41
+ @url = process_url_params(args[:url], headers)
42
+ else
43
+ raise ArgumentError, "must pass :url"
44
+ end
45
+ @cookies = @headers.delete(:cookies) || args[:cookies] || {}
46
+ @payload = Payload.generate(args[:payload])
47
+ @user = args[:user]
48
+ @password = args[:password]
49
+ @timeout = args[:timeout]
50
+ @open_timeout = args[:open_timeout]
51
+ @block_response = args[:block_response]
52
+ @raw_response = args[:raw_response] || false
53
+ @verify_ssl = args[:verify_ssl] || false
54
+ @ssl_client_cert = args[:ssl_client_cert] || nil
55
+ @ssl_client_key = args[:ssl_client_key] || nil
56
+ @ssl_ca_file = args[:ssl_ca_file] || nil
57
+ @tf = nil # If you are a raw request, this is your tempfile
58
+ @max_redirects = args[:max_redirects] || 10
59
+ @processed_headers = make_headers headers
60
+ @args = args
61
+ end
62
+
63
+ def execute & block
64
+ uri = parse_url_with_auth(url)
65
+ transmit uri, net_http_request_class(method).new(uri.request_uri, processed_headers), payload, & block
66
+ ensure
67
+ payload.close if payload
68
+ end
69
+
70
+ # Extract the query parameters and append them to the url
71
+ def process_url_params url, headers
72
+ url_params = {}
73
+ headers.delete_if do |key, value|
74
+ if 'params' == key.to_s.downcase && value.is_a?(Hash)
75
+ url_params.merge! value
76
+ true
77
+ else
78
+ false
79
+ end
80
+ end
81
+ unless url_params.empty?
82
+ query_string = url_params.collect { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
83
+ url + "?#{query_string}"
84
+ else
85
+ url
86
+ end
87
+ end
88
+
89
+ def make_headers user_headers
90
+ unless @cookies.empty?
91
+ user_headers[:cookie] = @cookies.map { |(key, val)| "#{key.to_s}=#{CGI::unescape(val.to_s)}" }.sort.join('; ')
92
+ end
93
+ headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
94
+ headers.merge!(@payload.headers) if @payload
95
+ headers
96
+ end
97
+
98
+ def net_http_class
99
+ if RestClient.proxy
100
+ proxy_uri = URI.parse(RestClient.proxy)
101
+ Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
102
+ else
103
+ Net::HTTP
104
+ end
105
+ end
106
+
107
+ def net_http_request_class(method)
108
+ Net::HTTP.const_get(method.to_s.capitalize)
109
+ end
110
+
111
+ def parse_url(url)
112
+ url = "http://#{url}" unless url.match(/^http/)
113
+ URI.parse(url)
114
+ end
115
+
116
+ def parse_url_with_auth(url)
117
+ uri = parse_url(url)
118
+ @user = CGI.unescape(uri.user) if uri.user
119
+ @password = CGI.unescape(uri.password) if uri.password
120
+ if !@user && !@password
121
+ @user, @password = Netrc.read[uri.host]
122
+ end
123
+ uri
124
+ end
125
+
126
+ def process_payload(p=nil, parent_key=nil)
127
+ unless p.is_a?(Hash)
128
+ p
129
+ else
130
+ @headers[:content_type] ||= 'application/x-www-form-urlencoded'
131
+ p.keys.map do |k|
132
+ key = parent_key ? "#{parent_key}[#{k}]" : k
133
+ if p[k].is_a? Hash
134
+ process_payload(p[k], key)
135
+ else
136
+ value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
137
+ "#{key}=#{value}"
138
+ end
139
+ end.join("&")
140
+ end
141
+ end
142
+
143
+ def transmit uri, req, payload, & block
144
+ setup_credentials req
145
+
146
+ net = net_http_class.new(uri.host, uri.port)
147
+ net.use_ssl = uri.is_a?(URI::HTTPS)
148
+ if (@verify_ssl == false) || (@verify_ssl == OpenSSL::SSL::VERIFY_NONE)
149
+ net.verify_mode = OpenSSL::SSL::VERIFY_NONE
150
+ elsif @verify_ssl.is_a? Integer
151
+ net.verify_mode = @verify_ssl
152
+ net.verify_callback = lambda do |preverify_ok, ssl_context|
153
+ if (!preverify_ok) || ssl_context.error != 0
154
+ err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
155
+ raise SSLCertificateNotVerified.new(err_msg)
156
+ end
157
+ true
158
+ end
159
+ end
160
+ net.cert = @ssl_client_cert if @ssl_client_cert
161
+ net.key = @ssl_client_key if @ssl_client_key
162
+ net.ca_file = @ssl_ca_file if @ssl_ca_file
163
+ net.read_timeout = @timeout if @timeout
164
+ net.open_timeout = @open_timeout if @open_timeout
165
+
166
+ # disable the timeout if the timeout value is -1
167
+ net.read_timeout = nil if @timeout == -1
168
+ net.out_timeout = nil if @open_timeout == -1
169
+
170
+ RestClient.before_execution_procs.each do |before_proc|
171
+ before_proc.call(req, args)
172
+ end
173
+
174
+ log_request
175
+
176
+ net.start do |http|
177
+ if @block_response
178
+ http.request(req, payload ? payload.to_s : nil, & @block_response)
179
+ else
180
+ res = http.request(req, payload ? payload.to_s : nil) { |http_response| fetch_body(http_response) }
181
+ log_response res
182
+ process_result res, & block
183
+ end
184
+ end
185
+ rescue EOFError
186
+ raise RestClient::ServerBrokeConnection
187
+ rescue Timeout::Error
188
+ raise RestClient::RequestTimeout
189
+ end
190
+
191
+ def setup_credentials(req)
192
+ req.basic_auth(user, password) if user
193
+ end
194
+
195
+ def fetch_body(http_response)
196
+ if @raw_response
197
+ # Taken from Chef, which as in turn...
198
+ # Stolen from http://www.ruby-forum.com/topic/166423
199
+ # Kudos to _why!
200
+ @tf = Tempfile.new("rest-client")
201
+ size, total = 0, http_response.header['Content-Length'].to_i
202
+ http_response.read_body do |chunk|
203
+ @tf.write chunk
204
+ size += chunk.size
205
+ if RestClient.log
206
+ if size == 0
207
+ RestClient.log << "#{@method} #{@url} done (0 length file\n)"
208
+ elsif total == 0
209
+ RestClient.log << "#{@method} #{@url} (zero content length)\n"
210
+ else
211
+ RestClient.log << "#{@method} #{@url} %d%% done (%d of %d)\n" % [(size * 100) / total, size, total]
212
+ end
213
+ end
214
+ end
215
+ @tf.close
216
+ @tf
217
+ else
218
+ http_response.read_body
219
+ end
220
+ http_response
221
+ end
222
+
223
+ def process_result res, & block
224
+ if @raw_response
225
+ # We don't decode raw requests
226
+ response = RawResponse.new(@tf, res, args)
227
+ else
228
+ response = Response.create(Request.decode(res['content-encoding'], res.body), res, args)
229
+ end
230
+
231
+ if block_given?
232
+ block.call(response, self, res, & block)
233
+ else
234
+ response.return!(self, res, & block)
235
+ end
236
+
237
+ end
238
+
239
+ def self.decode content_encoding, body
240
+ if (!body) || body.empty?
241
+ body
242
+ elsif content_encoding == 'gzip'
243
+ Zlib::GzipReader.new(StringIO.new(body)).read
244
+ elsif content_encoding == 'deflate'
245
+ begin
246
+ Zlib::Inflate.new.inflate body
247
+ rescue Zlib::DataError
248
+ # No luck with Zlib decompression. Let's try with raw deflate,
249
+ # like some broken web servers do.
250
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate body
251
+ end
252
+ else
253
+ body
254
+ end
255
+ end
256
+
257
+ def log_request
258
+ if RestClient.log
259
+ out = []
260
+ out << "RestClient.#{method} #{url.inspect}"
261
+ out << payload.short_inspect if payload
262
+ out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
263
+ RestClient.log << out.join(', ') + "\n"
264
+ end
265
+ end
266
+
267
+ def log_response res
268
+ if RestClient.log
269
+ size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
270
+ RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
271
+ end
272
+ end
273
+
274
+ # Return a hash of headers whose keys are capitalized strings
275
+ def stringify_headers headers
276
+ headers.inject({}) do |result, (key, value)|
277
+ if key.is_a? Symbol
278
+ key = key.to_s.split(/_/).map { |w| w.capitalize }.join('-')
279
+ end
280
+ if 'CONTENT-TYPE' == key.upcase
281
+ target_value = value.to_s
282
+ result[key] = MIME::Types.type_for_extension target_value
283
+ elsif 'ACCEPT' == key.upcase
284
+ # Accept can be composed of several comma-separated values
285
+ if value.is_a? Array
286
+ target_values = value
287
+ else
288
+ target_values = value.to_s.split ','
289
+ end
290
+ result[key] = target_values.map { |ext| MIME::Types.type_for_extension(ext.to_s.strip) }.join(', ')
291
+ else
292
+ result[key] = value.to_s
293
+ end
294
+ result
295
+ end
296
+ end
297
+
298
+ def default_headers
299
+ {:accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate'}
300
+ end
301
+
302
+ end
303
+ end
304
+
305
+ module MIME
306
+ class Types
307
+
308
+ # Return the first found content-type for a value considered as an extension or the value itself
309
+ def type_for_extension ext
310
+ candidates = @extension_index[ext]
311
+ candidates.empty? ? ext : candidates[0].content_type
312
+ end
313
+
314
+ class << self
315
+ def type_for_extension ext
316
+ @__types__.type_for_extension ext
317
+ end
318
+ end
319
+ end
320
+ end