http-form_data 1.0.3 → 2.0.0.pre1
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/.travis.yml +3 -2
- data/CHANGES.md +7 -6
- data/Gemfile +15 -1
- data/README.md +1 -4
- data/Rakefile +17 -1
- data/http-form_data.gemspec +0 -2
- data/lib/http/form_data/composite_io.rb +73 -0
- data/lib/http/form_data/file.rb +19 -32
- data/lib/http/form_data/multipart.rb +18 -23
- data/lib/http/form_data/multipart/param.rb +45 -31
- data/lib/http/form_data/part.rb +13 -22
- data/lib/http/form_data/readable.rb +38 -0
- data/lib/http/form_data/version.rb +1 -1
- data/spec/lib/http/form_data/composite_io_spec.rb +88 -0
- data/spec/lib/http/form_data/file_spec.rb +99 -8
- data/spec/lib/http/form_data/multipart_spec.rb +97 -15
- data/spec/lib/http/form_data/part_spec.rb +28 -7
- data/spec/lib/http/form_data/urlencoded_spec.rb +1 -1
- data/spec/lib/http/form_data_spec.rb +4 -4
- data/spec/spec_helper.rb +10 -0
- metadata +17 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89b84a117e41ca60f5962948d9ce6b4932497080
|
4
|
+
data.tar.gz: 93238b392e0c1817768d6f24d9d44cb9368b2c30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70d163f00ea75806e0268fed63956f554f706ce869bf22bffb13e1aa8f728f7b69b73e8a1ede97bf05efbbd85a62609d1ee4df7eddb81120b95e3f8f88731d36
|
7
|
+
data.tar.gz: be03a767b75746a513e78a56ac7dace8da976da47b35b5a3ba15c149c2807e4d2c8a3a06d107e09cd566703d34471cde4b47316a636f6ffd2fe8ce8a18357936
|
data/.travis.yml
CHANGED
@@ -15,9 +15,7 @@ env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
|
15
15
|
|
16
16
|
rvm:
|
17
17
|
# Include JRuby first because it takes the longest
|
18
|
-
- jruby-1.7.27
|
19
18
|
- jruby-9.1.8.0
|
20
|
-
- 1.9.3
|
21
19
|
- 2.0.0
|
22
20
|
- 2.1
|
23
21
|
- 2.2
|
@@ -26,6 +24,9 @@ rvm:
|
|
26
24
|
|
27
25
|
matrix:
|
28
26
|
fast_finish: true
|
27
|
+
include:
|
28
|
+
- rvm: 2.4.1
|
29
|
+
env: SUITE="rubocop"
|
29
30
|
|
30
31
|
branches:
|
31
32
|
only:
|
data/CHANGES.md
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
##
|
1
|
+
## 2.0.0-pre1 (2017-05-10)
|
2
2
|
|
3
|
-
* [#
|
4
|
-
|
5
|
-
[@
|
3
|
+
* [#12](https://github.com/httprb/form_data.rb/pull/12)
|
4
|
+
Enable form data streaming.
|
5
|
+
[@janko-m][]
|
6
6
|
|
7
7
|
|
8
8
|
## 1.0.2 (2017-05-08)
|
9
9
|
|
10
10
|
* [#5](https://github.com/httprb/form_data.rb/issues/5)
|
11
|
-
Allow setting Content-Type non-file parts
|
11
|
+
Allow setting Content-Type non-file parts
|
12
12
|
[@abotalov][]
|
13
13
|
|
14
14
|
* [#6](https://github.com/httprb/form_data.rb/issues/6)
|
15
|
-
Creation of file parts without filename
|
15
|
+
Creation of file parts without filename
|
16
16
|
[@abotalov][]
|
17
17
|
|
18
18
|
* [#11](https://github.com/httprb/form_data.rb/pull/11)
|
@@ -43,3 +43,4 @@
|
|
43
43
|
|
44
44
|
[@ixti]: https://github.com/ixti
|
45
45
|
[@abotalov]: https://github.com/abotalov
|
46
|
+
[@janko-m]: https://github.com/janko-m
|
data/Gemfile
CHANGED
@@ -4,8 +4,22 @@ source "https://rubygems.org"
|
|
4
4
|
|
5
5
|
gem "rake"
|
6
6
|
|
7
|
+
group :development do
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec", :require => false
|
10
|
+
gem "pry"
|
11
|
+
end
|
12
|
+
|
7
13
|
group :test do
|
8
|
-
gem "
|
14
|
+
gem "coveralls"
|
15
|
+
gem "rspec", "~> 3.1"
|
16
|
+
gem "rubocop", "= 0.48.1"
|
17
|
+
gem "simplecov", ">= 0.9"
|
18
|
+
end
|
19
|
+
|
20
|
+
group :doc do
|
21
|
+
gem "redcarpet"
|
22
|
+
gem "yard"
|
9
23
|
end
|
10
24
|
|
11
25
|
# Specify your gem's dependencies in form_data.gemspec
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# FormData
|
1
|
+
# HTTP::FormData
|
2
2
|
|
3
3
|
[](http://rubygems.org/gems/http-form_data)
|
4
4
|
[](http://travis-ci.org/httprb/form_data.rb)
|
@@ -58,13 +58,10 @@ form = HTTP::FormData.create({
|
|
58
58
|
This library aims to support and is [tested against][ci] the following Ruby
|
59
59
|
versions:
|
60
60
|
|
61
|
-
* Ruby 1.9.x
|
62
|
-
* Ruby 2.0.x
|
63
61
|
* Ruby 2.1.x
|
64
62
|
* Ruby 2.2.x
|
65
63
|
* Ruby 2.3.x
|
66
64
|
* Ruby 2.4.x
|
67
|
-
* JRuby 1.7.x
|
68
65
|
* JRuby 9.1.x.x
|
69
66
|
|
70
67
|
If something doesn't work on one of these versions, it's a bug.
|
data/Rakefile
CHANGED
@@ -5,4 +5,20 @@ require "bundler/gem_tasks"
|
|
5
5
|
require "rspec/core/rake_task"
|
6
6
|
RSpec::Core::RakeTask.new
|
7
7
|
|
8
|
-
|
8
|
+
begin
|
9
|
+
require "rubocop/rake_task"
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
rescue LoadError
|
12
|
+
task :rubocop do
|
13
|
+
$stderr.puts "RuboCop is disabled"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
if ENV["CI"].nil?
|
18
|
+
task :default => %i[spec rubocop]
|
19
|
+
else
|
20
|
+
case ENV["SUITE"]
|
21
|
+
when "rubocop" then task :default => :rubocop
|
22
|
+
else task :default => :spec
|
23
|
+
end
|
24
|
+
end
|
data/http-form_data.gemspec
CHANGED
@@ -18,8 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
> `multipart/form-data` types.
|
19
19
|
DESC
|
20
20
|
|
21
|
-
spec.required_ruby_version = ">= 1.9"
|
22
|
-
|
23
21
|
spec.files = `git ls-files -z`.split("\x0")
|
24
22
|
spec.executables = spec.files.grep(%r{^bin\/}).map { |f| File.basename(f) }
|
25
23
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)\/})
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module HTTP
|
6
|
+
module FormData
|
7
|
+
# Provides IO interface across multiple IO objects.
|
8
|
+
class CompositeIO
|
9
|
+
# @param [Array<IO>] ios Array of IO objects
|
10
|
+
def initialize(ios)
|
11
|
+
@index = 0
|
12
|
+
@buffer = String.new
|
13
|
+
@ios = ios.map do |io|
|
14
|
+
if io.is_a?(String)
|
15
|
+
StringIO.new(io)
|
16
|
+
elsif io.respond_to?(:read)
|
17
|
+
io
|
18
|
+
else
|
19
|
+
raise ArgumentError,
|
20
|
+
"#{io.inspect} is neither a String nor an IO object"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Reads and returns partial content acrosss multiple IO objects.
|
26
|
+
#
|
27
|
+
# @param [Integer] length Number of bytes to retrieve
|
28
|
+
# @param [String] outbuf String to be replaced with retrieved data
|
29
|
+
#
|
30
|
+
# @return [String, nil]
|
31
|
+
def read(length = nil, outbuf = nil)
|
32
|
+
outbuf = outbuf.to_s.replace("")
|
33
|
+
|
34
|
+
while current_io
|
35
|
+
current_io.read(length, @buffer)
|
36
|
+
outbuf << @buffer
|
37
|
+
|
38
|
+
if length
|
39
|
+
length -= @buffer.length
|
40
|
+
break if length.zero?
|
41
|
+
end
|
42
|
+
|
43
|
+
advance_io
|
44
|
+
end
|
45
|
+
|
46
|
+
outbuf unless length && outbuf.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns sum of all IO sizes.
|
50
|
+
def size
|
51
|
+
@size ||= @ios.map(&:size).inject(0, :+)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Rewinds all IO objects and set cursor to the first IO object.
|
55
|
+
def rewind
|
56
|
+
@ios.each(&:rewind)
|
57
|
+
@index = 0
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Returns IO object under the cursor.
|
63
|
+
def current_io
|
64
|
+
@ios[@index]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Advances cursor to the next IO object.
|
68
|
+
def advance_io
|
69
|
+
@index += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/http/form_data/file.rb
CHANGED
@@ -26,15 +26,14 @@ module HTTP
|
|
26
26
|
alias mime_type content_type
|
27
27
|
|
28
28
|
# @see DEFAULT_MIME
|
29
|
-
# @param [String,
|
29
|
+
# @param [String, Pathname, IO] path_or_io Filename or IO instance.
|
30
30
|
# @param [#to_h] opts
|
31
31
|
# @option opts [#to_s] :content_type (DEFAULT_MIME)
|
32
32
|
# Value of Content-Type header
|
33
33
|
# @option opts [#to_s] :filename
|
34
|
-
# When `
|
35
|
-
# When `
|
36
|
-
|
37
|
-
def initialize(file_or_io, opts = {})
|
34
|
+
# When `path_or_io` is a String, Pathname or File, defaults to basename.
|
35
|
+
# When `path_or_io` is a IO, defaults to `"stream-{object_id}"`.
|
36
|
+
def initialize(path_or_io, opts = {})
|
38
37
|
opts = FormData.ensure_hash(opts)
|
39
38
|
|
40
39
|
if opts.key? :mime_type
|
@@ -42,40 +41,28 @@ module HTTP
|
|
42
41
|
opts[:content_type] = opts[:mime_type]
|
43
42
|
end
|
44
43
|
|
45
|
-
@
|
44
|
+
@io = make_io(path_or_io)
|
46
45
|
@content_type = opts.fetch(:content_type, DEFAULT_MIME).to_s
|
47
|
-
@filename = opts.fetch
|
48
|
-
case file_or_io
|
49
|
-
when String then ::File.basename file_or_io
|
50
|
-
when ::File then ::File.basename file_or_io.path
|
51
|
-
else "stream-#{file_or_io.object_id}"
|
52
|
-
end
|
53
|
-
end
|
46
|
+
@filename = opts.fetch(:filename, filename_for(@io))
|
54
47
|
end
|
55
48
|
|
56
|
-
|
57
|
-
#
|
58
|
-
# @return [Integer]
|
59
|
-
def size
|
60
|
-
with_io(&:size)
|
61
|
-
end
|
49
|
+
private
|
62
50
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
51
|
+
def make_io(path_or_io)
|
52
|
+
if path_or_io.is_a?(String)
|
53
|
+
::File.open(path_or_io, :binmode => true)
|
54
|
+
elsif defined?(Pathname) && path_or_io.is_a?(Pathname)
|
55
|
+
path_or_io.open(:binmode => true)
|
56
|
+
else
|
57
|
+
path_or_io
|
58
|
+
end
|
68
59
|
end
|
69
60
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# @return result of yielded block
|
74
|
-
def with_io
|
75
|
-
if @file_or_io.is_a?(::File) || @file_or_io.is_a?(StringIO)
|
76
|
-
yield @file_or_io
|
61
|
+
def filename_for(io)
|
62
|
+
if io.respond_to?(:path)
|
63
|
+
::File.basename io.path
|
77
64
|
else
|
78
|
-
|
65
|
+
"stream-#{io.object_id}"
|
79
66
|
end
|
80
67
|
end
|
81
68
|
end
|
@@ -3,23 +3,31 @@
|
|
3
3
|
require "securerandom"
|
4
4
|
|
5
5
|
require "http/form_data/multipart/param"
|
6
|
+
require "http/form_data/readable"
|
7
|
+
require "http/form_data/composite_io"
|
6
8
|
|
7
9
|
module HTTP
|
8
10
|
module FormData
|
9
11
|
# `multipart/form-data` form data.
|
10
12
|
class Multipart
|
13
|
+
include Readable
|
14
|
+
|
15
|
+
attr_reader :boundary
|
16
|
+
|
11
17
|
# @param [#to_h, Hash] data form data key-value Hash
|
12
|
-
def initialize(data)
|
13
|
-
|
14
|
-
|
15
|
-
@
|
18
|
+
def initialize(data, boundary: self.class.generate_boundary)
|
19
|
+
parts = Param.coerce FormData.ensure_hash data
|
20
|
+
|
21
|
+
@boundary = boundary.to_s.freeze
|
22
|
+
@io = CompositeIO.new [*parts.flat_map { |part| [glue, part] }, tail]
|
16
23
|
end
|
17
24
|
|
18
|
-
#
|
25
|
+
# Generates a string suitable for using as a boundary in multipart form
|
26
|
+
# data.
|
19
27
|
#
|
20
28
|
# @return [String]
|
21
|
-
def
|
22
|
-
|
29
|
+
def self.generate_boundary
|
30
|
+
("-" * 21) << SecureRandom.hex(21)
|
23
31
|
end
|
24
32
|
|
25
33
|
# Returns MIME type to be used for HTTP request `Content-Type` header.
|
@@ -33,31 +41,18 @@ module HTTP
|
|
33
41
|
# `Content-Length` header.
|
34
42
|
#
|
35
43
|
# @return [Integer]
|
36
|
-
|
37
|
-
unless @content_length
|
38
|
-
@content_length = head.bytesize + tail.bytesize
|
39
|
-
@content_length += @parts.map(&:size).reduce(:+)
|
40
|
-
@content_length += (glue.bytesize * (@parts.count - 1))
|
41
|
-
end
|
42
|
-
|
43
|
-
@content_length
|
44
|
-
end
|
44
|
+
alias content_length size
|
45
45
|
|
46
46
|
private
|
47
47
|
|
48
|
-
# @return [String]
|
49
|
-
def head
|
50
|
-
@head ||= "--#{@boundary}#{CRLF}"
|
51
|
-
end
|
52
|
-
|
53
48
|
# @return [String]
|
54
49
|
def glue
|
55
|
-
@glue ||= "
|
50
|
+
@glue ||= "--#{@boundary}#{CRLF}"
|
56
51
|
end
|
57
52
|
|
58
53
|
# @return [String]
|
59
54
|
def tail
|
60
|
-
@tail ||= "
|
55
|
+
@tail ||= "--#{@boundary}--"
|
61
56
|
end
|
62
57
|
end
|
63
58
|
end
|
@@ -1,34 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "http/form_data/readable"
|
4
|
+
require "http/form_data/composite_io"
|
5
|
+
|
3
6
|
module HTTP
|
4
7
|
module FormData
|
5
8
|
class Multipart
|
6
9
|
# Utility class to represent multi-part chunks
|
7
10
|
class Param
|
8
|
-
|
9
|
-
# @param [FormData::File, FormData::Part, #to_s] value
|
10
|
-
def initialize(name, value)
|
11
|
-
@name = name.to_s
|
12
|
-
|
13
|
-
@part =
|
14
|
-
if value.is_a?(FormData::Part)
|
15
|
-
value
|
16
|
-
else
|
17
|
-
FormData::Part.new(value)
|
18
|
-
end
|
19
|
-
|
20
|
-
parameters = { :name => @name }
|
21
|
-
parameters[:filename] = @part.filename if @part.filename
|
22
|
-
parameters = parameters.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
|
11
|
+
include Readable
|
23
12
|
|
24
|
-
|
25
|
-
|
26
|
-
return unless @part.content_type
|
27
|
-
|
28
|
-
@header += "#{CRLF}Content-Type: #{@part.content_type}"
|
29
|
-
end
|
30
|
-
|
31
|
-
# Returns body part with headers and data.
|
13
|
+
# Initializes body part with headers and data.
|
32
14
|
#
|
33
15
|
# @example With {FormData::File} value
|
34
16
|
#
|
@@ -44,15 +26,19 @@ module HTTP
|
|
44
26
|
# ixti
|
45
27
|
#
|
46
28
|
# @return [String]
|
47
|
-
|
48
|
-
|
49
|
-
|
29
|
+
# @param [#to_s] name
|
30
|
+
# @param [FormData::File, FormData::Part, #to_s] value
|
31
|
+
def initialize(name, value)
|
32
|
+
@name = name.to_s
|
50
33
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
34
|
+
@part =
|
35
|
+
if value.is_a?(FormData::Part)
|
36
|
+
value
|
37
|
+
else
|
38
|
+
FormData::Part.new(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
@io = CompositeIO.new [header, @part, footer]
|
56
42
|
end
|
57
43
|
|
58
44
|
# Flattens given `data` Hash into an array of `Param`'s.
|
@@ -72,6 +58,34 @@ module HTTP
|
|
72
58
|
|
73
59
|
params
|
74
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def header
|
65
|
+
header = String.new
|
66
|
+
header << "Content-Disposition: form-data; #{parameters}#{CRLF}"
|
67
|
+
header << "Content-Type: #{content_type}#{CRLF}" if content_type
|
68
|
+
header << CRLF
|
69
|
+
header
|
70
|
+
end
|
71
|
+
|
72
|
+
def parameters
|
73
|
+
parameters = { :name => @name }
|
74
|
+
parameters[:filename] = filename if filename
|
75
|
+
parameters.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
|
76
|
+
end
|
77
|
+
|
78
|
+
def content_type
|
79
|
+
@part.content_type
|
80
|
+
end
|
81
|
+
|
82
|
+
def filename
|
83
|
+
@part.filename
|
84
|
+
end
|
85
|
+
|
86
|
+
def footer
|
87
|
+
CRLF.dup
|
88
|
+
end
|
75
89
|
end
|
76
90
|
end
|
77
91
|
end
|
data/lib/http/form_data/part.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
require "http/form_data/readable"
|
6
|
+
|
3
7
|
module HTTP
|
4
8
|
module FormData
|
5
9
|
# Represents a body part of multipart/form-data request.
|
@@ -9,30 +13,17 @@ module HTTP
|
|
9
13
|
# body = "Message"
|
10
14
|
# FormData::Part.new body, :content_type => 'foobar.txt; charset="UTF-8"'
|
11
15
|
class Part
|
12
|
-
|
16
|
+
include Readable
|
13
17
|
|
14
|
-
|
15
|
-
# @param opts [Hash]
|
16
|
-
# @option opts [String] :content_type Value of Content-Type header
|
17
|
-
# @option opts [String] :filename Value of filename parameter
|
18
|
-
def initialize(body, opts = {})
|
19
|
-
@body = body.to_s
|
20
|
-
@content_type = opts[:content_type]
|
21
|
-
@filename = opts[:filename]
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns content size.
|
25
|
-
#
|
26
|
-
# @return [Integer]
|
27
|
-
def size
|
28
|
-
@body.bytesize
|
29
|
-
end
|
18
|
+
attr_reader :content_type, :filename
|
30
19
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# @
|
34
|
-
def
|
35
|
-
@body
|
20
|
+
# @param [#to_s] body
|
21
|
+
# @param [String] content_type Value of Content-Type header
|
22
|
+
# @param [String] filename Value of filename parameter
|
23
|
+
def initialize(body, content_type: nil, filename: nil)
|
24
|
+
@io = StringIO.new(body.to_s)
|
25
|
+
@content_type = content_type
|
26
|
+
@filename = filename
|
36
27
|
end
|
37
28
|
end
|
38
29
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTP
|
4
|
+
module FormData
|
5
|
+
# Common behaviour for objects defined by an IO object.
|
6
|
+
module Readable
|
7
|
+
# Returns IO content.
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
def to_s
|
11
|
+
rewind
|
12
|
+
read
|
13
|
+
end
|
14
|
+
|
15
|
+
# Reads and returns part of IO content.
|
16
|
+
#
|
17
|
+
# @param [Integer] length Number of bytes to retrieve
|
18
|
+
# @param [String] outbuf String to be replaced with retrieved data
|
19
|
+
#
|
20
|
+
# @return [String, nil]
|
21
|
+
def read(length = nil, outbuf = nil)
|
22
|
+
@io.read(length, outbuf)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns IO size.
|
26
|
+
#
|
27
|
+
# @return [Integer]
|
28
|
+
def size
|
29
|
+
@io.size
|
30
|
+
end
|
31
|
+
|
32
|
+
# Rewinds the IO.
|
33
|
+
def rewind
|
34
|
+
@io.rewind
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe HTTP::FormData::CompositeIO do
|
4
|
+
subject(:composite_io) { HTTP::FormData::CompositeIO.new(ios) }
|
5
|
+
|
6
|
+
let(:ios) { ["Hello", " ", "", "world", "!"].map { |s| StringIO.new(s) } }
|
7
|
+
|
8
|
+
describe "#initialize" do
|
9
|
+
it "accepts IOs and strings" do
|
10
|
+
io = HTTP::FormData::CompositeIO.new(["Hello ", StringIO.new("world!")])
|
11
|
+
expect(io.read).to eq "Hello world!"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "fails if an IO is neither a String nor an IO" do
|
15
|
+
expect { HTTP::FormData::CompositeIO.new %i[hello world] }
|
16
|
+
.to raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#read" do
|
21
|
+
it "reads all data" do
|
22
|
+
expect(composite_io.read).to eq "Hello world!"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "reads partial data" do
|
26
|
+
expect(composite_io.read(3)).to eq "Hel"
|
27
|
+
expect(composite_io.read(2)).to eq "lo"
|
28
|
+
expect(composite_io.read(1)).to eq " "
|
29
|
+
expect(composite_io.read(6)).to eq "world!"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns empty string when no data was retrieved" do
|
33
|
+
composite_io.read
|
34
|
+
expect(composite_io.read).to eq ""
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns nil when no partial data was retrieved" do
|
38
|
+
composite_io.read
|
39
|
+
expect(composite_io.read(3)).to eq nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "reads partial data with a buffer" do
|
43
|
+
outbuf = String.new
|
44
|
+
expect(composite_io.read(3, outbuf)).to eq "Hel"
|
45
|
+
expect(composite_io.read(2, outbuf)).to eq "lo"
|
46
|
+
expect(composite_io.read(1, outbuf)).to eq " "
|
47
|
+
expect(composite_io.read(6, outbuf)).to eq "world!"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "fills the buffer with retrieved content" do
|
51
|
+
outbuf = String.new
|
52
|
+
composite_io.read(3, outbuf)
|
53
|
+
expect(outbuf).to eq "Hel"
|
54
|
+
composite_io.read(2, outbuf)
|
55
|
+
expect(outbuf).to eq "lo"
|
56
|
+
composite_io.read(1, outbuf)
|
57
|
+
expect(outbuf).to eq " "
|
58
|
+
composite_io.read(6, outbuf)
|
59
|
+
expect(outbuf).to eq "world!"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns nil when no partial data was retrieved with a buffer" do
|
63
|
+
outbuf = String.new("content")
|
64
|
+
composite_io.read
|
65
|
+
expect(composite_io.read(3, outbuf)).to eq nil
|
66
|
+
expect(outbuf).to eq ""
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#rewind" do
|
71
|
+
it "rewinds all IOs" do
|
72
|
+
composite_io.read
|
73
|
+
composite_io.rewind
|
74
|
+
expect(composite_io.read).to eq "Hello world!"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#size" do
|
79
|
+
it "returns sum of all IO sizes" do
|
80
|
+
expect(composite_io.size).to eq 12
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns 0 when there are no IOs" do
|
84
|
+
empty_composite_io = HTTP::FormData::CompositeIO.new []
|
85
|
+
expect(empty_composite_io.size).to eq 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
2
|
+
# coding: utf-8
|
3
3
|
|
4
4
|
RSpec.describe HTTP::FormData::File do
|
5
5
|
let(:opts) { nil }
|
@@ -12,9 +12,9 @@ RSpec.describe HTTP::FormData::File do
|
|
12
12
|
it { is_expected.to eq fixture("the-http-gem.info").size }
|
13
13
|
end
|
14
14
|
|
15
|
-
context "when file given as
|
16
|
-
let(:file) {
|
17
|
-
it { is_expected.to eq
|
15
|
+
context "when file given as a Pathname" do
|
16
|
+
let(:file) { fixture("the-http-gem.info") }
|
17
|
+
it { is_expected.to eq fixture("the-http-gem.info").size }
|
18
18
|
end
|
19
19
|
|
20
20
|
context "when file given as File" do
|
@@ -22,6 +22,11 @@ RSpec.describe HTTP::FormData::File do
|
|
22
22
|
after { file.close }
|
23
23
|
it { is_expected.to eq fixture("the-http-gem.info").size }
|
24
24
|
end
|
25
|
+
|
26
|
+
context "when file given as IO" do
|
27
|
+
let(:file) { StringIO.new "привет мир!" }
|
28
|
+
it { is_expected.to eq 20 }
|
29
|
+
end
|
25
30
|
end
|
26
31
|
|
27
32
|
describe "#to_s" do
|
@@ -32,16 +37,91 @@ RSpec.describe HTTP::FormData::File do
|
|
32
37
|
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
33
38
|
end
|
34
39
|
|
35
|
-
context "when file given as
|
40
|
+
context "when file given as a Pathname" do
|
41
|
+
let(:file) { fixture("the-http-gem.info") }
|
42
|
+
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when file given as File" do
|
46
|
+
let(:file) { fixture("the-http-gem.info").open("rb") }
|
47
|
+
after { file.close }
|
48
|
+
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when file given as IO" do
|
36
52
|
let(:file) { StringIO.new "привет мир!" }
|
37
53
|
it { is_expected.to eq "привет мир!" }
|
38
54
|
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#read" do
|
58
|
+
subject { described_class.new(file, opts).read }
|
59
|
+
|
60
|
+
context "when file given as a String" do
|
61
|
+
let(:file) { fixture("the-http-gem.info").to_s }
|
62
|
+
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when file given as a Pathname" do
|
66
|
+
let(:file) { fixture("the-http-gem.info") }
|
67
|
+
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
68
|
+
end
|
39
69
|
|
40
70
|
context "when file given as File" do
|
41
71
|
let(:file) { fixture("the-http-gem.info").open("rb") }
|
42
72
|
after { file.close }
|
43
73
|
it { is_expected.to eq fixture("the-http-gem.info").read(:mode => "rb") }
|
44
74
|
end
|
75
|
+
|
76
|
+
context "when file given as IO" do
|
77
|
+
let(:file) { StringIO.new "привет мир!" }
|
78
|
+
it { is_expected.to eq "привет мир!" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#rewind" do
|
83
|
+
subject { described_class.new(file, opts) }
|
84
|
+
|
85
|
+
context "when file given as a String" do
|
86
|
+
let(:file) { fixture("the-http-gem.info").to_s }
|
87
|
+
|
88
|
+
it "rewinds the underlying IO object" do
|
89
|
+
content = subject.read
|
90
|
+
subject.rewind
|
91
|
+
expect(subject.read).to eq content
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when file given as a Pathname" do
|
96
|
+
let(:file) { fixture("the-http-gem.info") }
|
97
|
+
|
98
|
+
it "rewinds the underlying IO object" do
|
99
|
+
content = subject.read
|
100
|
+
subject.rewind
|
101
|
+
expect(subject.read).to eq content
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when file given as File" do
|
106
|
+
let(:file) { fixture("the-http-gem.info").open("rb") }
|
107
|
+
after { file.close }
|
108
|
+
|
109
|
+
it "rewinds the underlying IO object" do
|
110
|
+
content = subject.read
|
111
|
+
subject.rewind
|
112
|
+
expect(subject.read).to eq content
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "when file given as IO" do
|
117
|
+
let(:file) { StringIO.new "привет мир!" }
|
118
|
+
|
119
|
+
it "rewinds the underlying IO object" do
|
120
|
+
content = subject.read
|
121
|
+
subject.rewind
|
122
|
+
expect(subject.read).to eq content
|
123
|
+
end
|
124
|
+
end
|
45
125
|
end
|
46
126
|
|
47
127
|
describe "#filename" do
|
@@ -58,10 +138,10 @@ RSpec.describe HTTP::FormData::File do
|
|
58
138
|
end
|
59
139
|
end
|
60
140
|
|
61
|
-
context "when file given as
|
62
|
-
let(:file) {
|
141
|
+
context "when file given as a Pathname" do
|
142
|
+
let(:file) { fixture("the-http-gem.info") }
|
63
143
|
|
64
|
-
it { is_expected.to eq
|
144
|
+
it { is_expected.to eq ::File.basename file }
|
65
145
|
|
66
146
|
context "and filename given with options" do
|
67
147
|
let(:opts) { { :filename => "foobar.txt" } }
|
@@ -80,6 +160,17 @@ RSpec.describe HTTP::FormData::File do
|
|
80
160
|
it { is_expected.to eq "foobar.txt" }
|
81
161
|
end
|
82
162
|
end
|
163
|
+
|
164
|
+
context "when file given as IO" do
|
165
|
+
let(:file) { StringIO.new }
|
166
|
+
|
167
|
+
it { is_expected.to eq "stream-#{file.object_id}" }
|
168
|
+
|
169
|
+
context "and filename given with options" do
|
170
|
+
let(:opts) { { :filename => "foobar.txt" } }
|
171
|
+
it { is_expected.to eq "foobar.txt" }
|
172
|
+
end
|
173
|
+
end
|
83
174
|
end
|
84
175
|
|
85
176
|
describe "#content_type" do
|
@@ -6,19 +6,6 @@ RSpec.describe HTTP::FormData::Multipart do
|
|
6
6
|
let(:boundary) { /-{21}[a-f0-9]{42}/ }
|
7
7
|
subject(:form_data) { HTTP::FormData::Multipart.new params }
|
8
8
|
|
9
|
-
describe "#content_type" do
|
10
|
-
subject { form_data.content_type }
|
11
|
-
|
12
|
-
let(:content_type) { %r{^multipart\/form-data; boundary=#{boundary}$} }
|
13
|
-
|
14
|
-
it { is_expected.to match(content_type) }
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "#content_length" do
|
18
|
-
subject { form_data.content_length }
|
19
|
-
it { is_expected.to eq form_data.to_s.bytesize }
|
20
|
-
end
|
21
|
-
|
22
9
|
describe "#to_s" do
|
23
10
|
def disposition(params)
|
24
11
|
params = params.map { |k, v| "#{k}=#{v.inspect}" }.join("; ")
|
@@ -36,14 +23,31 @@ RSpec.describe HTTP::FormData::Multipart do
|
|
36
23
|
"#{crlf}bar#{crlf}",
|
37
24
|
"--#{boundary_value}#{crlf}",
|
38
25
|
"#{disposition 'name' => 'baz', 'filename' => file.filename}#{crlf}",
|
39
|
-
"Content-Type: #{file.
|
26
|
+
"Content-Type: #{file.content_type}#{crlf}",
|
40
27
|
"#{crlf}#{file}#{crlf}",
|
41
28
|
"--#{boundary_value}--"
|
42
29
|
].join("")
|
43
30
|
end
|
44
31
|
|
32
|
+
context "with user-defined boundary" do
|
33
|
+
let(:form_data) { HTTP::FormData::Multipart.new params, boundary: "my-boundary" }
|
34
|
+
|
35
|
+
it "uses the given boundary" do
|
36
|
+
expect(form_data.to_s).to eq [
|
37
|
+
"--my-boundary#{crlf}",
|
38
|
+
"#{disposition 'name' => 'foo'}#{crlf}",
|
39
|
+
"#{crlf}bar#{crlf}",
|
40
|
+
"--my-boundary#{crlf}",
|
41
|
+
"#{disposition 'name' => 'baz', 'filename' => file.filename}#{crlf}",
|
42
|
+
"Content-Type: #{file.content_type}#{crlf}",
|
43
|
+
"#{crlf}#{file}#{crlf}",
|
44
|
+
"--my-boundary--"
|
45
|
+
].join("")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
45
49
|
context "with filename set to nil" do
|
46
|
-
let(:part) { HTTP::FormData::Part.new("s", :
|
50
|
+
let(:part) { HTTP::FormData::Part.new("s", :content_type => "mime/type") }
|
47
51
|
let(:form_data) { HTTP::FormData::Multipart.new(:foo => part) }
|
48
52
|
|
49
53
|
it "doesn't include a filename" do
|
@@ -52,10 +56,88 @@ RSpec.describe HTTP::FormData::Multipart do
|
|
52
56
|
expect(form_data.to_s).to eq [
|
53
57
|
"--#{boundary_value}#{crlf}",
|
54
58
|
"#{disposition 'name' => 'foo'}#{crlf}",
|
59
|
+
"Content-Type: #{part.content_type}#{crlf}",
|
55
60
|
"#{crlf}s#{crlf}",
|
56
61
|
"--#{boundary_value}--"
|
57
62
|
].join("")
|
58
63
|
end
|
59
64
|
end
|
65
|
+
|
66
|
+
context "with content type set to nil" do
|
67
|
+
let(:part) { HTTP::FormData::Part.new("s") }
|
68
|
+
let(:form_data) { HTTP::FormData::Multipart.new(:foo => part) }
|
69
|
+
|
70
|
+
it "doesn't include a filename" do
|
71
|
+
boundary_value = form_data.content_type[/(#{boundary})$/, 1]
|
72
|
+
|
73
|
+
expect(form_data.to_s).to eq [
|
74
|
+
"--#{boundary_value}#{crlf}",
|
75
|
+
"#{disposition 'name' => 'foo'}#{crlf}",
|
76
|
+
"#{crlf}s#{crlf}",
|
77
|
+
"--#{boundary_value}--"
|
78
|
+
].join("")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#size" do
|
84
|
+
it "returns bytesize of multipart data" do
|
85
|
+
expect(form_data.size).to eq form_data.to_s.bytesize
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#read" do
|
90
|
+
it "returns multipart data" do
|
91
|
+
expect(form_data.read).to eq form_data.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#rewind" do
|
96
|
+
it "rewinds the multipart data IO" do
|
97
|
+
form_data.read
|
98
|
+
form_data.rewind
|
99
|
+
expect(form_data.read).to eq form_data.to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#content_type" do
|
104
|
+
subject { form_data.content_type }
|
105
|
+
|
106
|
+
let(:content_type) { %r{^multipart\/form-data; boundary=#{boundary}$} }
|
107
|
+
|
108
|
+
it { is_expected.to match(content_type) }
|
109
|
+
|
110
|
+
context "with user-defined boundary" do
|
111
|
+
let(:form_data) { HTTP::FormData::Multipart.new params, boundary: "my-boundary" }
|
112
|
+
|
113
|
+
it "includes the given boundary" do
|
114
|
+
expect(form_data.content_type).to eq "multipart/form-data; boundary=my-boundary"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#content_length" do
|
120
|
+
subject { form_data.content_length }
|
121
|
+
it { is_expected.to eq form_data.to_s.bytesize }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#boundary" do
|
125
|
+
it "returns a new boundary" do
|
126
|
+
expect(form_data.boundary).to match(boundary)
|
127
|
+
end
|
128
|
+
|
129
|
+
context "with user-defined boundary" do
|
130
|
+
let(:form_data) { HTTP::FormData::Multipart.new params, boundary: "my-boundary" }
|
131
|
+
|
132
|
+
it "returns the given boundary" do
|
133
|
+
expect(form_data.boundary).to eq "my-boundary"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe ".generate_boundary" do
|
139
|
+
it "returns a string suitable as a multipart boundary" do
|
140
|
+
expect(form_data.class.generate_boundary).to match(boundary)
|
141
|
+
end
|
60
142
|
end
|
61
143
|
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
RSpec.describe HTTP::FormData::Part do
|
5
|
-
let(:body)
|
6
|
-
let(:opts)
|
4
|
+
let(:body) { "" }
|
5
|
+
let(:opts) { {} }
|
6
|
+
subject(:part) { HTTP::FormData::Part.new(body, opts) }
|
7
7
|
|
8
8
|
describe "#size" do
|
9
|
-
subject {
|
9
|
+
subject { part.size }
|
10
10
|
|
11
11
|
context "when body given as a String" do
|
12
12
|
let(:body) { "привет мир!" }
|
@@ -15,7 +15,7 @@ RSpec.describe HTTP::FormData::Part do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
describe "#to_s" do
|
18
|
-
subject {
|
18
|
+
subject! { part.to_s }
|
19
19
|
|
20
20
|
context "when body given as String" do
|
21
21
|
let(:body) { "привет мир!" }
|
@@ -23,8 +23,29 @@ RSpec.describe HTTP::FormData::Part do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe "#read" do
|
27
|
+
subject { part.read }
|
28
|
+
|
29
|
+
context "when body given as String" do
|
30
|
+
let(:body) { "привет мир!" }
|
31
|
+
it { is_expected.to eq "привет мир!" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#rewind" do
|
36
|
+
context "when body given as String" do
|
37
|
+
let(:body) { "привет мир!" }
|
38
|
+
|
39
|
+
it "rewinds the underlying IO object" do
|
40
|
+
part.read
|
41
|
+
part.rewind
|
42
|
+
expect(part.read).to eq "привет мир!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
26
47
|
describe "#filename" do
|
27
|
-
subject {
|
48
|
+
subject { part.filename }
|
28
49
|
|
29
50
|
it { is_expected.to eq nil }
|
30
51
|
|
@@ -35,7 +56,7 @@ RSpec.describe HTTP::FormData::Part do
|
|
35
56
|
end
|
36
57
|
|
37
58
|
describe "#content_type" do
|
38
|
-
subject {
|
59
|
+
subject { part.content_type }
|
39
60
|
|
40
61
|
it { is_expected.to eq nil }
|
41
62
|
|
@@ -10,14 +10,14 @@ RSpec.describe HTTP::FormData do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
context "when form has at least one file param" do
|
13
|
-
let(:
|
14
|
-
let(:params)
|
13
|
+
let(:file) { HTTP::FormData::File.new(fixture("the-http-gem.info").to_s) }
|
14
|
+
let(:params) { { :foo => :bar, :baz => file } }
|
15
15
|
it { is_expected.to be_a HTTP::FormData::Multipart }
|
16
16
|
end
|
17
17
|
|
18
18
|
context "when form has file in an array param" do
|
19
|
-
let(:
|
20
|
-
let(:params)
|
19
|
+
let(:file) { HTTP::FormData::File.new(fixture("the-http-gem.info").to_s) }
|
20
|
+
let(:params) { { :foo => :bar, :baz => [file] } }
|
21
21
|
it { is_expected.to be_a HTTP::FormData::Multipart }
|
22
22
|
end
|
23
23
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "simplecov"
|
4
|
+
require "coveralls"
|
5
|
+
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
7
|
+
SimpleCov::Formatter::HTMLFormatter,
|
8
|
+
Coveralls::SimpleCov::Formatter
|
9
|
+
]
|
10
|
+
|
11
|
+
SimpleCov.start { add_filter "/spec/" }
|
12
|
+
|
3
13
|
require "http/form_data"
|
4
14
|
require "support/fixtures_helper"
|
5
15
|
|
metadata
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http-form_data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksey V Zapparov
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
14
15
|
requirement: !ruby/object:Gem::Requirement
|
15
16
|
requirements:
|
16
17
|
- - "~>"
|
17
18
|
- !ruby/object:Gem::Version
|
18
19
|
version: '1.7'
|
19
|
-
name: bundler
|
20
|
-
prerelease: false
|
21
20
|
type: :development
|
21
|
+
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
@@ -46,14 +46,17 @@ files:
|
|
46
46
|
- appveyor.yml
|
47
47
|
- http-form_data.gemspec
|
48
48
|
- lib/http/form_data.rb
|
49
|
+
- lib/http/form_data/composite_io.rb
|
49
50
|
- lib/http/form_data/file.rb
|
50
51
|
- lib/http/form_data/multipart.rb
|
51
52
|
- lib/http/form_data/multipart/param.rb
|
52
53
|
- lib/http/form_data/part.rb
|
54
|
+
- lib/http/form_data/readable.rb
|
53
55
|
- lib/http/form_data/urlencoded.rb
|
54
56
|
- lib/http/form_data/version.rb
|
55
57
|
- spec/fixtures/expected-multipart-body.tpl
|
56
58
|
- spec/fixtures/the-http-gem.info
|
59
|
+
- spec/lib/http/form_data/composite_io_spec.rb
|
57
60
|
- spec/lib/http/form_data/file_spec.rb
|
58
61
|
- spec/lib/http/form_data/multipart_spec.rb
|
59
62
|
- spec/lib/http/form_data/part_spec.rb
|
@@ -65,7 +68,7 @@ homepage: https://github.com/httprb/form_data.rb
|
|
65
68
|
licenses:
|
66
69
|
- MIT
|
67
70
|
metadata: {}
|
68
|
-
post_install_message:
|
71
|
+
post_install_message:
|
69
72
|
rdoc_options: []
|
70
73
|
require_paths:
|
71
74
|
- lib
|
@@ -73,21 +76,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
76
|
requirements:
|
74
77
|
- - ">="
|
75
78
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
79
|
+
version: '0'
|
77
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
81
|
requirements:
|
79
|
-
- - "
|
82
|
+
- - ">"
|
80
83
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
84
|
+
version: 1.3.1
|
82
85
|
requirements: []
|
83
|
-
rubyforge_project:
|
84
|
-
rubygems_version: 2.6.
|
85
|
-
signing_key:
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 2.6.11
|
88
|
+
signing_key:
|
86
89
|
specification_version: 4
|
87
|
-
summary: http-form_data-
|
90
|
+
summary: http-form_data-2.0.0.pre1
|
88
91
|
test_files:
|
89
92
|
- spec/fixtures/expected-multipart-body.tpl
|
90
93
|
- spec/fixtures/the-http-gem.info
|
94
|
+
- spec/lib/http/form_data/composite_io_spec.rb
|
91
95
|
- spec/lib/http/form_data/file_spec.rb
|
92
96
|
- spec/lib/http/form_data/multipart_spec.rb
|
93
97
|
- spec/lib/http/form_data/part_spec.rb
|