Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pkg
*.swo
*.swp
/Gemfile.lock
/.rspec_status
108 changes: 0 additions & 108 deletions lib/composite_io.rb

This file was deleted.

3 changes: 1 addition & 2 deletions lib/multipart_post.rb → lib/multipart.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
# software license details.
#++

module MultipartPost
VERSION = "2.1.1"
module Multipart
end
4 changes: 4 additions & 0 deletions lib/multipart/post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Multipart
module Post
end
end
112 changes: 112 additions & 0 deletions lib/multipart/post/composite_read_io.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#--
# Copyright (c) 2007-2012 Nick Sieger.
# See the file README.txt included with the distribution for
# software license details.
#++

# Concatenate together multiple IO objects into a single, composite IO object
# for purposes of reading as a single stream.
#
# @example
# crio = CompositeReadIO.new(StringIO.new('one'),
# StringIO.new('two'),
# StringIO.new('three'))
# puts crio.read # => "onetwothree"
module Multipart
module Post
class CompositeReadIO
# Create a new composite-read IO from the arguments, all of which should
# respond to #read in a manner consistent with IO.
def initialize(*ios)
@ios = ios.flatten
@index = 0
end

# Read from IOs in order until `length` bytes have been received.
def read(length = nil, outbuf = nil)
got_result = false
outbuf = outbuf ? outbuf.replace("") : ""

while io = current_io
if result = io.read(length)
got_result ||= !result.nil?
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
outbuf << result
length -= result.length if length
break if length == 0
end
advance_io
end
(!got_result && length) ? nil : outbuf
end

def rewind
@ios.each { |io| io.rewind }
@index = 0
end

private

def current_io
@ios[@index]
end

def advance_io
@index += 1
end
end

# Convenience methods for dealing with files and IO that are to be uploaded.
class UploadIO
attr_reader :content_type, :original_filename, :local_path, :io, :opts

# Create an upload IO suitable for including in the params hash of a
# Net::HTTP::Post::Multipart.
#
# Can take two forms. The first accepts a filename and content type, and
# opens the file for reading (to be closed by finalizer).
#
# The second accepts an already-open IO, but also requires a third argument,
# the filename from which it was opened (particularly useful/recommended if
# uploading directly from a form in a framework, which often save the file to
# an arbitrarily named RackMultipart file in /tmp).
#
# @example
# UploadIO.new("file.txt", "text/plain")
# UploadIO.new(file_io, "text/plain", "file.txt")
def initialize(filename_or_io, content_type, filename = nil, opts = {})
io = filename_or_io
local_path = ""
if io.respond_to? :read
# in Ruby 1.9.2, StringIOs no longer respond to path
# (since they respond to :length, so we don't need their local path, see parts.rb:41)
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
else
io = File.open(filename_or_io)
local_path = filename_or_io
end
filename ||= local_path

@content_type = content_type
@original_filename = File.basename(filename)
@local_path = local_path
@io = io
@opts = opts
end

def self.convert!(io, content_type, original_filename, local_path)
raise ArgumentError, "convert! has been removed. You must now wrap IOs " \
"using:\nUploadIO.new(filename_or_io, content_type, " \
"filename=nil)\nPlease update your code."
end

def method_missing(*args)
@io.send(*args)
end

def respond_to?(meth, include_all = false)
@io.respond_to?(meth, include_all) || super(meth, include_all)
end
end
end
end
53 changes: 53 additions & 0 deletions lib/multipart/post/multipartable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#--
# Copyright (c) 2007-2013 Nick Sieger.
# See the file README.txt included with the distribution for
# software license details.
#++

require 'multipart/post/parts'
require 'multipart/post/composite_read_io'
require 'securerandom'

module Multipart
module Post
module Multipartable
def self.secure_boundary
# https://tools.ietf.org/html/rfc7230
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
# / DIGIT / ALPHA

# https://tools.ietf.org/html/rfc2046
# bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
# "+" / "_" / "," / "-" / "." /
# "/" / ":" / "=" / "?"

"--#{SecureRandom.uuid}"
end

def initialize(path, params, headers={}, boundary = Multipartable.secure_boundary)
headers = headers.clone # don't want to modify the original variable
parts_headers = headers.delete(:parts) || {}
super(path, headers)
parts = params.map do |k,v|
case v
when Array
v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
else
Parts::Part.new(boundary, k, v, parts_headers[k])
end
end.flatten
parts << Parts::EpiloguePart.new(boundary)
ios = parts.map {|p| p.to_io }
self.set_content_type(headers["Content-Type"] || "multipart/form-data",
{ "boundary" => boundary })
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
self.body_stream = CompositeReadIO.new(*ios)

@boundary = boundary
end

attr :boundary
end
end
end
Loading