multipart-post 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|