multipart_body 0.1.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/lib/multipart_body.rb +2 -0
- data/lib/multipart_body/multipart_body.rb +26 -0
- data/lib/multipart_body/part.rb +43 -0
- data/readme.md +30 -0
- data/test/test.rb +122 -0
- data/test/test_helper.rb +7 -0
- metadata +73 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
class MultipartBody
|
2
|
+
attr_accessor :parts, :boundary
|
3
|
+
|
4
|
+
def initialize(parts = nil, boundary = nil)
|
5
|
+
@parts = []
|
6
|
+
@boundary = boundary || "----multipart-boundary-#{rand(1000000)}"
|
7
|
+
|
8
|
+
if parts.is_a? Hash
|
9
|
+
@parts = parts.map {|name, body| Part.new(:name => name, :body => body) }
|
10
|
+
elsif parts.is_a?(Array) && parts.first.is_a?(Part)
|
11
|
+
@parts = parts
|
12
|
+
end
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_hash(parts_hash)
|
18
|
+
multipart = self.new(parts_hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
output = "--#{@boundary}\r\n"
|
23
|
+
output << @parts.join("\r\n--#{@boundary}\r\n")
|
24
|
+
output << "\r\n--#{@boundary}--"
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Part < Struct.new(:name, :body, :filename, :content_type, :encoding)
|
2
|
+
def initialize(*args)
|
3
|
+
if args.flatten.first.is_a? Hash
|
4
|
+
from_hash(args.flatten.first)
|
5
|
+
elsif args.length > 0
|
6
|
+
from_args(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_hash(hash)
|
11
|
+
hash.each_pair do |k, v|
|
12
|
+
self[k] = v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_args(name, body, filename=nil)
|
17
|
+
self[:name] = name
|
18
|
+
self[:body] = body
|
19
|
+
self[:filename] = filename
|
20
|
+
end
|
21
|
+
|
22
|
+
def header
|
23
|
+
header = "Content-Disposition: form-data; name=\"#{name}\""
|
24
|
+
header << "; filename=\"#{filename}\"" if filename
|
25
|
+
header << "\r\nContent-Type: #{content_type}" if content_type
|
26
|
+
header << "\r\nContent-Transfer-Encoding: #{encoding}" if encoding
|
27
|
+
header
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: Implement encodings
|
31
|
+
def encoded_body
|
32
|
+
case encoding
|
33
|
+
when nil
|
34
|
+
body
|
35
|
+
else
|
36
|
+
raise "Encodings have not been implemented"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"#{header}\r\n\r\n#{encoded_body}"
|
42
|
+
end
|
43
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# MultipartBody
|
2
|
+
The multipart body is an attempt to bring consistency to multipart content in Ruby. When developing CloudMailin we struggled to find a gem to help us create multipart bodies. Many different libraries had implemented multipart bodies through their own implementation but non could be used independently.
|
3
|
+
|
4
|
+
The aim of MultipartBody is to ensure consistency when creating (and parsing in future) multipart content
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
require 'multipart_body'
|
9
|
+
|
10
|
+
# From a hash
|
11
|
+
multipart = Multipart.new(:field1 => 'content', :field2 => 'something else')
|
12
|
+
|
13
|
+
# With parts
|
14
|
+
part = Part.new(:name => 'name', :body => 'body', :filename => 'f.txt', :content_type => 'text/plain', :encoding => :base64)
|
15
|
+
|
16
|
+
# or to specify just the name, body and optional filename
|
17
|
+
part = Part.new('name', 'content', 'file.txt')
|
18
|
+
multipart = Multipart.new([part])
|
19
|
+
|
20
|
+
# Output
|
21
|
+
part.to_s #=> The part with headers and content
|
22
|
+
multipart.to_s #=> The full list of parts joined by boundaries
|
23
|
+
|
24
|
+
## TODO
|
25
|
+
* Implement Parsing
|
26
|
+
* Add different encodings
|
27
|
+
* Add the ability to automatically add files and have the filename set
|
28
|
+
|
29
|
+
## License
|
30
|
+
Copyright 2010 by Steve Smith ([CloudMailin](http://cloudmailin.com)) and is released under the MIT license.
|
data/test/test.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class MultipartBodyTest < Test::Unit::TestCase
|
4
|
+
context "MultipartBodyBody" do
|
5
|
+
setup do
|
6
|
+
@hash = {:test => 'test', :two => 'two'}
|
7
|
+
@parts = [Part.new('name', 'value'), Part.new('name2', 'value2')]
|
8
|
+
end
|
9
|
+
|
10
|
+
should "return a new multipart when sent #from_hash" do
|
11
|
+
multipart = MultipartBody.from_hash(@hash)
|
12
|
+
assert_equal MultipartBody, multipart.class
|
13
|
+
end
|
14
|
+
|
15
|
+
should "create a list of parts from the hash when sent #from_hash" do
|
16
|
+
multipart = MultipartBody.from_hash(@hash)
|
17
|
+
assert_equal @hash, Hash[multipart.parts.map{|part| [part.name, part.body] }]
|
18
|
+
end
|
19
|
+
|
20
|
+
should "add to the list of parts when sent #new with a hash" do
|
21
|
+
multipart = MultipartBody.new(@hash)
|
22
|
+
assert_equal @hash, Hash[multipart.parts.map{|part| [part.name, part.body] }]
|
23
|
+
end
|
24
|
+
|
25
|
+
should "correctly add parts sent #new with parts" do
|
26
|
+
multipart = MultipartBody.new(@parts)
|
27
|
+
assert_same_elements @parts, multipart.parts
|
28
|
+
end
|
29
|
+
|
30
|
+
should "assign a boundary if it is not given" do
|
31
|
+
multpart = MultipartBody.new()
|
32
|
+
assert_match /[\w\d-]{10,}/, multpart.boundary
|
33
|
+
end
|
34
|
+
|
35
|
+
should "use the boundary provided if given" do
|
36
|
+
multipart = MultipartBody.new(nil, "my-boundary")
|
37
|
+
assert_equal "my-boundary", multipart.boundary
|
38
|
+
end
|
39
|
+
|
40
|
+
should "starts with a boundary when sent #to_s" do
|
41
|
+
multipart = MultipartBody.new(@parts)
|
42
|
+
assert_match /^--#{multipart.boundary}/i, multipart.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
should "end with a boundary when sent #to_s" do
|
46
|
+
multipart = MultipartBody.new(@parts)
|
47
|
+
assert_match /--#{multipart.boundary}--\z/i, multipart.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
should "contain the parts joined by a boundary when sent #to_s" do
|
51
|
+
multipart = MultipartBody.new(@parts)
|
52
|
+
assert_match multipart.parts.join("\r\n--#{multipart.boundary}\r\n"), multipart.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "a Part" do
|
57
|
+
setup do
|
58
|
+
@part = Part
|
59
|
+
end
|
60
|
+
|
61
|
+
should "assign values when sent #new with a hash" do
|
62
|
+
part = Part.new(:name => 'test', :body => 'content', :filename => 'name')
|
63
|
+
assert_equal 'test', part.name
|
64
|
+
assert_equal 'content', part.body
|
65
|
+
assert_equal 'name', part.filename
|
66
|
+
end
|
67
|
+
|
68
|
+
should "assign values when sent #new with values" do
|
69
|
+
part = Part.new('test', 'content', 'name')
|
70
|
+
assert_equal 'test', part.name
|
71
|
+
assert_equal 'content', part.body
|
72
|
+
assert_equal 'name', part.filename
|
73
|
+
end
|
74
|
+
|
75
|
+
should "be happy when sent #new with args without a filename" do
|
76
|
+
part = Part.new('test', 'content')
|
77
|
+
assert_equal 'test', part.name
|
78
|
+
assert_equal 'content', part.body
|
79
|
+
assert_equal nil, part.filename
|
80
|
+
end
|
81
|
+
|
82
|
+
should "create an empty part when sent #new with nothing" do
|
83
|
+
part = Part.new()
|
84
|
+
assert_equal nil, part.name
|
85
|
+
assert_equal nil, part.body
|
86
|
+
assert_equal nil, part.filename
|
87
|
+
end
|
88
|
+
|
89
|
+
should "include a content disposition when sent #header name" do
|
90
|
+
part = Part.new(:name => 'key', :body => 'content')
|
91
|
+
assert_match /content-disposition: form-data; name="key"/i, part.header
|
92
|
+
end
|
93
|
+
|
94
|
+
should "include no filename when sent #header and a filename is not set" do
|
95
|
+
part = Part.new(:name => 'key', :body => 'content')
|
96
|
+
assert_no_match /content-disposition: .+; name=".+"; filename="?.*"?/i, part.header
|
97
|
+
end
|
98
|
+
|
99
|
+
should "include a filename when sent #header and a filename is set" do
|
100
|
+
part = Part.new(:name => 'key', :body => 'content', :filename => 'file.jpg')
|
101
|
+
assert_match /content-disposition: .+; name=".+"; filename="file.jpg"/i, part.header
|
102
|
+
end
|
103
|
+
|
104
|
+
should "return the original body if encoding is not set" do
|
105
|
+
part = Part.new(:name => 'key', :body => 'content')
|
106
|
+
assert_equal 'content', part.encoded_body
|
107
|
+
end
|
108
|
+
|
109
|
+
# TODO: Implement encoding tests
|
110
|
+
should "raise an exception when an encoding is passed" do
|
111
|
+
part = Part.new(:name => 'key', :body => 'content', :encoding => :base64)
|
112
|
+
assert_raises RuntimeError do
|
113
|
+
part.encoded_body
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
should "output the header and body when sent #to_s" do
|
118
|
+
part = Part.new(:name => 'key', :body => 'content')
|
119
|
+
assert_equal "#{part.header}\r\n\r\n#{part.body}", part.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multipart_body
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Steve Smith
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-17 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A ruby library to create multipart bodies.
|
23
|
+
email: gems@scsworld.co.uk
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- readme.md
|
32
|
+
- lib/multipart_body/multipart_body.rb
|
33
|
+
- lib/multipart_body/part.rb
|
34
|
+
- lib/multipart_body.rb
|
35
|
+
- test/test.rb
|
36
|
+
- test/test_helper.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/cloudmailin/multipart_body
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
hash: 3
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.7
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: MultipartBody allows you to create consistant multipart bodies
|
71
|
+
test_files:
|
72
|
+
- test/test.rb
|
73
|
+
- test/test_helper.rb
|