mirror of
https://github.com/rest-client/rest-client.git
synced 2022-11-09 13:49:40 -05:00

Use keyword arguments for RestClient::Request#initialize. This means that when you call RestClient::Request.new() it will automatically require :url and :method. It will also raise ArgumentError for unexpected options. Change the API for the RestClient.get() helper methods so that they accept a URL, possibly a body, and then *options* rather than headers. This is the single biggest wart in the API that has been rankling for ages. Drop compatibility for all of the super hacky usage where options were hidden inside the headers hash. No longer accept passing :params or :cookies as part of the headers hash. Also remove a few other bits of vestigial code, and remove the not-very-useful SSLCertificateNotVerified exception.
197 lines
4.4 KiB
Ruby
197 lines
4.4 KiB
Ruby
require 'tempfile'
|
|
require 'securerandom'
|
|
require 'stringio'
|
|
|
|
require 'mime/types'
|
|
|
|
module RestClient
|
|
module Payload
|
|
extend self
|
|
|
|
def generate(params)
|
|
if params.is_a?(String)
|
|
Base.new(params)
|
|
elsif params.is_a?(Hash)
|
|
if params.delete(:multipart) == true || has_file?(params)
|
|
Multipart.new(params)
|
|
else
|
|
UrlEncoded.new(params)
|
|
end
|
|
elsif params.respond_to?(:read)
|
|
Streamed.new(params)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def has_file?(params)
|
|
unless params.is_a?(Hash)
|
|
raise ArgumentError.new("Must pass Hash, not #{params.inspect}")
|
|
end
|
|
_has_file?(params)
|
|
end
|
|
|
|
def _has_file?(obj)
|
|
case obj
|
|
when Hash, ParamsArray
|
|
obj.any? {|_, v| _has_file?(v) }
|
|
when Array
|
|
obj.any? {|v| _has_file?(v) }
|
|
else
|
|
obj.respond_to?(:path) && obj.respond_to?(:read)
|
|
end
|
|
end
|
|
|
|
class Base
|
|
def initialize(params)
|
|
build_stream(params)
|
|
end
|
|
|
|
def build_stream(params)
|
|
@stream = StringIO.new(params)
|
|
@stream.seek(0)
|
|
end
|
|
|
|
def read(*args)
|
|
@stream.read(*args)
|
|
end
|
|
|
|
def to_s
|
|
result = read
|
|
@stream.seek(0)
|
|
result
|
|
end
|
|
|
|
def headers
|
|
{'Content-Length' => size.to_s}
|
|
end
|
|
|
|
def size
|
|
@stream.size
|
|
end
|
|
|
|
alias :length :size
|
|
|
|
def close
|
|
@stream.close unless @stream.closed?
|
|
end
|
|
|
|
def to_s_inspect
|
|
to_s.inspect
|
|
end
|
|
|
|
def short_inspect
|
|
(size > 500 ? "#{size} byte(s) length" : to_s_inspect)
|
|
end
|
|
|
|
end
|
|
|
|
class Streamed < Base
|
|
def build_stream(params = nil)
|
|
@stream = params
|
|
end
|
|
|
|
def size
|
|
if @stream.respond_to?(:size)
|
|
@stream.size
|
|
elsif @stream.is_a?(IO)
|
|
@stream.stat.size
|
|
end
|
|
end
|
|
|
|
alias :length :size
|
|
end
|
|
|
|
class UrlEncoded < Base
|
|
def build_stream(params = nil)
|
|
@stream = StringIO.new(Utils.encode_query_string(params))
|
|
@stream.seek(0)
|
|
end
|
|
|
|
def headers
|
|
super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
|
|
end
|
|
end
|
|
|
|
class Multipart < Base
|
|
EOL = "\r\n"
|
|
|
|
def build_stream(params)
|
|
b = '--' + boundary
|
|
|
|
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
|
@stream.binmode
|
|
@stream.write(b + EOL)
|
|
|
|
case params
|
|
when Hash, ParamsArray
|
|
x = Utils.flatten_params(params)
|
|
else
|
|
x = params
|
|
end
|
|
|
|
last_index = x.length - 1
|
|
x.each_with_index do |a, index|
|
|
k, v = * a
|
|
if v.respond_to?(:read) && v.respond_to?(:path)
|
|
create_file_field(@stream, k, v)
|
|
else
|
|
create_regular_field(@stream, k, v)
|
|
end
|
|
@stream.write(EOL + b)
|
|
@stream.write(EOL) unless last_index == index
|
|
end
|
|
@stream.write('--')
|
|
@stream.write(EOL)
|
|
@stream.seek(0)
|
|
end
|
|
|
|
def create_regular_field(s, k, v)
|
|
s.write("Content-Disposition: form-data; name=\"#{k}\"")
|
|
s.write(EOL)
|
|
s.write(EOL)
|
|
s.write(v)
|
|
end
|
|
|
|
def create_file_field(s, k, v)
|
|
begin
|
|
s.write("Content-Disposition: form-data;")
|
|
s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
|
|
s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
|
|
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
|
|
s.write(EOL)
|
|
while (data = v.read(8124))
|
|
s.write(data)
|
|
end
|
|
ensure
|
|
v.close if v.respond_to?(:close)
|
|
end
|
|
end
|
|
|
|
def mime_for(path)
|
|
mime = MIME::Types.type_for path
|
|
mime.empty? ? 'text/plain' : mime[0].content_type
|
|
end
|
|
|
|
def boundary
|
|
return @boundary if defined?(@boundary) && @boundary
|
|
|
|
# Use the same algorithm used by WebKit: generate 16 random
|
|
# alphanumeric characters, replacing `+` `/` with `A` `B` (included in
|
|
# the list twice) to round out the set of 64.
|
|
s = SecureRandom.base64(12)
|
|
s.tr!('+/', 'AB')
|
|
|
|
@boundary = '----RubyFormBoundary' + s
|
|
end
|
|
|
|
def headers
|
|
super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
|
|
end
|
|
|
|
def close
|
|
@stream.close!
|
|
end
|
|
end
|
|
end
|
|
end
|