1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2008-2009, by McClain Looney.
# Copyright, 2009-2013, by Nick Sieger.
# Copyright, 2011, by Johannes Wagener.
# Copyright, 2011, by Gerrit Riessen.
# Copyright, 2011, by Jason Moore.
# Copyright, 2012, by Steven Davidovitz.
# Copyright, 2012, by hexfet.
# Copyright, 2013, by Vincent Pellé.
# Copyright, 2013, by Gustav Ernberg.
# Copyright, 2013, by Socrates Vicente.
# Copyright, 2017, by David Moles.
# Copyright, 2017, by Matt Colyer.
# Copyright, 2017, by Eric Hutzelman.
# Copyright, 2019-2021, by Olle Jonsson.
# Copyright, 2019, by Ethan Turkeltaub.
# Copyright, 2019, by Patrick Davey.
# Copyright, 2021-2024, by Samuel Williams.
require 'stringio'
module Multipart
module Post
module Parts
module Part
def self.new(boundary, name, value, headers = {})
headers ||= {} # avoid nil values
if file?(value)
FilePart.new(boundary, name, value, headers)
else
ParamPart.new(boundary, name, value, headers)
end
end
def self.file?(value)
value.respond_to?(:content_type) && value.respond_to?(:original_filename)
end
def length
@part.length
end
def to_io
@io
end
end
# Represents a parametric part to be filled with given value.
class ParamPart
include Part
# @param boundary [String]
# @param name [#to_s]
# @param value [String]
# @param headers [Hash] Content-Type and Content-ID are used, if present.
def initialize(boundary, name, value, headers = {})
@part = build_part(boundary, name, value, headers)
@io = StringIO.new(@part)
end
def length
@part.bytesize
end
# @param boundary [String]
# @param name [#to_s]
# @param value [String]
# @param headers [Hash] Content-Type is used, if present.
def build_part(boundary, name, value, headers = {})
part = String.new
part << "--#{boundary}\r\n"
part << "Content-ID: #{headers["Content-ID"]}\r\n" if headers["Content-ID"]
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
part << "\r\n"
part << "#{value}\r\n"
end
end
# Represents a part to be filled from file IO.
class FilePart
include Part
attr_reader :length
# @param boundary [String]
# @param name [#to_s]
# @param io [IO]
# @param headers [Hash]
def initialize(boundary, name, io, headers = {})
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
@foot = "\r\n"
@length = @head.bytesize + file_length + @foot.length
@io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
end
# @param boundary [String]
# @param name [#to_s]
# @param filename [String]
# @param type [String]
# @param content_len [Integer]
# @param opts [Hash]
def build_head(boundary, name, filename, type, content_len, opts = {})
opts = opts.clone
trans_encoding = opts.delete("Content-Transfer-Encoding") || "binary"
content_disposition = opts.delete("Content-Disposition") || "form-data"
part = String.new
part << "--#{boundary}\r\n"
part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
part << "Content-Length: #{content_len}\r\n"
if content_id = opts.delete("Content-ID")
part << "Content-ID: #{content_id}\r\n"
end
if opts["Content-Type"] != nil
part << "Content-Type: " + opts["Content-Type"] + "\r\n"
else
part << "Content-Type: #{type}\r\n"
end
part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
opts.each do |k, v|
part << "#{k}: #{v}\r\n"
end
part << "\r\n"
end
end
# Represents the epilogue or closing boundary.
class EpiloguePart
include Part
def initialize(boundary)
@part = String.new("--#{boundary}--\r\n")
@io = StringIO.new(@part)
end
end
end
end
end
|