class Stripe::MultipartEncoder

Encodes parameters into a `multipart/form-data` payload as described by RFC 2388:

https://tools.ietf.org/html/rfc2388

This is most useful for transferring file-like objects.

Parameters should be added with `#encode`. When ready, use `#body` to get the encoded result and `#content_type` to get the value that should be placed in the `Content-Type` header of a subsequent request (which includes a boundary value).

Constants

MULTIPART_FORM_DATA

Attributes

boundary[R]

Gets the object's randomly generated boundary string.

Public Class Methods

encode(params) click to toggle source

A shortcut for encoding a single set of parameters and finalizing a result.

Returns an encoded body and the value that should be set in the content type header of a subsequent request.

# File lib/stripe/multipart_encoder.rb, line 26
def self.encode(params)
  encoder = MultipartEncoder.new
  encoder.encode(params)
  encoder.close
  [encoder.body, encoder.content_type]
end
new() click to toggle source

Initializes a new multipart encoder.

# File lib/stripe/multipart_encoder.rb, line 37
def initialize
  # Kind of weird, but required by Rubocop because the unary plus operator
  # is considered faster than `Stripe.new`.
  @body = +""

  # Chose the same number of random bytes that Go uses in its standard
  # library implementation. Easily enough entropy to ensure that it won't
  # be present in a file we're sending.
  @boundary = SecureRandom.hex(30)

  @closed = false
  @first_field = true
end

Public Instance Methods

body() click to toggle source

Gets the encoded body. `#close` must be called first.

# File lib/stripe/multipart_encoder.rb, line 52
def body
  raise "object must be closed before getting body" unless @closed

  @body
end
close() click to toggle source

Finalizes the object by writing the final boundary.

# File lib/stripe/multipart_encoder.rb, line 59
def close
  raise "object already closed" if @closed

  @body << "\r\n"
  @body << "--#{@boundary}--"

  @closed = true

  nil
end
content_type() click to toggle source

Gets the value including boundary that should be put into a multipart request's `Content-Type`.

# File lib/stripe/multipart_encoder.rb, line 72
def content_type
  "#{MULTIPART_FORM_DATA}; boundary=#{@boundary}"
end
encode(params) click to toggle source

Encodes a set of parameters to the body.

Note that parameters are expected to be a hash, but a “flat” hash such that complex substructures like hashes and arrays have already been appropriately Stripe-encoded. Pass a complex structure through `Util.flatten_params` first before handing it off to this method.

# File lib/stripe/multipart_encoder.rb, line 82
def encode(params)
  raise "no more parameters can be written to closed object" if @closed

  params.each do |name, val|
    if val.is_a?(::File) || val.is_a?(::Tempfile)
      write_field(name, val.read, filename: ::File.basename(val.path))
    elsif val.respond_to?(:read)
      write_field(name, val.read, filename: "blob")
    else
      write_field(name, val, filename: nil)
    end
  end

  nil
end

Private Instance Methods

escape(str) click to toggle source

Escapes double quotes so that the given value can be used in a double-quoted string and replaces any linebreak characters with spaces.

# File lib/stripe/multipart_encoder.rb, line 104
        def escape(str)
  str.gsub('"', "%22").tr("\n", " ").tr("\r", " ")
end
write_field(name, data, filename:) click to toggle source
# File lib/stripe/multipart_encoder.rb, line 108
        def write_field(name, data, filename:)
  if !@first_field
    @body << "\r\n"
  else
    @first_field = false
  end

  @body << "--#{@boundary}\r\n"

  if filename
    @body << %(Content-Disposition: form-data) +
             %(; name="#{escape(name.to_s)}") +
             %(; filename="#{escape(filename)}"\r\n)
    @body << %(Content-Type: application/octet-stream\r\n)
  else
    @body << %(Content-Disposition: form-data) +
             %(; name="#{escape(name.to_s)}"\r\n)
  end

  @body << "\r\n"
  @body << data.to_s
end