multipart-post 1.2.0 → 2.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.
- data/.gitignore +1 -0
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/History.txt +13 -0
- data/{README.txt → README.md} +24 -9
- data/lib/composite_io.rb +2 -2
- data/lib/multipart_post.rb +1 -1
- data/lib/multipartable.rb +12 -3
- data/lib/parts.rb +26 -14
- data/multipart-post.gemspec +1 -0
- data/test/net/http/post/test_multipart.rb +55 -1
- data/test/test_parts.rb +29 -0
- metadata +16 -6
- data/Gemfile.lock +0 -39
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
=== 2.0.0 / 2013-12-21
|
2
|
+
|
3
|
+
- Drop Ruby 1.8 compatibility
|
4
|
+
- GH #21: Fix FilePart length calculation for Ruby 1.9 when filename contains
|
5
|
+
multibyte characters (hexfet)
|
6
|
+
- GH #20: Ensure upload responds to both #content_type and #original_filename
|
7
|
+
(Steven Davidovitz)
|
8
|
+
- GH #31: Support setting headers on any part of the request (Socrates Vicente)
|
9
|
+
- GH #30: Support array values for params (Gustav Ernberg)
|
10
|
+
- GH #32: Fix respond_to? signature (Leo Cassarani)
|
11
|
+
- GH #33: Update README to markdown (Jagtesh Chadha)
|
12
|
+
- GH #35: Improved handling of array-type parameters (Steffen Grunwald)
|
13
|
+
|
1
14
|
=== 1.2.0 / 2013-02-25
|
2
15
|
|
3
16
|
- #25: Ruby 2 compatibility (thanks mislav)
|
data/{README.txt → README.md}
RENAMED
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
## multipart-post
|
2
2
|
|
3
3
|
* http://github.com/nicksieger/multipart-post
|
4
4
|
|
5
|
-
|
5
|
+

