multipart-post 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,21 +2,22 @@
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
  require "multipart_post"
4
4
 
5
- Gem::Specification.new do |s|
6
- s.name = "multipart-post"
7
- s.version = MultipartPost::VERSION
8
- s.authors = ["Nick Sieger"]
9
- s.email = ["nick@nicksieger.com"]
10
- s.homepage = "https://github.com/nicksieger/multipart-post"
11
- s.summary = %q{A multipart form post accessory for Net::HTTP.}
12
- s.license = "MIT"
13
- s.description = %q{Use with Net::HTTP to do multipart form posts. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file.}
14
-
15
- s.rubyforge_project = "caldersphere"
16
-
17
- s.files = `git ls-files`.split("\n")
18
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.rdoc_options = ["--main", "README.md", "-SHN", "-f", "darkfish"]
21
- s.require_paths = ["lib"]
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "multipart-post"
7
+ spec.version = MultipartPost::VERSION
8
+ spec.authors = ["Nick Sieger", "Samuel Williams"]
9
+ spec.email = ["nick@nicksieger.com", "samuel.williams@oriontransfer.co.nz"]
10
+ spec.homepage = "https://github.com/nicksieger/multipart-post"
11
+ spec.summary = %q{A multipart form post accessory for Net::HTTP.}
12
+ spec.license = "MIT"
13
+ spec.description = %q{Use with Net::HTTP to do multipart form postspec. IO values that have #content_type, #original_filename, and #local_path will be posted as a binary file.}
14
+
15
+ spec.files = `git ls-files`.split("\n")
16
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency 'bundler', ['>= 1.3', '< 3']
21
+ spec.add_development_dependency 'rspec', '~> 3.4'
22
+ spec.add_development_dependency 'rake'
22
23
  end
@@ -0,0 +1,138 @@
1
+ # Copyright, 2012, by Nick Sieger.
2
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require 'composite_io'
23
+ require 'stringio'
24
+ require 'timeout'
25
+
26
+ RSpec.shared_context "composite io" do
27
+ it "test_full_read_from_several_ios" do
28
+ expect(subject.read).to be == 'the quick brown fox'
29
+ end
30
+
31
+ it "test_partial_read" do
32
+ expect(subject.read(9)).to be == 'the quick'
33
+ end
34
+
35
+ it "test_partial_read_to_boundary" do
36
+ expect(subject.read(10)).to be == 'the quick '
37
+ end
38
+
39
+ it "test_read_with_size_larger_than_available" do
40
+ expect(subject.read(32)).to be == 'the quick brown fox'
41
+ end
42
+
43
+ it "test_read_into_buffer" do
44
+ buf = ''
45
+ subject.read(nil, buf)
46
+ expect(buf).to be == 'the quick brown fox'
47
+ end
48
+
49
+ it "test_multiple_reads" do
50
+ expect(subject.read(4)).to be == 'the '
51
+ expect(subject.read(4)).to be == 'quic'
52
+ expect(subject.read(4)).to be == 'k br'
53
+ expect(subject.read(4)).to be == 'own '
54
+ expect(subject.read(4)).to be == 'fox'
55
+ end
56
+
57
+ it "test_read_after_end" do
58
+ subject.read
59
+ expect(subject.read).to be == ""
60
+ end
61
+
62
+ it "test_read_after_end_with_amount" do
63
+ subject.read(32)
64
+ expect(subject.read(32)).to be_nil
65
+ end
66
+
67
+ it "test_second_full_read_after_rewinding" do
68
+ subject.read
69
+ subject.rewind
70
+ expect(subject.read).to be == 'the quick brown fox'
71
+ end
72
+
73
+ # Was apparently broken on JRuby due to http://jira.codehaus.org/browse/JRUBY-7109
74
+ it "test_compatible_with_copy_stream" do
75
+ target_io = StringIO.new
76
+ Timeout.timeout(1) do # Not sure why we need this in the spec?
77
+ IO.copy_stream(subject, target_io)
78
+ end
79
+ expect(target_io.string).to be == "the quick brown fox"
80
+ end
81
+ end
82
+
83
+ RSpec.describe CompositeReadIO do
84
+ describe "generic io" do
85
+ subject {StringIO.new('the quick brown fox')}
86
+
87
+ include_context "composite io"
88
+ end
89
+
90
+ describe "composite io" do
91
+ subject {CompositeReadIO.new(StringIO.new('the '), StringIO.new('quick '), StringIO.new('brown '), StringIO.new('fox'))}
92
+
93
+ include_context "composite io"
94
+ end
95
+
96
+ describe "nested composite io" do
97
+ subject {CompositeReadIO.new(CompositeReadIO.new(StringIO.new('the '), StringIO.new('quick ')), StringIO.new('brown '), StringIO.new('fox'))}
98
+
99
+ include_context "composite io"
100
+ end
101
+
102
+ describe "unicode composite io" do
103
+ let(:utf8_io) {File.open(File.dirname(__FILE__)+'/multibyte.txt')}
104
+ let(:binary_io) {StringIO.new("\x86")}
105
+
106
+ subject {CompositeReadIO.new(binary_io, utf8_io)}
107
+
108
+ it "test_read_from_multibyte" do
109
+ expect(subject.read).to be == "\x86\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB\n".b
110
+ end
111
+ end
112
+
113
+ it "test_convert_error" do
114
+ expect do
115
+ UploadIO.convert!('tmp.txt', 'text/plain', 'tmp.txt', 'tmp.txt')
116
+ end.to raise_error(ArgumentError, /convert! has been removed/)
117
+ end
118
+
119
+ it "test_empty" do
120
+ expect(subject.read).to be == ""
121
+ end
122
+
123
+ it "test_empty_limited" do
124
+ expect(subject.read(1)).to be_nil
125
+ end
126
+
127
+ it "test_empty_parts" do
128
+ io = CompositeReadIO.new(StringIO.new, StringIO.new('the '), StringIO.new, StringIO.new('quick'))
129
+ expect(io.read(3)).to be == "the"
130
+ expect(io.read(3)).to be == " qu"
131
+ expect(io.read(3)).to be == "ick"
132
+ end
133
+
134
+ it "test_all_empty_parts" do
135
+ io = CompositeReadIO.new(StringIO.new, StringIO.new)
136
+ expect(io.read(1)).to be_nil
137
+ end
138
+ end
File without changes
@@ -5,42 +5,72 @@
5
5
  #++
6
6
 
7
7
  require 'net/http/post/multipart'
8
- require 'test/unit'
9
8
 
10
- class Net::HTTP::Post::MultiPartTest < Test::Unit::TestCase
11
- TEMP_FILE = "temp.txt"
12
-
13
- HTTPPost = Struct.new("HTTPPost", :content_length, :body_stream, :content_type)
14
- HTTPPost.module_eval do
15
- def set_content_type(type, params = {})
16
- self.content_type = type + params.map{|k,v|"; #{k}=#{v}"}.join('')
9
+ RSpec.shared_context "net http multipart" do
10
+ let(:temp_file) {"temp.txt"}
11
+ let(:http_post) do
12
+ Struct.new("HTTPPost", :content_length, :body_stream, :content_type) do
13
+ def set_content_type(type, params = {})
14
+ self.content_type = type + params.map{|k,v|"; #{k}=#{v}"}.join('')
15
+ end
16
+ end
17
+ end
18
+
19
+ after(:each) do
20
+ File.delete(temp_file) rescue nil
21
+ end
22
+
23
+ def assert_results(post)
24
+ expect(post.content_length).to be > 0
25
+ expect(post.body_stream).to_not be_nil
26
+
27
+ expect(post['content-type']).to be == "multipart/form-data; boundary=#{post.boundary}"
28
+
29
+ body = post.body_stream.read
30
+ boundary_regex = Regexp.quote(post.boundary)
31
+
32
+ expect(body).to be =~ /1234567890/
33
+
34
+ # ensure there is at least one boundary
35
+ expect(body).to be =~ /^--#{boundary_regex}\r\n/
36
+
37
+ # ensure there is an epilogue
38
+ expect(body).to be =~ /^--#{boundary_regex}--\r\n/
39
+ expect(body).to be =~ /text\/plain/
40
+
41
+ if (body =~ /multivalueParam/)
42
+ expect(body.scan(/^.*multivalueParam.*$/).size).to be == 2
17
43
  end
18
44
  end
19
45
 
20
- def teardown
21
- File.delete(TEMP_FILE) rescue nil
46
+ def assert_additional_headers_added(post, parts_headers)
47
+ post.body_stream.rewind
48
+ body = post.body_stream.read
49
+ parts_headers.each do |part, headers|
50
+ headers.each do |k,v|
51
+ expect(body).to be =~ /#{k}: #{v}/
52
+ end
53
+ end
22
54
  end
55
+ end
23
56
 
24
- def test_form_multipart_body
57
+ RSpec.describe Net::HTTP::Post::Multipart do
58
+ include_context "net http multipart"
59
+
60
+ it "test_form_multipart_body" do
25
61
  File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
26
62
  @io = File.open(TEMP_FILE)
27
63
  @io = UploadIO.new @io, "text/plain", TEMP_FILE
28
64
  assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
29
65
  end
30
- def test_form_multipart_body_put
31
- File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
32
- @io = File.open(TEMP_FILE)
33
- @io = UploadIO.new @io, "text/plain", TEMP_FILE
34
- assert_results Net::HTTP::Put::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
35
- end
36
66
 
37
- def test_form_multipart_body_with_stringio
67
+ it "test_form_multipart_body_with_stringio" do
38
68
  @io = StringIO.new("1234567890")
39
69
  @io = UploadIO.new @io, "text/plain", TEMP_FILE
40
70
  assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
41
71
  end
42
72
 
43
- def test_form_multiparty_body_with_parts_headers
73
+ it "test_form_multiparty_body_with_parts_headers" do
44
74
  @io = StringIO.new("1234567890")
45
75
  @io = UploadIO.new @io, "text/plain", TEMP_FILE
46
76
  parts = { :text => 'bar', :file => @io }
@@ -56,55 +86,38 @@ class Net::HTTP::Post::MultiPartTest < Test::Unit::TestCase
56
86
  assert_additional_headers_added(request, headers[:parts])
57
87
  end
58
88
 
59
- def test_form_multipart_body_with_array_value
89
+ it "test_form_multipart_body_with_array_value" do
60
90
  File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
61
91
  @io = File.open(TEMP_FILE)
62
92
  @io = UploadIO.new @io, "text/plain", TEMP_FILE
63
93
  params = {:foo => ['bar', 'quux'], :file => @io}
64
94
  headers = { :parts => {
65
95
  :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
96
+ post = Net::HTTP::Post::Multipart.new("/foo/bar", params, headers)
97
+
98
+ expect(post.content_length).to be > 0
99
+ expect(post.body_stream).to_not be_nil
71
100
 
72
101
  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
102
+ expect(body.lines.grep(/name="foo"/).length).to be == 2
103
+ expect(body) =~ /Content-Type: application\/json; charset=UTF-8/
75
104
  end
76
105
 
77
- def test_form_multipart_body_with_arrayparam
106
+ it "test_form_multipart_body_with_arrayparam" do
78
107
  File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
79
108
  @io = File.open(TEMP_FILE)
80
109
  @io = UploadIO.new @io, "text/plain", TEMP_FILE
81
110
  assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :multivalueParam => ['bar','bah'], :file => @io)
82
111
  end
112
+ end
83
113
 
84
- def assert_results(post)
85
- assert post.content_length && post.content_length > 0
86
- assert post.body_stream
87
- assert_equal "multipart/form-data; boundary=#{Multipartable::DEFAULT_BOUNDARY}", post['content-type']
88
- body = post.body_stream.read
89
- boundary_regex = Regexp.quote Multipartable::DEFAULT_BOUNDARY
90
- assert body =~ /1234567890/
91
- # ensure there is at least one boundary
92
- assert body =~ /^--#{boundary_regex}\r\n/
93
- # ensure there is an epilogue
94
- assert body =~ /^--#{boundary_regex}--\r\n/
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
114
+ RSpec.describe Net::HTTP::Put::Multipart do
115
+ include_context "net http multipart"
116
+
117
+ it "test_form_multipart_body_put" do
118
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
119
+ @io = File.open(TEMP_FILE)
120
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
121
+ assert_results Net::HTTP::Put::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
109
122
  end
110
123
  end
@@ -0,0 +1,102 @@
1
+ # Copyright, 2012, by Nick Sieger.
2
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require 'parts'
23
+ require 'stringio'
24
+ require 'composite_io'
25
+ require 'tempfile'
26
+
27
+ MULTIBYTE = File.dirname(__FILE__)+'/multibyte.txt'
28
+ TEMP_FILE = "temp.txt"
29
+
30
+ module AssertPartLength
31
+ def assert_part_length(part)
32
+ bytes = part.to_io.read
33
+ bytesize = bytes.respond_to?(:bytesize) ? bytes.bytesize : bytes.length
34
+ expect(bytesize).to be == part.length
35
+ end
36
+ end
37
+
38
+ RSpec.describe Parts do
39
+ let(:string_with_content_type) do
40
+ Class.new(String) do
41
+ def content_type; 'application/data'; end
42
+ end
43
+ end
44
+
45
+ it "test_file_with_upload_io" do
46
+ expect(Parts::Part.file?(UploadIO.new(__FILE__, "text/plain"))).to be true
47
+ end
48
+
49
+ it "test_file_with_modified_string" do
50
+ expect(Parts::Part.file?(string_with_content_type.new("Hello"))).to be false
51
+ end
52
+
53
+ it "test_new_with_modified_string" do
54
+ expect(Parts::Part.new("boundary", "multibyte", string_with_content_type.new("Hello"))).to be_kind_of(Parts::ParamPart)
55
+ end
56
+ end
57
+
58
+ RSpec.describe Parts::FilePart do
59
+ include AssertPartLength
60
+
61
+ before(:each) do
62
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
63
+ io = UploadIO.new(TEMP_FILE, "text/plain")
64
+ @part = Parts::FilePart.new("boundary", "afile", io)
65
+ end
66
+
67
+ after(:each) do
68
+ File.delete(TEMP_FILE) rescue nil
69
+ end
70
+
71
+ it "test_correct_length" do
72
+ assert_part_length @part
73
+ end
74
+
75
+ it "test_multibyte_file_length" do
76
+ assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(MULTIBYTE, "text/plain"))
77
+ end
78
+
79
+ it "test_multibyte_filename" do
80
+ name = File.read(MULTIBYTE, 300)
81
+ file = Tempfile.new(name.respond_to?(:force_encoding) ? name.force_encoding("UTF-8") : name)
82
+ assert_part_length Parts::FilePart.new("boundary", "multibyte", UploadIO.new(file, "text/plain"))
83
+ file.close
84
+ end
85
+
86
+ it "test_force_content_type_header" do
87
+ part = Parts::FilePart.new("boundary", "afile", UploadIO.new(TEMP_FILE, "text/plain"), { "Content-Type" => "application/pdf" })
88
+ expect(part.to_io.read).to match(/Content-Type: application\/pdf/)
89
+ end
90
+ end
91
+
92
+ RSpec.describe Parts::ParamPart do
93
+ include AssertPartLength
94
+
95
+ before(:each) do
96
+ @part = Parts::ParamPart.new("boundary", "multibyte", File.read(MULTIBYTE))
97
+ end
98
+
99
+ it "test_correct_length" do
100
+ assert_part_length @part
101
+ end
102
+ end
@@ -0,0 +1,29 @@
1
+
2
+ if ENV['COVERAGE']
3
+ begin
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ add_filter "/spec/"
8
+ end
9
+
10
+ if ENV['TRAVIS']
11
+ require 'coveralls'
12
+ Coveralls.wear!
13
+ end
14
+ rescue LoadError
15
+ warn "Could not load simplecov: #{$!}"
16
+ end
17
+ end
18
+
19
+ require "bundler/setup"
20
+ require "multipart_post"
21
+
22
+ RSpec.configure do |config|
23
+ # Enable flags like --only-failures and --next-failure
24
+ config.example_status_persistence_file_path = ".rspec_status"
25
+
26
+ config.expect_with :rspec do |c|
27
+ c.syntax = :expect
28
+ end
29
+ end