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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b7f48186ca2c1e47dbb18d8ba20ed99e1d2dbabac5735c47a5fdf3f3de2ff6c1
4
+ data.tar.gz: 86bdb76a37261133d45eba29e94c6026118564fa0168120549d64a8cf89e19f7
5
+ SHA512:
6
+ metadata.gz: 47022f82aba55743549c58e82cda833a1f769927ca7c348b35e8dbcfeec851ca5570ef4b43755d33c4d5957a97b915412e86d9b333769f5cd11d0b9cdeede744
7
+ data.tar.gz: c9c767b946971349722a5798b0594f9da22095262f0f322391656dc79e73a677a48b0fdc53942f5bfbfe58d258c7e6df8bd3fb02821b49a64f794c6f898012d5
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ --format documentation
3
+ --backtrace
4
+ --require spec_helper
5
+ --warnings
@@ -1,7 +1,18 @@
1
+ language: ruby
2
+ cache: bundler
3
+
1
4
  rvm:
2
- - 1.9.3
3
- - 2.0.0
4
- - jruby
5
- branches:
6
- only:
7
- - master
5
+ - 2.0
6
+ - 2.1
7
+ - 2.2
8
+ - 2.3
9
+ - 2.4
10
+ - 2.5
11
+ - 2.6
12
+ - ruby-head
13
+ - jruby-head
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: "ruby-head"
18
+ - rvm: "jruby-head"
@@ -0,0 +1,6 @@
1
+ --no-private
2
+ lib/**/*.rb
3
+ -
4
+ History.txt
5
+ LICENSE
6
+ README.md
data/Gemfile CHANGED
@@ -1,14 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
- platforms :mri_19 do
5
- gem 'ruby-debug19'
6
- end
7
-
8
- platforms :mri_18 do
9
- gem 'ruby-debug'
10
- end
11
-
12
4
  group :development, :test do
13
5
  gem 'rake'
14
6
  end
@@ -1,3 +1,8 @@
1
+ <!--
2
+ # @markup rdoc
3
+ # @title CHANGELOG
4
+ -->
5
+
1
6
  === 2.0.0 / 2013-12-21
2
7
 
3
8
  - Drop Ruby 1.8 compatibility
@@ -57,4 +62,3 @@
57
62
  * 1 major enhancement
58
63
 
59
64
  * Birthday!
60
-
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2007-2013 Nick Sieger nick@nicksieger.com
2
+ Copyright, 2017, by Samuel G. D. Williams.
3
+
4
+ MIT license.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,77 +1,127 @@
1
- ## multipart-post
1
+ # `Multipart::Post`
2
2
 
3
- * http://github.com/nicksieger/multipart-post
3
+ Adds a streamy multipart form post capability to `Net::HTTP`. Also supports other
4
+ methods besides `POST`.
4
5
 
