diff -Nru ruby-mime-0.4.2/debian/changelog ruby-mime-0.4.3/debian/changelog --- ruby-mime-0.4.2/debian/changelog 2015-04-28 19:39:09.000000000 +0000 +++ ruby-mime-0.4.3/debian/changelog 2015-11-08 20:54:32.000000000 +0000 @@ -1,3 +1,13 @@ +ruby-mime (0.4.3-1) unstable; urgency=medium + + * New upstream release. + * debian/copyright: updated the upstream copyright years. + * debian/rules: removed the override_dh_installchangelogs target. I am + dropping the 'unofficial' upstream changelog. + * debian/upstream.changelog: removed. + + -- Joao Eriberto Mota Filho Sun, 08 Nov 2015 18:47:15 -0200 + ruby-mime (0.4.2-3) unstable; urgency=medium * Upload to unstable. Welcome Jessie Stable! @@ -11,7 +21,7 @@ - Added an override to debian/rules to install the debian/upstream.changelog file. * debian/copyright: - - Upadted the packaging years. + - Updated the packaging years. - Updated the Source field. * debian/watch: added new search lines to avoid the error 500. diff -Nru ruby-mime-0.4.2/debian/copyright ruby-mime-0.4.3/debian/copyright --- ruby-mime-0.4.2/debian/copyright 2015-01-02 11:20:17.000000000 +0000 +++ ruby-mime-0.4.3/debian/copyright 2015-11-08 20:49:31.000000000 +0000 @@ -3,7 +3,7 @@ Source: https://rubygems.org/gems/mime Files: * -Copyright: 2008-2014 Clint Pachl +Copyright: 2008-2015 Clint Pachl License: ISC Files: debian/* diff -Nru ruby-mime-0.4.2/debian/rules ruby-mime-0.4.3/debian/rules --- ruby-mime-0.4.2/debian/rules 2015-01-02 11:25:59.000000000 +0000 +++ ruby-mime-0.4.3/debian/rules 2015-11-08 20:54:00.000000000 +0000 @@ -3,6 +3,3 @@ %: dh $@ --buildsystem=ruby --with ruby - -override_dh_installchangelogs: - dh_installchangelogs debian/upstream.changelog diff -Nru ruby-mime-0.4.2/debian/upstream.changelog ruby-mime-0.4.3/debian/upstream.changelog --- ruby-mime-0.4.2/debian/upstream.changelog 2015-01-02 11:23:22.000000000 +0000 +++ ruby-mime-0.4.3/debian/upstream.changelog 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -(From http://ecentryx.com/gems/mime/) - - 2008-11-05, v0.1 - - First public release. - - 2013-12-18, v0.2.0 - - Update for Ruby 1.9.3. - - Update Rakefile test, package, and rdoc tasks. - - Change test suite from Test::Unit to Minitest. - - Cleanup existing and add new tests cases. - - Clarify code comments and README examples. - - Fix content type detection. - - 2014-02-28, v0.3.0 - - Simplify API of DiscreteMediaType subclasses. - - Disallow Content-Type changes after instantiating DiscreteMediaType. - - Add flowed format support for text/plain (RFC 2646). - - 2014-04-18, v0.4.0 - - Major API disruption! - - Rename classes: - - HeaderContainter => Header - - remove “Media” suffix from the 5 DiscreteMedia and 2 CompositeMedia subclasses. See commit for details. - - MIME::Message => MIME::Mail - - Rename methods: - - remove “_entity” suffix from add, inline, and attach in CompositeMedia. - - remove “content_” prefix from id, disposition, description, and transfer_encoding in Headers::MIME. - - Remove methods: - - Header#add - - Add methods: - - Header#set (replace Header#add) - - Header#get - - Header#delete - - Reverse order of entities in CompositeMedia::Body, which most likely reverses all CompositeMedia add, inline, and attach method calls. See commit for details. - - Other changes - - Use From header field domain in the Message-ID header. - - Add more randomness when generating header IDs. - - Header field names are now case-insensitive to comply with RFCs. - - Accept String, Array, and Hash for originator and destination mailboxes. - - Add CompositeMedia::Body class for nesting MIME entities. - - Improve docs and examples for Content-Disposition (inline/attachment). - - FIX: remove trailing CRLF in Mail#to_s and update tests. - - Add README links to message lint tools on IETF.org. - - Add Send, In-Reply-To, and References RFC 5322 header fields. - - Comply with RFC regarding parameter quoting in header field bodies. I.e., do not quote atom/dot-atom parameter values. - - Many fixes and improvements in code, tests, documentation, and examples. - - 2014-04-20, v0.4.1 - - Add bug tracker URL. - - Link to referenced commit messages. - - 2014-06-13, v0.4.2 - - FIX: remove header field when set to nil. - - RFC compliance: set Sender field to first From address if multiple From addresses and the Sender header is not already set. - - Improve TextFlowed tests. - - Add TODO.mkd. - - FIX: Ruby 1.8.7 compatibility issue. diff -Nru ruby-mime-0.4.2/lib/mime/composite_media.rb ruby-mime-0.4.3/lib/mime/composite_media.rb --- ruby-mime-0.4.2/lib/mime/composite_media.rb 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/lib/mime/composite_media.rb 2015-11-08 20:46:32.000000000 +0000 @@ -67,7 +67,7 @@ # msg.add(MIME::Text.new('html text', 'html')) # # The order in which the entities are added is significant. Add the simplest - # representations first. + # representations first or in increasing order of complexity. # def add entity raise Error.new('can only add Media objects') unless entity.is_a? Media @@ -143,9 +143,13 @@ # # The Alternative subtype indicates that each contained entity is an - # alternatively formatted version of the same content. The most complex - # version should be added to the message first, i.e. it will be sequentially - # last in the message. + # alternatively formatted version of the same content. + # + # In general, when composing multipart/alternative entities, add the body + # parts in increasing order of preference; that is, with the preferred format + # last. For example, to prioritize for fancy text, add the plainest format + # first and the richest format last. Typically, receiving user agents select + # the last format they are capable of displaying or interpreting. # class Multipart::Alternative < Multipart @@ -225,6 +229,21 @@ super('related') end + # + # The first entity added becomes the root object. The related message + # type is set to the value of the root object media type. + #-- + # The "type" parameter is required and should equal the root media type. + # https://tools.ietf.org/html/rfc2387#section-3.1 + # + def add entity + unless type.include? '; type=' + root_type = entity.type.partition(';').first # omit parameters + self.type = append_field_params(type, 'type' => root_type) + end + super + end + end end diff -Nru ruby-mime-0.4.2/lib/mime.rb ruby-mime-0.4.3/lib/mime.rb --- ruby-mime-0.4.2/lib/mime.rb 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/lib/mime.rb 2015-11-08 20:46:32.000000000 +0000 @@ -18,7 +18,7 @@ # module MIME - VERSION = '0.4.2' + VERSION = '0.4.3' # Defined in RFC: https://tools.ietf.org/html/rfc5322#section-2.1.1 MAX_LINE_LENGTH = 998 diff -Nru ruby-mime-0.4.2/metadata.yml ruby-mime-0.4.3/metadata.yml --- ruby-mime-0.4.2/metadata.yml 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/metadata.yml 2015-11-08 20:46:32.000000000 +0000 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: mime version: !ruby/object:Gem::Version - version: 0.4.2 + version: 0.4.3 platform: ruby authors: - Clint Pachl autorequire: bindir: bin cert_chain: [] -date: 2014-06-13 00:00:00.000000000 Z +date: 2015-11-05 00:00:00.000000000 Z dependencies: [] description: | A library for building RFC compliant Multipurpose Internet Mail Extensions @@ -61,6 +61,7 @@ - test/scaffold/unknown.yyy - test/scaffold/video.msg - test/test_mime.rb +- test/test_mime.rb-try - test/test_text_flowed.rb homepage: http://ecentryx.com/gems/mime licenses: @@ -82,10 +83,10 @@ version: '0' requirements: [] rubyforge_project: -rubygems_version: 2.2.0 +rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: Multipurpose Internet Mail Extensions (MIME) Library test_files: -- test/test_text_flowed.rb - test/test_mime.rb +- test/test_text_flowed.rb diff -Nru ruby-mime-0.4.2/Rakefile ruby-mime-0.4.3/Rakefile --- ruby-mime-0.4.2/Rakefile 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/Rakefile 2015-11-08 20:46:32.000000000 +0000 @@ -16,3 +16,12 @@ Rake::TestTask.new do |t| t.warning = true end + +desc 'Upload RDoc HTML files to server' +task :upload_rdoc => :rdoc do +%x[ + rdir=/var/www/sites/ecentryx.com/public/gems/mime + rsvr=hercules + cd ./doc && pax -w . | ssh $rsvr "cd $rdir && rm -rf ./*; pax -r" +] +end diff -Nru ruby-mime-0.4.2/README.rdoc ruby-mime-0.4.3/README.rdoc --- ruby-mime-0.4.2/README.rdoc 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/README.rdoc 2015-11-08 20:46:32.000000000 +0000 @@ -217,22 +217,18 @@ image = DiscreteMediaFactory.create('/tmp/ruby.png') image.transfer_encoding = 'binary' # could base64 encode the image instead - html_msg = Text.new(< 'iso-8859-1') + html_msg = Text.new(< 'utf-8') -