|
6
|
+
|
7
|
+
#### DESCRIPTION:
|
6
8
|
|
7
9
|
Adds a streamy multipart form post capability to Net::HTTP. Also
|
8
10
|
supports other methods besides POST.
|
9
11
|
|
10
|
-
|
12
|
+
#### FEATURES/PROBLEMS:
|
11
13
|
|
12
14
|
* Appears to actually work. A good feature to have.
|
13
15
|
* Encapsulates posting of file/binary parts and name/value parameter parts, similar to
|
@@ -15,7 +17,7 @@ supports other methods besides POST.
|
|
15
17
|
* Provides an UploadIO helper class to prepare IO objects for inclusion in the params
|
16
18
|
hash of the multipart post object.
|
17
19
|
|
18
|
-
|
20
|
+
#### SYNOPSIS:
|
19
21
|
|
20
22
|
require 'net/http/post/multipart'
|
21
23
|
|
@@ -28,19 +30,32 @@ supports other methods besides POST.
|
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
|
-
|
33
|
+
To post multiple files or attachments, simply include multiple parameters with
|
34
|
+
UploadIO values:
|
35
|
+
|
36
|
+
require 'net/http/post/multipart'
|
37
|
+
|
38
|
+
url = URI.parse('http://www.example.com/upload')
|
39
|
+
req = Net::HTTP::Post::Multipart.new url.path,
|
40
|
+
"file1" => UploadIO.new(File.new("./image.jpg"), "image/jpeg", "image.jpg"),
|
41
|
+
"file2" => UploadIO.new(File.new("./image2.jpg"), "image/jpeg", "image2.jpg")
|
42
|
+
res = Net::HTTP.start(url.host, url.port) do |http|
|
43
|
+
http.request(req)
|
44
|
+
end
|
45
|
+
|
46
|
+
#### REQUIREMENTS:
|
32
47
|
|
33
48
|
None
|
34
49
|
|
35
|
-
|
50
|
+
#### INSTALL:
|
36
51
|
|
37
|
-
gem install multipart-post
|
52
|
+
gem install multipart-post
|
38
53
|
|
39
|
-
|
54
|
+
#### LICENSE:
|
40
55
|
|
41
56
|
(The MIT License)
|
42
57
|
|
43
|
-
Copyright (c) 2007-
|
58
|
+
Copyright (c) 2007-2013 Nick Sieger <nick@nicksieger.com>
|
44
59
|
|
45
60
|
Permission is hereby granted, free of charge, to any person obtaining
|
46
61
|
a copy of this software and associated documentation files (the
|
data/lib/composite_io.rb
CHANGED
data/lib/multipart_post.rb
CHANGED
data/lib/multipartable.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2007-
|
2
|
+
# Copyright (c) 2007-2013 Nick Sieger.
|
3
3
|
# See the file README.txt included with the distribution for
|
4
4
|
# software license details.
|
5
5
|
#++
|
@@ -8,10 +8,19 @@ require 'parts'
|
|
8
8
|
module Multipartable
|
9
9
|
DEFAULT_BOUNDARY = "-----------RubyMultipartPost"
|
10
10
|
def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
|
11
|
+
headers = headers.clone # don't want to modify the original variable
|
12
|
+
parts_headers = headers.delete(:parts) || {}
|
11
13
|
super(path, headers)
|
12
|
-
parts = params.map
|
14
|
+
parts = params.map do |k,v|
|
15
|
+
case v
|
16
|
+
when Array
|
17
|
+
v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
|
18
|
+
else
|
19
|
+
Parts::Part.new(boundary, k, v, parts_headers[k])
|
20
|
+
end
|
21
|
+
end.flatten
|
13
22
|
parts << Parts::EpiloguePart.new(boundary)
|
14
|
-
ios = parts.map{|p| p.to_io }
|
23
|
+
ios = parts.map {|p| p.to_io }
|
15
24
|
self.set_content_type(headers["Content-Type"] || "multipart/form-data",
|
16
25
|
{ "boundary" => boundary })
|
17
26
|
self.content_length = parts.inject(0) {|sum,i| sum + i.length }
|
data/lib/parts.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2007-
|
2
|
+
# Copyright (c) 2007-2013 Nick Sieger.
|
3
3
|
# See the file README.txt included with the distribution for
|
4
4
|
# software license details.
|
5
5
|
#++
|
6
6
|
|
7
7
|
module Parts
|
8
8
|
module Part #:nodoc:
|
9
|
-
def self.new(boundary, name, value)
|
10
|
-
|
11
|
-
|
9
|
+
def self.new(boundary, name, value, headers = {})
|
10
|
+
headers ||= {} # avoid nil values
|
11
|
+
if file?(value)
|
12
|
+
FilePart.new(boundary, name, value, headers)
|
12
13
|
else
|
13
|
-
ParamPart.new(boundary, name, value)
|
14
|
+
ParamPart.new(boundary, name, value, headers)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
18
|
+
def self.file?(value)
|
19
|
+
value.respond_to?(:content_type) && value.respond_to?(:original_filename)
|
20
|
+
end
|
21
|
+
|
17
22
|
def length
|
18
23
|
@part.length
|
19
24
|
end
|
@@ -25,19 +30,20 @@ module Parts
|
|
25
30
|
|
26
31
|
class ParamPart
|
27
32
|
include Part
|
28
|
-
def initialize(boundary, name, value)
|
29
|
-
@part = build_part(boundary, name, value)
|
33
|
+
def initialize(boundary, name, value, headers = {})
|
34
|
+
@part = build_part(boundary, name, value, headers)
|
30
35
|
@io = StringIO.new(@part)
|
31
36
|
end
|
32
37
|
|
33
38
|
def length
|
34
39
|
@part.bytesize
|
35
|
-
end
|
40
|
+
end
|
36
41
|
|
37
|
-
def build_part(boundary, name, value)
|
42
|
+
def build_part(boundary, name, value, headers = {})
|
38
43
|
part = ''
|
39
44
|
part << "--#{boundary}\r\n"
|
40
45
|
part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
|
46
|
+
part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
|
41
47
|
part << "\r\n"
|
42
48
|
part << "#{value}\r\n"
|
43
49
|
end
|
@@ -47,16 +53,16 @@ module Parts
|
|
47
53
|
class FilePart
|
48
54
|
include Part
|
49
55
|
attr_reader :length
|
50
|
-
def initialize(boundary, name, io)
|
56
|
+
def initialize(boundary, name, io, headers = {})
|
51
57
|
file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
|
52
58
|
@head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
|
53
|
-
io.respond_to?(:opts) ? io.opts :
|
59
|
+
io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
|
54
60
|
@foot = "\r\n"
|
55
|
-
@length = @head.
|
61
|
+
@length = @head.bytesize + file_length + @foot.length
|
56
62
|
@io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
|
57
63
|
end
|
58
64
|
|
59
|
-
def build_head(boundary, name, filename, type, content_len, opts = {})
|
65
|
+
def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
|
60
66
|
trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
|
61
67
|
content_disposition = opts["Content-Disposition"] || "form-data"
|
62
68
|
|
@@ -67,7 +73,13 @@ module Parts
|
|
67
73
|
if content_id = opts["Content-ID"]
|
68
74
|
part << "Content-ID: #{content_id}\r\n"
|
69
75
|
end
|
70
|
-
|
76
|
+
|
77
|
+
if headers["Content-Type"] != nil
|
78
|
+
part << "Content-Type: " + headers["Content-Type"] + "\r\n"
|
79
|
+
else
|
80
|
+
part << "Content-Type: #{type}\r\n"
|
81
|
+
end
|
82
|
+
|
71
83
|
part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
|
72
84
|
part << "\r\n"
|
73
85
|
end
|
data/multipart-post.gemspec
CHANGED
@@ -17,5 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.rdoc_options = ["--main", "README.md", "-SHN", "-f", "darkfish"]
|
20
21
|
s.require_paths = ["lib"]
|
21
22
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2007-
|
2
|
+
# Copyright (c) 2007-2013 Nick Sieger.
|
3
3
|
# See the file README.txt included with the distribution for
|
4
4
|
# software license details.
|
5
5
|
#++
|
@@ -40,6 +40,47 @@ class Net::HTTP::Post::MultiPartTest < Test::Unit::TestCase
|
|
40
40
|
assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
|
41
41
|
end
|
42
42
|
|
43
|
+
def test_form_multiparty_body_with_parts_headers
|
44
|
+
@io = StringIO.new("1234567890")
|
45
|
+
@io = UploadIO.new @io, "text/plain", TEMP_FILE
|
46
|
+
parts = { :text => 'bar', :file => @io }
|
47
|
+
headers = {
|
48
|
+
:parts => {
|
49
|
+
:text => { "Content-Type" => "part/type" },
|
50
|
+
:file => { "Content-Transfer-Encoding" => "part-encoding" }
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
request = Net::HTTP::Post::Multipart.new("/foo/bar", parts, headers)
|
55
|
+
assert_results request
|
56
|
+
assert_additional_headers_added(request, headers[:parts])
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_form_multipart_body_with_array_value
|
60
|
+
File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
|
61
|
+
@io = File.open(TEMP_FILE)
|
62
|
+
@io = UploadIO.new @io, "text/plain", TEMP_FILE
|
63
|
+
params = {:foo => ['bar', 'quux'], :file => @io}
|
64
|
+
headers = { :parts => {
|
65
|
+
:foo => { "Content-Type" => "application/json; charset=UTF-8" } } }
|
66
|
+
post = Net::HTTP::Post::Multipart.new("/foo/bar", params, headers,
|
67
|
+
Net::HTTP::Post::Multipart::DEFAULT_BOUNDARY)
|
68
|
+
|
69
|
+
assert post.content_length && post.content_length > 0
|
70
|
+
assert post.body_stream
|
71
|
+
|
72
|
+
body = post.body_stream.read
|
73
|
+
assert_equal 2, body.lines.grep(/name="foo"/).length
|
74
|
+
assert body =~ /Content-Type: application\/json; charset=UTF-8/, body
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_form_multipart_body_with_arrayparam
|
78
|
+
File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
|
79
|
+
@io = File.open(TEMP_FILE)
|
80
|
+
@io = UploadIO.new @io, "text/plain", TEMP_FILE
|
81
|
+
assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :multivalueParam => ['bar','bah'], :file => @io)
|
82
|
+
end
|
83
|
+
|
43
84
|
def assert_results(post)
|
44
85
|
assert post.content_length && post.content_length > 0
|
45
86
|
assert post.body_stream
|
@@ -52,5 +93,18 @@ class Net::HTTP::Post::MultiPartTest < Test::Unit::TestCase
|
|
52
93
|
# ensure there is an epilogue
|
53
94
|
assert body =~ /^--#{boundary_regex}--\r\n/
|
54
95
|
assert body =~ /text\/plain/
|
96
|
+
if (body =~ /multivalueParam/)
|
97
|
+
assert_equal 2, body.scan(/^.*multivalueParam.*$/).size
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def assert_additional_headers_added(post, parts_headers)
|
102
|
+
post.body_stream.rewind
|
103
|
+
body = post.body_stream.read
|
104
|
+
parts_headers.each do |part, headers|
|
105
|
+
headers.each do |k,v|
|
106
|
+
assert body =~ /#{k}: #{v}/
|
107
|
+
end
|
108
|
+
end
|
55
109
|
end
|
56
110
|
end
|
data/test/test_parts.rb
CHANGED
@@ -9,6 +9,7 @@ require 'test/unit'
|
|
9
9
|
require 'parts'
|
10
10
|
require 'stringio'
|
11
11
|
require 'composite_io'
|
12
|
+
require 'tempfile'
|
12
13
|
|
13
14
|
|
14
15
|
MULTIBYTE = File.dirname(__FILE__)+'/multibyte.txt'
|
@@ -22,6 +23,27 @@ module AssertPartLength
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
26
|
+
class PartTest < Test::Unit::TestCase
|
27
|
+
def setup
|
28
|
+
@string_with_content_type = Class.new(String) do
|
29
|
+
def content_type; 'application/data'; end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_file_with_upload_io
|
34
|
+
assert Parts::Part.file?(UploadIO.new(__FILE__, "text/plain"))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_file_with_modified_string
|
38
|
+
assert !Parts::Part.file?(@string_with_content_type.new("Hello"))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_new_with_modified_string
|
42
|
+
assert_kind_of Parts::ParamPart,
|
43
|
+
Parts::Part.new("boundary", "multibyte", @string_with_content_type.new("Hello"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
25
47
|
class FilePartTest < Test::Unit::TestCase
|
26
48
|
include AssertPartLength
|
27
49
|
|
@@ -42,6 +64,13 @@ class FilePartTest < Test::Unit::TestCase
|
|
42
64
|
def test_multibyte_file_length
|
43
65
|
assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(MULTIBYTE, "text/plain"))
|
44
66
|
end
|
67
|
+
|
68
|
+
def test_multibyte_filename
|
69
|
+
name = File.read(MULTIBYTE, 300)
|
70
|
+
file = Tempfile.new(name.respond_to?(:force_encoding) ? name.force_encoding("UTF-8") : name)
|
71
|
+
assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(file, "text/plain"))
|
72
|
+
file.close
|
73
|
+
end
|
45
74
|
end
|
46
75
|
|
47
76
|
class ParamPartTest < Test::Unit::TestCase
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multipart-post
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-12-21 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'Use with Net::HTTP to do multipart form posts. IO values that have
|
15
15
|
#content_type, #original_filename, and #local_path will be posted as a binary file.'
|
@@ -22,10 +22,9 @@ files:
|
|
22
22
|
- .gitignore
|
23
23
|
- .travis.yml
|
24
24
|
- Gemfile
|
25
|
-
- Gemfile.lock
|
26
25
|
- History.txt
|
27
26
|
- Manifest.txt
|
28
|
-
- README.
|
27
|
+
- README.md
|
29
28
|
- Rakefile
|
30
29
|
- lib/composite_io.rb
|
31
30
|
- lib/multipart_post.rb
|
@@ -41,7 +40,12 @@ homepage: https://github.com/nicksieger/multipart-post
|
|
41
40
|
licenses:
|
42
41
|
- MIT
|
43
42
|
post_install_message:
|
44
|
-
rdoc_options:
|
43
|
+
rdoc_options:
|
44
|
+
- --main
|
45
|
+
- README.md
|
46
|
+
- -SHN
|
47
|
+
- -f
|
48
|
+
- darkfish
|
45
49
|
require_paths:
|
46
50
|
- lib
|
47
51
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -50,15 +54,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
54
|
- - ! '>='
|
51
55
|
- !ruby/object:Gem::Version
|
52
56
|
version: '0'
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
hash: 3851181222699685043
|
53
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
61
|
none: false
|
55
62
|
requirements:
|
56
63
|
- - ! '>='
|
57
64
|
- !ruby/object:Gem::Version
|
58
65
|
version: '0'
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
hash: 3851181222699685043
|
59
69
|
requirements: []
|
60
70
|
rubyforge_project: caldersphere
|
61
|
-
rubygems_version: 1.8.
|
71
|
+
rubygems_version: 1.8.23
|
62
72
|
signing_key:
|
63
73
|
specification_version: 3
|
64
74
|
summary: A multipart form post accessory for Net::HTTP.
|
data/Gemfile.lock
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
multipart-post (1.2.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: http://rubygems.org/
|
8
|
-
specs:
|
9
|
-
archive-tar-minitar (0.5.2)
|
10
|
-
columnize (0.3.6)
|
11
|
-
linecache (0.43)
|
12
|
-
linecache19 (0.5.12)
|
13
|
-
ruby_core_source (>= 0.1.4)
|
14
|
-
rake (0.9.2.2)
|
15
|
-
ruby-debug (0.10.3)
|
16
|
-
columnize (>= 0.1)
|
17
|
-
ruby-debug-base (~> 0.10.3.0)
|
18
|
-
ruby-debug-base (0.10.3)
|
19
|
-
linecache (>= 0.3)
|
20
|
-
ruby-debug-base19 (0.11.25)
|
21
|
-
columnize (>= 0.3.1)
|
22
|
-
linecache19 (>= 0.5.11)
|
23
|
-
ruby_core_source (>= 0.1.4)
|
24
|
-
ruby-debug19 (0.11.6)
|
25
|
-
columnize (>= 0.3.1)
|
26
|
-
linecache19 (>= 0.5.11)
|
27
|
-
ruby-debug-base19 (>= 0.11.19)
|
28
|
-
ruby_core_source (0.1.5)
|
29
|
-
archive-tar-minitar (>= 0.5.2)
|
30
|
-
|
31
|
-
PLATFORMS
|
32
|
-
java
|
33
|
-
ruby
|
34
|
-
|
35
|
-
DEPENDENCIES
|
36
|
-
multipart-post!
|
37
|
-
rake
|
38
|
-
ruby-debug
|
39
|
-
ruby-debug19
|