multipart-post 2.1.0 → 2.3.0
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/changelog.md +14 -0
- data/lib/composite_io.rb +17 -108
- data/lib/multipart/post/composite_read_io.rb +73 -0
- data/lib/multipart/post/multipartable.rb +76 -0
- data/lib/multipart/post/parts.rb +148 -0
- data/lib/multipart/post/upload_io.rb +64 -0
- data/lib/multipart/post/version.rb +11 -0
- data/lib/multipart/post.rb +8 -0
- data/lib/multipart_post.rb +10 -8
- data/lib/multipartable.rb +17 -46
- data/lib/net/http/post/multipart.rb +12 -12
- data/lib/parts.rb +25 -126
- data/license.md +57 -0
- data/readme.md +170 -0
- data.tar.gz.sig +0 -0
- metadata +87 -44
- metadata.gz.sig +0 -0
- data/.gitignore +0 -6
- data/.rspec +0 -5
- data/.travis.yml +0 -18
- 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 -9
- 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: f1a26f1b06e253bdd1a96889881cd28090f1bb294754de989b5d01ee173f2251
|
4
|
+
data.tar.gz: f0df38197a55f9470d21d0284fb12c16deabe86e1ab71e39f409bcbabfb9cf86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8048da79e851331b26fae693ce8e5329481c729934f3653fce804332df8b3f8edcdc566dc543a14e461ec674bb0700e87b92d26b5fe24dc4fcb13312574faf04
|
7
|
+
data.tar.gz: 1323dbcffd530f36bbb14e7ca5f90033ce3bf558ef669387df855638da7a6a619b4fe355ab5b2470428edfd2bb5c1fdbd267b848a940d84a0bbcb451c341c743
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/changelog.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Add the ability to set Content-ID header for ParamPart [#62](https://github.com/socketry/multipart-post/pull/62)
|
8
|
+
- Allow mixed key types for parts headers [#79](https://github.com/socketry/multipart-post/pull/79)
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
|
12
|
+
- Refactor `Parts` into a `Multipart::Post` namespace [#65](https://github.com/socketry/multipart-post/pull/65)
|
13
|
+
- Use mutable strings where needed [#70](https://github.com/socketry/multipart-post/pull/70)
|
14
|
+
- Use `frozen_string_literal` everywhere [#78](https://github.com/socketry/multipart-post/pull/78)
|
data/lib/composite_io.rb
CHANGED
@@ -1,108 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2006-2013, by Nick Sieger.
|
5
|
+
# Copyright, 2010, by Tohru Hashimoto.
|
6
|
+
# Copyright, 2011, by Jeff Hodges.
|
7
|
+
# Copyright, 2011, by Alex Koppel.
|
8
|
+
# Copyright, 2011, by Christine Yen.
|
9
|
+
# Copyright, 2011, by Gerrit Riessen.
|
10
|
+
# Copyright, 2011, by Luke Redpath.
|
11
|
+
# Copyright, 2013, by Mislav Marohnić.
|
12
|
+
# Copyright, 2013, by Leo Cassarani.
|
13
|
+
# Copyright, 2019, by Olle Jonsson.
|
14
|
+
# Copyright, 2022, by Samuel Williams.
|
15
|
+
|
16
|
+
warn "Top level ::CompositeIO is deprecated, require 'multipart/post' and use `Multipart::Post::CompositeReadIO` instead!"
|
17
|
+
require_relative 'multipart/post'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2006-2013, by Nick Sieger.
|
5
|
+
# Copyright, 2010, by Tohru Hashimoto.
|
6
|
+
# Copyright, 2011, by Jeff Hodges.
|
7
|
+
# Copyright, 2011, by Alex Koppel.
|
8
|
+
# Copyright, 2011, by Christine Yen.
|
9
|
+
# Copyright, 2011, by Gerrit Riessen.
|
10
|
+
# Copyright, 2011, by Luke Redpath.
|
11
|
+
# Copyright, 2013, by Mislav Marohnić.
|
12
|
+
# Copyright, 2013, by Leo Cassarani.
|
13
|
+
# Copyright, 2019, by Olle Jonsson.
|
14
|
+
# Copyright, 2019, by Patrick Davey.
|
15
|
+
# Copyright, 2021, by Lewis Cowles.
|
16
|
+
# Copyright, 2021-2022, by Samuel Williams.
|
17
|
+
|
18
|
+
module Multipart
|
19
|
+
module Post
|
20
|
+
# Concatenate together multiple IO objects into a single, composite IO object
|
21
|
+
# for purposes of reading as a single stream.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# crio = CompositeReadIO.new(StringIO.new('one'),
|
25
|
+
# StringIO.new('two'),
|
26
|
+
# StringIO.new('three'))
|
27
|
+
# puts crio.read # => "onetwothree"
|
28
|
+
class CompositeReadIO
|
29
|
+
# Create a new composite-read IO from the arguments, all of which should
|
30
|
+
# respond to #read in a manner consistent with IO.
|
31
|
+
def initialize(*ios)
|
32
|
+
@ios = ios.flatten
|
33
|
+
@index = 0
|
34
|
+
end
|
35
|
+
|
36
|
+
# Read from IOs in order until `length` bytes have been received.
|
37
|
+
def read(length = nil, outbuf = nil)
|
38
|
+
got_result = false
|
39
|
+
outbuf = outbuf ? outbuf.replace("") : String.new
|
40
|
+
|
41
|
+
while io = current_io
|
42
|
+
if result = io.read(length)
|
43
|
+
got_result ||= !result.nil?
|
44
|
+
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
|
45
|
+
outbuf << result
|
46
|
+
length -= result.length if length
|
47
|
+
break if length == 0
|
48
|
+
end
|
49
|
+
advance_io
|
50
|
+
end
|
51
|
+
(!got_result && length) ? nil : outbuf
|
52
|
+
end
|
53
|
+
|
54
|
+
def rewind
|
55
|
+
@ios.each { |io| io.rewind }
|
56
|
+
@index = 0
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def current_io
|
62
|
+
@ios[@index]
|
63
|
+
end
|
64
|
+
|
65
|
+
def advance_io
|
66
|
+
@index += 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
CompositeIO = Multipart::Post::CompositeReadIO
|
73
|
+
Object.deprecate_constant :CompositeIO
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008, by McClain Looney.
|
5
|
+
# Copyright, 2008-2013, by Nick Sieger.
|
6
|
+
# Copyright, 2011, by Gerrit Riessen.
|
7
|
+
# Copyright, 2013, by Vincent Pellé.
|
8
|
+
# Copyright, 2013, by Gustav Ernberg.
|
9
|
+
# Copyright, 2013, by Socrates Vicente.
|
10
|
+
# Copyright, 2013, by Steffen Grunwald.
|
11
|
+
# Copyright, 2019, by Olle Jonsson.
|
12
|
+
# Copyright, 2019-2022, by Samuel Williams.
|
13
|
+
# Copyright, 2019, by Patrick Davey.
|
14
|
+
# Copyright, 2022, by Jason York.
|
15
|
+
|
16
|
+
require_relative 'parts'
|
17
|
+
require_relative 'composite_read_io'
|
18
|
+
|
19
|
+
require 'securerandom'
|
20
|
+
|
21
|
+
module Multipart
|
22
|
+
module Post
|
23
|
+
module Multipartable
|
24
|
+
def self.secure_boundary
|
25
|
+
# https://tools.ietf.org/html/rfc7230
|
26
|
+
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
27
|
+
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
28
|
+
# / DIGIT / ALPHA
|
29
|
+
|
30
|
+
# https://tools.ietf.org/html/rfc2046
|
31
|
+
# bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
|
32
|
+
# "+" / "_" / "," / "-" / "." /
|
33
|
+
# "/" / ":" / "=" / "?"
|
34
|
+
|
35
|
+
"--#{SecureRandom.uuid}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(path, params, headers={}, boundary = Multipartable.secure_boundary)
|
39
|
+
headers = headers.clone # don't want to modify the original variable
|
40
|
+
parts_headers = symbolize_keys(headers.delete(:parts) || {})
|
41
|
+
|
42
|
+
super(path, headers)
|
43
|
+
parts = symbolize_keys(params).map do |k,v|
|
44
|
+
case v
|
45
|
+
when Array
|
46
|
+
v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
|
47
|
+
else
|
48
|
+
Parts::Part.new(boundary, k, v, parts_headers[k])
|
49
|
+
end
|
50
|
+
end.flatten
|
51
|
+
parts << Parts::EpiloguePart.new(boundary)
|
52
|
+
ios = parts.map {|p| p.to_io }
|
53
|
+
self.set_content_type(headers["Content-Type"] || "multipart/form-data",
|
54
|
+
{ "boundary" => boundary })
|
55
|
+
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
|
56
|
+
self.body_stream = CompositeReadIO.new(*ios)
|
57
|
+
|
58
|
+
@boundary = boundary
|
59
|
+
end
|
60
|
+
|
61
|
+
attr :boundary
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
if RUBY_VERSION >= "2.5.0"
|
66
|
+
def symbolize_keys(hash)
|
67
|
+
hash.transform_keys(&:to_sym)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
def symbolize_keys(hash)
|
71
|
+
hash.map{|key,value| [key.to_sym, value]}.to_h
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008-2009, by McClain Looney.
|
5
|
+
# Copyright, 2009-2013, by Nick Sieger.
|
6
|
+
# Copyright, 2011, by Johannes Wagener.
|
7
|
+
# Copyright, 2011, by Gerrit Riessen.
|
8
|
+
# Copyright, 2011, by Jason Moore.
|
9
|
+
# Copyright, 2012, by Steven Davidovitz.
|
10
|
+
# Copyright, 2012, by hexfet.
|
11
|
+
# Copyright, 2013, by Vincent Pellé.
|
12
|
+
# Copyright, 2013, by Gustav Ernberg.
|
13
|
+
# Copyright, 2013, by Socrates Vicente.
|
14
|
+
# Copyright, 2017, by David Moles.
|
15
|
+
# Copyright, 2017, by Matt Colyer.
|
16
|
+
# Copyright, 2017, by Eric Hutzelman.
|
17
|
+
# Copyright, 2019-2021, by Olle Jonsson.
|
18
|
+
# Copyright, 2019, by Ethan Turkeltaub.
|
19
|
+
# Copyright, 2019, by Patrick Davey.
|
20
|
+
# Copyright, 2021-2022, by Samuel Williams.
|
21
|
+
|
22
|
+
require 'stringio'
|
23
|
+
|
24
|
+
module Multipart
|
25
|
+
module Post
|
26
|
+
module Parts
|
27
|
+
module Part
|
28
|
+
def self.new(boundary, name, value, headers = {})
|
29
|
+
headers ||= {} # avoid nil values
|
30
|
+
if file?(value)
|
31
|
+
FilePart.new(boundary, name, value, headers)
|
32
|
+
else
|
33
|
+
ParamPart.new(boundary, name, value, headers)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.file?(value)
|
38
|
+
value.respond_to?(:content_type) && value.respond_to?(:original_filename)
|
39
|
+
end
|
40
|
+
|
41
|
+
def length
|
42
|
+
@part.length
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_io
|
46
|
+
@io
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Represents a parametric part to be filled with given value.
|
51
|
+
class ParamPart
|
52
|
+
include Part
|
53
|
+
|
54
|
+
# @param boundary [String]
|
55
|
+
# @param name [#to_s]
|
56
|
+
# @param value [String]
|
57
|
+
# @param headers [Hash] Content-Type and Content-ID are used, if present.
|
58
|
+
def initialize(boundary, name, value, headers = {})
|
59
|
+
@part = build_part(boundary, name, value, headers)
|
60
|
+
@io = StringIO.new(@part)
|
61
|
+
end
|
62
|
+
|
63
|
+
def length
|
64
|
+
@part.bytesize
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param boundary [String]
|
68
|
+
# @param name [#to_s]
|
69
|
+
# @param value [String]
|
70
|
+
# @param headers [Hash] Content-Type is used, if present.
|
71
|
+
def build_part(boundary, name, value, headers = {})
|
72
|
+
part = String.new
|
73
|
+
part << "--#{boundary}\r\n"
|
74
|
+
part << "Content-ID: #{headers["Content-ID"]}\r\n" if headers["Content-ID"]
|
75
|
+
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
|
76
|
+
part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
|
77
|
+
part << "\r\n"
|
78
|
+
part << "#{value}\r\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Represents a part to be filled from file IO.
|
83
|
+
class FilePart
|
84
|
+
include Part
|
85
|
+
|
86
|
+
attr_reader :length
|
87
|
+
|
88
|
+
# @param boundary [String]
|
89
|
+
# @param name [#to_s]
|
90
|
+
# @param io [IO]
|
91
|
+
# @param headers [Hash]
|
92
|
+
def initialize(boundary, name, io, headers = {})
|
93
|
+
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
|
94
|
+
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
|
95
|
+
io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
|
96
|
+
@foot = "\r\n"
|
97
|
+
@length = @head.bytesize + file_length + @foot.length
|
98
|
+
@io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param boundary [String]
|
102
|
+
# @param name [#to_s]
|
103
|
+
# @param filename [String]
|
104
|
+
# @param type [String]
|
105
|
+
# @param content_len [Integer]
|
106
|
+
# @param opts [Hash]
|
107
|
+
def build_head(boundary, name, filename, type, content_len, opts = {})
|
108
|
+
opts = opts.clone
|
109
|
+
|
110
|
+
trans_encoding = opts.delete("Content-Transfer-Encoding") || "binary"
|
111
|
+
content_disposition = opts.delete("Content-Disposition") || "form-data"
|
112
|
+
|
113
|
+
part = String.new
|
114
|
+
part << "--#{boundary}\r\n"
|
115
|
+
part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
|
116
|
+
part << "Content-Length: #{content_len}\r\n"
|
117
|
+
if content_id = opts.delete("Content-ID")
|
118
|
+
part << "Content-ID: #{content_id}\r\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
if opts["Content-Type"] != nil
|
122
|
+
part << "Content-Type: " + opts["Content-Type"] + "\r\n"
|
123
|
+
else
|
124
|
+
part << "Content-Type: #{type}\r\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
|
128
|
+
|
129
|
+
opts.each do |k, v|
|
130
|
+
part << "#{k}: #{v}\r\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
part << "\r\n"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Represents the epilogue or closing boundary.
|
138
|
+
class EpiloguePart
|
139
|
+
include Part
|
140
|
+
|
141
|
+
def initialize(boundary)
|
142
|
+
@part = String.new("--#{boundary}--\r\n")
|
143
|
+
@io = StringIO.new(@part)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2022, by Samuel Williams.
|
5
|
+
|
6
|
+
module Multipart
|
7
|
+
module Post
|
8
|
+
# Convenience methods for dealing with files and IO that are to be uploaded.
|
9
|
+
class UploadIO
|
10
|
+
attr_reader :content_type, :original_filename, :local_path, :io, :opts
|
11
|
+
|
12
|
+
# Create an upload IO suitable for including in the params hash of a
|
13
|
+
# Net::HTTP::Post::Multipart.
|
14
|
+
#
|
15
|
+
# Can take two forms. The first accepts a filename and content type, and
|
16
|
+
# opens the file for reading (to be closed by finalizer).
|
17
|
+
#
|
18
|
+
# The second accepts an already-open IO, but also requires a third argument,
|
19
|
+
# the filename from which it was opened (particularly useful/recommended if
|
20
|
+
# uploading directly from a form in a framework, which often save the file to
|
21
|
+
# an arbitrarily named RackMultipart file in /tmp).
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# UploadIO.new("file.txt", "text/plain")
|
25
|
+
# UploadIO.new(file_io, "text/plain", "file.txt")
|
26
|
+
def initialize(filename_or_io, content_type, filename = nil, opts = {})
|
27
|
+
io = filename_or_io
|
28
|
+
local_path = ""
|
29
|
+
if io.respond_to? :read
|
30
|
+
# in Ruby 1.9.2, StringIOs no longer respond to path
|
31
|
+
# (since they respond to :length, so we don't need their local path, see parts.rb:41)
|
32
|
+
local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
|
33
|
+
else
|
34
|
+
io = File.open(filename_or_io)
|
35
|
+
local_path = filename_or_io
|
36
|
+
end
|
37
|
+
filename ||= local_path
|
38
|
+
|
39
|
+
@content_type = content_type
|
40
|
+
@original_filename = File.basename(filename)
|
41
|
+
@local_path = local_path
|
42
|
+
@io = io
|
43
|
+
@opts = opts
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.convert!(io, content_type, original_filename, local_path)
|
47
|
+
raise ArgumentError, "convert! has been removed. You must now wrap IOs " \
|
48
|
+
"using:\nUploadIO.new(filename_or_io, content_type, " \
|
49
|
+
"filename=nil)\nPlease update your code."
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(*args)
|
53
|
+
@io.send(*args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def respond_to?(meth, include_all = false)
|
57
|
+
@io.respond_to?(meth, include_all) || super(meth, include_all)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
UploadIO = Multipart::Post::UploadIO
|
64
|
+
Object.deprecate_constant :UploadIO
|
data/lib/multipart_post.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
# Copyright (c) 2007-2013 Nick Sieger.
|
3
|
-
# See the file README.txt included with the distribution for
|
4
|
-
# software license details.
|
5
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2009-2013, by Nick Sieger.
|
5
|
+
# Copyright, 2019-2022, by Samuel Williams.
|
6
|
+
|
7
|
+
warn "Top level ::MultipartPost is deprecated, require 'multipart/post' and use `Multipart::Post` instead!"
|
8
|
+
require_relative 'multipart/post'
|
9
|
+
|
10
|
+
MultipartPost = Multipart::Post
|
11
|
+
Object.deprecate_constant :MultipartPost
|
data/lib/multipartable.rb
CHANGED
@@ -1,48 +1,19 @@
|
|
1
|
-
|
2
|
-
# Copyright (c) 2007-2013 Nick Sieger.
|
3
|
-
# See the file README.txt included with the distribution for
|
4
|
-
# software license details.
|
5
|
-
#++
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2008, by McClain Looney.
|
5
|
+
# Copyright, 2008-2013, by Nick Sieger.
|
6
|
+
# Copyright, 2011, by Gerrit Riessen.
|
7
|
+
# Copyright, 2013, by Vincent Pellé.
|
8
|
+
# Copyright, 2013, by Gustav Ernberg.
|
9
|
+
# Copyright, 2013, by Socrates Vicente.
|
10
|
+
# Copyright, 2013, by Steffen Grunwald.
|
11
|
+
# Copyright, 2019, by Olle Jonsson.
|
12
|
+
# Copyright, 2019-2022, by Samuel Williams.
|
13
|
+
# Copyright, 2019, by Patrick Davey.
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# / DIGIT / ALPHA
|
16
|
-
|
17
|
-
# https://tools.ietf.org/html/rfc2046
|
18
|
-
# bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
|
19
|
-
# "+" / "_" / "," / "-" / "." /
|
20
|
-
# "/" / ":" / "=" / "?"
|
21
|
-
|
22
|
-
"--#{SecureRandom.alphanumeric(60)}"
|
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
|
15
|
+
warn "Top level ::Multipartable is deprecated, require 'multipart/post' and use `Multipart::Post::Multipartable` instead!"
|
16
|
+
require_relative 'multipart/post'
|
17
|
+
|
18
|
+
Multipartable = Multipart::Post::Multipartable
|
19
|
+
Object.deprecate_constant :Multipartable
|
@@ -1,27 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2006-2012, by Nick Sieger.
|
5
|
+
# Copyright, 2008, by McClain Looney.
|
6
|
+
# Copyright, 2019, by Olle Jonsson.
|
7
|
+
# Copyright, 2019, by Patrick Davey.
|
8
|
+
# Copyright, 2021-2022, by Samuel Williams.
|
6
9
|
|
7
10
|
require 'net/http'
|
8
|
-
|
9
|
-
|
10
|
-
require 'composite_io'
|
11
|
-
require 'multipartable'
|
12
|
-
require 'parts'
|
11
|
+
|
12
|
+
require_relative '../../../multipart/post'
|
13
13
|
|
14
14
|
module Net
|
15
15
|
class HTTP
|
16
16
|
class Put
|
17
17
|
class Multipart < Put
|
18
|
-
include Multipartable
|
18
|
+
include ::Multipart::Post::Multipartable
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
class Post
|
23
23
|
class Multipart < Post
|
24
|
-
include Multipartable
|
24
|
+
include ::Multipart::Post::Multipartable
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|