multipart_body 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|