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 CHANGED
@@ -3,3 +3,4 @@ pkg
3
3
  *~
4
4
  *.swo
5
5
  *.swp
6
+ /Gemfile.lock
@@ -1,6 +1,6 @@
1
1
  rvm:
2
- - 1.8.7
3
- - 1.9.2
2
+ - 1.9.3
3
+ - 2.0.0
4
4
  - jruby
5
5
  branches:
6
6
  only:
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  platforms :mri_19 do
@@ -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)
@@ -1,13 +1,15 @@
1
- = multipart-post
1
+ ## multipart-post
2
2
 
3
3
  * http://github.com/nicksieger/multipart-post
4
4
 
5
- == DESCRIPTION:
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
- == FEATURES/PROBLEMS:
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
- == SYNOPSIS:
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
- == REQUIREMENTS:
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
- == INSTALL:
50
+ #### INSTALL:
36
51
 
37
- gem install multipart-post
52
+ gem install multipart-post
38
53
 
39
- == LICENSE:
54
+ #### LICENSE:
40
55
 
41
56
  (The MIT License)
42
57
 
43
- Copyright (c) 2007-2012 Nick Sieger <nick@nicksieger.com>
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
@@ -102,7 +102,7 @@ class UploadIO
102
102
  @io.send(*args)
103
103
  end
104
104
 
105
- def respond_to?(meth)
106
- @io.respond_to?(meth) || super(meth)
105
+ def respond_to?(meth, include_all = false)
106
+ @io.respond_to?(meth, include_all) || super(meth, include_all)
107
107
  end
108
108
  end
@@ -5,5 +5,5 @@
5
5
  #++
6
6
 
7
7
  module MultipartPost
8
- VERSION = "1.2.0"
8
+ VERSION = "2.0.0"
9
9
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2007-2012 Nick Sieger.
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 {|k,v| Parts::Part.new(boundary, k, v)}
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 }
@@ -1,19 +1,24 @@
1
1
  #--
2
- # Copyright (c) 2007-2012 Nick Sieger.
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
- if value.respond_to? :content_type
11
- FilePart.new(boundary, name, value)
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.length + file_length + @foot.length
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
- part << "Content-Type: #{type}\r\n"
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
@@ -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-2012 Nick Sieger.
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
@@ -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: 1.2.0
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-02-26 00:00:00.000000000 Z
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.txt
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.24
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.
@@ -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