mime 0.4.1 → 0.4.2

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
+ SHA1:
3
+ metadata.gz: 4cfd511f4221d7e04909d860f85eddb218d46e99
4
+ data.tar.gz: 3e43d9945ffdee1a5fe5e4c8f0365ea64707c36e
5
+ SHA512:
6
+ metadata.gz: bd1a127ac7d9f8fef49a346ccc64c10f4f1f028357ac36a254960aa61e48168b95d202d4acb1a741c897bc34697a21041fa19e4586862c24b8ffa5697855cec0
7
+ data.tar.gz: d70123f3578684bbe430fe85a5d440d2a393a74e1b633941f610de737a51fa8acf2863af7fdc415f591c055b7cdccf5c44b5259deb334b357db0b60cd970c283
@@ -372,6 +372,8 @@ Validators :: Please check *all* of your messages using the following lint
372
372
  5. 2014-04-20, v0.4.1
373
373
  * Add bug tracker URL.
374
374
  * Link to referenced commit messages.
375
+ 6. 2014-06-13, v0.4.2
376
+ * Ruby 1.8.7 compatibility fix.
375
377
 
376
378
 
377
379
  == License
@@ -18,7 +18,7 @@
18
18
  #
19
19
  module MIME
20
20
 
21
- VERSION = '0.4.1'
21
+ VERSION = '0.4.2'
22
22
 
23
23
  # Defined in RFC: https://tools.ietf.org/html/rfc5322#section-2.1.1
24
24
  MAX_LINE_LENGTH = 998
@@ -1,9 +1,16 @@
1
+ #
2
+ # == Content Formats
3
+ #
4
+ # [Text/Plain Flowed] TextFlowed
5
+ # [Quoted-Printable] To be implemented ...
6
+ # [Base64] To be implemented ...
7
+ #
1
8
  module MIME::ContentFormats
2
9
 
3
10
  #
4
11
  # Minimal implementation of RFC 2646: The Text/Plain Format Parameter
5
12
  #
6
- # https://www.ietf.org/rfc/rfc2646.txt
13
+ # https://tools.ietf.org/html/rfc2646
7
14
  #
8
15
  # == Excerpts from RFC
9
16
  #
@@ -34,14 +34,14 @@ module MIME
34
34
  #
35
35
  def set name, value
36
36
  delete(name)
37
- @headers.store(name, value)
37
+ @headers.store(name, value) unless value.nil?
38
38
  end
39
39
 
40
40
  #
41
41
  # Delete header associated with +name+.
42
42
  #
43
43
  def delete name
44
- @headers.delete_if {|k| name.downcase == k.downcase }
44
+ @headers.delete_if {|k,v| name.downcase == k.downcase }
45
45
  end
46
46
 
47
47
  end
@@ -7,7 +7,7 @@ module MIME
7
7
  # Mailbox fields #to, #from, #cc, #bcc, and #reply_to may be a single email
8
8
  # address, an array of email addresses, or a hash of _email_ => _name_
9
9
  # pairs. When using a hash, set _name_ to +nil+ to omit email display name.
10
- # The #sender field is a special case and contain only a single mailbox.
10
+ # The #sender field is a special case and can only contain a single mailbox.
11
11
  #
12
12
  module Internet
13
13
 
@@ -90,9 +90,9 @@ module MIME
90
90
  # +params+ is a Hash with zero or more of the following keys:
91
91
  #
92
92
  # +filename+ :: name of file
93
- # +creation-date+ :: RFC2822 data-time
94
- # +modification-date+ :: RFC2822 data-time
95
- # +read-date+ :: RFC2822 data-time
93
+ # +creation-date+ :: RFC2822 date-time
94
+ # +modification-date+ :: RFC2822 date-time
95
+ # +read-date+ :: RFC2822 date-time
96
96
  # +size+ :: file size in octets
97
97
  #
98
98
  # The values for the *-date keys may use Time::rfc2822.
@@ -25,14 +25,14 @@ module MIME
25
25
  # Format the Mail object as an Internet message.
26
26
  #
27
27
  def to_s
28
+ self.sender ||= sender_address
28
29
  self.message_id ||= ID.generate_gid(domain)
29
30
  body.mime_version ||= "1.0 (Ruby MIME v#{VERSION})"
30
- # TODO if From contains multiple mailboxes Sender must be present
31
31
 
32
32
  #--
33
33
  # In an RFC 2822 message, the header and body sections must be separated
34
34
  # by two line breaks (i.e., 2*CRLF). One line break is deliberately
35
- # omitted here to allowing the body supplier to append headers to the
35
+ # omitted here so the MIME body supplier can append headers to the
36
36
  # top-level message header section.
37
37
  #++
38
38
  "#{headers}\r\n#{body}"
@@ -44,6 +44,16 @@ module MIME
44
44
 
45
45
  private
46
46
 
47
+ #
48
+ # Return the first From address as the sender if multiple From addresses.
49
+ #
50
+ def sender_address
51
+ case from
52
+ when Hash; Hash[*from.first] if from.size > 1
53
+ when Array; from.first if from.size > 1
54
+ end
55
+ end
56
+
47
57
  def domain
48
58
  headers.get('from').match(/@([[:alnum:].-]+)/)[1] rescue nil
49
59
  end
@@ -301,6 +301,24 @@ EOF
301
301
  assert_equal("#{msg_id}", msg.message_id)
302
302
  end
303
303
 
304
+ def test_sender_field_auto_generation
305
+ msg = MIME::Mail.new
306
+ msg.from = addresses = %w(john@example.com jane@example.com)
307
+ assert_nil msg.sender
308
+ msg.to_s # add sender header when multiple from addresses
309
+ assert_equal addresses.first, msg.sender
310
+ end
311
+
312
+ def test_sender_field_manual_assignment
313
+ msg = MIME::Mail.new
314
+ msg.from = %w(john@example.com jane@example.com)
315
+ assert_nil msg.sender
316
+ msg.sender = sender = 'jack@example.com'
317
+ assert_equal sender, msg.sender
318
+ msg.to_s # should not affect sender header
319
+ assert_equal sender, msg.sender
320
+ end
321
+
304
322
  def test_rfc2822_date_format
305
323
  msg = MIME::Mail.new
306
324
  assert_kind_of(Time, msg.date)
@@ -372,6 +390,22 @@ EOF
372
390
  assert_equal '', headers.to_s
373
391
  end
374
392
 
393
+ def test_header_field_removal_via_set_nil
394
+ headers = MIME::Header.new
395
+ headers.set 'a', 'b'
396
+ assert_equal 'b', headers.get('a')
397
+ headers.set 'a', nil
398
+ assert_nil headers.get('a')
399
+ end
400
+
401
+ def test_header_field_removal_via_delete
402
+ headers = MIME::Header.new
403
+ headers.set 'a', 'b'
404
+ assert_equal 'b', headers.get('a')
405
+ headers.delete 'a'
406
+ assert_nil headers.get('a')
407
+ end
408
+
375
409
  def test_mailbox_types
376
410
  e1 = 'john@example.com'
377
411
  e2 = 'jane@example.com'
@@ -6,6 +6,10 @@ class TextFlowedTest < Minitest::Test
6
6
 
7
7
  include MIME::ContentFormats
8
8
 
9
+ def test_empty_string
10
+ assert_equal '', TextFlowed.encode('')
11
+ end
12
+
9
13
  def test_single_word_will_not_break_regardless_of_line_length
10
14
  txt = "1234567890"
11
15
  assert_equal txt, TextFlowed.encode(txt, 9) # <
@@ -107,8 +111,9 @@ class TextFlowedTest < Minitest::Test
107
111
  end
108
112
 
109
113
  def test_rfc_79_character_max_flowed_line_length
110
- TextFlowed.encode('123', 79) # no error
111
- assert_raises(ArgumentError) { TextFlowed.encode('123', 80) }
114
+ assert_equal (max = 79), TextFlowed::MAX_FLOWED_LINE
115
+ TextFlowed.encode('', max) # no error
116
+ assert_raises(ArgumentError) { TextFlowed.encode('', max+1) }
112
117
  end
113
118
 
114
119
  def test_rfc_max_smtp_line_length_boundary
metadata CHANGED
@@ -1,25 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
5
- prerelease:
4
+ version: 0.4.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Clint Pachl
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-04-21 00:00:00.000000000 Z
11
+ date: 2014-06-13 00:00:00.000000000 Z
13
12
  dependencies: []
14
- description: ! 'A library for building RFC compliant Multipurpose Internet Mail Extensions
15
-
13
+ description: |
14
+ A library for building RFC compliant Multipurpose Internet Mail Extensions
16
15
  (MIME) messages. It can be used to construct standardized MIME messages for use
17
-
18
16
  in client/server communications, such as Internet mail or HTTP
19
-
20
17
  multipart/form-data transactions.
21
-
22
- '
23
18
  email: pachl@ecentryx.com
24
19
  executables: []
25
20
  extensions: []
@@ -27,72 +22,70 @@ extra_rdoc_files: []
27
22
  files:
28
23
  - README.rdoc
29
24
  - Rakefile
30
- - mime.gemspec
31
- - lib/mime/headers/mime.rb
32
- - lib/mime/headers/internet.rb
33
- - lib/mime/discrete_media_factory.rb
34
- - lib/mime/error.rb
35
- - lib/mime/parser.rb
36
- - lib/mime/content_types.rb
37
- - lib/mime/media.rb
25
+ - lib/mime.rb
38
26
  - lib/mime/composite_media.rb
39
27
  - lib/mime/content_formats/text_flowed.rb
28
+ - lib/mime/content_types.rb
29
+ - lib/mime/discrete_media.rb
30
+ - lib/mime/discrete_media_factory.rb
31
+ - lib/mime/error.rb
40
32
  - lib/mime/header.rb
33
+ - lib/mime/headers/internet.rb
34
+ - lib/mime/headers/mime.rb
41
35
  - lib/mime/mail.rb
42
- - lib/mime/discrete_media.rb
43
- - lib/mime.rb
36
+ - lib/mime/media.rb
37
+ - lib/mime/parser.rb
38
+ - mime.gemspec
44
39
  - test/scaffold/application.msg
45
- - test/scaffold/unknown.yyy
46
- - test/scaffold/video.msg
47
- - test/scaffold/mini.mov
48
- - test/scaffold/image.jpg
40
+ - test/scaffold/audio.msg
49
41
  - test/scaffold/book.pdf
50
- - test/scaffold/song.mp3
51
- - test/scaffold/ruby.png
52
- - test/scaffold/multipart_alternative.msg
53
- - test/scaffold/multipart_mixed_inline_and_attachment2.msg
54
- - test/scaffold/image.msg
55
- - test/scaffold/multipart_form_data_file_and_text.msg
42
+ - test/scaffold/data.htm
56
43
  - test/scaffold/data.xml
57
- - test/scaffold/rfc822_composite.msg
58
- - test/scaffold/text.msg
44
+ - test/scaffold/image.jpg
45
+ - test/scaffold/image.msg
59
46
  - test/scaffold/main.css
47
+ - test/scaffold/mini.mov
48
+ - test/scaffold/multipart_alternative.msg
60
49
  - test/scaffold/multipart_alternative_related.msg
50
+ - test/scaffold/multipart_form_data_file_and_text.msg
51
+ - test/scaffold/multipart_form_data_mixed.msg
61
52
  - test/scaffold/multipart_form_data_text.msg
62
- - test/scaffold/audio.msg
53
+ - test/scaffold/multipart_mixed_inline_and_attachment.msg
54
+ - test/scaffold/multipart_mixed_inline_and_attachment2.msg
63
55
  - test/scaffold/multipart_related.msg
56
+ - test/scaffold/rfc822_composite.msg
64
57
  - test/scaffold/rfc822_discrete.msg
65
- - test/scaffold/data.htm
66
- - test/scaffold/multipart_form_data_mixed.msg
67
- - test/scaffold/multipart_mixed_inline_and_attachment.msg
68
- - test/test_mime.rb-try
58
+ - test/scaffold/ruby.png
59
+ - test/scaffold/song.mp3
60
+ - test/scaffold/text.msg
61
+ - test/scaffold/unknown.yyy
62
+ - test/scaffold/video.msg
69
63
  - test/test_mime.rb
70
64
  - test/test_text_flowed.rb
71
65
  homepage: http://ecentryx.com/gems/mime
72
66
  licenses:
73
67
  - ISC
68
+ metadata: {}
74
69
  post_install_message:
75
70
  rdoc_options: []
76
71
  require_paths:
77
72
  - lib
78
73
  required_ruby_version: !ruby/object:Gem::Requirement
79
- none: false
80
74
  requirements:
81
- - - ! '>='
75
+ - - ">="
82
76
  - !ruby/object:Gem::Version
83
77
  version: '0'
84
78
  required_rubygems_version: !ruby/object:Gem::Requirement
85
- none: false
86
79
  requirements:
87
- - - ! '>='
80
+ - - ">="
88
81
  - !ruby/object:Gem::Version
89
82
  version: '0'
90
83
  requirements: []
91
84
  rubyforge_project:
92
- rubygems_version: 1.8.23
85
+ rubygems_version: 2.2.0
93
86
  signing_key:
94
- specification_version: 3
87
+ specification_version: 4
95
88
  summary: Multipurpose Internet Mail Extensions (MIME) Library
96
89
  test_files:
97
- - test/test_mime.rb
98
90
  - test/test_text_flowed.rb
