mirror of
https://github.com/rest-client/rest-client.git
synced 2022-11-09 13:49:40 -05:00
209 lines
4.8 KiB
Ruby
209 lines
4.8 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
|
|
|
|
# for Multipart do not escape the keys
|
|
#
|
|
# Ostensibly multipart keys MAY be percent encoded per RFC 7578, but in
|
|
# practice no major browser that I'm aware of uses percent encoding.
|
|
#
|
|
# Further discussion of multipart encoding:
|
|
# https://github.com/rest-client/rest-client/pull/403#issuecomment-156976930
|
|
#
|
|
def handle_key key
|
|
key
|
|
end
|
|
|
|
def headers
|
|
super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
|
|
end
|
|
|
|
def close
|
|
@stream.close!
|
|
end
|
|
end
|
|
end
|
|
end
|