sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit c62aec33a8c98ceb90d052e211574fa54d701bd7
parent e791c9e33e510d98bea0c1b505d95c469a8d369c
Author: Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
Date:   Fri, 16 Jan 2015 20:12:42 +0100

Merge pull request #379 from rakoo/fix-attachment-filename

Escape attachments filenames so they can be safely saved
Diffstat:
M lib/sup/message_chunks.rb | 1 +
M lib/sup/modes/thread_view_mode.rb | 4 ++--
M test/test_message.rb | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/lib/sup/message_chunks.rb b/lib/sup/message_chunks.rb
@@ -159,6 +159,7 @@ EOS
         "Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
       end
     end
+    def safe_filename; Shellwords.escape(@filename).gsub("/", "_") end
 
     ## an attachment is exapndable if we've managed to decode it into
     ## something we can display inline. otherwise, it's viewable.
diff --git a/lib/sup/modes/thread_view_mode.rb b/lib/sup/modes/thread_view_mode.rb
@@ -388,7 +388,7 @@ EOS
     when Chunk::Attachment
       default_dir = $config[:default_attachment_save_dir]
       default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
-      default_fn = File.expand_path File.join(default_dir, chunk.filename)
+      default_fn = File.expand_path File.join(default_dir, chunk.safe_filename)
       fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
 
       # if user selects directory use file name from message
@@ -417,7 +417,7 @@ EOS
     num_errors = 0
     m.chunks.each do |chunk|
       next unless chunk.is_a?(Chunk::Attachment)
-      fn = File.join(folder, chunk.filename)
+      fn = File.join(folder, chunk.safe_filename)
       num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content }
       num += 1
     end
diff --git a/test/test_message.rb b/test/test_message.rb
@@ -520,6 +520,82 @@ EOS
 
   end
 
+  def test_malicious_attachment_names
+
+
+    message = <<EOS
+From: Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
+To: reply+0007a7cb7174d1d188fcd420fce83e0f68fe03fc7416cdae92cf0000000110ce4efd92a169ce033d18e1 <reply+0007a7cb7174d1d188fcd420fce83e0f68fe03fc7416cdae92cf0000000110ce4efd92a169ce033d18e1@reply.github.com>
+Subject: Re: [sup] Attachment saving and special characters in filenames (#378)
+In-reply-to: <sup-heliotrope/sup/issues/378@github.com>
+References: <sup-heliotrope/sup/issues/378@github.com>
+X-pgp-key: http://otokar.looc2011.eu/static/matthieu.rakotojaona.asc
+Date: Wed, 14 Jan 2015 22:13:37 +0100
+Message-Id: <1421269972-sup-5245@kpad>
+User-Agent: Sup/git
+Content-Transfer-Encoding: 8bit
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-1421270017-526778-1064-1628-1-="
+
+
+--=-1421270017-526778-1064-1628-1-=
+Content-Type: text/plain; charset=UTF-8
+Content-Disposition: inline
+
+Excerpts from Felix Kaiser's message of 2015-01-14 16:36:29 +0100:
+> When saving attachments, sup should replace special characters when suggesting a filename to save the attachment to.
+> 
+> I just got an attachment with a name like "foo/2.pdf". sup suggests saving it to /home/fxkr/foo/2.pdf (and fails to save it, of course, if /home/fxkr/foo isn't a directory).
+> 
+> I haven't tested the "Save All" feature, but I hope nothing bad happens when there's an attachment called "../../../../../../../home/fxkr/.bashrc" ;-)
+> 
+> ---
+> Reply to this email directly or view it on GitHub:
+> https://github.com/sup-heliotrope/sup/issues/378
+
+For tests, here's an email with an attachment filename set to
+sup/.travis.yml (really, this time)
+
+-- 
+Matthieu Rakotojaona
+
+--=-1421270017-526778-1064-1628-1-=
+Content-Disposition: attachment; filename="sup/.travis.yml"
+Content-Type: text/x-yaml; name="sup/.travis.yml"
+Content-Transfer-Encoding: 8bit
+
+language: ruby
+
+rvm:
+  - 2.1.1
+  - 2.0.0
+  - 1.9.3
+
+before_install:
+  - sudo apt-get update -qq
+  - sudo apt-get install -qq uuid-dev uuid libncursesw5-dev libncursesw5 gnupg2 pandoc
+  - git submodule update --init --recursive
+
+script: bundle exec rake travis
+
+--=-1421270017-526778-1064-1628-1-=--
+EOS
+
+    source = DummySource.new("sup-test://test_blank_header_lines")
+    source.messages = [ message ]
+    source_info = 0
+
+    sup_message = Message.build_from_source(source, source_info)
+    chunks = sup_message.load_from_source!
+
+    # See if attachment filenames can be safely used for saving.
+    # We do that by verifying that any folder-related character (/ or \)
+    # are not interpreted: the filename must not be interpreted into a
+    # path.
+    fn = chunks[3].safe_filename
+    assert_equal(fn, File.basename(fn))
+
+  end
   # TODO: test different error cases, malformed messages etc.
 
   # TODO: test different quoting styles, see that they are all divided