multipart-post 2.1.1

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: e64b8bb7510028e6d5c5ca9626ff5448a12ee4e393938a69edd9eddfedf04e70
4
+ data.tar.gz: dddcaa65fa823b59d0d3a025c8ad1e88eae0e2fe976469c9b69931a0bc66af6a
5
+ SHA512:
6
+ metadata.gz: a046600778502bf34933ca1f4b2abd7ce0c7e8b4911ab9abc2bc74316cd42ce2f9167c6a40254ffb290ede586de81500a03500c09bfcb15ed96c0495bed19964
7
+ data.tar.gz: 422aa7086f923b29e545cba81cd1caca270ed4e86f0d29a50a8c61bc111ad30d546b353ef3db5eb52f63f697d03a5671e759f3119c8c200807184c9cfe3c1f58
@@ -0,0 +1,6 @@
1
+ doc
2
+ pkg
3
+ *~
4
+ *.swo
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
@@ -0,0 +1,20 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
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 ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development, :test do
5
+ gem 'rake'
6
+ end
@@ -0,0 +1,64 @@
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
+
23
+ === 1.1.5 / 2012-02-12
24
+
25
+ - Fix length/bytesize of parts in 1.9 (#7, #14) (Jason Moore)
26
+ - Allow CompositeIO objects to be re-read by rewinding, like other IO
27
+ objects. (Luke Redpath)
28
+
29
+ === 1.1.4 / 2011-11-23
30
+
31
+ - Non-functional changes in release (switch to Bundler gem tasks)
32
+
33
+ === 1.1.3 / 2011-07-25
34
+
35
+ - More configurable header specification for parts (Gerrit Riessen)
36
+
37
+ === 1.1.2 / 2011-05-24
38
+
39
+ - Fix CRLF file part miscalculation (Johannes Wagener)
40
+ - Fix Epilogue CRLF issue (suggestion by Neil Spring)
41
+
42
+ === 1.1.1 / 2011-05-13
43
+
44
+ - GH# 9: Fixed Ruby 1.9.2 StringIO bug (thanks Alex Koppel)
45
+
46
+ === 1.1.0 / 2011-01-11
47
+
48
+ - API CHANGE: UploadIO.convert! removed in favor of UploadIO.new
49
+ (Jeff Hodges)
50
+
51
+ === 1.0.1 / 2010-04-27
52
+
53
+ - Doc updates, make gemspec based on more modern Rubygems
54
+
55
+ === 1.0 / 2009-02-12
56
+
57
+ - Many fixes from mlooney, seems to work now. Putting the 0.9 seal of
58
+ approval on it.
59
+
60
+ === 0.1 / 2008-08-12
61
+
62
+ * 1 major enhancement
63
+
64
+ * Birthday!
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,9 @@
1
+ lib/composite_io.rb
2
+ lib/multipartable.rb
3
+ lib/parts.rb
4
+ lib/net/http/post/multipart.rb
5
+ Manifest.txt
6
+ Rakefile
7
+ README.txt
8
+ test/test_composite_io.rb
9
+ test/net/http/post/test_multipart.rb
@@ -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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+
6
+ task :default => :test
@@ -0,0 +1,108 @@
1
+ #--
2
+ # Copyright (c) 2007-2012 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ # Concatenate together multiple IO objects into a single, composite IO object
8
+ # for purposes of reading as a single stream.
9
+ #
10
+ # @example
11
+ # crio = CompositeReadIO.new(StringIO.new('one'),
12
+ # StringIO.new('two'),
13
+ # StringIO.new('three'))
14
+ # puts crio.read # => "onetwothree"
15
+ class CompositeReadIO
16
+ # Create a new composite-read IO from the arguments, all of which should
17
+ # respond to #read in a manner consistent with IO.
18
+ def initialize(*ios)
19
+ @ios = ios.flatten
20
+ @index = 0
21
+ end
22
+
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("") : ""
27
+
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
35
+ end
36
+ advance_io
37
+ end
38
+ (!got_result && length) ? nil : outbuf
39
+ end
40
+
41
+ def rewind
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
54
+ end
55
+ end
56
+
57
+ # Convenience methods for dealing with files and IO that are to be uploaded.
58
+ class UploadIO
59
+ attr_reader :content_type, :original_filename, :local_path, :io, :opts
60
+
61
+ # Create an upload IO suitable for including in the params hash of a
62
+ # Net::HTTP::Post::Multipart.
63
+ #
64
+ # Can take two forms. The first accepts a filename and content type, and
65
+ # opens the file for reading (to be closed by finalizer).
66
+ #
67
+ # The second accepts an already-open IO, but also requires a third argument,
68
+ # the filename from which it was opened (particularly useful/recommended if
69
+ # uploading directly from a form in a framework, which often save the file to
70
+ # an arbitrarily named RackMultipart file in /tmp).
71
+ #
72
+ # @example
73
+ # UploadIO.new("file.txt", "text/plain")
74
+ # UploadIO.new(file_io, "text/plain", "file.txt")
75
+ def initialize(filename_or_io, content_type, filename = nil, opts = {})
76
+ io = filename_or_io
77
+ local_path = ""
78
+ if io.respond_to? :read
79
+ # in Ruby 1.9.2, StringIOs no longer respond to path
80
+ # (since they respond to :length, so we don't need their local path, see parts.rb:41)
81
+ local_path = filename_or_io.respond_to?(:path) ? filename_or_io.path : "local.path"
82
+ else
83
+ io = File.open(filename_or_io)
84
+ local_path = filename_or_io
85
+ end
86
+ filename ||= local_path
87
+
88
+ @content_type = content_type
89
+ @original_filename = File.basename(filename)
90
+ @local_path = local_path
91
+ @io = io
92
+ @opts = opts
93
+ end
94
+
95
+ def self.convert!(io, content_type, original_filename, local_path)
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
+ end
100
+
101
+ def method_missing(*args)
102
+ @io.send(*args)
103
+ end
104
+
105
+ def respond_to?(meth, include_all = false)
106
+ @io.respond_to?(meth, include_all) || super(meth, include_all)
107
+ end
108
+ end
@@ -0,0 +1,9 @@
1
+ #--
2
+ # Copyright (c) 2007-2013 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ module MultipartPost
8
+ VERSION = "2.1.1"
9
+ end
@@ -0,0 +1,48 @@
1
+ #--
2
+ # Copyright (c) 2007-2013 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ require 'parts'
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
45
+ end
46
+
47
+ attr :boundary
48
+ end
@@ -0,0 +1,28 @@
1
+ #--
2
+ # Copyright (c) 2007-2012 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ require 'net/http'
8
+ require 'stringio'
9
+ require 'cgi'
10
+ require 'composite_io'
11
+ require 'multipartable'
12
+ require 'parts'
13
+
14
+ module Net
15
+ class HTTP
16
+ class Put
17
+ class Multipart < Put
18
+ include Multipartable
19
+ end
20
+ end
21
+
22
+ class Post
23
+ class Multipart < Post
24
+ include Multipartable
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,126 @@
1
+ #--
2
+ # Copyright (c) 2007-2013 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ module Parts
8
+ module Part
9
+ def self.new(boundary, name, value, headers = {})
10
+ headers ||= {} # avoid nil values
11
+ if file?(value)
12
+ FilePart.new(boundary, name, value, headers)
13
+ else
14
+ ParamPart.new(boundary, name, value, headers)
15
+ end
16
+ end
17
+
18
+ def self.file?(value)
19
+ value.respond_to?(:content_type) && value.respond_to?(:original_filename)
20
+ end
21
+
22
+ def length
23
+ @part.length
24
+ end
25
+
26
+ def to_io
27
+ @io
28
+ end
29
+ end
30
+
31
+ # Represents a parametric part to be filled with given value.
32
+ class ParamPart
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.
39
+ def initialize(boundary, name, value, headers = {})
40
+ @part = build_part(boundary, name, value, headers)
41
+ @io = StringIO.new(@part)
42
+ end
43
+
44
+ def length
45
+ @part.bytesize
46
+ end
47
+
48
+ # @param boundary [String]
49
+ # @param name [#to_s]
50
+ # @param value [String]
51
+ # @param headers [Hash] Content-Type is used, if present.
52
+ def build_part(boundary, name, value, headers = {})
53
+ part = ''
54
+ part << "--#{boundary}\r\n"
55
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
56
+ part << "Content-Type: #{headers["Content-Type"]}\r\n" if headers["Content-Type"]
57
+ part << "\r\n"
58
+ part << "#{value}\r\n"
59
+ end
60
+ end
61
+
62
+ # Represents a part to be filled from file IO.
63
+ class FilePart
64
+ include Part
65
+
66
+ attr_reader :length
67
+
68
+ # @param boundary [String]
69
+ # @param name [#to_s]
70
+ # @param io [IO]
71
+ # @param headers [Hash]
72
+ def initialize(boundary, name, io, headers = {})
73
+ file_length = io.respond_to?(:length) ? io.length : File.size(io.local_path)
74
+ @head = build_head(boundary, name, io.original_filename, io.content_type, file_length,
75
+ io.respond_to?(:opts) ? io.opts.merge(headers) : headers)
76
+ @foot = "\r\n"
77
+ @length = @head.bytesize + file_length + @foot.length
78
+ @io = CompositeReadIO.new(StringIO.new(@head), io, StringIO.new(@foot))
79
+ end
80
+
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"
92
+
93
+ part = ''
94
+ part << "--#{boundary}\r\n"
95
+ part << "Content-Disposition: #{content_disposition}; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
96
+ part << "Content-Length: #{content_len}\r\n"
97
+ if content_id = opts.delete("Content-ID")
98
+ part << "Content-ID: #{content_id}\r\n"
99
+ end
100
+
101
+ if opts["Content-Type"] != nil
102
+ part << "Content-Type: " + opts["Content-Type"] + "\r\n"
103
+ else
104
+ part << "Content-Type: #{type}\r\n"
105
+ end
106
+
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
+
113
+ part << "\r\n"
114
+ end
115
+ end
116
+
117
+ # Represents the epilogue or closing boundary.
118
+ class EpiloguePart
119
+ include Part
120
+
121
+ def initialize(boundary)
122
+ @part = "--#{boundary}--\r\n"
123
+ @io = StringIO.new(@part)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "multipart_post"
4
+
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'
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
@@ -0,0 +1 @@
1
+ ファイル
@@ -0,0 +1,123 @@
1
+ #--
2
+ # Copyright (c) 2007-2013 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ require 'net/http/post/multipart'
8
+
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
43
+ end
44
+ end
45
+
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
54
+ end
55
+ end
56
+
57
+ RSpec.describe Net::HTTP::Post::Multipart do
58
+ include_context "net http multipart"
59
+
60
+ it "test_form_multipart_body" do
61
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
62
+ @io = File.open(TEMP_FILE)
63
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
64
+ assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
65
+ end
66
+
67
+ it "test_form_multipart_body_with_stringio" do
68
+ @io = StringIO.new("1234567890")
69
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
70
+ assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
71
+ end
72
+
73
+ it "test_form_multiparty_body_with_parts_headers" do
74
+ @io = StringIO.new("1234567890")
75
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
76
+ parts = { :text => 'bar', :file => @io }
77
+ headers = {
78
+ :parts => {
79
+ :text => { "Content-Type" => "part/type" },
80
+ :file => { "Content-Transfer-Encoding" => "part-encoding" }
81
+ }
82
+ }
83
+
84
+ request = Net::HTTP::Post::Multipart.new("/foo/bar", parts, headers)
85
+ assert_results request
86
+ assert_additional_headers_added(request, headers[:parts])
87
+ end
88
+
89
+ it "test_form_multipart_body_with_array_value" do
90
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
91
+ @io = File.open(TEMP_FILE)
92
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
93
+ params = {:foo => ['bar', 'quux'], :file => @io}
94
+ headers = { :parts => {
95
+ :foo => { "Content-Type" => "application/json; charset=UTF-8" } } }
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
100
+
101
+ body = post.body_stream.read
102
+ expect(body.lines.grep(/name="foo"/).length).to be == 2
103
+ expect(body).to be =~ /Content-Type: application\/json; charset=UTF-8/
104
+ end
105
+
106
+ it "test_form_multipart_body_with_arrayparam" do
107
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
108
+ @io = File.open(TEMP_FILE)
109
+ @io = UploadIO.new @io, "text/plain", TEMP_FILE
110
+ assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :multivalueParam => ['bar','bah'], :file => @io)
111
+ end
112
+ end
113
+
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)
122
+ end
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
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multipart-post
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Sieger
8
+ - Samuel Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-05-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '1.3'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '3'
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '1.3'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: 'Use with Net::HTTP to do multipart form postspec. IO values that have
63
+ #content_type, #original_filename, and #local_path will be posted as a binary file.'
64
+ email:
65
+ - nick@nicksieger.com
66
+ - samuel.williams@oriontransfer.co.nz
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - ".gitignore"
72
+ - ".rspec"
73
+ - ".travis.yml"
74
+ - ".yardopts"
75
+ - Gemfile
76
+ - History.txt
77
+ - LICENSE
78
+ - Manifest.txt
79
+ - README.md
80
+ - Rakefile
81
+ - lib/composite_io.rb
82
+ - lib/multipart_post.rb
83
+ - lib/multipartable.rb
84
+ - lib/net/http/post/multipart.rb
85
+ - lib/parts.rb
86
+ - multipart-post.gemspec
87
+ - spec/composite_io_spec.rb
88
+ - spec/multibyte.txt
89
+ - spec/net/http/post/multipart_spec.rb
90
+ - spec/parts_spec.rb
91
+ - spec/spec_helper.rb
92
+ homepage: https://github.com/nicksieger/multipart-post
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.0.3
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: A multipart form post accessory for Net::HTTP.
115
+ test_files:
116
+ - spec/composite_io_spec.rb
117
+ - spec/multibyte.txt
118
+ - spec/net/http/post/multipart_spec.rb
119
+ - spec/parts_spec.rb
120
+ - spec/spec_helper.rb