commit c0979b3eb0412e410e1215171c043eeb29253823
Author: Marc Dequenes <duck@Orfeo.duckcorp.org>
Date:   Sun Feb 22 22:20:11 2009 +0100

    [evol] moved signed/encrypted mail creation from original mail to TMail class, and removed mail object duplication in the process (references to inners objects caused original mail to be altered, copying original headers instead)

diff --git a/lib/cyborghood/mail.rb b/lib/cyborghood/mail.rb
index 98f1ceb..0a5b749 100644
--- a/lib/cyborghood/mail.rb
+++ b/lib/cyborghood/mail.rb
@@ -80,58 +80,18 @@ module CyborgHood
     end
 
     def crypt(fingerprint)
-      clear_data = build_intermediate_mail()
-      encrypted_data = clear_data.pgp_crypt(fingerprint)
-
-      # build properly encrypted mail
-      # (modify original mail parts)
-      @mail.set_content_type("multipart", "encrypted", {'boundary' => TMail.new_boundary, "protocol" => "application/pgp-encrypted"})
-      @mail.transfer_encoding = "7bit"
-      @mail['content-disposition'] = nil
-      @mail.body = "This mail is a RFC3156 encrypted message.\n"
-      @mail.parts.clear
-      p_pgp = TMail::Mail.new
-      p_pgp.set_content_type("application", "pgp-encrypted")
-      p_pgp.transfer_encoding = "7bit"
-      p_pgp.content_disposition = "inline"
-      p_pgp.body = "Version: 1\n"
-      @mail.parts << p_pgp
-      p_encrypted = TMail::Mail.new
-      p_encrypted.set_content_type("application", "octet-stream")
-      p_encrypted.transfer_encoding = "7bit"
-      p_encrypted.content_disposition = "inline"
-      p_encrypted.body = encrypted_data
-      @mail.parts << p_encrypted
+      @mail = @mail.create_encrypted(fingerprint)
     end
 
     def sign
-      data = build_intermediate_mail()
-      sign_data = data.pgp_sign([@config.mail.key_id]) do |uid_hint, passphrase_info, prev_was_bad|
+      # we don't check the uid, as their is only one signer
+      @mail = @mail.create_signed(@config.mail.key_id) do |uid_hint, passphrase_info, prev_was_bad|
         @config.mail.key_passphrase
       end
-
-      original_content_type = @mail.content_type
-      original_content_transfer_encoding = @mail.content_transfer_encoding
-      original_content_disposition = @mail.content_disposition
-
-      # build properly signed mail
-      # (modify original mail parts)
-      @mail.set_content_type("multipart", "signed", {'boundary' => TMail.new_boundary, 'protocol' => "application/pgp-signature", 'micalg' => sign_data[:micalg]})
-      @mail.transfer_encoding = "7bit"
-      @mail['content-disposition'] = nil
-      @mail.body = "This mail is a RFC3156 signed message.\n"
-      @mail.parts.clear
-      p_signed = data
-      @mail.parts << p_signed
-      p_signature = TMail::Mail.new
-      p_signature.set_content_type("application", "pgp-signature")
-      p_signature.transfer_encoding = "7bit"
-      p_signature.content_disposition = "inline"
-      p_signature.body = sign_data[:signature]
-      @mail.parts << p_signature
     end
 
     def sign_and_crypt(fingerprint)
+      # not using sign_and_crypt(), to avoid repeating code
       sign()
       crypt(fingerprint)
     end
@@ -143,24 +103,6 @@ module CyborgHood
 
     private
 
-    def build_intermediate_mail
-      # build a fake mail to get the generated content to be crypted/signed
-      fake_mail = TMail::Mail.new
-      fake_mail['content-type'] = @mail['content-type'].to_s
-      fake_mail.transfer_encoding = @mail.transfer_encoding if @mail.transfer_encoding
-      fake_mail.content_disposition = @mail.content_disposition if @mail.content_disposition
-      if @mail.multipart?
-        @mail.each_part {|p| fake_mail.parts << p }
-      else
-        fake_mail.body = @mail.body
-      end
-
-      # store the calculated content, to be able to use the raw() method
-      fake_mail.write_back
-
-      fake_mail
-    end
-
     def parse_signed
       order = {:ok => false, :msg => "mail not formatted correctly"}
 
diff --git a/lib/tmail_extra.rb b/lib/tmail_extra.rb
index d38dac8..4551b6f 100644
--- a/lib/tmail_extra.rb
+++ b/lib/tmail_extra.rb
@@ -65,11 +65,14 @@ module TMail
                       :passphrase_callback_value => passphrase_callback, :textmode => true})
     end
 
-    def pgp_crypt(fingerprint)
-      GPGME.encrypt([gpg_key(fingerprint)], self.to_s, {:armor => true, :always_trust => true})
+    def pgp_crypt(crypters_id)
+      crypters_id = [crypters_id] unless crypters_id.is_a? Array
+      crypters = crypters_id.collect{|key_id| gpg_key(key_id, false) }
+      GPGME.encrypt(crypters, self.to_s, {:armor => true, :always_trust => true})
     end
 
     def pgp_sign(signers_id, &passphrase_callback)
+      signers_id = [signers_id] unless signers_id.is_a? Array
       signers = signers_id.collect{|key_id| gpg_key(key_id, true) }
       # we don't use GPGME.sign(), because we need to get operation information to get the hash_algo and compute the micalg parameter
       gpg = GPGME::Ctx.new({:signers => signers, :passphrase_callback => method(:gpg_passphrase_callback_wrapper),
@@ -89,8 +92,108 @@ module TMail
       raw.chomp.gsub(/\r?\n/, "\r\n")
     end
 
+    def create_encrypted(crypters_id)
+      clear_data = build_intermediate_mail()
+      encrypted_data = clear_data.pgp_crypt(crypters_id)
+
+      # build properly encrypted mail
+      # (preserving headers from original mail)
+      mail = TMail::Mail.new
+      mail.copy_headers_from(self)
+      mail.set_content_type("multipart", "encrypted", {'boundary' => TMail.new_boundary, "protocol" => "application/pgp-encrypted"})
+      mail.transfer_encoding = "7bit"
+      mail['content-disposition'] = nil
+      mail.body = "This mail is a RFC3156 encrypted message.\n"
+      mail.parts.clear
+
+      # cryptographic info
+      p_pgp = TMail::Mail.new
+      p_pgp.set_content_type("application", "pgp-encrypted")
+      p_pgp.transfer_encoding = "7bit"
+      p_pgp.content_disposition = "inline"
+      p_pgp.body = "Version: 1\n"
+      mail.parts << p_pgp
+
+      # encrypted message
+      p_encrypted = TMail::Mail.new
+      p_encrypted.set_content_type("application", "octet-stream")
+      p_encrypted.transfer_encoding = "7bit"
+      p_encrypted.content_disposition = "inline"
+      p_encrypted.body = encrypted_data
+      mail.parts << p_encrypted
+
+      # store the calculated content, to be able to use parsing methods
+      mail.write_back
+
+      mail
+    end
+
+    def create_signed(signers_id)
+      data = build_intermediate_mail()
+      sign_data = data.pgp_sign(signers_id) do |uid_hint, passphrase_info, prev_was_bad|
+        yield(uid_hint, passphrase_info, prev_was_bad)
+      end
+
+      # build properly signed mail
+      # (preserving headers from original mail)
+      mail = TMail::Mail.new
+      mail.copy_headers_from(self)
+      mail.set_content_type("multipart", "signed", {'boundary' => TMail.new_boundary, 'protocol' => "application/pgp-signature", 'micalg' => sign_data[:micalg]})
+      mail.transfer_encoding = "7bit"
+      mail['content-disposition'] = nil
+      mail.body = "This mail is a RFC3156 signed message.\n"
+      mail.parts.clear
+
+      # signed message
+      p_signed = data
+      mail.parts << p_signed
+
+      # signature
+      p_signature = TMail::Mail.new
+      p_signature.set_content_type("application", "pgp-signature")
+      p_signature.transfer_encoding = "7bit"
+      p_signature.content_disposition = "inline"
+      p_signature.body = sign_data[:signature]
+      mail.parts << p_signature
+
+      # store the calculated content, to be able to use parsing methods
+      mail.write_back
+
+      mail
+    end
+
+    def create_signed_and_encrypted(signers_id, crypters_id)
+      create_signed(signers_id) do |uid_hint, passphrase_info, prev_was_bad|
+        yield(uid_hint, passphrase_info, prev_was_bad)
+      end.create_encrypted(crypters_id)
+    end
+
+    def copy_headers_from(mail)
+      mail.header.keys.each do |h|
+        self[h] = mail[h].to_s
+      end
+    end
+
     protected
 
+    def build_intermediate_mail
+      # build a fake mail to get the generated content to be crypted/signed
+      fake_mail = TMail::Mail.new
+      fake_mail['content-type'] = self['content-type'].to_s
+      fake_mail.transfer_encoding = self.transfer_encoding if self.transfer_encoding
+      fake_mail.content_disposition = self.content_disposition if self.content_disposition
+      if self.multipart?
+        self.each_part {|p| fake_mail.parts << p }
+      else
+        fake_mail.body = self.body
+      end
+
+      # store the calculated content, to be able to use parsing methods
+      fake_mail.write_back
+
+      fake_mail
+    end
+
     def gpg_key(fingerprint, secret = false)
       gpg = GPGME::Ctx.new
       gpg.get_key(fingerprint, secret)
