http-form_data 2.3.0 → 3.0.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
- data/CHANGELOG.md +161 -0
- data/LICENSE.txt +1 -1
- data/README.md +11 -10
- data/http-form_data.gemspec +28 -14
- data/lib/http/form_data/composite_io.rb +46 -28
- data/lib/http/form_data/file.rb +44 -19
- data/lib/http/form_data/multipart/param.rb +18 -48
- data/lib/http/form_data/multipart.rb +59 -12
- data/lib/http/form_data/part.rb +24 -2
- data/lib/http/form_data/readable.rb +24 -6
- data/lib/http/form_data/urlencoded.rb +100 -10
- data/lib/http/form_data/version.rb +1 -1
- data/lib/http/form_data.rb +38 -18
- data/sig/http/form_data/composite_io.rbs +32 -0
- data/sig/http/form_data/file.rbs +23 -0
- data/sig/http/form_data/multipart/param.rbs +23 -0
- data/sig/http/form_data/multipart.rbs +40 -0
- data/sig/http/form_data/part.rbs +16 -0
- data/sig/http/form_data/readable.rbs +19 -0
- data/sig/http/form_data/urlencoded.rbs +46 -0
- data/sig/http/form_data/version.rbs +5 -0
- data/sig/http/form_data.rbs +30 -0
- metadata +24 -43
- data/.editorconfig +0 -9
- data/.gitignore +0 -15
- data/.rspec +0 -2
- data/.rubocop.yml +0 -64
- data/.travis.yml +0 -34
- data/.yardopts +0 -2
- data/CHANGES.md +0 -96
- data/Gemfile +0 -26
- data/Guardfile +0 -16
- data/Rakefile +0 -24
- data/appveyor.yml +0 -8
- data/spec/fixtures/expected-multipart-body.tpl +0 -0
- data/spec/fixtures/the-http-gem.info +0 -1
- data/spec/lib/http/form_data/composite_io_spec.rb +0 -109
- data/spec/lib/http/form_data/file_spec.rb +0 -217
- data/spec/lib/http/form_data/multipart_spec.rb +0 -157
- data/spec/lib/http/form_data/part_spec.rb +0 -74
- data/spec/lib/http/form_data/urlencoded_spec.rb +0 -78
- data/spec/lib/http/form_data_spec.rb +0 -50
- data/spec/spec_helper.rb +0 -83
- data/spec/support/fixtures_helper.rb +0 -13
|
@@ -12,48 +12,95 @@ module HTTP
|
|
|
12
12
|
class Multipart
|
|
13
13
|
include Readable
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# Default MIME type for multipart form data
|
|
16
|
+
DEFAULT_CONTENT_TYPE = "multipart/form-data"
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# Returns the multipart boundary string
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# multipart.boundary # => "-----abc123"
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @return [String]
|
|
25
|
+
attr_reader :boundary
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
# Creates a new Multipart form data instance
|
|
28
|
+
#
|
|
29
|
+
# @example Basic form data
|
|
30
|
+
# Multipart.new({ foo: "bar" })
|
|
31
|
+
#
|
|
32
|
+
# @example With custom content type
|
|
33
|
+
# Multipart.new(parts, content_type: "multipart/related")
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
|
|
37
|
+
# @param [String] boundary custom boundary string
|
|
38
|
+
# @param [String] content_type MIME type for the Content-Type header
|
|
39
|
+
def initialize(data, boundary: self.class.generate_boundary, content_type: DEFAULT_CONTENT_TYPE)
|
|
40
|
+
@boundary = boundary.to_s.freeze
|
|
41
|
+
@content_type = content_type
|
|
42
|
+
@io = CompositeIO.new(parts(data).flat_map { |part| [glue, part] } << tail)
|
|
23
43
|
end
|
|
24
44
|
|
|
25
|
-
# Generates a string
|
|
26
|
-
# data.
|
|
45
|
+
# Generates a boundary string for multipart form data
|
|
27
46
|
#
|
|
47
|
+
# @example
|
|
48
|
+
# Multipart.generate_boundary # => "-----abc123..."
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
28
51
|
# @return [String]
|
|
29
52
|
def self.generate_boundary
|
|
30
53
|
("-" * 21) << SecureRandom.hex(21)
|
|
31
54
|
end
|
|
32
55
|
|
|
33
|
-
# Returns MIME type
|
|
56
|
+
# Returns MIME type for the Content-Type header
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# multipart.content_type
|
|
60
|
+
# # => "multipart/form-data; boundary=-----abc123"
|
|
34
61
|
#
|
|
62
|
+
# @api public
|
|
35
63
|
# @return [String]
|
|
36
64
|
def content_type
|
|
37
|
-
"
|
|
65
|
+
"#{@content_type}; boundary=#{@boundary}"
|
|
38
66
|
end
|
|
39
67
|
|
|
40
|
-
# Returns form data content size
|
|
41
|
-
# `Content-Length` header.
|
|
68
|
+
# Returns form data content size for Content-Length
|
|
42
69
|
#
|
|
70
|
+
# @example
|
|
71
|
+
# multipart.content_length # => 123
|
|
72
|
+
#
|
|
73
|
+
# @api public
|
|
43
74
|
# @return [Integer]
|
|
44
75
|
alias content_length size
|
|
45
76
|
|
|
46
77
|
private
|
|
47
78
|
|
|
79
|
+
# Returns the boundary glue between parts
|
|
80
|
+
#
|
|
81
|
+
# @api private
|
|
48
82
|
# @return [String]
|
|
49
83
|
def glue
|
|
50
84
|
@glue ||= "--#{@boundary}#{CRLF}"
|
|
51
85
|
end
|
|
52
86
|
|
|
87
|
+
# Returns the closing boundary tail
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
53
90
|
# @return [String]
|
|
54
91
|
def tail
|
|
55
92
|
@tail ||= "--#{@boundary}--#{CRLF}"
|
|
56
93
|
end
|
|
94
|
+
|
|
95
|
+
# Coerces data into an array of Param objects
|
|
96
|
+
#
|
|
97
|
+
# @api private
|
|
98
|
+
# @return [Array<Param>]
|
|
99
|
+
def parts(data)
|
|
100
|
+
FormData.ensure_data(data).flat_map do |name, values|
|
|
101
|
+
Array(values).map { |value| Param.new(name, value) }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
57
104
|
end
|
|
58
105
|
end
|
|
59
106
|
end
|
data/lib/http/form_data/part.rb
CHANGED
|
@@ -11,12 +11,34 @@ module HTTP
|
|
|
11
11
|
# @example Usage with String
|
|
12
12
|
#
|
|
13
13
|
# body = "Message"
|
|
14
|
-
# FormData::Part.new body, :
|
|
14
|
+
# FormData::Part.new body, content_type: 'foobar.txt; charset="UTF-8"'
|
|
15
15
|
class Part
|
|
16
16
|
include Readable
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
# Returns the content type of this part
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# part.content_type # => "application/json"
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
# @return [String, nil]
|
|
25
|
+
attr_reader :content_type
|
|
19
26
|
|
|
27
|
+
# Returns the filename of this part
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# part.filename # => "avatar.png"
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
# @return [String, nil]
|
|
34
|
+
attr_reader :filename
|
|
35
|
+
|
|
36
|
+
# Creates a new Part with the given body and options
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# Part.new("hello", content_type: "text/plain")
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
20
42
|
# @param [#to_s] body
|
|
21
43
|
# @param [String] content_type Value of Content-Type header
|
|
22
44
|
# @param [String] filename Value of filename parameter
|
|
@@ -4,34 +4,52 @@ module HTTP
|
|
|
4
4
|
module FormData
|
|
5
5
|
# Common behaviour for objects defined by an IO object.
|
|
6
6
|
module Readable
|
|
7
|
-
# Returns IO content
|
|
7
|
+
# Returns IO content as a String
|
|
8
8
|
#
|
|
9
|
+
# @example
|
|
10
|
+
# readable.to_s # => "content"
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
9
13
|
# @return [String]
|
|
10
14
|
def to_s
|
|
11
15
|
rewind
|
|
12
|
-
content = read
|
|
16
|
+
content = read #: String
|
|
13
17
|
rewind
|
|
14
18
|
content
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
# Reads and returns part of IO content
|
|
21
|
+
# Reads and returns part of IO content
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# readable.read # => "full content"
|
|
25
|
+
# readable.read(5) # => "full "
|
|
18
26
|
#
|
|
27
|
+
# @api public
|
|
19
28
|
# @param [Integer] length Number of bytes to retrieve
|
|
20
29
|
# @param [String] outbuf String to be replaced with retrieved data
|
|
21
|
-
#
|
|
22
30
|
# @return [String, nil]
|
|
23
31
|
def read(length = nil, outbuf = nil)
|
|
24
32
|
@io.read(length, outbuf)
|
|
25
33
|
end
|
|
26
34
|
|
|
27
|
-
# Returns IO size
|
|
35
|
+
# Returns IO size in bytes
|
|
28
36
|
#
|
|
37
|
+
# @example
|
|
38
|
+
# readable.size # => 42
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
29
41
|
# @return [Integer]
|
|
30
42
|
def size
|
|
31
43
|
@io.size
|
|
32
44
|
end
|
|
33
45
|
|
|
34
|
-
# Rewinds the IO
|
|
46
|
+
# Rewinds the IO to the beginning
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# readable.rewind
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
# @return [void]
|
|
35
53
|
def rewind
|
|
36
54
|
@io.rewind
|
|
37
55
|
end
|
|
@@ -12,7 +12,7 @@ module HTTP
|
|
|
12
12
|
include Readable
|
|
13
13
|
|
|
14
14
|
class << self
|
|
15
|
-
#
|
|
15
|
+
# Sets custom form data encoder implementation
|
|
16
16
|
#
|
|
17
17
|
# @example
|
|
18
18
|
#
|
|
@@ -44,40 +44,130 @@ module HTTP
|
|
|
44
44
|
#
|
|
45
45
|
# HTTP::FormData::Urlencoded.encoder = CustomFormDataEncoder
|
|
46
46
|
#
|
|
47
|
-
# @
|
|
47
|
+
# @api public
|
|
48
|
+
# @raise [ArgumentError] if implementation does not respond to `#call`
|
|
48
49
|
# @param implementation [#call]
|
|
49
50
|
# @return [void]
|
|
50
51
|
def encoder=(implementation)
|
|
51
52
|
raise ArgumentError unless implementation.respond_to? :call
|
|
53
|
+
|
|
52
54
|
@encoder = implementation
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
# Returns form data encoder implementation
|
|
56
|
-
#
|
|
57
|
+
# Returns form data encoder implementation
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# Urlencoded.encoder # => #<Method: DefaultEncoder.encode>
|
|
57
61
|
#
|
|
62
|
+
# @api public
|
|
58
63
|
# @see .encoder=
|
|
59
64
|
# @return [#call]
|
|
60
65
|
def encoder
|
|
61
|
-
@encoder
|
|
66
|
+
@encoder || DefaultEncoder
|
|
62
67
|
end
|
|
68
|
+
|
|
69
|
+
# Default encoder for urlencoded form data
|
|
70
|
+
module DefaultEncoder
|
|
71
|
+
class << self
|
|
72
|
+
# Recursively encodes form data value
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# DefaultEncoder.encode({ foo: "bar" }) # => "foo=bar"
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
# @param [Hash, Array, String, nil] value
|
|
79
|
+
# @param [String, nil] prefix
|
|
80
|
+
# @return [String]
|
|
81
|
+
def encode(value, prefix = nil)
|
|
82
|
+
case value
|
|
83
|
+
when Hash then encode_hash(value, prefix)
|
|
84
|
+
when Array then encode_array(value, prefix)
|
|
85
|
+
when nil then prefix.to_s
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
88
|
+
|
|
89
|
+
"#{prefix}=#{escape(value)}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
alias call encode
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Encodes an Array value
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
# @return [String]
|
|
101
|
+
def encode_array(value, prefix)
|
|
102
|
+
if prefix
|
|
103
|
+
value.map { |v| encode(v, "#{prefix}[]") }.join("&")
|
|
104
|
+
else
|
|
105
|
+
encode_pairs(value)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Encodes an Array of key-value pairs
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
112
|
+
# @return [String]
|
|
113
|
+
def encode_pairs(pairs)
|
|
114
|
+
pairs.map { |k, v| encode(v, escape(k)) }.reject(&:empty?).join("&")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Encodes a Hash value
|
|
118
|
+
#
|
|
119
|
+
# @api private
|
|
120
|
+
# @return [String]
|
|
121
|
+
def encode_hash(hash, prefix)
|
|
122
|
+
hash.map do |k, v|
|
|
123
|
+
encode(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
|
124
|
+
end.reject(&:empty?).join("&")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# URL-encodes a value
|
|
128
|
+
#
|
|
129
|
+
# @api private
|
|
130
|
+
# @return [String]
|
|
131
|
+
def escape(value)
|
|
132
|
+
URI.encode_www_form_component(value)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private_constant :DefaultEncoder
|
|
63
138
|
end
|
|
64
139
|
|
|
65
|
-
#
|
|
140
|
+
# Creates a new Urlencoded form data instance
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# Urlencoded.new({ "foo" => "bar" })
|
|
144
|
+
#
|
|
145
|
+
# @api public
|
|
146
|
+
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
|
|
147
|
+
# @param [#call] encoder custom encoder implementation
|
|
66
148
|
def initialize(data, encoder: nil)
|
|
67
149
|
encoder ||= self.class.encoder
|
|
68
|
-
@io = StringIO.new(encoder.call(FormData.
|
|
150
|
+
@io = StringIO.new(encoder.call(FormData.ensure_data(data)))
|
|
69
151
|
end
|
|
70
152
|
|
|
71
|
-
# Returns MIME type
|
|
153
|
+
# Returns MIME type for the Content-Type header
|
|
72
154
|
#
|
|
155
|
+
# @example
|
|
156
|
+
# urlencoded.content_type
|
|
157
|
+
# # => "application/x-www-form-urlencoded"
|
|
158
|
+
#
|
|
159
|
+
# @api public
|
|
73
160
|
# @return [String]
|
|
74
161
|
def content_type
|
|
75
162
|
"application/x-www-form-urlencoded"
|
|
76
163
|
end
|
|
77
164
|
|
|
78
|
-
# Returns form data content size
|
|
79
|
-
#
|
|
165
|
+
# Returns form data content size for Content-Length
|
|
166
|
+
#
|
|
167
|
+
# @example
|
|
168
|
+
# urlencoded.content_length # => 17
|
|
80
169
|
#
|
|
170
|
+
# @api public
|
|
81
171
|
# @return [Integer]
|
|
82
172
|
alias content_length size
|
|
83
173
|
end
|
data/lib/http/form_data.rb
CHANGED
|
@@ -16,8 +16,8 @@ module HTTP
|
|
|
16
16
|
# @example Usage
|
|
17
17
|
#
|
|
18
18
|
# form = FormData.create({
|
|
19
|
-
# :
|
|
20
|
-
# :
|
|
19
|
+
# username: "ixti",
|
|
20
|
+
# avatar_file: FormData::File.new("/home/ixti/avatar.png")
|
|
21
21
|
# })
|
|
22
22
|
#
|
|
23
23
|
# # Assuming socket is an open socket to some HTTP server
|
|
@@ -35,46 +35,66 @@ module HTTP
|
|
|
35
35
|
class Error < StandardError; end
|
|
36
36
|
|
|
37
37
|
class << self
|
|
38
|
-
#
|
|
39
|
-
# `data` Hash.
|
|
38
|
+
# Selects encoder type based on given data
|
|
40
39
|
#
|
|
41
|
-
# @
|
|
40
|
+
# @example
|
|
41
|
+
# FormData.create({ username: "ixti" })
|
|
42
|
+
#
|
|
43
|
+
# @api public
|
|
44
|
+
# @param [Enumerable, Hash, #to_h] data
|
|
42
45
|
# @return [Multipart] if any of values is a {FormData::File}
|
|
43
46
|
# @return [Urlencoded] otherwise
|
|
44
47
|
def create(data, encoder: nil)
|
|
45
|
-
data =
|
|
48
|
+
data = ensure_data data
|
|
46
49
|
|
|
47
50
|
if multipart?(data)
|
|
48
51
|
Multipart.new(data)
|
|
49
52
|
else
|
|
50
|
-
Urlencoded.new(data, :
|
|
53
|
+
Urlencoded.new(data, encoder: encoder)
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
56
|
|
|
54
|
-
#
|
|
57
|
+
# Coerces obj to Hash
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# FormData.ensure_hash({ foo: :bar }) # => { foo: :bar }
|
|
55
61
|
#
|
|
56
|
-
# @
|
|
57
|
-
# @raise [Error] `obj` can't be coerced
|
|
62
|
+
# @api public
|
|
63
|
+
# @raise [Error] `obj` can't be coerced
|
|
58
64
|
# @return [Hash]
|
|
59
65
|
def ensure_hash(obj)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
when obj.is_a?(Hash) then obj
|
|
63
|
-
when obj.respond_to?(:to_h) then obj.to_h
|
|
66
|
+
if obj.is_a?(Hash) then obj
|
|
67
|
+
elsif obj.respond_to?(:to_h) then obj.to_h
|
|
64
68
|
else raise Error, "#{obj.inspect} is neither Hash nor responds to :to_h"
|
|
65
69
|
end
|
|
66
70
|
end
|
|
67
71
|
|
|
72
|
+
# Coerces obj to an Enumerable of key-value pairs
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# FormData.ensure_data([[:foo, :bar]]) # => [[:foo, :bar]]
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
# @raise [Error] `obj` can't be coerced
|
|
79
|
+
# @return [Enumerable]
|
|
80
|
+
def ensure_data(obj)
|
|
81
|
+
if obj.nil? then []
|
|
82
|
+
elsif obj.is_a?(Enumerable) then obj
|
|
83
|
+
elsif obj.respond_to?(:to_h) then obj.to_h
|
|
84
|
+
else raise Error, "#{obj.inspect} is neither Enumerable nor responds to :to_h"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
68
88
|
private
|
|
69
89
|
|
|
70
|
-
#
|
|
90
|
+
# Checks if data contains multipart data
|
|
71
91
|
#
|
|
72
|
-
# @
|
|
92
|
+
# @api private
|
|
93
|
+
# @param [Enumerable] data
|
|
73
94
|
# @return [Boolean]
|
|
74
95
|
def multipart?(data)
|
|
75
96
|
data.any? do |_, v|
|
|
76
|
-
|
|
77
|
-
v.respond_to?(:to_ary) && v.to_ary.any? { |e| e.is_a? FormData::Part }
|
|
97
|
+
v.is_a?(Part) || (v.respond_to?(:to_ary) && v.to_ary.any?(Part))
|
|
78
98
|
end
|
|
79
99
|
end
|
|
80
100
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class CompositeIO
|
|
4
|
+
@index: Integer
|
|
5
|
+
@ios: Array[untyped]
|
|
6
|
+
@size: Integer?
|
|
7
|
+
|
|
8
|
+
# Creates a new CompositeIO from an array of IO-like objects
|
|
9
|
+
def initialize: (Array[untyped] ios) -> void
|
|
10
|
+
|
|
11
|
+
# Reads and returns content across multiple IO objects
|
|
12
|
+
def read: (?Integer? length, ?String? outbuf) -> String?
|
|
13
|
+
|
|
14
|
+
# Returns sum of all IO sizes
|
|
15
|
+
def size: () -> Integer
|
|
16
|
+
|
|
17
|
+
# Rewinds all IO objects and resets cursor
|
|
18
|
+
def rewind: () -> void
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Yields chunks with total length up to `length`
|
|
23
|
+
def read_chunks: (Integer? length) { (String chunk) -> void } -> void
|
|
24
|
+
|
|
25
|
+
# Reads chunk from current IO with length up to `max_length`
|
|
26
|
+
def readpartial: (Integer? max_length) -> String?
|
|
27
|
+
|
|
28
|
+
# Advances cursor to the next IO object
|
|
29
|
+
def advance_io: () -> void
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class File < Part
|
|
4
|
+
DEFAULT_MIME: String
|
|
5
|
+
|
|
6
|
+
@autoclose: bool
|
|
7
|
+
|
|
8
|
+
# Creates a new File from a path or IO object
|
|
9
|
+
def initialize: (String | Pathname | untyped path_or_io, ?Hash[Symbol, untyped]? opts) -> void
|
|
10
|
+
|
|
11
|
+
# Closes the underlying IO if it was opened by this instance
|
|
12
|
+
def close: () -> void
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Wraps path_or_io into an IO object
|
|
17
|
+
def make_io: (String | Pathname | untyped path_or_io) -> untyped
|
|
18
|
+
|
|
19
|
+
# Determines filename for the given IO
|
|
20
|
+
def filename_for: (untyped io) -> String
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class Multipart
|
|
4
|
+
class Param
|
|
5
|
+
include Readable
|
|
6
|
+
|
|
7
|
+
@name: String
|
|
8
|
+
@part: Part
|
|
9
|
+
|
|
10
|
+
# Initializes body part with headers and data
|
|
11
|
+
def initialize: (untyped name, untyped value) -> void
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# Builds the MIME header for this part
|
|
16
|
+
def header: () -> String
|
|
17
|
+
|
|
18
|
+
# Builds Content-Disposition parameters string
|
|
19
|
+
def parameters: () -> String
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class Multipart
|
|
4
|
+
include Readable
|
|
5
|
+
|
|
6
|
+
DEFAULT_CONTENT_TYPE: String
|
|
7
|
+
|
|
8
|
+
# Returns the multipart boundary string
|
|
9
|
+
attr_reader boundary: String
|
|
10
|
+
|
|
11
|
+
@content_type: _ToS
|
|
12
|
+
|
|
13
|
+
# Creates a new Multipart form data instance
|
|
14
|
+
def initialize: (untyped data, ?boundary: _ToS, ?content_type: _ToS) -> void
|
|
15
|
+
|
|
16
|
+
# Generates a boundary string for multipart form data
|
|
17
|
+
def self.generate_boundary: () -> String
|
|
18
|
+
|
|
19
|
+
# Returns MIME type for the Content-Type header
|
|
20
|
+
def content_type: () -> String
|
|
21
|
+
|
|
22
|
+
# Returns form data content size for Content-Length
|
|
23
|
+
alias content_length size
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
@glue: String?
|
|
28
|
+
@tail: String?
|
|
29
|
+
|
|
30
|
+
# Returns the boundary glue between parts
|
|
31
|
+
def glue: () -> String
|
|
32
|
+
|
|
33
|
+
# Returns the closing boundary tail
|
|
34
|
+
def tail: () -> String
|
|
35
|
+
|
|
36
|
+
# Coerces data into an array of Param objects
|
|
37
|
+
def parts: (untyped data) -> Array[Param]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class Part
|
|
4
|
+
include Readable
|
|
5
|
+
|
|
6
|
+
# Returns the content type of this part
|
|
7
|
+
attr_reader content_type: String?
|
|
8
|
+
|
|
9
|
+
# Returns the filename of this part
|
|
10
|
+
attr_reader filename: String?
|
|
11
|
+
|
|
12
|
+
# Creates a new Part with the given body and options
|
|
13
|
+
def initialize: (_ToS body, ?content_type: String?, ?filename: String?) -> void
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
module Readable
|
|
4
|
+
@io: StringIO | CompositeIO
|
|
5
|
+
|
|
6
|
+
# Returns IO content as a String
|
|
7
|
+
def to_s: () -> String
|
|
8
|
+
|
|
9
|
+
# Reads and returns part of IO content
|
|
10
|
+
def read: (?Integer? length, ?String? outbuf) -> String?
|
|
11
|
+
|
|
12
|
+
# Returns IO size in bytes
|
|
13
|
+
def size: () -> Integer
|
|
14
|
+
|
|
15
|
+
# Rewinds the IO to the beginning
|
|
16
|
+
def rewind: () -> (Integer | void)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
module FormData
|
|
3
|
+
class Urlencoded
|
|
4
|
+
include Readable
|
|
5
|
+
|
|
6
|
+
self.@encoder: _Encoder?
|
|
7
|
+
|
|
8
|
+
# Sets custom form data encoder implementation
|
|
9
|
+
def self.encoder=: (untyped implementation) -> void
|
|
10
|
+
|
|
11
|
+
# Returns form data encoder implementation
|
|
12
|
+
def self.encoder: () -> _Encoder
|
|
13
|
+
|
|
14
|
+
# Default encoder for urlencoded form data
|
|
15
|
+
module DefaultEncoder
|
|
16
|
+
# Recursively encodes form data value
|
|
17
|
+
def self.encode: (untyped value, ?String? prefix) -> String
|
|
18
|
+
|
|
19
|
+
alias self.call self.encode
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Encodes an Array value
|
|
24
|
+
def self.encode_array: (Array[untyped] value, String? prefix) -> String
|
|
25
|
+
|
|
26
|
+
# Encodes an Array of key-value pairs
|
|
27
|
+
def self.encode_pairs: (Array[untyped] pairs) -> String
|
|
28
|
+
|
|
29
|
+
# Encodes a Hash value
|
|
30
|
+
def self.encode_hash: (Hash[untyped, untyped] hash, String? prefix) -> String
|
|
31
|
+
|
|
32
|
+
# URL-encodes a value
|
|
33
|
+
def self.escape: (untyped value) -> String
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Creates a new Urlencoded form data instance
|
|
37
|
+
def initialize: (untyped data, ?encoder: _Encoder?) -> void
|
|
38
|
+
|
|
39
|
+
# Returns MIME type for the Content-Type header
|
|
40
|
+
def content_type: () -> String
|
|
41
|
+
|
|
42
|
+
# Returns form data content size for Content-Length
|
|
43
|
+
alias content_length size
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|