Last active
December 11, 2015 04:59
-
-
Save jcasts/4549450 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Base64IO | |
def initialize io | |
@io = io | |
@buff = "" | |
@eof = false | |
end | |
def eof? | |
@eof | |
end | |
def size | |
return unless @io.size | |
((@io.size / 3.0).ceil * 4) + 1 | |
end | |
def close | |
@io.close | |
end | |
def closed? | |
@io.closed? | |
end | |
def rewind | |
if @io.rewind == 0 | |
@buff = "" | |
@eof = false | |
0 | |
end | |
end | |
def read_all outbuf=nil, nonblock=false | |
output = "" | |
while str = read(1024, outbuf, nonblock); output << str; end | |
output.empty? ? nil : output | |
end | |
def read_nonblock bytes, outbuf=nil | |
raise TypeError, "no implicit conversion from nil to integer" unless | |
Integer === bytes | |
read bytes, outbuf, true | |
end | |
def read bytes=nil, outbuf=nil, nonblock=false | |
raise EOFError, "end of file reached" if @eof && nonblock | |
return if @eof | |
return read_all(outbuf, nonblock) if bytes.nil? | |
out = @buff.slice!(0,bytes) | |
if out.bytesize < bytes | |
meth = nonblock ? :read_nonblock : :read | |
iostr = @io.send(meth, iobytes(bytes - out.bytesize)).to_s | |
out << [iostr].pack('m')[0...-1] | |
@buff = out.slice!(bytes..-1).to_s | |
end | |
if out.length < bytes | |
out << "\n" | |
@eof = true | |
end | |
outbuf << out if outbuf | |
out | |
end | |
private | |
## | |
# Find the number pre-base64 bytes to read, then get | |
# the closest multiple of 3 >= to it (to prevent padding) | |
def iobytes bytes | |
(bytes / 4.0).ceil * 3 | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# Builder for the body of a multipart request. | |
class Multipart | |
# An array of parts for the multipart body. | |
attr_reader :parts | |
# The separator used between parts. | |
attr_reader :boundary | |
def initialize boundary | |
@boundary = boundary | |
@parts = [] | |
end | |
## | |
# Add a new part to the body which will be base64 encoded before writing. | |
# Supports String and IO values. | |
def add_encode64 name, value, headers=nil | |
headers ||= {} | |
headers['Content-Transfer-Encoding'] = 'BASE64' | |
if value.respond_to?(:read) | |
value = Base64IO.new value | |
else | |
value = [value].pack('m') | |
end | |
add name, value, headers | |
end | |
## | |
# Add a new part to the body. | |
# Supports String and IO values. | |
def add name, value, headers=nil | |
headers ||= {} | |
headers['content-disposition'] = "form-data; name=\"#{name}\"" | |
if value.respond_to?(:path) | |
headers['content-disposition'] << | |
"; filename=\"#{File.basename value.path}\"" | |
headers['Content-Type'] ||= MIME::Types.of(value.path)[0] | |
headers['Content-Type'] &&= headers['Content-Type'].to_s | |
end | |
if value.respond_to?(:read) | |
headers['Content-Type'] ||= "application/octet-stream" | |
headers['Content-Transfer-Encoding'] ||= 'binary' | |
end | |
parts << [headers, value] | |
end | |
## | |
# Convert the instance into a MultipartIO instance. | |
def to_io | |
io = MultipartIO.new | |
buff = "" | |
parts.each do |(headers, value)| | |
buff << "--#{@boundary}\r\n" | |
buff << "content-disposition: #{headers['content-disposition']}\r\n" | |
headers.each do |hname, hvalue| | |
next if hname == 'content-disposition' | |
hvalue = hvalue.to_s.inspect if hvalue.to_s.index ":" | |
buff << "#{hname}: #{hvalue}\r\n" | |
end | |
buff << "\r\n" | |
if value.respond_to?(:read) | |
io.add buff.dup | |
io.add value | |
buff.replace "" | |
else | |
buff << value.to_s | |
end | |
buff << "\r\n" | |
end | |
buff << "--#{@boundary}--" | |
io.add buff | |
io | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MultipartIO | |
attr_reader :parts, :curr_part | |
def initialize *parts | |
@parts = [] | |
@curr_part = 0 | |
parts.each do |part| | |
add part | |
end | |
end | |
def add part | |
if String === part | |
@parts << StringIO.new(part) | |
elsif part.respond_to?(:read) | |
@parts << part | |
else | |
raise ArgumentError, "Invalid part #{part.inspect}" | |
end | |
@curr_part ||= @parts.length - 1 | |
@parts.last | |
end | |
def close | |
@parts.each(&:close) | |
nil | |
end | |
def read bytes=nil, outbuf=nil | |
return read_all(outbuf) if bytes.nil? | |
return if @parts.empty? || eof? | |
buff = "" | |
until @curr_part.nil? | |
bytes = bytes - buff.bytes.count | |
buff << @parts[@curr_part].read(bytes).to_s | |
break if buff.bytes.count >= bytes | |
@curr_part += 1 | |
@curr_part = nil if @curr_part >= @parts.length | |
end | |
return if buff.empty? | |
outbuf << buff if outbuf | |
buff | |
end | |
def read_all outbuf=nil | |
return if eof? | |
output = @parts[@curr_part..-1].inject("") do |out, curr| | |
@curr_part += 1 | |
out << curr.read | |
end | |
@curr_part = nil | |
outbuf << output if outbuf | |
output | |
end | |
def eof? | |
@curr_part.nil? | |
end | |
def size | |
total = 0 | |
@parts.each do |part| | |
return nil unless part.respond_to?(:size) && part.size | |
total += part.size | |
end | |
total | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment