mime 0.4.1 → 0.4.2

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