multipart-post 2.1.1 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/composite_io.rb +2 -108
- data/lib/multipart/post/composite_read_io.rb +78 -0
- data/lib/multipart/post/multipartable.rb +86 -0
- data/lib/multipart/post/parts.rb +152 -0
- data/lib/multipart/post/upload_io.rb +81 -0
- data/lib/multipart/post/version.rb +27 -0
- data/lib/multipart/post.rb +24 -0
- data/lib/multipart_post.rb +3 -9
- data/lib/multipartable.rb +2 -48
- data/lib/net/http/post/multipart.rb +25 -12
- data/lib/parts.rb +2 -126
- data.tar.gz.sig +0 -0
- metadata +81 -57
- metadata.gz.sig +0 -0
- data/.gitignore +0 -6
- data/.rspec +0 -5
- data/.travis.yml +0 -20
- data/.yardopts +0 -6
- data/Gemfile +0 -6
- data/History.txt +0 -64
- data/LICENSE +0 -21
- data/Manifest.txt +0 -9
- data/README.md +0 -127
- data/Rakefile +0 -6
- data/multipart-post.gemspec +0 -23
- data/spec/composite_io_spec.rb +0 -138
- data/spec/multibyte.txt +0 -1
- data/spec/net/http/post/multipart_spec.rb +0 -123
- data/spec/parts_spec.rb +0 -102
- data/spec/spec_helper.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a490fccc98196854246d34c31cfb5a02027e6de137056244c64243861c4300d4
|
4
|
+
data.tar.gz: f30b2512b74ff3000ca0f8d6fcc2a0fe323532049a2710870a95b3a719fe8391
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 020ed9e1c057b7a71278c7fa3b3449084d96d3088165e8e1954175b5df28a73528fda7740a8598ed16de3ed111dd5b693d0363dced16f66d2fe48edbcdd624d0
|
7
|
+
data.tar.gz: dad4720dca5d10ff7b5806931a4ce91c2858217e4212b5d4c9355383945b4750b17e19d7290d521287f402b23108ddfb2d351215332f96579bec13156f219d71
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/lib/composite_io.rb
CHANGED
@@ -1,108 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# See the file README.txt included with the distribution for
|
4
|
-
# software license details.
|
5
|
-
#++
|
6
|
-
|
7
|
-
# Concatenate together multiple IO objects into a single, composite IO object
|
8
|
-
# for purposes of reading as a single stream.
|
9
|
-
#
|
10
|
-
# @example
|
11
|
-
# crio = CompositeReadIO.new(StringIO.new('one'),
|
12
|
-
# StringIO.new('two'),
|
13
|
-
# StringIO.new('three'))
|
14
|
-
# puts crio.read # => "onetwothree"
|
15
|
-
class CompositeReadIO
|
16
|
-
# Create a new composite-read IO from the arguments, all of which should
|
17
|
-
# respond to #read in a manner consistent with IO.
|
18
|
-
def initialize(*ios)
|
19
|
-
@ios = ios.flatten
|
20
|
-
@index = 0
|
21
|
-
end
|
22
|
-
|
23
|
-
# Read from IOs in order until `length` bytes have been received.
|
24
|
-
def read(length = nil, outbuf = nil)
|
25
|
-
got_result = false
|
26
|
-
outbuf = outbuf ? outbuf.replace("") : ""
|
27
|
-
|
28
|
-
while io = current_io
|
29
|
-
if result = io.read(length)
|
30
|
-
got_result ||= !result.nil?
|
31
|
-
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
|
32
|
-
outbuf << result
|
33
|
-
length -= result.length if length
|
34
|
-
break if length == 0
|
35
|
-
end
|
36
|
-
advance_io
|
37
|
-
end
|
38
|
-
(!got_result && length) ? nil : outbuf
|
39
|
-
end
|
40
|
-
|
41
|
-
def rewind
|
42
|
-
@ios.each { |io| io.rewind }
|
43
|
-
@index = 0
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def current_io
|
49
|
-
@ios[@index]
|
50
|
-
end
|
51
|
-
|
52
|
-
def advance_io
|
53
|
-
@index += 1
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Convenience methods for dealing with files and IO that are to be uploaded.
|
58
|
-
class UploadIO
|
59
|
-
attr_reader :content_type, :original_filename, :local_path, :io, :opts
|
60
|
-
|
61
|
-
# Create an upload IO suitable for including in the params hash of a
|
62
|
-
# Net::HTTP::Post::Multipart.
|
63
|
-
#
|
64
|
-
# Can take two forms. The first accepts a filename and content type, and
|
65
|
-
# opens the file for reading (to be closed by finalizer).
|
66
|
-
#
|
67
|
-
# The second accepts an already-open IO, but also requires a third argument,
|
68
|
-
# the filename from which it was opened (particularly useful/recommended if
|
69
|
-
# uploading directly from a form in a framework, which often save the file to
|
70
|
-
# an arbitrarily named RackMultipart file in /tmp).
|
71
|
-
#
|
72
|
-
# @example
|
73
|
-
# UploadIO.new("file.txt", "text/plain")
|
74
|
-
# UploadIO.new(file_io, "text/plain", "file.txt")
|
75
|
-
def initialize(filename_or_io, content_type, filename = nil, opts = {})
|
76
|
-
io = filename_or_io
|
77
|
-
local_path = ""
|
78
|
-
if io.respond_to? :read
|
79
|
-
# in Ruby 1.9.2, StringIOs no longer respond to path
|
80
|
-
# (since they respond to :length, so we don't need their local path, see parts.rb:41)
|
81
|
-
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
|
82
|
-
else
|
83
|
-
io = File.open(filename_or_io)
|
84
|
-
local_path = filename_or_io
|
85
|
-
end
|
86
|
-
filename ||= local_path
|
87
|
-
|
88
|
-
@content_type = content_type
|
89
|
-
@original_filename = File.basename(filename)
|
90
|
-
@local_path = local_path
|
91
|
-
@io = io
|
92
|
-
@opts = opts
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.convert!(io, content_type, original_filename, local_path)
|
96
|
-
raise ArgumentError, "convert! has been removed. You must now wrap IOs " \
|
97
|
-
"using:\nUploadIO.new(filename_or_io, content_type, " \
|
98
|
-
"filename=nil)\nPlease update your code."
|
99
|
-
end
|
100
|
-
|
101
|
-
def method_missing(*args)
|
102
|
-
@io.send(*args)
|
103
|
-
end
|
104
|
-
|
105
|
-
def respond_to?(meth, include_all = false)
|
106
|
-
@io.respond_to?(meth, include_all) || super(meth, include_all)
|
107
|
-
end
|
108
|
-
end
|
1
|
+
warn "Top level ::CompositeIO is deprecated, require 'multipart/post' and use `Multipart::Post::CompositeReadIO` instead!"
|
2
|
+
require_relative 'multipart/post'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
module Multipart
|
24
|
+
module Post
|
25
|
+
# Concatenate together multiple IO objects into a single, composite IO object
|
26
|
+
# for purposes of reading as a single stream.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# crio = CompositeReadIO.new(StringIO.new('one'),
|
30
|
+
# StringIO.new('two'),
|
31
|
+
# StringIO.new('three'))
|
32
|
+
# puts crio.read # => "onetwothree"
|
33
|
+
class CompositeReadIO
|
34
|
+
# Create a new composite-read IO from the arguments, all of which should
|
35
|
+
# respond to #read in a manner consistent with IO.
|
36
|
+
def initialize(*ios)
|
37
|
+
@ios = ios.flatten
|
38
|
+
@index = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
# Read from IOs in order until `length` bytes have been received.
|
42
|
+
def read(length = nil, outbuf = nil)
|
43
|
+
got_result = false
|
44
|
+
outbuf = outbuf ? outbuf.replace("") : String.new
|
45
|
+
|
46
|
+
while io = current_io
|
47
|
+
if result = io.read(length)
|
48
|
+
got_result ||= !result.nil?
|
49
|
+
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
|
50
|
+
outbuf << result
|
51
|
+
length -= result.length if length
|
52
|
+
break if length == 0
|
53
|
+
end
|
54
|
+
advance_io
|
55
|
+
end
|
56
|
+
(!got_result && length) ? nil : outbuf
|
57
|
+
end
|
58
|
+
|
59
|
+
def rewind
|
60
|
+
@ios.each { |io| io.rewind }
|
61
|
+
@index = 0
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def current_io
|
67
|
+
@ios[@index]
|
68
|
+
end
|
69
|
+
|
70
|
+
def advance_io
|
71
|
+
@index += 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
CompositeIO = Multipart::Post::CompositeReadIO
|
78
|
+
Object.deprecate_constant :CompositeIO
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'parts'
|
24
|
+
require_relative 'composite_read_io'
|
25
|
+
|
26
|
+
require 'securerandom'
|
27
|
+
|
28
|
+
module Multipart
|
29
|
+
module Post
|
30
|
+
module Multipartable
|
31
|
+
def self.secure_boundary
|
32
|
+
# https://tools.ietf.org/html/rfc7230
|
33
|
+
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
34
|
+
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
35
|
+
# / DIGIT / ALPHA
|
36
|
+
|
37
|
+
# https://tools.ietf.org/html/rfc2046
|
38
|
+
# bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
|
39
|
+
# "+" / "_" / "," / "-" / "." /
|
40
|
+
# "/" / ":" / "=" / "?"
|
41
|
+
|
42
|
+
"--#{SecureRandom.uuid}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(path, params, headers={}, boundary = Multipartable.secure_boundary)
|
46
|
+
headers = headers.clone # don't want to modify the original variable
|
47
|
+
parts_headers = symbolize_keys(headers.delete(:parts) || {})
|
48
|
+
|
49
|
+
super(path, headers)
|
50
|
+
parts = symbolize_keys(params).map do |k,v|
|
51
|
+
case v
|
52
|
+
when Array
|
53
|
+
v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
|
54
|
+
else
|
55
|
+
Parts::Part.new(boundary, k, v, parts_headers[k])
|
56
|
+
end
|
57
|
+
end.flatten
|
58
|
+
parts << Parts::EpiloguePart.new(boundary)
|
59
|
+
ios = parts.map {|p| p.to_io }
|
60
|
+
self.set_content_type(headers["Content-Type"] || "multipart/form-data",
|
61
|
+
{ "boundary" => boundary })
|
62
|
+
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
|
63
|
+
self.body_stream = CompositeReadIO.new(*ios)
|
64
|
+
|
65
|
+
@boundary = boundary
|
66
|
+
end
|
67
|
+
|
68
|
+
attr :boundary
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
if RUBY_VERSION >= "2.5.0"
|
73
|
+
def symbolize_keys(hash)
|
74
|
+
hash.transform_keys(&:to_sym)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
def symbolize_keys(hash)
|
78
|
+
hash.map{|key,value| [key.to_sym, value]}.to_h
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Multipartable = Multipart::Post::Multipartable
|
86
|
+
Object.deprecate_constant :Multipartable
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'stringio'
|
24
|
+
|
25
|
+
module Multipart
|
26
|
+
module Post
|
27
|
+
module Parts
|
28
|
+
module Part
|
29
|
+
def self.new(boundary, name, value, headers = {})
|
30
|
+
headers ||= {} # avoid nil values
|
31
|
+
if file?(value)
|
32
|
+
FilePart.new(boundary, name, value, headers)
|
33
|
+
else
|
34
|
+
ParamPart.new(boundary, name, value, headers)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.file?(value)
|
39
|
+
value.respond_to?(:content_type) && value.respond_to?(:original_filename)
|
40
|
+
end
|
41
|
+
|
42
|
+
def length
|
43
|
+
@part.length
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_io
|
47
|
+
@io
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Represents a parametric part to be filled with given value.
|
52
|
+
class ParamPart
|
53
|
+
include Part
|
54
|
+
|
55
|
+
# @param boundary [String]
|
56
|
+
# @param name [#to_s]
|
57
|
+
# @param value [String]
|
58
|
+
# @param headers [Hash] Content-Type and Content-ID are used, if present.
|
59
|
+
def initialize(boundary, name, value, headers = {})
|
60
|
+
@part = build_part(boundary, name, value, headers)
|
61
|
+
@io = StringIO.new(@part)
|
62
|
+
end
|
63
|
+
|
64
|
+
def length
|
65
|
+
@part.bytesize
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param boundary [String]
|
69
|
+
# @param name [#to_s]
|
70
|
+
# @param value [String]
|
71
|
+
# @param headers [Hash] Content-Type is used, if present.
|
72
|
+
def build_part(boundary, name, value, headers = {})
|
73
|
+
part = String.new
|
74
|
+
part << "--#{boundary}\r\n"
|
75
|
+
part << "Content-ID: #{headers["Content-ID"]}\r\n" if headers["Content-ID"]
|
76
|
+
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
|
77
|
+
part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
|
78
|
+
part << "\r\n"
|
79
|
+
part << "#{value}\r\n"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Represents a part to be filled from file IO.
|
84
|
+
class FilePart
|
85
|
+
include Part
|
86
|
+
|
87
|
+
attr_reader :length
|
88
|
+
|
89
|
+
# @param boundary [String]
|
90
|
+
# @param name [#to_s]
|
91
|
+
# @param io [IO]
|
92
|
+
# @param headers [Hash]
|
93
|
+
def initialize(boundary, name, io, headers = {})
|
94
|
+
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
|
95
|
+
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
|
96
|
+
io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
|
97
|
+
@foot = "\r\n"
|
98
|
+
@length = @head.bytesize + file_length + @foot.length
|
99
|
+
@io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param boundary [String]
|
103
|
+
# @param name [#to_s]
|
104
|
+
# @param filename [String]
|
105
|
+
# @param type [String]
|
106
|
+
# @param content_len [Integer]
|
107
|
+
# @param opts [Hash]
|
108
|
+
def build_head(boundary, name, filename, type, content_len, opts = {})
|
109
|
+
opts = opts.clone
|
110
|
+
|
111
|
+
trans_encoding = opts.delete("Content-Transfer-Encoding") || "binary"
|
112
|
+
content_disposition = opts.delete("Content-Disposition") || "form-data"
|
113
|
+
|
114
|
+
part = String.new
|
115
|
+
part << "--#{boundary}\r\n"
|
116
|
+
part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
|
117
|
+
part << "Content-Length: #{content_len}\r\n"
|
118
|
+
if content_id = opts.delete("Content-ID")
|
119
|
+
part << "Content-ID: #{content_id}\r\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
if opts["Content-Type"] != nil
|
123
|
+
part << "Content-Type: " + opts["Content-Type"] + "\r\n"
|
124
|
+
else
|
125
|
+
part << "Content-Type: #{type}\r\n"
|
126
|
+
end
|
127
|
+
|
128
|
+
part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
|
129
|
+
|
130
|
+
opts.each do |k, v|
|
131
|
+
part << "#{k}: #{v}\r\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
part << "\r\n"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Represents the epilogue or closing boundary.
|
139
|
+
class EpiloguePart
|
140
|
+
include Part
|
141
|
+
|
142
|
+
def initialize(boundary)
|
143
|
+
@part = String.new("--#{boundary}--\r\n")
|
144
|
+
@io = StringIO.new(@part)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
Parts = Multipart::Post::Parts
|
152
|
+
Object.deprecate_constant :Parts
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
module Multipart
|
24
|
+
module Post
|
25
|
+
# Convenience methods for dealing with files and IO that are to be uploaded.
|
26
|
+
class UploadIO
|
27
|
+
attr_reader :content_type, :original_filename, :local_path, :io, :opts
|
28
|
+
|
29
|
+
# Create an upload IO suitable for including in the params hash of a
|
30
|
+
# Net::HTTP::Post::Multipart.
|
31
|
+
#
|
32
|
+
# Can take two forms. The first accepts a filename and content type, and
|
33
|
+
# opens the file for reading (to be closed by finalizer).
|
34
|
+
#
|
35
|
+
# The second accepts an already-open IO, but also requires a third argument,
|
36
|
+
# the filename from which it was opened (particularly useful/recommended if
|
37
|
+
# uploading directly from a form in a framework, which often save the file to
|
38
|
+
# an arbitrarily named RackMultipart file in /tmp).
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# UploadIO.new("file.txt", "text/plain")
|
42
|
+
# UploadIO.new(file_io, "text/plain", "file.txt")
|
43
|
+
def initialize(filename_or_io, content_type, filename = nil, opts = {})
|
44
|
+
io = filename_or_io
|
45
|
+
local_path = ""
|
46
|
+
if io.respond_to? :read
|
47
|
+
# in Ruby 1.9.2, StringIOs no longer respond to path
|
48
|
+
# (since they respond to :length, so we don't need their local path, see parts.rb:41)
|
49
|
+
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
|
50
|
+
else
|
51
|
+
io = File.open(filename_or_io)
|
52
|
+
local_path = filename_or_io
|
53
|
+
end
|
54
|
+
filename ||= local_path
|
55
|
+
|
56
|
+
@content_type = content_type
|
57
|
+
@original_filename = File.basename(filename)
|
58
|
+
@local_path = local_path
|
59
|
+
@io = io
|
60
|
+
@opts = opts
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.convert!(io, content_type, original_filename, local_path)
|
64
|
+
raise ArgumentError, "convert! has been removed. You must now wrap IOs " \
|
65
|
+
"using:\nUploadIO.new(filename_or_io, content_type, " \
|
66
|
+
"filename=nil)\nPlease update your code."
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(*args)
|
70
|
+
@io.send(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def respond_to?(meth, include_all = false)
|
74
|
+
@io.respond_to?(meth, include_all) || super(meth, include_all)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
UploadIO = Multipart::Post::UploadIO
|
81
|
+
Object.deprecate_constant :UploadIO
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
module Multipart
|
24
|
+
module Post
|
25
|
+
VERSION = "2.2.3"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2007-2013, by Nick Sieger.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'post/multipartable'
|
24
|
+
require_relative 'post/upload_io'
|
data/lib/multipart_post.rb
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
# software license details.
|
5
|
-
#++
|
6
|
-
|
7
|
-
module MultipartPost
|
8
|
-
VERSION = "2.1.1"
|
9
|
-
end
|
1
|
+
warn "Top level ::MultipartPost is deprecated, require 'multipart/post' and use `Multipart::Post` instead!"
|
2
|
+
require_relative 'multipart/post'
|
3
|
+
MultipartPost = Multipart::Post
|
data/lib/multipartable.rb
CHANGED
@@ -1,48 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# See the file README.txt included with the distribution for
|
4
|
-
# software license details.
|
5
|
-
#++
|
6
|
-
|
7
|
-
require 'parts'
|
8
|
-
require 'securerandom'
|
9
|
-
|
10
|
-
module Multipartable
|
11
|
-
def self.secure_boundary
|
12
|
-
# https://tools.ietf.org/html/rfc7230
|
13
|
-
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
14
|
-
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
15
|
-
# / DIGIT / ALPHA
|
16
|
-
|
17
|
-
# https://tools.ietf.org/html/rfc2046
|
18
|
-
# bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
|
19
|
-
# "+" / "_" / "," / "-" / "." /
|
20
|
-
# "/" / ":" / "=" / "?"
|
21
|
-
|
22
|
-
"--#{SecureRandom.uuid}"
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(path, params, headers={}, boundary = Multipartable.secure_boundary)
|
26
|
-
headers = headers.clone # don't want to modify the original variable
|
27
|
-
parts_headers = headers.delete(:parts) || {}
|
28
|
-
super(path, headers)
|
29
|
-
parts = params.map do |k,v|
|
30
|
-
case v
|
31
|
-
when Array
|
32
|
-
v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
|
33
|
-
else
|
34
|
-
Parts::Part.new(boundary, k, v, parts_headers[k])
|
35
|
-
end
|
36
|
-
end.flatten
|
37
|
-
parts << Parts::EpiloguePart.new(boundary)
|
38
|
-
ios = parts.map {|p| p.to_io }
|
39
|
-
self.set_content_type(headers["Content-Type"] || "multipart/form-data",
|
40
|
-
{ "boundary" => boundary })
|
41
|
-
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
|
42
|
-
self.body_stream = CompositeReadIO.new(*ios)
|
43
|
-
|
44
|
-
@boundary = boundary
|
45
|
-
end
|
46
|
-
|
47
|
-
attr :boundary
|
48
|
-
end
|
1
|
+
warn "Top level ::Multipartable is deprecated, require 'multipart/post' and use `Multipart::Post::Multipartable` instead!"
|
2
|
+
require_relative 'multipart/post'
|