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 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