multipart-post 1.1.5 → 2.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e64b8bb7510028e6d5c5ca9626ff5448a12ee4e393938a69edd9eddfedf04e70
4
+ data.tar.gz: dddcaa65fa823b59d0d3a025c8ad1e88eae0e2fe976469c9b69931a0bc66af6a
5
+ SHA512:
6
+ metadata.gz: a046600778502bf34933ca1f4b2abd7ce0c7e8b4911ab9abc2bc74316cd42ce2f9167c6a40254ffb290ede586de81500a03500c09bfcb15ed96c0495bed19964
7
+ data.tar.gz: 422aa7086f923b29e545cba81cd1caca270ed4e86f0d29a50a8c61bc111ad30d546b353ef3db5eb52f63f697d03a5671e759f3119c8c200807184c9cfe3c1f58
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ pkg
3
3
  *~
4
4
  *.swo
5
5
  *.swp
6
+ /Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ --format documentation
3
+ --backtrace
4
+ --require spec_helper
5
+ --warnings
@@ -1,7 +1,20 @@
1
+ language: ruby
2
+ cache: bundler
3
+
1
4
  rvm:
2
- - 1.8.7
3
- - 1.9.2
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
+ - truffleruby
15
+
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: ruby-head
19
+ - rvm: jruby-head
20
+ - rvm: truffleruby
@@ -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
- source :rubygems
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,25 @@
1
+ <!--
2
+ # @markup rdoc
3
+ # @title CHANGELOG
4
+ -->
5
+
6
+ === 2.0.0 / 2013-12-21
7
+
8
+ - Drop Ruby 1.8 compatibility
9
+ - GH #21: Fix FilePart length calculation for Ruby 1.9 when filename contains
10
+ multibyte characters (hexfet)
11
+ - GH #20: Ensure upload responds to both #content_type and #original_filename
12
+ (Steven Davidovitz)
13
+ - GH #31: Support setting headers on any part of the request (Socrates Vicente)
14
+ - GH #30: Support array values for params (Gustav Ernberg)
15
+ - GH #32: Fix respond_to? signature (Leo Cassarani)
16
+ - GH #33: Update README to markdown (Jagtesh Chadha)
17
+ - GH #35: Improved handling of array-type parameters (Steffen Grunwald)
18
+
19
+ === 1.2.0 / 2013-02-25
20
+
21
+ - #25: Ruby 2 compatibility (thanks mislav)
22
+
1
23
  === 1.1.5 / 2012-02-12
2
24
 