Ruby Image

-

- Check out this cool pic. - ruby is cool -

-

Wasn't it cool?

+

A Shiny Red Ruby

+ a ruby gem EOF related_msg = Multipart::Related.new - related_msg.add(html_msg) - related_msg.inline(image) + related_msg.add(html_msg) # first entity added becomes the root object + related_msg.inline(image) # the root object references the inline image email_msg = Mail.new(related_msg) email_msg.to = 'jane@example.com' @@ -373,7 +369,15 @@ * Add bug tracker URL. * Link to referenced commit messages. 6. 2014-06-13, v0.4.2 - * Ruby 1.8.7 compatibility fix. + * FIX: remove header field when set to nil. + * RFC compliance: set Sender field to first From address if multiple From + addresses and the Sender header is not already set. + * Improve TextFlowed tests. + * Add TODO.mkd. + * FIX: Ruby 1.8.7 compatibility issue. +7. 2015-11-05, v0.4.3 + * FIX: Add "type" parameter to Multipart/Related messages (RFC conformance). + * FIX: Documention bug regarding Multipart/Alternative entity order. == License Binary files /tmp/yUAuR_uKf5/ruby-mime-0.4.2/test/scaffold/multipart_alternative_related.msg and /tmp/w3DHxjGvKK/ruby-mime-0.4.3/test/scaffold/multipart_alternative_related.msg differ Binary files /tmp/yUAuR_uKf5/ruby-mime-0.4.2/test/scaffold/multipart_related.msg and /tmp/w3DHxjGvKK/ruby-mime-0.4.3/test/scaffold/multipart_related.msg differ Binary files /tmp/yUAuR_uKf5/ruby-mime-0.4.2/test/scaffold/rfc822_composite.msg and /tmp/w3DHxjGvKK/ruby-mime-0.4.3/test/scaffold/rfc822_composite.msg differ diff -Nru ruby-mime-0.4.2/test/test_mime.rb ruby-mime-0.4.3/test/test_mime.rb --- ruby-mime-0.4.2/test/test_mime.rb 2014-12-18 21:13:35.000000000 +0000 +++ ruby-mime-0.4.3/test/test_mime.rb 2015-11-08 20:46:32.000000000 +0000 @@ -1,8 +1,7 @@ -gem 'minitest' # minitest in 1.9 stdlib is crufty require 'minitest/autorun' require 'mime' -Encoding.default_external = 'ASCII-8BIT' # may be able to remove in 2.0 +Encoding.default_external = 'ASCII-8BIT' class MIMETest < Minitest::Test @@ -319,6 +318,29 @@ assert_equal sender, msg.sender end + # Related type parameter: https://tools.ietf.org/html/rfc2387#section-3.1 + def test_related_message_inherits_type_from_root + msg = MIME::Multipart::Related.new + refute_includes(msg.type, 'type') + msg.add(MIME::Text.new('body', 'plain')) # Root object (first entity) + assert_includes(msg.type, 'type=text/plain') # sets the related msg type. + msg.add(MIME::Text.new('body', 'html')) # Additional entities have + refute_includes(msg.type, 'type=text/html') # no effect on msg type. + assert_includes(msg.type, 'type=text/plain') + end + + def test_related_message_inherits_parameterized_type_from_root + msg = MIME::Multipart::Related.new + refute_includes(msg.type, 'type') + + root = MIME::Text.new('a message', 'plain', 'charset' => 'us-ascii') + assert_equal(root.type, 'text/plain; charset=us-ascii') + + msg.add(root) + assert_includes(msg.type, 'type=text/plain') # Inherits root mime type, + refute_includes(msg.type, 'charset=us-ascii') # but params are omitted. + end + def test_rfc2822_date_format msg = MIME::Mail.new assert_kind_of(Time, msg.date) diff -Nru ruby-mime-0.4.2/test/test_mime.rb-try ruby-mime-0.4.3/test/test_mime.rb-try --- ruby-mime-0.4.2/test/test_mime.rb-try 1970-01-01 00:00:00.000000000 +0000 +++ ruby-mime-0.4.3/test/test_mime.rb-try 2015-11-08 20:46:32.000000000 +0000 @@ -0,0 +1,616 @@ +# THIS IS STILL HERE BECAUSE A LOT OF WORK WENT INTO MAKING TESTS AGNOSTIC OF MIME HEADER ORDER. +# HOWEVER, THINGS GOT MESSY SO REVERTED BACK TO COMPARING AGAINST KNOWN GOOD MESSAGES. +# THIS IS HERE JUST IN CASE. IN THE FUTURE, REMOVE IT IF THERE IS NO NEED FOR IT. + +gem 'minitest' # minitest in 1.9 stdlib is crufty +require 'minitest/autorun' +require 'mime' + +# may be able to remove in 2.0 +Encoding.default_external = 'ASCII-8BIT' + +class MIMETest < Minitest::Test + + CRLF = "\r\n" + HDR_BDY_SEP = CRLF * 2 + MESSAGE_ID = /^Message-ID: <\d+@\d+>\r\n/ + CONTENT_ID = /^Content-ID: <\d+\.\d+>\r\n/ + + CDISPOSITION = /^Content-Disposition: .*\r\n/ + CTYPE = /^Content-Type: .*\r\n/ + + DATE = /^Date: ..., \d{1,2} ... \d{4} \d\d:\d\d:\d\d -\d{4}\r\n/ + VERSION = /^MIME-Version: 1.0 \(Ruby MIME v\d\.\d\)\r\n/ + #BOUNDARY = /^--Boundary_\d+\.\d+\r\n/ + BOUNDARY = /^--Boundary_\d+\.\d+(--)?\r\n/ + BOUNDARY_LAST = /^--Boundary_\d+\.\d+--\r\n/ + CID = /cid:\d+\.\d+/ + + CTYPE_TEXT_PLAIN = /^Content-Type: text\/plain; charset=us-ascii\r\n/ + CTYPE_TEXT_HTML = /^Content-Type: text\/html\r\n/ + CTYPE_TEXT_XML = /^Content-Type: text\/xml\r\n/ + CTYPE_IMAGE_JPEG = /^Content-Type: image\/jpeg\r\n/ + CTYPE_IMAGE_PNG = /^Content-Type: image\/png\r\n/ + CTYPE_VIDEO_MPEG = /^Content-Type: video\/mpeg\r\n/ + CTYPE_AUDIO_MIDI = /^Content-Type: audio\/midi\r\n/ + CTYPE_APPLICATION = /^Content-Type: application\/octet-stream\r\n/ + + CTYPE_MPART_FORM = /^Content-Type: multipart\/form-data; boundary=Boundary_\d+\.\d+\r\n/ + CTYPE_MPART_ALT = /^Content-Type: multipart\/alternative; boundary=Boundary_\d+\.\d+\r\n/ + CTYPE_MPART_MIXED = /^Content-Type: multipart\/mixed; boundary=Boundary_\d+\.\d+\r\n/ + + XFER_ENC_BINARY = /^Content-Transfer-Encoding: binary\r\n/ + XFER_ENC_8BIT = /^Content-Transfer-Encoding: 8bit\r\n/ + + BINARY_DATA = '0110000101110101011001000110100101101111' + + def test_make_top_level_rfc2822_message + rfc2822_msg = MIME::Message.new + rfc2822_msg.body = "\r\nmessage body" + msg = rfc2822_msg.to_s + + assert_match MESSAGE_ID, msg + assert_match DATE, msg + assert_match VERSION, msg + assert_equal_num_headers 3, msg + assert_equal_body "message body", msg + end + + # TODO remove audio.msg + def test_make_audio_message + audio_media = MIME::AudioMedia.new(BINARY_DATA, 'audio/midi') + audio_media.content_transfer_encoding = 'binary' + msg = MIME::Message.new(audio_media).to_s + + [CONTENT_ID, XFER_ENC_BINARY, CTYPE_AUDIO_MIDI].each do |header| + assert_match header, msg + end + assert_equal_num_headers 6, msg + assert_equal_body BINARY_DATA, msg + end + + # TODO remove expected_mime_msg = IO.read(sd('/application.msg')) + def test_make_application_message + application_media = MIME::ApplicationMedia.new(BINARY_DATA) + application_media.content_transfer_encoding = 'binary' + msg = MIME::Message.new(application_media).to_s + + [CONTENT_ID, XFER_ENC_BINARY, CTYPE_APPLICATION].each do |header| + assert_match header, msg + end + assert_equal_num_headers 6, msg + assert_equal_body BINARY_DATA, msg + end + + # TODO remove image.msg + def test_make_image_message + image = IO.read(sd('/image.jpg')) + image_media = MIME::ImageMedia.new(image) + image_media.content_type = 'image/jpeg' + image_media.content_transfer_encoding = 'binary' + msg = MIME::Message.new(image_media).to_s + + [CONTENT_ID, XFER_ENC_BINARY, CTYPE_IMAGE_JPEG].each do |header| + assert_match header, msg + end + + assert_equal_num_headers 6, msg + assert_equal_body image, msg + end + + # TODO remove text.msg + def test_make_text_message + body = 'a plain text message' + msg = MIME::Message.new(MIME::TextMedia.new(body)).to_s + + [CTYPE_TEXT_PLAIN].each do |header| + assert_match header, msg + end + end + + # TODO remove video.msg + def test_make_video_message + video_media = MIME::VideoMedia.new(BINARY_DATA) + video_media.content_type = 'video/mpeg' + video_media.content_transfer_encoding = 'binary' + msg = MIME::Message.new(video_media).to_s + + [CONTENT_ID, XFER_ENC_BINARY, CTYPE_VIDEO_MPEG].each do |header| + assert_match header, msg + end + assert_equal_num_headers 6, msg + assert_equal_body BINARY_DATA, msg + end + + def test_no_instantiation_of_abstract_classes + e = MIME::AbstractClassError + assert_raises(e) {MIME::MediaType.new(nil, nil)} + assert_raises(e) {MIME::DiscreteMediaType.new(nil)} + assert_raises(e) {MIME::CompositeMediaType.new(nil)} + assert_raises(e) {MIME::MultipartMedia.new(nil)} + end + +# def test_boundaries +# # CREATE a multipart message; take simplified version of next form data +# # test +# e = msg.scan(BOUNDARY).each +# first_boundary = e.next +# assert_equal first_boundary, e.next +# assert_equal first_boundary, e.next +# last_boundary = e.next +# refute_equal first_boundary, last_boundary +# assert_match /^--Boundary_\d+\.\d+\r\n/, first_boundary +# assert_match /^--Boundary_\d+\.\d+--\r\n/, last_boundary +# end +# +# def test_unique_content_ids_in_multipart_message +# +# content_ids = msg.scan(/^Content-ID: <(\d+.\d+)>/) +# assert_equal 4, content_ids.flatten.uniq.size # all IDs must be unique +# end + + # TODO rm multipart_form_data_text.msg + def test_multipart_form_data_with_text_entities + txt_data = 'text body' + htm_data = IO.read(sd('data.htm')) + xml_data = IO.read(sd('data.xml')) + + txt = MIME::TextMedia.new('text body') + htm = MIME::TextMedia.new(htm_data, 'text/html') + xml = MIME::TextMedia.new(xml_data, 'text/xml') + + form_data = MIME::MultipartMedia::FormData.new + form_data.add_entity txt, 'txt' + form_data.add_entity htm, 'htm' + form_data.add_entity xml, 'xml' + msg = form_data.to_s + + parts = msg.split(BOUNDARY) + assert_equal 5, parts.size, 'main header, 3 entities, last boundary' + assert_equal '--', parts.pop, 'remnant of last boundary marker' + + # main header + assert_match CONTENT_ID, parts[0] + assert_match CTYPE_MPART_FORM, parts[0] + assert_equal_num_headers 2, parts[0] + assert_equal_body '', parts[0] + + # xml entity + assert_match CONTENT_ID, parts[1] + assert_match CTYPE_TEXT_XML, parts[1] + assert_match /^Content-Disposition: form-data; name="xml"\r\n/, parts[1] + assert_equal_num_headers 3, parts[1] + assert_equal_body xml_data, parts[1] + + # html entity + assert_match CONTENT_ID, parts[2] + assert_match CTYPE_TEXT_HTML, parts[2] + assert_match /^Content-Disposition: form-data; name="htm"\r\n/, parts[2] + assert_equal_num_headers 3, parts[2] + assert_equal_body htm_data, parts[2] + + # text entity + assert_match CONTENT_ID, parts[3] + assert_match CTYPE_TEXT_PLAIN, parts[3] + assert_match /^Content-Disposition: form-data; name="txt"\r\n/, parts[3] + assert_equal_num_headers 3, parts[3] + assert_equal_body txt_data, parts[3] + end + + # TODO test that only basename of the file is included in header + # test if no filename is used, then probably no filename param + # see next test for ideas + def test_content_disposition_filename + +pass + end + + # rm multipart_form_data_file.msg + # rm multipart_form_data_file_and_text.msg + def test_multipart_form_data_with_text_and_file_entities + img1_filename = 'image.jpg' + img2_filename = 'ruby.png' + img1_data = IO.read(sd(img1_filename)) + img2_data = IO.read(sd(img2_filename)) + img1 = MIME::ImageMedia.new(img1_data, 'image/jpeg') + img2 = MIME::ImageMedia.new(img2_data, 'image/png') + img1.content_transfer_encoding = '8bit' + img2.content_transfer_encoding = '8bit' + + desc_data = 'This is plain text description of images.' + desc = MIME::TextMedia.new(desc_data) + + form_data = MIME::MultipartMedia::FormData.new + form_data.add_entity desc, 'description' + form_data.add_entity img2, 'image_2', img2_filename + form_data.add_entity img1, 'image_1', img1_filename + msg = form_data.to_s + + parts = msg.split(BOUNDARY) + assert_equal 5, parts.size, 'main header, 3 entities, last boundary' + assert_equal '--', parts.pop, 'remnant of last boundary marker' + + # main header + assert_match CONTENT_ID, parts[0] + assert_match CTYPE_MPART_FORM, parts[0] + assert_equal_num_headers 2, parts[0] + assert_equal_body '', parts[0] + + # first image entity + assert_match CONTENT_ID, parts[1] + assert_match CTYPE_IMAGE_JPEG, parts[1] + assert_match XFER_ENC_8BIT, parts[1] + assert_match /^Content-Disposition: form-data; name="image_1"; filename="#{img1_filename}"\r\n/, parts[1] + assert_equal_num_headers 4, parts[1] + assert_equal_body img1_data, parts[1] + + # second image entity + assert_match CONTENT_ID, parts[2] + assert_match CTYPE_IMAGE_PNG, parts[2] + assert_match XFER_ENC_8BIT, parts[2] + assert_match /^Content-Disposition: form-data; name="image_2"; filename="#{img2_filename}"\r\n/, parts[2] + assert_equal_num_headers 4, parts[2] + assert_equal_body img2_data, parts[2] + + # plain text entity + assert_match CONTENT_ID, parts[3] + assert_match CTYPE_TEXT_PLAIN, parts[3] + assert_match /^Content-Disposition: form-data; name="description"\r\n/, parts[3] + assert_equal_num_headers 3, parts[3] + assert_equal_body desc_data, parts[3] + end + + # rm plain_text_email.msg + def test_construction_of_plain_text_email_message + subject = 'This is an important email' + body = 'This is the all important email body.' + + email_msg = MIME::Message.new + email_msg.to = { + 'john@example.com' => 'John', + 'paul@example.com' => nil, + 'mary@example.com' => 'Mary' + } + email_msg.cc = {'Head Honcho' => 'boss@example.com'} + email_msg.from = {'jane@example.com' => nil} + email_msg.subject = subject + email_msg.body = MIME::TextMedia.new(body) + + msg = email_msg.to_s + + assert_equal_num_headers 9, msg + assert_match MESSAGE_ID, msg + assert_match CONTENT_ID, msg + assert_match CTYPE_TEXT_PLAIN, msg + assert_match DATE, msg + assert_match VERSION, msg + assert_match /^Subject: #{subject}\r\n/, msg + assert_match /^From: jane@example.com\r\n/, msg + assert_match /^To: .+, .+, .+\r\n/, msg # 3 addresses + assert_match /^To: .*John .*\r\n/, msg + assert_match /^To: .*Mary .*\r\n/, msg + assert_match /^To: .*paul@example.com.*\r\n/, msg + assert_match /^Cc: boss@example.com \r\n/, msg + assert_equal_body body, msg + end + + def test_content_type_detection + (o = Object.new).extend(MIME::ContentTypes) + + # test using file path, relative and absolute + assert_equal 'application/pdf', o.file_type('book.pdf') + assert_equal 'video/quicktime', o.file_type('mini.mov') + assert_equal 'application/octet-stream', o.file_type('dsk.iso') + assert_equal 'audio/mpeg', o.file_type('/tmp/song.mp3') + assert_equal 'text/css', o.file_type('/tmp/main.css') + assert_equal nil, o.file_type('unknown.yyy') + + # test using file object + img_type = open(sd('ruby.png')) {|f| o.file_type(f)} + assert_equal 'image/png', img_type + refute_equal 'image/jpeg', img_type + end + + def test_object_instantiation_using_discrete_media_factory + app_file = sd('book.pdf') + audio_file = sd('song.mp3') + text_file = sd('data.xml') + video_file = sd('mini.mov') + image_file = sd('image.jpg') + unknown_file = sd('unknown.yyy') + + dmf = MIME::DiscreteMediaFactory + + # test using file path + assert_kind_of MIME::ApplicationMedia, dmf.create(app_file) + assert_kind_of MIME::AudioMedia, dmf.create(audio_file) + assert_kind_of MIME::TextMedia, dmf.create(text_file) + assert_kind_of MIME::VideoMedia, dmf.create(video_file) + assert_kind_of MIME::ImageMedia, dmf.create(image_file) + + # test using file object + open(image_file) do |image_file_obj| + assert_kind_of MIME::ImageMedia, dmf.create(image_file_obj) + end + open(text_file) do |text_file_obj| + assert_kind_of MIME::TextMedia, dmf.create(text_file_obj) + end + + # raise for unknown file path and File object + assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file)} + open(unknown_file) do |unknown_file_obj| + assert_raises(MIME::UnknownContentError) {dmf.create(unknown_file_obj)} + end + end + + def test_discrete_media_factory_creates_path_singleton_method + pdf_file_path = sd('book.pdf') + + media_obj = MIME::DiscreteMediaFactory.create(pdf_file_path) + assert_equal pdf_file_path, media_obj.path + + open(pdf_file_path) do |pdf_file_obj| + media_obj = MIME::DiscreteMediaFactory.create(pdf_file_obj) + assert_equal pdf_file_path, media_obj.path + end + end + + # rm multipart_alternative.msg + def test_multipart_alternative_message_construction + txt_data = "*Header*\nmessage" + htm_data = "

Header

message

" + txt_msg = MIME::TextMedia.new(txt_data) + htm_msg = MIME::TextMedia.new(htm_data) + + txt_msg.content_type = (txt_type = 'text/enhanced; charset=us-ascii') + htm_msg.content_type = (htm_type = 'text/html; charset=iso-8859-1') + + alt_msg = MIME::MultipartMedia::Alternative.new + alt_msg.add_entity htm_msg + alt_msg.add_entity txt_msg + msg = alt_msg.to_s + + parts = msg.split(BOUNDARY) + assert_equal 4, parts.size, 'main header, 2 entities, last boundary' + assert_equal '--', parts.pop, 'remnant of last boundary marker' + + # main header + assert_match CONTENT_ID, parts[0] + assert_match CTYPE_MPART_ALT, parts[0] + assert_equal_num_headers 2, parts[0] + assert_equal_body '', parts[0] + + # text entity + assert_match CONTENT_ID, parts[1] + assert_match /^Content-Type: #{txt_type}\r\n/, parts[1] + assert_equal_num_headers 2, parts[1] + assert_equal_body txt_data, parts[1] + + # html entity + assert_match CONTENT_ID, parts[2] + assert_match /^Content-Type: #{htm_type}\r\n/, parts[2] + assert_equal_num_headers 2, parts[2] + assert_equal_body htm_data, parts[2] + end + + # rm multipart_mixed_inline_and_attachment.msg + def test_multipart_mixed_with_inline_and_attachment + mixed_msg = MIME::MultipartMedia::Mixed.new + + img_filename = 'image.jpg' + img_data = '' + open(sd(img_filename)) do |img_file| + img_data = img_file.read + img_msg = MIME::ImageMedia.new(img_data, 'image/jpeg') + mixed_msg.attach_entity(img_msg, 'filename' => img_file.path) + end + + txt_data = 'This is plain text.' + mixed_msg.inline_entity(MIME::TextMedia.new(txt_data)) + msg = mixed_msg.to_s + + parts = msg.split(BOUNDARY) + assert_equal 4, parts.size, 'main header, 2 entities, last boundary' + assert_equal '--', parts.pop, 'remnant of last boundary marker' + + # main header + assert_match CONTENT_ID, parts[0] + assert_match CTYPE_MPART_MIXED, parts[0] + assert_equal_num_headers 2, parts[0] + assert_equal_body '', parts[0] + + # text entity + assert_match CONTENT_ID, parts[1] + assert_match CTYPE_TEXT_PLAIN, parts[1] + assert_match /^Content-Disposition: inline\r\n/, parts[1] + assert_equal_num_headers 3, parts[1] + assert_equal_body txt_data, parts[1] + + # image entity + assert_match CONTENT_ID, parts[2] + assert_match CTYPE_IMAGE_JPEG, parts[2] + assert_match /^Content-Disposition: attachment; filename="#{img_filename}"\r\n/, parts[2] + assert_equal_num_headers 3, parts[2] + assert_equal_body img_data, parts[2] + end + + # rm multipart_mixed_inline_and_attachment2.msg + def test_multipart_mixed_message_construction_using_media_factory + img1 = sd(img1_filename = 'image.jpg') + img2 = sd(img2_filename = 'ruby.png') + txt = sd(txt_filename = 'data.htm') + bot_img = MIME::DiscreteMediaFactory.create(img1) + top_img = MIME::DiscreteMediaFactory.create(img2) + top_txt = MIME::DiscreteMediaFactory.create(txt) + + mixed_msg = MIME::MultipartMedia::Mixed.new + mixed_msg.attach_entity(bot_img) + mixed_msg.attach_entity(top_img) + mixed_msg.inline_entity(top_txt) + msg = mixed_msg.to_s + + parts = msg.split(BOUNDARY) + assert_equal 5, parts.size, 'main header, 2 entities, last boundary' + assert_equal '--', parts.pop, 'remnant of last boundary marker' + + # main header + assert_match CONTENT_ID, parts[0] + assert_match CTYPE_MPART_MIXED, parts[0] + assert_equal_num_headers 2, parts[0] + assert_equal_body '', parts[0] + + # html entity + assert_match CONTENT_ID, parts[1] + assert_match CTYPE_TEXT_HTML, parts[1] + assert_match /^Content-Disposition: inline; filename="#{txt_filename}"\r\n/, parts[1] + assert_equal_num_headers 3, parts[1] + assert_equal_body top_txt.send(:body), parts[1] + + # png image entity + assert_match CONTENT_ID, parts[2] + assert_match CTYPE_IMAGE_PNG, parts[2] + assert_match /^Content-Disposition: attachment; filename="#{img2_filename}"\r\n/, parts[2] + assert_equal_num_headers 3, parts[2] + assert_equal_body top_img.send(:body), parts[2] + + # jpg image entity + assert_match CONTENT_ID, parts[3] + assert_match CTYPE_IMAGE_JPEG, parts[3] + assert_match /^Content-Disposition: attachment; filename="#{img1_filename}"\r\n/, parts[3] + assert_equal_num_headers 3, parts[3] + assert_equal_body bot_img.send(:body), parts[3] + end + + def test_multipart_form_data_with_mixed_entity + txt = MIME::TextMedia.new('Joe Blow') + img1 = MIME::DiscreteMediaFactory.create(sd('image.jpg')) + img2 = MIME::DiscreteMediaFactory.create(sd('ruby.png')) + + mixed_msg = MIME::MultipartMedia::Mixed.new + mixed_msg.attach_entity(img2) + mixed_msg.attach_entity(img1) + + form = MIME::MultipartMedia::FormData.new + form.add_entity(mixed_msg, 'pics') + form.add_entity(txt, 'field1') + + # similar to example 6 in RFC1867 + expected = IO.read(sd('multipart_form_data_mixed.msg')) + + assert_equal_mime_message expected, form.to_s + +#IO.write('/tmp/mime.out', msg) + end + +# def test_multipart_related_html_message_with_embedded_image +# img = DiscreteMediaFactory.create(sd('/ruby.png')) +# img.content_transfer_encoding = 'binary' +# +# html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1') +# +# +#

