+ ## here's where we handle decoding mime attachments. unfortunately
+ ## but unsurprisingly, the world of mime attachments is a bit of a
+ ## mess. as an empiricist, i'm basing the following behavior on
+ ## observed mail rather than on interpretations of rfcs, so probably
+ ## this will have to be tweaked.
+ ##
+ ## the general behavior i want is: ignore content-disposition, at
+ ## least in so far as it suggests something being inline vs being an
+ ## attachment. (because really, that should be the recipient's
+ ## decision to make.) if a mime part is text/plain, OR if the user
+ ## decoding hook converts it, then decode it and display it
+ ## inline. for these decoded attachments, if it has associated
+ ## filename, then make it collapsable and individually saveable;
+ ## otherwise, treat it as regular body text.
+ ##
+ ## everything else is just an attachment and is not displayed
+ ## inline.
+ ##
+ ## so, in contrast to mutt, the user is not exposed to the workings
+ ## of the gruesome slaughterhouse and sausage factory that is a
+ ## mime-encoded message, but need only see the delicious end
+ ## product.
+
+ def multipart_signed_to_chunks m
+ if m.body.size != 2
+ Redwood::log "warning: multipart/signed with #{m.body.size} parts (expecting 2)"
+ return
+ end
+
+ payload, signature = m.body
+ if signature.multipart?
+ Redwood::log "warning: multipart/signed with payload multipart #{payload.multipart?} and signature multipart #{signature.multipart?}"
+ return
+ end
+
+ ## this probably will never happen
+ if payload.header.content_type == "application/pgp-signature"
+ Redwood::log "warning: multipart/signed with payload content type #{payload.header.content_type}"
+ return
+ end
+
+ if signature.header.content_type != "application/pgp-signature"
+ ## unknown signature type; just ignore.
+ #Redwood::log "warning: multipart/signed with signature content type #{signature.header.content_type}"
+ return
+ end
+
+ [CryptoManager.verify(payload, signature), message_to_chunks(payload)].flatten.compact
+ end
+
+ def multipart_encrypted_to_chunks m
+ if m.body.size != 2
+ Redwood::log "warning: multipart/encrypted with #{m.body.size} parts (expecting 2)"
+ return
+ end
+
+ control, payload = m.body
+ if control.multipart?
+ Redwood::log "warning: multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}"
+ return
+ end
+
+ if payload.header.content_type != "application/octet-stream"
+ Redwood::log "warning: multipart/encrypted with payload content type #{payload.header.content_type}"
+ return
+ end
+
+ if control.header.content_type != "application/pgp-encrypted"
+ Redwood::log "warning: multipart/encrypted with control content type #{signature.header.content_type}"
+ return
+ end
+
+ decryptedm, sig, notice = CryptoManager.decrypt payload
+ children = message_to_chunks(decryptedm, true) if decryptedm
+ [notice, sig, children].flatten.compact
+ end
+
+ def message_to_chunks m, encrypted=false, sibling_types=[]
+ if m.multipart?
+ chunks =
+ case m.header.content_type
+ when "multipart/signed"
+ multipart_signed_to_chunks m
+ when "multipart/encrypted"
+ multipart_encrypted_to_chunks m
+ end
+
+ unless chunks
+ sibling_types = m.body.map { |p| p.header.content_type }
+ chunks = m.body.map { |p| message_to_chunks p, encrypted, sibling_types }.flatten.compact
+ end
+
+ chunks
+ elsif m.header.content_type == "message/rfc822"
+ payload = RMail::Parser.read(m.body)
+ from = payload.header.from.first
+ from_person = from ? PersonManager.person_for(from.format) : nil
+ [Chunk::EnclosedMessage.new(from_person, payload.to_s)]
+ else
+ filename =
+ ## first, paw through the headers looking for a filename
+ if m.header["Content-Disposition"] && m.header["Content-Disposition"] =~ /filename="?(.*?[^\\])("|;|$)/
+ $1
+ elsif m.header["Content-Type"] && m.header["Content-Type"] =~ /name="?(.*?[^\\])("|;|$)/
+ $1
+
+ ## haven't found one, but it's a non-text message. fake
+ ## it.
+ ##
+ ## TODO: make this less lame.
+ elsif m.header["Content-Type"] && m.header["Content-Type"] !~ /^text\/plain/
+ extension =
+ case m.header["Content-Type"]
+ when /text\/html/: "html"
+ when /image\/(.*)/: $1
+ end
+
+ ["sup-attachment-#{Time.now.to_i}-#{rand 10000}", extension].join(".")
+ end
+
+ ## if there's a filename, we'll treat it as an attachment.
+ if filename
+ [Chunk::Attachment.new(m.header.content_type, filename, m, sibling_types)]
+
+ ## otherwise, it's body text