3
25
  - Fix length/bytesize of parts in 1.9 (#7, #14) (Jason Moore)
@@ -40,4 +62,3 @@
40
62
  * 1 major enhancement
41
63
 
42
64
  * Birthday!
43
-
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.
@@ -0,0 +1,127 @@
1
+ # Multipart::Post
2
+
3
+ Adds a streamy multipart form post capability to `Net::HTTP`. Also supports other
4
+ methods besides `POST`.
5
+
6
+ [![Build Status](https://secure.travis-ci.org/socketry/multipart-post.svg)](http://travis-ci.org/socketry/multipart-post)
7
+
8
+ ## Features/Problems
9
+
10
+ * Appears to actually work. A good feature to have.
11
+ * Encapsulates posting of file/binary parts and name/value parameter parts, similar to
12
+ most browsers' file upload forms.
13
+ * Provides an `UploadIO` helper class to prepare IO objects for inclusion in the params
14
+ hash of the multipart post object.
15
+
16
+ ## Installation
17
+
18
+ gem install multipart-post
19
+
20
+ or in your Gemfile
21
+
22
+ gem 'multipart-post'
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ require 'net/http/post/multipart'
28
+
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
+ ```
38
+
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.
data/Rakefile CHANGED
@@ -1,9 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
2
3
 
3
- task :default => :test
4
+ RSpec::Core::RakeTask.new(:test)
4
5
 
5
- require 'rake/testtask'
6
- Rake::TestTask.new do |t|
7
- t.libs << "test"
8
- t.test_files = FileList['test/**/test*.rb']
9
- end
6
+ task :default => :test
@@ -7,54 +7,57 @@
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.
18
18
  def initialize(*ios)
19
19
  @ios = ios.flatten
20
+ @index = 0
20
21
  end
21
22
 
22
- # Read from the IO object, overlapping across underlying streams as necessary.
23
- def read(amount = nil, buf = nil)
24
- buffer = buf || ''
25
- done = if amount; nil; else ''; end
26
- partial_amount = amount
27
- parts = @ios.dup
28
-
29
- loop do
30
- result = done
23
+ # Read from IOs in order until `length` bytes have been received.
24
+ def read(length = nil, outbuf = nil)
25
+ got_result = false
26
+ outbuf = outbuf ? outbuf.replace("") : ""
31
27
 
32
- while !parts.empty? && (result = parts.first.read(partial_amount)) == done
33
- parts.shift
28
+ while io = current_io
29
+ if result = io.read(length)
30
+ got_result ||= !result.nil?
31
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
32
+ outbuf << result
33
+ length -= result.length if length
34
+ break if length == 0
34
35
  end
35
-
36
- result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
37
- buffer << result if result
38
- partial_amount -= result.length if partial_amount && result != done
39
-
40
- break if partial_amount && partial_amount <= 0
41
- break if result == done
42
- end
43
-
44
- if buffer.length > 0
45
- buffer
46
- else
47
- done
36
+ advance_io
48
37
  end
38
+ (!got_result && length) ? nil : outbuf
49
39
  end
50
-
40
+
51
41
  def rewind
52
42
  @ios.each { |io| io.rewind }
43
+ @index = 0
44
+ end
45
+
46
+ private
47
+
48
+ def current_io
49
+ @ios[@index]
50
+ end
51
+
52
+ def advance_io
53
+ @index += 1
53
54
  end
54
55
  end
55
56
 
56
57
  # Convenience methods for dealing with files and IO that are to be uploaded.
57
58
  class UploadIO
59
+ attr_reader :content_type, :original_filename, :local_path, :io, :opts
60
+
58
61
  # Create an upload IO suitable for including in the params hash of a
59
62
  # Net::HTTP::Post::Multipart.
60
63
  #
@@ -66,13 +69,9 @@ class UploadIO
66
69
  # uploading directly from a form in a framework, which often save the file to
67
70
  # an arbitrarily named RackMultipart file in /tmp).
68
71
  #
69
- # Usage:
70
- #
72
+ # @example
71
73
  # UploadIO.new("file.txt", "text/plain")
72
74
  # UploadIO.new(file_io, "text/plain", "file.txt")
73
- #
74
- attr_reader :content_type, :original_filename, :local_path, :io, :opts
75
-
76
75
  def initialize(filename_or_io, content_type, filename = nil, opts = {})
77
76
  io = filename_or_io
78
77
  local_path = ""
@@ -94,14 +93,16 @@ class UploadIO
94
93
  end
95
94
 
96
95
  def self.convert!(io, content_type, original_filename, local_path)
97
- 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."
98
99
  end
99
100
 
100
101
  def method_missing(*args)
101
102
  @io.send(*args)
102
103
  end
103
104
 
104
- def respond_to?(meth)
105
- @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)
106
107
  end
107
108
  end
@@ -1,9 +1,9 @@
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 MultipartPost
8
- VERSION = "1.1.5"
8
+ VERSION = "2.1.1"
9
9
  end
@@ -1,20 +1,48 @@
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
  require 'parts'
8
- module Multipartable
9
- DEFAULT_BOUNDARY = "-----------RubyMultipartPost"
10
- def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
11
- super(path, headers)
12
- parts = params.map {|k,v| Parts::Part.new(boundary, k, v)}
13
- parts << Parts::EpiloguePart.new(boundary)
14
- ios = parts.map{|p| p.to_io }
15
- self.set_content_type(headers["Content-Type"] || "multipart/form-data",
16
- { "boundary" => boundary })
17
- self.content_length = parts.inject(0) {|sum,i| sum + i.length }
18
- self.body_stream = CompositeReadIO.new(*ios)
19
- 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.uuid}"
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
20
45
  end
46
+
47
+ attr :boundary
48
+ end