multipart-post 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![build status](https://travis-ci.org/nicksieger/multipart-post.png)
|
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
|