require 'cgi' require 'mime/types' # Takes a hash of string and file parameters and returns a string of text # formatted to be sent as a multipart form post. # # Author:: Cody Brimhall # Created:: 22 Feb 2008 module Sdk4me module Multipart VERSION = '1.0.0'.freeze unless const_defined?(:VERSION) # Formats a given hash as a multipart form post # If a hash value responds to :string or :read messages, then it is # interpreted as a file and processed accordingly; otherwise, it is assumed # to be a string class Post # We have to pretend like we're a web browser... USERAGENT = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6 4me/#{Sdk4me::Client::VERSION}".freeze unless const_defined?(:USERAGENT) BOUNDARY = '0123456789ABLEWASIEREISAWELBA9876543210'.freeze unless const_defined?(:BOUNDARY) CONTENT_TYPE = "multipart/form-data; boundary=#{BOUNDARY}".freeze unless const_defined?(:CONTENT_TYPE) HEADER = { 'Content-Type' => CONTENT_TYPE, 'User-Agent' => USERAGENT }.freeze unless const_defined?(:HEADER) def self.prepare_query(params) fp = [] params.each do |k, v| if v.respond_to?(:path) && v.respond_to?(:read) fp.push(FileParam.new(k, v.path, v.read)) else fp.push(StringParam.new(k, v)) end end # Assemble the request body using the special multipart format query = fp.map { |p| "--#{BOUNDARY}\r\n#{p.to_multipart}" }.join + "--#{BOUNDARY}--" [query, HEADER] end end # Formats a basic string key/value pair for inclusion with a multipart post class StringParam attr_accessor :k, :v def initialize(key, value) @k = key @v = value end def to_multipart %(Content-Disposition: form-data; name="#{CGI.escape(k.to_s)}"\r\n\r\n#{v}\r\n) end end # Formats the contents of a file or string for inclusion with a multipart # form post class FileParam attr_accessor :k, :filename, :content def initialize(key, filename, content) @k = key @filename = filename @content = content end def to_multipart # If we can tell the possible mime-type from the filename, use the first in the list; otherwise, use "application/octet-stream" mime_type = MIME::Types.type_for(filename)[0] || MIME::Types['application/octet-stream'][0] %(Content-Disposition: form-data; name="#{CGI.escape(k.to_s)}"; filename="#{filename}"\r\n) + %(Content-Type: #{mime_type.simplified}\r\n\r\n#{content.force_encoding('UTF-8')}\r\n) end end end end