sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit e2bdf6ac790fc2926ee0d8da2685ba2cadca253b
parent 7085ec55ef5bd7d770fead5c6e16000ebc1a2629
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 12 May 2024 17:52:15 +1000

treat message/global as an embedded message per RFC6532

Fixes #602.

Diffstat:
M Manifest.txt | 1 +
M lib/sup/message.rb | 7 ++++++-
A test/fixtures/embedded-message-rfc6532.eml | 33 +++++++++++++++++++++++++++++++++
M test/test_message.rb | 29 +++++++++++++++++++++++++++++
4 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/Manifest.txt b/Manifest.txt
@@ -127,6 +127,7 @@ test/fixtures/bad-content-transfer-encoding-1.eml
 test/fixtures/binary-content-transfer-encoding-2.eml
 test/fixtures/blank-header-fields.eml
 test/fixtures/contacts.txt
+test/fixtures/embedded-message-rfc6532.eml
 test/fixtures/embedded-message.eml
 test/fixtures/invalid-date.eml
 test/fixtures/mailing-list-header.eml
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -460,6 +460,11 @@ private
     end
   end
 
+  def has_embedded_message? m
+    return false unless m.header.content_type
+    %w(message/rfc822 message/global).include? m.header.content_type.downcase
+  end
+
   ## takes a RMail::Message, breaks it into Chunk:: classes.
   def message_to_chunks m, encrypted=false, sibling_types=[]
     if m.multipart?
@@ -477,7 +482,7 @@ private
       end
 
       chunks
-    elsif m.header.content_type && m.header.content_type.downcase == "message/rfc822"
+    elsif has_embedded_message? m
       encoding = m.header["Content-Transfer-Encoding"]
       if m.body
         body =
diff --git a/test/fixtures/embedded-message-rfc6532.eml b/test/fixtures/embedded-message-rfc6532.eml
@@ -0,0 +1,33 @@
+Return-Path: <return@example.com>
+From: Sender <sender@example.com>
+To: <recipient@example.invalid>
+Subject: Email with embedded message
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----------=_4F506AC2.EE281DC4"
+Message-Id: <9181f493-df49-4af5-8ad2-e1a8eb692a98>
+Date: Wed, 15 Jul 2020 19:48:41 +0100
+
+This is a multi-part message in MIME format.
+
+------------=_4F506AC2.EE281DC4
+Content-Type: text/plain; charset=iso-8859-1
+Content-Disposition: inline
+Content-Transfer-Encoding: 8bit
+
+Example outer message.
+
+
+------------=_4F506AC2.EE281DC4
+Content-Type: message/global
+Content-Transfer-Encoding: 8bit
+
+From: "Embed sender" <embed@example.com>
+To: <rcpt2@example.invalid>
+Subject: Embedded subject line with emoji ✨
+X-heävy-mëtal-ümlaut: yes
+Date: Sun, 12 May 2024 17:34:29 +1000
+
+Example embedded message, with UTF-8 headers.
+
+------------=_4F506AC2.EE281DC4--
+
diff --git a/test/test_message.rb b/test/test_message.rb
@@ -344,6 +344,35 @@ class TestMessage < Minitest::Test
     assert_equal("Second line.", chunks[2].lines[1])
   end
 
+  def test_embedded_message_rfc6532
+    source = DummySource.new("sup-test://test_embedded_message_rfc6532")
+    source.messages = [ fixture_path("embedded-message-rfc6532.eml") ]
+
+    sup_message = Message.build_from_source(source, 0)
+
+    chunks = sup_message.load_from_source!
+    assert_equal(3, chunks.length)
+
+    assert_equal("Email with embedded message", sup_message.subj)
+
+    assert(chunks[0].is_a? Redwood::Chunk::Text)
+    assert_equal("Example outer message.", chunks[0].lines[0])
+
+    assert(chunks[1].is_a? Redwood::Chunk::EnclosedMessage)
+    assert_equal(4, chunks[1].lines.length)
+    assert_equal("From: Embed sender <embed@example.com>", chunks[1].lines[0])
+    assert_equal("To: rcpt2 <rcpt2@example.invalid>", chunks[1].lines[1])
+    assert_equal("Date: ", chunks[1].lines[2][0..5])
+    assert_equal(
+      Time.rfc2822("Sun, 12 May 2024 17:34:29 +1000"),
+      Time.rfc2822(chunks[1].lines[2][6..-1])
+    )
+    assert_equal("Subject: Embedded subject line with emoji ✨", chunks[1].lines[3])
+
+    assert(chunks[2].is_a? Redwood::Chunk::Text)
+    assert_equal("Example embedded message, with UTF-8 headers.", chunks[2].lines[0])
+  end
+
   def test_malicious_attachment_names
     source = DummySource.new("sup-test://test_blank_header_lines")
     source.messages = [ fixture_path('malicious-attachment-names.eml') ]