91
+ - test/test_mime.rb
@@ -1,616 +0,0 @@
1
- # THIS IS STILL HERE BECAUSE A LOT OF WORK WENT INTO MAKING TESTS AGNOSTIC OF MIME HEADER ORDER.
2
- # HOWEVER, THINGS GOT MESSY SO REVERTED BACK TO COMPARING AGAINST KNOWN GOOD MESSAGES.
3
- # THIS IS HERE JUST IN CASE. IN THE FUTURE, REMOVE IT IF THERE IS NO NEED FOR IT.
4
-
5
- gem 'minitest' # minitest in 1.9 stdlib is crufty
6
- require 'minitest/autorun'
7
- require 'mime'
8
-
9
- # may be able to remove in 2.0
10
- Encoding.default_external = 'ASCII-8BIT'
11
-
12
- class MIMETest < Minitest::Test
13
-
14
- CRLF = "\r\n"
15
- HDR_BDY_SEP = CRLF * 2
16
- MESSAGE_ID = /^Message-ID: <\d+@\d+>\r\n/
17
- CONTENT_ID = /^Content-ID: <\d+\.\d+>\r\n/
18
-
19
- CDISPOSITION = /^Content-Disposition: .*\r\n/
20
- CTYPE = /^Content-Type: .*\r\n/
21
-
22
- DATE = /^Date: ..., \d{1,2} ... \d{4} \d\d:\d\d:\d\d -\d{4}\r\n/
23
- VERSION = /^MIME-Version: 1.0 \(Ruby MIME v\d\.\d\)\r\n/
24
- #BOUNDARY = /^--Boundary_\d+\.\d+\r\n/
25
- BOUNDARY = /^--Boundary_\d+\.\d+(--)?\r\n/
26
- BOUNDARY_LAST = /^--Boundary_\d+\.\d+--\r\n/
27
- CID = /cid:\d+\.\d+/
28
-
29
- CTYPE_TEXT_PLAIN = /^Content-Type: text\/plain; charset=us-ascii\r\n/
30
- CTYPE_TEXT_HTML = /^Content-Type: text\/html\r\n/
31
- CTYPE_TEXT_XML = /^Content-Type: text\/xml\r\n/
32
- CTYPE_IMAGE_JPEG = /^Content-Type: image\/jpeg\r\n/
33
- CTYPE_IMAGE_PNG = /^Content-Type: image\/png\r\n/
34
- CTYPE_VIDEO_MPEG = /^Content-Type: video\/mpeg\r\n/
35
- CTYPE_AUDIO_MIDI = /^Content-Type: audio\/midi\r\n/
36
- CTYPE_APPLICATION = /^Content-Type: application\/octet-stream\r\n/
37
-
38
- CTYPE_MPART_FORM = /^Content-Type: multipart\/form-data; boundary=Boundary_\d+\.\d+\r\n/
39
- CTYPE_MPART_ALT = /^Content-Type: multipart\/alternative; boundary=Boundary_\d+\.\d+\r\n/
40
- CTYPE_MPART_MIXED = /^Content-Type: multipart\/mixed; boundary=Boundary_\d+\.\d+\r\n/
41
-
42
- XFER_ENC_BINARY = /^Content-Transfer-Encoding: binary\r\n/
43
- XFER_ENC_8BIT = /^Content-Transfer-Encoding: 8bit\r\n/
44
-
45
- BINARY_DATA = '0110000101110101011001000110100101101111'
46
-
47
- def test_make_top_level_rfc2822_message
48
- rfc2822_msg = MIME::Message.new
49
- rfc2822_msg.body = "\r\nmessage body"
50
- msg = rfc2822_msg.to_s
51
-
52
- assert_match MESSAGE_ID, msg
53
- assert_match DATE, msg
54
- assert_match VERSION, msg
55
- assert_equal_num_headers 3, msg
56
- assert_equal_body "message body", msg
57
- end
58
-
59
- # TODO remove audio.msg
60
- def test_make_audio_message
61
- audio_media = MIME::AudioMedia.new(BINARY_DATA, 'audio/midi')
62
- audio_media.content_transfer_encoding = 'binary'
63
- msg = MIME::Message.new(audio_media).to_s
64
-
65
- [CONTENT_ID, XFER_ENC_BINARY, CTYPE_AUDIO_MIDI].each do |header|
66
- assert_match header, msg
67
- end
68
- assert_equal_num_headers 6, msg
69
- assert_equal_body BINARY_DATA, msg
70
- end
71
-
72
- # TODO remove expected_mime_msg = IO.read(sd('/application.msg'))
73
- def test_make_application_message
74
- application_media = MIME::ApplicationMedia.new(BINARY_DATA)
75
- application_media.content_transfer_encoding = 'binary'
76
- msg = MIME::Message.new(application_media).to_s
77
-
78
- [CONTENT_ID, XFER_ENC_BINARY, CTYPE_APPLICATION].each do |header|
79
- assert_match header, msg
80
- end
81
- assert_equal_num_headers 6, msg
82
- assert_equal_body BINARY_DATA, msg
83
- end
84
-
85
- # TODO remove image.msg
86
- def test_make_image_message
87
- image = IO.read(sd('/image.jpg'))
88
- image_media = MIME::ImageMedia.new(image)
89
- image_media.content_type = 'image/jpeg'
90
- image_media.content_transfer_encoding = 'binary'
91
- msg = MIME::Message.new(image_media).to_s
92
-
93
- [CONTENT_ID, XFER_ENC_BINARY, CTYPE_IMAGE_JPEG].each do |header|
94
- assert_match header, msg
95
- end
96
-
97
- assert_equal_num_headers 6, msg
98
- assert_equal_body image, msg
99
- end
100
-
101
- # TODO remove text.msg
102
- def test_make_text_message
103
- body = 'a plain text message'
104
- msg = MIME::Message.new(MIME::TextMedia.new(body)).to_s
105
-
106
- [CTYPE_TEXT_PLAIN].each do |header|
107
- assert_match header, msg
108
- end
109
- end
110
-
111
- # TODO remove video.msg
112
- def test_make_video_message
113
- video_media = MIME::VideoMedia.new(BINARY_DATA)
114
- video_media.content_type = 'video/mpeg'
115
- video_media.content_transfer_encoding = 'binary'
116
- msg = MIME::Message.new(video_media).to_s
117
-
118
- [CONTENT_ID, XFER_ENC_BINARY, CTYPE_VIDEO_MPEG].each do |header|
119
- assert_match header, msg
120
- end
121
- assert_equal_num_headers 6, msg
122
- assert_equal_body BINARY_DATA, msg
123
- end
124
-
125
- def test_no_instantiation_of_abstract_classes
126
- e = MIME::AbstractClassError
127
- assert_raises(e) {MIME::MediaType.new(nil, nil)}
128
- assert_raises(e) {MIME::DiscreteMediaType.new(nil)}
129
- assert_raises(e) {MIME::CompositeMediaType.new(nil)}
130
- assert_raises(e) {MIME::MultipartMedia.new(nil)}
131
- end
132
-
133
- # def test_boundaries
134
- # # CREATE a multipart message; take simplified version of next form data
135
- # # test
136
- # e = msg.scan(BOUNDARY).each
137
- # first_boundary = e.next
138
- # assert_equal first_boundary, e.next
139
- # assert_equal first_boundary, e.next
140
- # last_boundary = e.next
141
- # refute_equal first_boundary, last_boundary
142
- # assert_match /^--Boundary_\d+\.\d+\r\n/, first_boundary
143
- # assert_match /^--Boundary_\d+\.\d+--\r\n/, last_boundary
144
- # end
145
- #
146
- # def test_unique_content_ids_in_multipart_message
147
- #
148
- # content_ids = msg.scan(/^Content-ID: <(\d+.\d+)>/)
149
- # assert_equal 4, content_ids.flatten.uniq.size # all IDs must be unique
150
- # end
151
-
152
- # TODO rm multipart_form_data_text.msg
153
- def test_multipart_form_data_with_text_entities
154
- txt_data = 'text body'
155
- htm_data = IO.read(sd('data.htm'))
156
- xml_data = IO.read(sd('data.xml'))
157
-
158
- txt = MIME::TextMedia.new('text body')
159
- htm = MIME::TextMedia.new(htm_data, 'text/html')
160
- xml = MIME::TextMedia.new(xml_data, 'text/xml')
161
-
162
- form_data = MIME::MultipartMedia::FormData.new
163
- form_data.add_entity txt, 'txt'
164
- form_data.add_entity htm, 'htm'
165
- form_data.add_entity xml, 'xml'
166
- msg = form_data.to_s
167
-
168
- parts = msg.split(BOUNDARY)
169
- assert_equal 5, parts.size, 'main header, 3 entities, last boundary'
170
- assert_equal '--', parts.pop, 'remnant of last boundary marker'
171
-
172
- # main header
173
- assert_match CONTENT_ID, parts[0]
174
- assert_match CTYPE_MPART_FORM, parts[0]
175
- assert_equal_num_headers 2, parts[0]
176
- assert_equal_body '', parts[0]
177
-
178
- # xml entity
179
- assert_match CONTENT_ID, parts[1]
180
- assert_match CTYPE_TEXT_XML, parts[1]
181
- assert_match /^Content-Disposition: form-data; name="xml"\r\n/, parts[1]
182
- assert_equal_num_headers 3, parts[1]
183
- assert_equal_body xml_data, parts[1]
184
-
185
- # html entity
186
- assert_match CONTENT_ID, parts[2]
187
- assert_match CTYPE_TEXT_HTML, parts[2]
188
- assert_match /^Content-Disposition: form-data; name="htm"\r\n/, parts[2]
189
- assert_equal_num_headers 3, parts[2]
190
- assert_equal_body htm_data, parts[2]
191
-
192
- # text entity
193
- assert_match CONTENT_ID, parts[3]
194
- assert_match CTYPE_TEXT_PLAIN, parts[3]
195
- assert_match /^Content-Disposition: form-data; name="txt"\r\n/, parts[3]
196
- assert_equal_num_headers 3, parts[3]
197
- assert_equal_body txt_data, parts[3]
198
- end
199
-
200
- # TODO test that only basename of the file is included in header
201
- # test if no filename is used, then probably no filename param
202
- # see next test for ideas
203
- def test_content_disposition_filename
204
-
205
- pass
206
- end
207
-
208
- # rm multipart_form_data_file.msg
209
- # rm multipart_form_data_file_and_text.msg
210
- def test_multipart_form_data_with_text_and_file_entities
211
- img1_filename = 'image.jpg'
212
- img2_filename = 'ruby.png'
213
- img1_data = IO.read(sd(img1_filename))
214
- img2_data = IO.read(sd(img2_filename))
215
- img1 = MIME::ImageMedia.new(img1_data, 'image/jpeg')
216
- img2 = MIME::ImageMedia.new(img2_data, 'image/png')
217
- img1.content_transfer_encoding = '8bit'
218
- img2.content_transfer_encoding = '8bit'
219
-
220
- desc_data = 'This is plain text description of images.'
221
- desc = MIME::TextMedia.new(desc_data)
222
-
223
- form_data = MIME::MultipartMedia::FormData.new
224
- form_data.add_entity desc, 'description'
225
- form_data.add_entity img2, 'image_2', img2_filename
226
- form_data.add_entity img1, 'image_1', img1_filename
227
- msg = form_data.to_s
228
-
229
- parts = msg.split(BOUNDARY)
230
- assert_equal 5, parts.size, 'main header, 3 entities, last boundary'
231
- assert_equal '--', parts.pop, 'remnant of last boundary marker'
232
-
233
- # main header
234
- assert_match CONTENT_ID, parts[0]
235
- assert_match CTYPE_MPART_FORM, parts[0]
236
- assert_equal_num_headers 2, parts[0]
237
- assert_equal_body '', parts[0]
238
-
239
- # first image entity
240
- assert_match CONTENT_ID, parts[1]
241
- assert_match CTYPE_IMAGE_JPEG, parts[1]
242
- assert_match XFER_ENC_8BIT, parts[1]
243
- assert_match /^Content-Disposition: form-data; name="image_1"; filename="#{img1_filename}"\r\n/, parts[1]
244
- assert_equal_num_headers 4, parts[1]
245
- assert_equal_body img1_data, parts[1]
246
-
247
- # second image entity
248
- assert_match CONTENT_ID, parts[2]
249
- assert_match CTYPE_IMAGE_PNG, parts[2]
250
- assert_match XFER_ENC_8BIT, parts[2]
251
- assert_match /^Content-Disposition: form-data; name="image_2"; filename="#{img2_filename}"\r\n/, parts[2]
252
- assert_equal_num_headers 4, parts[2]
253
- assert_equal_body img2_data, parts[2]
254
-
255
- # plain text entity
256
- assert_match CONTENT_ID, parts[3]
257
- assert_match CTYPE_TEXT_PLAIN, parts[3]
258
- assert_match /^Content-Disposition: form-data; name="description"\r\n/, parts[3]
259
- assert_equal_num_headers 3, parts[3]
260
- assert_equal_body desc_data, parts[3]
261
- end
262
-
263
- # rm plain_text_email.msg
264
- def test_construction_of_plain_text_email_message
265
- subject = 'This is an important email'
266
- body = 'This is the all important email body.'
267
-
268
- email_msg = MIME::Message.new
269
- email_msg.to = {
270
- 'john@example.com' => 'John',
271
- 'paul@example.com' => nil,
272
- 'mary@example.com' => 'Mary'
273
- }
274
- email_msg.cc = {'Head Honcho' => 'boss@example.com'}
275
- email_msg.from = {'jane@example.com' => nil}
276
- email_msg.subject = subject
277
- email_msg.body = MIME::TextMedia.new(body)
278
-
279
- msg = email_msg.to_s
280
-
281
- assert_equal_num_headers 9, msg
282
- assert_match MESSAGE_ID, msg
283
- assert_match CONTENT_ID, msg
284
- assert_match CTYPE_TEXT_PLAIN, msg
285
- assert_match DATE, msg
286
- assert_match VERSION, msg
287
- assert_match /^Subject: #{subject}\r\n/, msg
288
- assert_match /^From: jane@example.com\r\n/, msg
289
- assert_match /^To: .+, .+, .+\r\n/, msg # 3 addresses
290
- assert_match /^To: .*John <john@example.com>.*\r\n/, msg
291
- assert_match /^To: .*Mary <mary@example.com>.*\r\n/, msg
292
- assert_match /^To: .*paul@example.com.*\r\n/, msg
293
- assert_match /^Cc: boss@example.com <Head Honcho>\r\n/, msg
294
- assert_equal_body body, msg
295
- end
296
-
297
- def test_content_type_detection
298
- (o = Object.new).extend(MIME::ContentTypes)
299
-
300
- # test using file path, relative and absolute
301
- assert_equal 'application/pdf', o.file_type('book.pdf')
302
- assert_equal 'video/quicktime', o.file_type('mini.mov')
303
- assert_equal 'application/octet-stream', o.file_type('dsk.iso')
304
- assert_equal 'audio/mpeg', o.file_type('/tmp/song.mp3')
305
- assert_equal 'text/css', o.file_type('/tmp/main.css')
306
- assert_equal nil, o.file_type('unknown.yyy')
307
-
308
- # test using file object
309
- img_type = open(sd('ruby.png')) {|f| o.file_type(f)}
310
- assert_equal 'image/png', img_type
311
- refute_equal 'image/jpeg', img_type
312
- end
313
-
314
- def test_object_instantiation_using_discrete_media_factory
315
- app_file = sd('book.pdf')
316
- audio_file = sd('song.mp3')
317
- text_file = sd('data.xml')
318
- video_file = sd('mini.mov')
319
- image_file = sd('image.jpg')
320
- unknown_file = sd('unknown.yyy')
321
-
322
- dmf = MIME::DiscreteMediaFactory
323
-
324
- # test using file path
325
- assert_kind_of MIME::ApplicationMedia, dmf.create(app_file)
326
- assert_kind_of MIME::AudioMedia, dmf.create(audio_file)
327
- assert_kind_of MIME::TextMedia, dmf.create(text_file)
328
- assert_kind_of MIME::VideoMedia, dmf.create(video_file)
329
- assert_kind_of MIME::ImageMedia, dmf.create(image_file)
330
-
331
- # test using file object
332
- open(image_file) do |image_file_obj|
333
- assert_kind_of MIME::ImageMedia, dmf.create(image_file_obj)
334
- end
335
- open(text_file) do |text_file_obj|
336
- assert_kind_of MIME::TextMedia, dmf.create(text_file_obj)
337
- end
338
-
339
- # raise for unknown file path and File object
340
- assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file)}
341
- open(unknown_file) do |unknown_file_obj|
342
- assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file_obj)}
343
- end
344
- end
345
-
346
- def test_discrete_media_factory_creates_path_singleton_method
347
- pdf_file_path = sd('book.pdf')
348
-
349
- media_obj = MIME::DiscreteMediaFactory.create(pdf_file_path)
350
- assert_equal pdf_file_path, media_obj.path
351
-
352
- open(pdf_file_path) do |pdf_file_obj|
353
- media_obj = MIME::DiscreteMediaFactory.create(pdf_file_obj)
354
- assert_equal pdf_file_path, media_obj.path
355
- end
356
- end
357
-
358
- # rm multipart_alternative.msg
359
- def test_multipart_alternative_message_construction
360
- txt_data = "*Header*\nmessage"
361
- htm_data = "<html><body><h1>Header</h1><p>message</p></body></html>"
362
- txt_msg = MIME::TextMedia.new(txt_data)
363
- htm_msg = MIME::TextMedia.new(htm_data)
364
-
365
- txt_msg.content_type = (txt_type = 'text/enhanced; charset=us-ascii')
366
- htm_msg.content_type = (htm_type = 'text/html; charset=iso-8859-1')
367
-
368
- alt_msg = MIME::MultipartMedia::Alternative.new
369
- alt_msg.add_entity htm_msg
370
- alt_msg.add_entity txt_msg
371
- msg = alt_msg.to_s
372
-
373
- parts = msg.split(BOUNDARY)
374
- assert_equal 4, parts.size, 'main header, 2 entities, last boundary'
375
- assert_equal '--', parts.pop, 'remnant of last boundary marker'
376
-
377
- # main header
378
- assert_match CONTENT_ID, parts[0]
379
- assert_match CTYPE_MPART_ALT, parts[0]
380
- assert_equal_num_headers 2, parts[0]
381
- assert_equal_body '', parts[0]
382
-
383
- # text entity
384
- assert_match CONTENT_ID, parts[1]
385
- assert_match /^Content-Type: #{txt_type}\r\n/, parts[1]
386
- assert_equal_num_headers 2, parts[1]
387
- assert_equal_body txt_data, parts[1]
388
-
389
- # html entity
390
- assert_match CONTENT_ID, parts[2]
391
- assert_match /^Content-Type: #{htm_type}\r\n/, parts[2]
392
- assert_equal_num_headers 2, parts[2]
393
- assert_equal_body htm_data, parts[2]
394
- end
395
-
396
- # rm multipart_mixed_inline_and_attachment.msg
397
- def test_multipart_mixed_with_inline_and_attachment
398
- mixed_msg = MIME::MultipartMedia::Mixed.new
399
-
400
- img_filename = 'image.jpg'
401
- img_data = ''
402
- open(sd(img_filename)) do |img_file|
403
- img_data = img_file.read
404
- img_msg = MIME::ImageMedia.new(img_data, 'image/jpeg')
405
- mixed_msg.attach_entity(img_msg, 'filename' => img_file.path)
406
- end
407
-
408
- txt_data = 'This is plain text.'
409
- mixed_msg.inline_entity(MIME::TextMedia.new(txt_data))
410
- msg = mixed_msg.to_s
411
-
412
- parts = msg.split(BOUNDARY)
413
- assert_equal 4, parts.size, 'main header, 2 entities, last boundary'
414
- assert_equal '--', parts.pop, 'remnant of last boundary marker'
415
-
416
- # main header
417
- assert_match CONTENT_ID, parts[0]
418
- assert_match CTYPE_MPART_MIXED, parts[0]
419
- assert_equal_num_headers 2, parts[0]
420
- assert_equal_body '', parts[0]
421
-
422
- # text entity
423
- assert_match CONTENT_ID, parts[1]
424
- assert_match CTYPE_TEXT_PLAIN, parts[1]
425
- assert_match /^Content-Disposition: inline\r\n/, parts[1]
426
- assert_equal_num_headers 3, parts[1]
427
- assert_equal_body txt_data, parts[1]
428
-
429
- # image entity
430
- assert_match CONTENT_ID, parts[2]
431
- assert_match CTYPE_IMAGE_JPEG, parts[2]
432
- assert_match /^Content-Disposition: attachment; filename="#{img_filename}"\r\n/, parts[2]
433
- assert_equal_num_headers 3, parts[2]
434
- assert_equal_body img_data, parts[2]
435
- end
436
-
437
- # rm multipart_mixed_inline_and_attachment2.msg
438
- def test_multipart_mixed_message_construction_using_media_factory
439
- img1 = sd(img1_filename = 'image.jpg')
440
- img2 = sd(img2_filename = 'ruby.png')
441
- txt = sd(txt_filename = 'data.htm')
442
- bot_img = MIME::DiscreteMediaFactory.create(img1)
443
- top_img = MIME::DiscreteMediaFactory.create(img2)
444
- top_txt = MIME::DiscreteMediaFactory.create(txt)
445
-
446
- mixed_msg = MIME::MultipartMedia::Mixed.new
447
- mixed_msg.attach_entity(bot_img)
448
- mixed_msg.attach_entity(top_img)
449
- mixed_msg.inline_entity(top_txt)
450
- msg = mixed_msg.to_s
451
-
452
- parts = msg.split(BOUNDARY)
453
- assert_equal 5, parts.size, 'main header, 2 entities, last boundary'
454
- assert_equal '--', parts.pop, 'remnant of last boundary marker'
455
-
456
- # main header
457
- assert_match CONTENT_ID, parts[0]
458
- assert_match CTYPE_MPART_MIXED, parts[0]
459
- assert_equal_num_headers 2, parts[0]
460
- assert_equal_body '', parts[0]
461
-
462
- # html entity
463
- assert_match CONTENT_ID, parts[1]
464
- assert_match CTYPE_TEXT_HTML, parts[1]
465
- assert_match /^Content-Disposition: inline; filename="#{txt_filename}"\r\n/, parts[1]
466
- assert_equal_num_headers 3, parts[1]
467
- assert_equal_body top_txt.send(:body), parts[1]
468
-
469
- # png image entity
470
- assert_match CONTENT_ID, parts[2]
471
- assert_match CTYPE_IMAGE_PNG, parts[2]
472
- assert_match /^Content-Disposition: attachment; filename="#{img2_filename}"\r\n/, parts[2]
473
- assert_equal_num_headers 3, parts[2]
474
- assert_equal_body top_img.send(:body), parts[2]
475
-
476
- # jpg image entity
477
- assert_match CONTENT_ID, parts[3]
478
- assert_match CTYPE_IMAGE_JPEG, parts[3]
479
- assert_match /^Content-Disposition: attachment; filename="#{img1_filename}"\r\n/, parts[3]
480
- assert_equal_num_headers 3, parts[3]
481
- assert_equal_body bot_img.send(:body), parts[3]
482
- end
483
-
484
- def test_multipart_form_data_with_mixed_entity
485
- txt = MIME::TextMedia.new('Joe Blow')
486
- img1 = MIME::DiscreteMediaFactory.create(sd('image.jpg'))
487
- img2 = MIME::DiscreteMediaFactory.create(sd('ruby.png'))
488
-
489
- mixed_msg = MIME::MultipartMedia::Mixed.new
490
- mixed_msg.attach_entity(img2)
491
- mixed_msg.attach_entity(img1)
492
-
493
- form = MIME::MultipartMedia::FormData.new
494
- form.add_entity(mixed_msg, 'pics')
495
- form.add_entity(txt, 'field1')
496
-
497
- # similar to example 6 in RFC1867
498
- expected = IO.read(sd('multipart_form_data_mixed.msg'))
499
-
500
- assert_equal_mime_message expected, form.to_s
501
-
502
- #IO.write('/tmp/mime.out', msg)
503
- end
504
-
505
- # def test_multipart_related_html_message_with_embedded_image
506
- # img = DiscreteMediaFactory.create(sd('/ruby.png'))
507
- # img.content_transfer_encoding = 'binary'
508
- #
509
- # html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1')
510
- # <html>
511
- # <body>
512
- # <h1>HTML multipart/related message</h1>
513
- # <p>txt before pix</p>
514
- # <img alt="cool ruby" src="cid:#{img.content_id}"/>
515
- # <p>txt after pix</p>
516
- # </body>
517
- # </html>
518
- # html
519
- # html_msg.content_transfer_encoding = '7bit'
520
- #
521
- # related_msg = MultipartMedia::Related.new
522
- # related_msg.inline_entity(img)
523
- # related_msg.add_entity(html_msg)
524
- #
525
- # expected = IO.read(sd('/multipart_related.msg'))
526
- #
527
- # assert_equal_mime_message expected, related_msg.to_s
528
- # end
529
- #
530
- # def test_multipart_alternative_with_related_html_entity
531
- # img = DiscreteMediaFactory.create(sd('/ruby.png'))
532
- # img.content_transfer_encoding = 'binary'
533
- #
534
- # html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1')
535
- # <html>
536
- # <body>
537
- # <h1>HTML multipart/alternative message</h1>
538
- # <p>txt before pix</p>
539
- # <img alt="cool ruby" src="cid:#{img.content_id}"/>
540
- # <p>txt after pix</p>
541
- # </body>
542
- # </html>
543
- # html
544
- # html_msg.content_transfer_encoding = '7bit'
545
- #
546
- # text_msg = TextMedia.new(<<-text)
547
- # *HTML multipart/alternative message*
548
- # txt before pix
549
- # <cool ruby image>
550
- # txt after pix
551
- # text
552
- # text_msg.content_transfer_encoding = '7bit'
553
- #
554
- # related_msg = MultipartMedia::Related.new
555
- # related_msg.inline_entity(img)
556
- # related_msg.add_entity(html_msg)
557
- #
558
- # alt_msg = MultipartMedia::Alternative.new
559
- # alt_msg.add_entity(related_msg)
560
- # alt_msg.add_entity(text_msg)
561
- #
562
- # expected = IO.read(sd('/multipart_alternative_related.msg'))
563
- #
564
- # assert_equal_mime_message expected, alt_msg.to_s
565
- # end
566
-
567
-
568
- private
569
-
570
- #
571
- # Test the equality of the normalized +expected+ and +actual+ MIME messages.
572
- #
573
- def assert_equal_mime_message expected, actual
574
- assert_equal normalize_message(expected), normalize_message(actual)
575
- end
576
-
577
- #
578
- # Make messages comparable by removing *-ID header values, library version
579
- # comment in MIME-Version header, Date header value, multipart/related
580
- # content IDs, and boundaries.
581
- #
582
- def normalize_message message
583
- # these are very delicate REs that are inter-dependent, be careful
584
- match_id_headers = /-ID: <[^>]+>\r\n/
585
- match_boundaries = /Boundary_\d+\.\d+/
586
- match_lib_version = / \(Ruby MIME v\d\.\d\)/
587
- match_date_header = /^Date: .*\d{4}\r\n/
588
- match_related_cid = /cid:\d+\.\d+/
589
-
590
- message.
591
- gsub(match_related_cid, "cid").
592
- gsub(match_id_headers, "-ID:\r\n").
593
- gsub(match_boundaries, "Boundary_").
594
- sub(match_date_header, "Date:\r\n").
595
- sub(match_lib_version, " (Ruby MIME v0.0)")
596
- end
597
-
598
- def assert_equal_num_headers num, msg
599
- assert_equal num, msg.split(HDR_BDY_SEP).first.split(CRLF).count
600
- end
601
-
602
- def assert_equal_body expected_body, msg
603
- # FIXME the next line should be a fix
604
- headers, actual_body = msg.split(HDR_BDY_SEP)
605
- assert_equal expected_body, actual_body.to_s.chomp
606
- #assert_equal body, msg.split("\r\n").last
607
- end
608
-
609
- #
610
- # Return the absolute path of +file+ under the test/scaffold directory.
611
- #
612
- def sd file
613
- File.join(File.dirname(__FILE__), 'scaffold', file)
614
- end
615
-
616
- end