HTML multipart/related message

+#

txt before pix

+# cool ruby +#

txt after pix

+# +# +# html +# html_msg.content_transfer_encoding = '7bit' +# +# related_msg = MultipartMedia::Related.new +# related_msg.inline_entity(img) +# related_msg.add_entity(html_msg) +# +# expected = IO.read(sd('/multipart_related.msg')) +# +# assert_equal_mime_message expected, related_msg.to_s +# end +# +# def test_multipart_alternative_with_related_html_entity +# img = DiscreteMediaFactory.create(sd('/ruby.png')) +# img.content_transfer_encoding = 'binary' +# +# html_msg = TextMedia.new(<<-html, 'text/html; charset=iso-8859-1') +# +# +#

HTML multipart/alternative message

+#

txt before pix

+# cool ruby +#

txt after pix

+# +# +# html +# html_msg.content_transfer_encoding = '7bit' +# +# text_msg = TextMedia.new(<<-text) +# *HTML multipart/alternative message* +# txt before pix +# +# txt after pix +# text +# text_msg.content_transfer_encoding = '7bit' +# +# related_msg = MultipartMedia::Related.new +# related_msg.inline_entity(img) +# related_msg.add_entity(html_msg) +# +# alt_msg = MultipartMedia::Alternative.new +# alt_msg.add_entity(related_msg) +# alt_msg.add_entity(text_msg) +# +# expected = IO.read(sd('/multipart_alternative_related.msg')) +# +# assert_equal_mime_message expected, alt_msg.to_s +# end + + + private + + # + # Test the equality of the normalized +expected+ and +actual+ MIME messages. + # + def assert_equal_mime_message expected, actual + assert_equal normalize_message(expected), normalize_message(actual) + end + + # + # Make messages comparable by removing *-ID header values, library version + # comment in MIME-Version header, Date header value, multipart/related + # content IDs, and boundaries. + # + def normalize_message message + # these are very delicate REs that are inter-dependent, be careful + match_id_headers = /-ID: <[^>]+>\r\n/ + match_boundaries = /Boundary_\d+\.\d+/ + match_lib_version = / \(Ruby MIME v\d\.\d\)/ + match_date_header = /^Date: .*\d{4}\r\n/ + match_related_cid = /cid:\d+\.\d+/ + + message. + gsub(match_related_cid, "cid"). + gsub(match_id_headers, "-ID:\r\n"). + gsub(match_boundaries, "Boundary_"). + sub(match_date_header, "Date:\r\n"). + sub(match_lib_version, " (Ruby MIME v0.0)") + end + + def assert_equal_num_headers num, msg + assert_equal num, msg.split(HDR_BDY_SEP).first.split(CRLF).count + end + + def assert_equal_body expected_body, msg + # FIXME the next line should be a fix + headers, actual_body = msg.split(HDR_BDY_SEP) + assert_equal expected_body, actual_body.to_s.chomp + #assert_equal body, msg.split("\r\n").last + end + + # + # Return the absolute path of +file+ under the test/scaffold directory. + # + def sd file + File.join(File.dirname(__FILE__), 'scaffold', file) + end + +end