5
- ![build status](https://travis-ci.org/nicksieger/multipart-post.png)
6
+ [![Build Status](https://secure.travis-ci.org/ioquatix/multipart-post.svg)](http://travis-ci.org/ioquatix/multipart-post)
6
7
 
7
- #### DESCRIPTION:
8
-
9
- Adds a streamy multipart form post capability to Net::HTTP. Also
10
- supports other methods besides POST.
11
-
12
- #### FEATURES/PROBLEMS:
8
+ ## Features/Problems
13
9
 
14
10
  * Appears to actually work. A good feature to have.
15
- * Encapsulates posting of file/binary parts and name/value parameter parts, similar to
11
+ * Encapsulates posting of file/binary parts and name/value parameter parts, similar to
16
12
  most browsers' file upload forms.
17
- * Provides an UploadIO helper class to prepare IO objects for inclusion in the params
13
+ * Provides an `UploadIO` helper class to prepare IO objects for inclusion in the params
18
14
  hash of the multipart post object.
19
15
 
20
- #### SYNOPSIS:
21
-
22
- require 'net/http/post/multipart'
23
-
24
- url = URI.parse('http://www.example.com/upload')
25
- File.open("./image.jpg") do |jpg|
26
- req = Net::HTTP::Post::Multipart.new url.path,
27
- "file" => UploadIO.new(jpg, "image/jpeg", "image.jpg")
28
- res = Net::HTTP.start(url.host, url.port) do |http|
29
- http.request(req)
30
- end
31
- end
32
-
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:
47
-
48
- None
49
-
50
- #### INSTALL:
16
+ ## Installation
51
17
 
52
18
  gem install multipart-post
53
19
 
54
- #### LICENSE:
20
+ or in your Gemfile
55
21
 
56
- (The MIT License)
22
+ gem 'multipart-post'
57
23
 
58
- Copyright (c) 2007-2013 Nick Sieger <nick@nicksieger.com>
24
+ ## Usage
59
25
 
60
- Permission is hereby granted, free of charge, to any person obtaining
61
- a copy of this software and associated documentation files (the
62
- 'Software'), to deal in the Software without restriction, including
63
- without limitation the rights to use, copy, modify, merge, publish,
64
- distribute, sublicense, and/or sell copies of the Software, and to
65
- permit persons to whom the Software is furnished to do so, subject to
66
- the following conditions:
26
+ ```ruby
27
+ require 'net/http/post/multipart'
67
28
 
68
- The above copyright notice and this permission notice shall be
69
- included in all copies or substantial portions of the Software.
29
+ url = URI.parse('http://www.example.com/upload')
30
+ File.open("./image.jpg") do |jpg|
31
+ req = Net::HTTP::Post::Multipart.new url.path,
32
+ "file" => UploadIO.new(jpg, "image/jpeg", "image.jpg")
33
+ res = Net::HTTP.start(url.host, url.port) do |http|
34
+ http.request(req)
35
+ end
36
+ end
37
+ ```
70
38
 
71
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
72
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
74
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
75
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
76
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
77
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39
+ To post multiple files or attachments, simply include multiple parameters with
40
+ `UploadIO` values:
41
+
42
+ ```ruby
43
+ require 'net/http/post/multipart'
44
+
45
+ url = URI.parse('http://www.example.com/upload')
46
+ req = Net::HTTP::Post::Multipart.new url.path,
47
+ "file1" => UploadIO.new(File.new("./image.jpg"), "image/jpeg", "image.jpg"),
48
+ "file2" => UploadIO.new(File.new("./image2.jpg"), "image/jpeg", "image2.jpg")
49
+ res = Net::HTTP.start(url.host, url.port) do |http|
50
+ http.request(req)
51
+ end
52
+ ```
53
+
54
+ To post files with other normal, non-file params such as input values, you need to pass hashes to the `Multipart.new` method.
55
+
56
+ In Rails 4 for example:
57
+
58
+ ```ruby
59
+ def model_params
60
+ require_params = params.require(:model).permit(:param_one, :param_two, :param_three, :avatar)
61
+ require_params[:avatar] = model_params[:avatar].present? ? UploadIO.new(model_params[:avatar].tempfile, model_params[:avatar].content_type, model_params[:avatar].original_filename) : nil
62
+ require_params
63
+ end
64
+
65
+ require 'net/http/post/multipart'
66
+
67
+ url = URI.parse('http://www.example.com/upload')
68
+ Net::HTTP.start(url.host, url.port) do |http|
69
+ req = Net::HTTP::Post::Multipart.new(url, model_params)
70
+ key = "authorization_key"
71
+ req.add_field("Authorization", key) #add to Headers
72
+ http.use_ssl = (url.scheme == "https")
73
+ http.request(req)
74
+ end
75
+ ```
76
+
77
+ Or in plain ruby:
78
+
79
+ ```ruby
80
+ def params(file)
81
+ params = { "description" => "A nice picture!" }
82
+ params[:datei] = UploadIO.new(file, "image/jpeg", "image.jpg")
83
+ params
84
+ end
85
+
86
+ url = URI.parse('http://www.example.com/upload')
87
+ File.open("./image.jpg") do |file|
88
+ req = Net::HTTP::Post::Multipart.new(url.path, params(file))
89
+ res = Net::HTTP.start(url.host, url.port) do |http|
90
+ return http.request(req).body
91
+ end
92
+ end
93
+ ```
94
+
95
+ ### Debugging
96
+
97
+ You can debug requests and responses (e.g. status codes) for all requests by adding the following code:
98
+
99
+ ```ruby
100
+ http = Net::HTTP.new(uri.host, uri.port)
101
+ http.set_debug_output($stdout)
102
+ ```
103
+
104
+ ## License
105
+
106
+ Released under the MIT license.
107
+
108
+ Copyright (c) 2007-2013 Nick Sieger <nick@nicksieger.com>
109
+ Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
110
+
111
+ Permission is hereby granted, free of charge, to any person obtaining a copy
112
+ of this software and associated documentation files (the "Software"), to deal
113
+ in the Software without restriction, including without limitation the rights
114
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
115
+ copies of the Software, and to permit persons to whom the Software is
116
+ furnished to do so, subject to the following conditions:
117
+
118
+ The above copyright notice and this permission notice shall be included in
119
+ all copies or substantial portions of the Software.
120
+
121
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
122
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
123
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
124
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
125
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
126
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
127
+ THE SOFTWARE.
@@ -7,11 +7,11 @@
7
7
  # Concatenate together multiple IO objects into a single, composite IO object
8
8
  # for purposes of reading as a single stream.
9
9
  #
10
- # Usage:
11
- #
12
- # crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
10
+ # @example
11
+ # crio = CompositeReadIO.new(StringIO.new('one'),
12
+ # StringIO.new('two'),
13
+ # StringIO.new('three'))
13
14
  # puts crio.read # => "onetwothree"
14
- #
15
15
  class CompositeReadIO
16
16
  # Create a new composite-read IO from the arguments, all of which should
17
17
  # respond to #read in a manner consistent with IO.
@@ -56,6 +56,8 @@ end
56
56
 
57
57
  # Convenience methods for dealing with files and IO that are to be uploaded.
58
58
  class UploadIO
59
+ attr_reader :content_type, :original_filename, :local_path, :io, :opts
60
+
59
61
  # Create an upload IO suitable for including in the params hash of a
60
62
  # Net::HTTP::Post::Multipart.
61
63
  #
@@ -67,13 +69,9 @@ class UploadIO
67
69
  # uploading directly from a form in a framework, which often save the file to
68
70
  # an arbitrarily named RackMultipart file in /tmp).
69
71
  #
70
- # Usage:
71
- #
72
+ # @example
72
73
  # UploadIO.new("file.txt", "text/plain")
73
74
  # UploadIO.new(file_io, "text/plain", "file.txt")
74
- #
75
- attr_reader :content_type, :original_filename, :local_path, :io, :opts
76
-
77
75
  def initialize(filename_or_io, content_type, filename = nil, opts = {})
78
76
  io = filename_or_io
79
77
  local_path = ""
@@ -95,7 +93,9 @@ class UploadIO
95
93
  end
96
94
 
97
95
  def self.convert!(io, content_type, original_filename, local_path)
98
- raise ArgumentError, "convert! has been removed. You must now wrap IOs using:\nUploadIO.new(filename_or_io, content_type, filename=nil)\nPlease update your code."
96
+ raise ArgumentError, "convert! has been removed. You must now wrap IOs " \
97
+ "using:\nUploadIO.new(filename_or_io, content_type, " \
98
+ "filename=nil)\nPlease update your code."
99
99
  end
100
100
 
101
101
  def method_missing(*args)
@@ -5,5 +5,5 @@
5
5
  #++
6
6
 
7
7
  module MultipartPost
8
- VERSION = "2.0.0"
8
+ VERSION = "2.1.0"
9
9
  end
@@ -5,25 +5,44 @@
5
5
  #++
6
6
 
7
7
  require 'parts'
8
- module Multipartable
9
- DEFAULT_BOUNDARY = "-----------RubyMultipartPost"
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) || {}
13
- super(path, headers)
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
22
- parts << Parts::EpiloguePart.new(boundary)
23
- ios = parts.map {|p| p.to_io }
24
- self.set_content_type(headers["Content-Type"] || "multipart/form-data",
25
- { "boundary" => boundary })
26
- self.content_length = parts.inject(0) {|sum,i| sum + i.length }
27
- self.body_stream = CompositeReadIO.new(*ios)
28
- end
8
+ require 'securerandom'
9
+
10
+ module Multipartable
11
+ def self.secure_boundary
12
+ # https://tools.ietf.org/html/rfc7230
13
+ # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
14
+ # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
15
+ # / DIGIT / ALPHA
16
+
17
+ # https://tools.ietf.org/html/rfc2046
18
+ # bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
19
+ # "+" / "_" / "," / "-" / "." /
20
+ # "/" / ":" / "=" / "?"
21
+
22
+ "--#{SecureRandom.alphanumeric(60)}"
23
+ end
24
+
25
+ def initialize(path, params, headers={}, boundary = Multipartable.secure_boundary)
26
+ headers = headers.clone # don't want to modify the original variable
27
+ parts_headers = headers.delete(:parts) || {}
28
+ super(path, headers)
29
+ parts = params.map do |k,v|
30
+ case v
31
+ when Array
32
+ v.map {|item| Parts::Part.new(boundary, k, item, parts_headers[k]) }
33
+ else
34
+ Parts::Part.new(boundary, k, v, parts_headers[k])
35
+ end
36
+ end.flatten
37
+ parts << Parts::EpiloguePart.new(boundary)
38
+ ios = parts.map {|p| p.to_io }
39
+ self.set_content_type(headers["Content-Type"] || "multipart/form-data",
40
+ { "boundary" => boundary })
41
+ self.content_length = parts.inject(0) {|sum,i| sum + i.length }
42
+ self.body_stream = CompositeReadIO.new(*ios)
43
+
44
+ @boundary = boundary
29
45
  end
46
+
47
+ attr :boundary
48
+ end
@@ -11,14 +11,15 @@ require 'composite_io'
11
11
  require 'multipartable'
12
12
  require 'parts'
13
13
 
14
- module Net #:nodoc:
15
- class HTTP #:nodoc:
14
+ module Net
15
+ class HTTP
16
16
  class Put
17
17
  class Multipart < Put
18
18
  include Multipartable
19
19
  end
20
20
  end
21
- class Post #:nodoc:
21
+
22
+ class Post
22
23
  class Multipart < Post
23
24
  include Multipartable
24
25
  end
@@ -5,7 +5,7 @@
5
5
  #++
6
6
 
7
7
  module Parts
8
- module Part #:nodoc:
8
+ module Part
9
9
  def self.new(boundary, name, value, headers = {})
10
10
  headers ||= {} # avoid nil values
11
11
  if file?(value)
@@ -28,8 +28,14 @@ module Parts
28
28
  end
29
29
  end
30
30
 
31
+ # Represents a parametric part to be filled with given value.
31
32
  class ParamPart
32
33
  include Part
34
+
35
+ # @param boundary [String]
36
+ # @param name [#to_s]
37
+ # @param value [String]
38
+ # @param headers [Hash] Content-Type is used, if present.
33
39
  def initialize(boundary, name, value, headers = {})
34
40
  @part = build_part(boundary, name, value, headers)
35
41
  @io = StringIO.new(@part)
@@ -39,6 +45,10 @@ module Parts
39
45
  @part.bytesize
40
46
  end
41
47
 
48
+ # @param boundary [String]
49
+ # @param name [#to_s]
50
+ # @param value [String]
51
+ # @param headers [Hash] Content-Type is used, if present.
42
52
  def build_part(boundary, name, value, headers = {})
43
53
  part = ''
44
54
  part << "--#{boundary}\r\n"
@@ -52,7 +62,13 @@ module Parts
52
62
  # Represents a part to be filled from file IO.
53
63
  class FilePart
54
64
  include Part
65
+
55
66
  attr_reader :length
67
+
68
+ # @param boundary [String]
69
+ # @param name [#to_s]
70
+ # @param io [IO]
71
+ # @param headers [Hash]
56
72
  def initialize(boundary, name, io, headers = {})
57
73
  file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
58
74
  @head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
@@ -62,25 +78,38 @@ module Parts
62
78
  @io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
63
79
  end
64
80
 
65
- def build_head(boundary, name, filename, type, content_len, opts = {}, headers = {})
66
- trans_encoding = opts["Content-Transfer-Encoding"] || "binary"
67
- content_disposition = opts["Content-Disposition"] || "form-data"
81
+ # @param boundary [String]
82
+ # @param name [#to_s]
83
+ # @param filename [String]
84
+ # @param type [String]
85
+ # @param content_len [Integer]
86
+ # @param opts [Hash]
87
+ def build_head(boundary, name, filename, type, content_len, opts = {})
88
+ opts = opts.clone
89
+
90
+ trans_encoding = opts.delete("Content-Transfer-Encoding") || "binary"
91
+ content_disposition = opts.delete("Content-Disposition") || "form-data"
68
92
 
69
93
  part = ''
70
94
  part << "--#{boundary}\r\n"
71
95
  part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
72
96
  part << "Content-Length: #{content_len}\r\n"
73
- if content_id = opts["Content-ID"]
97
+ if content_id = opts.delete("Content-ID")
74
98
  part << "Content-ID: #{content_id}\r\n"
75
99
  end
76
100
 
77
- if headers["Content-Type"] != nil
78
- part << "Content-Type: " + headers["Content-Type"] + "\r\n"
101
+ if opts["Content-Type"] != nil
102
+ part << "Content-Type: " + opts["Content-Type"] + "\r\n"
79
103
  else
80
104
  part << "Content-Type: #{type}\r\n"
81
105
  end
82
106
 
83
107
  part << "Content-Transfer-Encoding: #{trans_encoding}\r\n"
108
+
109
+ opts.each do |k, v|
110
+ part << "#{k}: #{v}\r\n"
111
+ end
112
+
84
113
  part << "\r\n"
85
114
  end
86
115
  end
@@ -88,8 +117,9 @@ module Parts
88
117
  # Represents the epilogue or closing boundary.
89
118
  class EpiloguePart
90
119
  include Part
120
+
91
121
  def initialize(boundary)
92
- @part = "--#{boundary}--\r\n\r\n"
122
+ @part = "--#{boundary}--\r\n"
93
123
  @io = StringIO.new(@part)
94
124
  end
95
125
  end