X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fmodes%2Fedit-message-mode.rb;h=8da316fe00593f8085adb167e0200b5a6c650d07;hb=aef0216d7f988ab87a3430c9a65210f0d55dfc64;hp=fa3479022603d2b67ea3a22fda8ed6adaca2fe68;hpb=e1750c0ec032b48d79947c7358a568d5aa52a8b5;p=sup diff --git a/lib/sup/modes/edit-message-mode.rb b/lib/sup/modes/edit-message-mode.rb index fa34790..8da316f 100644 --- a/lib/sup/modes/edit-message-mode.rb +++ b/lib/sup/modes/edit-message-mode.rb @@ -3,6 +3,10 @@ require 'socket' # just for gethostname! require 'pathname' require 'rmail' +# from jcode.rb, not included in ruby 1.9 +PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]' +RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n') + module Redwood class SendmailCommandFailed < StandardError; end @@ -12,7 +16,7 @@ class EditMessageMode < LineCursorMode FORCE_HEADERS = %w(From To Cc Bcc Subject) MULTI_HEADERS = %w(To Cc Bcc) - NON_EDITABLE_HEADERS = %w(Message-Id Date) + NON_EDITABLE_HEADERS = %w(Message-id Date) HookManager.register "signature", <= @header_lines.length + if lines > curpos + return + elsif (curpos - lines) >= @header_lines.length edit_message else edit_field @header_lines[curpos - lines] @@ -120,7 +133,7 @@ EOS @file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}" @file.puts format_headers(@header - NON_EDITABLE_HEADERS).first @file.puts - @file.puts @body + @file.puts @body.join("\n") @file.close editor = $config[:editor] || ENV['EDITOR'] || "/usr/bin/vi" @@ -143,12 +156,18 @@ EOS !edited? || BufferManager.ask_yes_or_no("Discard message?") end + def unsaved?; edited? end + def attach_file fn = BufferManager.ask_for_filename :attachment, "File name (enter for browser): " return unless fn - @attachments << RMail::Message.make_file_attachment(fn) - @attachment_names << fn - update + begin + @attachments << RMail::Message.make_file_attachment(fn) + @attachment_names << fn + update + rescue SystemCallError => e + BufferManager.flash "Can't read #{fn}: #{e.message}" + end end def delete_attachment @@ -162,16 +181,45 @@ EOS protected + def mime_encode string + string = [string].pack('M') # basic quoted-printable + string.gsub!(/=\n/,'') # .. remove trailing newline + string.gsub!(/_/,'=96') # .. encode underscores + string.gsub!(/\?/,'=3F') # .. encode question marks + string.gsub!(/ /,'_') # .. translate space to underscores + "=?utf-8?q?#{string}?=" + end + + def mime_encode_subject string + return string unless string.match(RE_UTF8) + mime_encode string + end + + RE_ADDRESS = /(.+)( <.*@.*>)/ + + # Encode "bælammet mitt " into + # "=?utf-8?q?b=C3=A6lammet_mitt?= + def mime_encode_address string + return string unless string.match(RE_UTF8) + string.sub(RE_ADDRESS) { |match| mime_encode($1) + $2 } + end + def move_cursor_left - return unless curpos < @selectors.length - @selectors[curpos].roll_left - buffer.mark_dirty + if curpos < @selectors.length + @selectors[curpos].roll_left + buffer.mark_dirty + else + col_left + end end def move_cursor_right - return unless curpos < @selectors.length - @selectors[curpos].roll_right - buffer.mark_dirty + if curpos < @selectors.length + @selectors[curpos].roll_right + buffer.mark_dirty + else + col_right + end end def add_selector s @@ -200,8 +248,8 @@ protected def parse_file fn File.open(fn) do |f| - header = MBox::read_header f - body = f.readlines + header = Source.parse_raw_email_header(f).inject({}) { |h, (k, v)| h[k.capitalize] = v; h } # lousy HACK + body = f.readlines.map { |l| l.chomp } header.delete_if { |k, v| NON_EDITABLE_HEADERS.member? k } header.each { |k, v| header[k] = parse_header k, v } @@ -245,7 +293,7 @@ protected if i == 0 header + " " + name else - (" " * (header.length + 1)) + name + (" " * (header.display_length + 1)) + name end + (i == things.length - 1 ? "" : ",") end end @@ -257,7 +305,6 @@ protected return false if $config[:confirm_no_attachments] && mentions_attachments? && @attachments.size == 0 && !BufferManager.ask_yes_or_no("You haven't added any attachments. Really send?")#" stupid ruby-mode return false if $config[:confirm_top_posting] && top_posting? && !BufferManager.ask_yes_or_no("You're top-posting. That makes you a bad person. Really send?") #" stupid ruby-mode - date = Time.now from_email = if @header["From"] =~ /?$/ $1 @@ -269,14 +316,16 @@ protected BufferManager.flash "Sending..." begin - IO.popen(acct.sendmail, "w") { |p| write_full_message_to p, date, false } + date = Time.now + m = build_message date + IO.popen(acct.sendmail, "w") { |p| p.puts m } raise SendmailCommandFailed, "Couldn't execute #{acct.sendmail}" unless $? == 0 - SentManager.write_sent_message(date, from_email) { |f| write_full_message_to f, date, true } + SentManager.write_sent_message(date, from_email) { |f| f.puts sanitize_body(m.to_s) } BufferManager.kill_buffer buffer BufferManager.flash "Message sent!" true - rescue SystemCallError, SendmailCommandFailed => e - Redwood::log "Problem sending mail: #{e.message}" + rescue SystemCallError, SendmailCommandFailed, CryptoManager::Error => e + warn "Problem sending mail: #{e.message}" BufferManager.flash "Problem sending mail: #{e.message}" false end @@ -288,40 +337,49 @@ protected BufferManager.flash "Saved for later editing." end - def write_full_message_to f, date=Time.now, escape=false + def build_message date m = RMail::Message.new + m.header["Content-Type"] = "text/plain; charset=#{$encoding}" + m.body = @body.join("\n") + m.body += sig_lines.join("\n") unless $config[:edit_signature] + ## body must end in a newline or GPG signatures will be WRONG! + m.body += "\n" unless m.body =~ /\n\Z/ + + ## there are attachments, so wrap body in an attachment of its own + unless @attachments.empty? + body_m = m + body_m.header["Content-Disposition"] = "inline" + m = RMail::Message.new + + m.add_part body_m + @attachments.each { |a| m.add_part a } + end + + ## do whatever crypto transformation is necessary + if @crypto_selector && @crypto_selector.val != :none + from_email = Person.from_address(@header["From"]).email + to_email = [@header["To"], @header["Cc"], @header["Bcc"]].flatten.compact.map { |p| Person.from_address(p).email } + + m = CryptoManager.send @crypto_selector.val, from_email, to_email, m + end + + ## finally, set the top-level headers @header.each do |k, v| next if v.nil? || v.empty? m.header[k] = case v when String - v + k.match(/subject/i) ? mime_encode_subject(v) : mime_encode_address(v) when Array - v.join ", " + v.map { |v| mime_encode_address v }.join ", " end end m.header["Date"] = date.rfc2822 m.header["Message-Id"] = @message_id m.header["User-Agent"] = "Sup/#{Redwood::VERSION}" - - if @attachments.empty? - m.header["Content-Type"] = "text/plain; charset=#{$encoding}" - m.body = @body.join - m.body = sanitize_body m.body if escape - m.body += sig_lines.join("\n") unless $config[:edit_signature] - else - body_m = RMail::Message.new - body_m.body = @body.join - body_m.body = sanitize_body body_m.body if escape - body_m.body += sig_lines.join("\n") unless $config[:edit_signature] - body_m.header["Content-Type"] = "text/plain; charset=#{$encoding}" - body_m.header["Content-Disposition"] = "inline" - - m.add_part body_m - @attachments.each { |a| m.add_part a } - end - f.puts m.to_s + m.header["Content-Transfer-Encoding"] = '8bit' + m end ## TODO: remove this. redundant with write_full_message_to. @@ -345,7 +403,7 @@ EOS end f.puts - f.puts sanitize_body(@body.join) + f.puts sanitize_body(@body.join("\n")) f.puts sig_lines if full unless $config[:edit_signature] end @@ -358,12 +416,11 @@ protected if text @header[field] = parse_header field, text update - field end else - default = - case field + default = case field when *MULTI_HEADERS + @header[field] ||= [] @header[field].join(", ") else @header[field] @@ -371,10 +428,9 @@ protected contacts = BufferManager.ask_for_contacts :people, "#{field}: ", default if contacts - text = contacts.map { |s| s.longname }.join(", ") + text = contacts.map { |s| s.full_address }.join(", ") @header[field] = parse_header field, text update - field end end end @@ -390,15 +446,17 @@ private end def top_posting? - @body.join =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/ + @body.join("\n") =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/ end def sig_lines - p = PersonManager.person_for(@header["From"]) + p = Person.from_address(@header["From"]) from_email = p && p.email ## first run the hook hook_sig = HookManager.run "signature", :header => @header, :from_email => from_email + + return [] if hook_sig == :none return ["", "-- "] + hook_sig.split("\n") if hook_sig ## no hook, do default signature generation based on config.yaml