1
0
Fork 0
mirror of https://github.com/rest-client/rest-client.git synced 2022-11-09 13:49:40 -05:00
rest-client--rest-client/lib/restclient/payload.rb
Jon Rowe 3e6504be32 remove warnings about URI.escape when available
(cherry picked from commit 0919b02cc5)
Signed-off-by: Larry Gilbert <larry@l2g.to>
2014-02-03 13:13:59 -08:00

240 lines
5.6 KiB
Ruby

require 'tempfile'
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)
params.any? do |_, v|
case v
when Hash
has_file?(v)
when Array
has_file_array?(v)
else
v.respond_to?(:path) && v.respond_to?(:read)
end
end
end
def has_file_array?(params)
params.any? do |v|
case v
when Hash
has_file?(v)
when Array
has_file_array?(v)
else
v.respond_to?(:path) && v.respond_to?(:read)
end
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(bytes=nil)
@stream.read(bytes)
end
alias :to_s :read
# Flatten parameters by converting hashes of hashes to flat hashes
# {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value]
def flatten_params(params, parent_key = nil)
result = []
params.each do |key, value|
calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key)
if value.is_a? Hash
result += flatten_params(value, calculated_key)
elsif value.is_a? Array
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
end
end
result
end
def flatten_params_array value, calculated_key
result = []
value.each do |elem|
if elem.is_a? Hash
result += flatten_params(elem, calculated_key)
elsif elem.is_a? Array
result += flatten_params_array(elem, calculated_key)
else
result << ["#{calculated_key}[]", elem]
end
end
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 inspect
result = to_s.inspect
@stream.seek(0)
result
end
def short_inspect
(size > 500 ? "#{size} byte(s) length" : 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(flatten_params(params).collect do |entry|
"#{entry[0]}=#{handle_key(entry[1])}"
end.join("&"))
@stream.seek(0)
end
# for UrlEncoded escape the keys
def handle_key key
parser.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
end
def headers
super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
end
private
def parser
URI.const_defined?(:Parser) ? URI::Parser.new : URI
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)
if params.is_a? Hash
x = 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
@boundary ||= rand(1_000_000).to_s
end
# for Multipart do not escape the keys
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