]> git.cworth.org Git - sup/blob - lib/sup/message-chunks.rb
Fix for some warnings on faked addresses and ids
[sup] / lib / sup / message-chunks.rb
1 require 'tempfile'
2
3 ## Here we define all the "chunks" that a message is parsed
4 ## into. Chunks are used by ThreadViewMode to render a message. Chunks
5 ## are used for both MIME stuff like attachments, for Sup's parsing of
6 ## the message body into text, quote, and signature regions, and for
7 ## notices like "this message was decrypted" or "this message contains
8 ## a valid signature"---basically, anything we want to differentiate
9 ## at display time.
10 ##
11 ## A chunk can be inlineable, expandable, or viewable. If it's
12 ## inlineable, #color and #lines are called and the output is treated
13 ## as part of the message text. This is how Text and one-line Quotes
14 ## and Signatures work.
15 ##
16 ## If it's not inlineable but is expandable, #patina_color and
17 ## #patina_text are called to generate a "patina" (a one-line widget,
18 ## basically), and the user can press enter to toggle the display of
19 ## the chunk content, which is generated from #color and #lines as
20 ## above. This is how Quote, Signature, and most widgets
21 ## work. Exandable chunks can additionally define #initial_state to be
22 ## :open if they want to start expanded (default is to start collapsed).
23 ##
24 ## If it's not expandable but is viewable, a patina is displayed using
25 ## #patina_color and #patina_text, but no toggling is allowed. Instead,
26 ## if #view! is defined, pressing enter on the widget calls view! and
27 ## (if that returns false) #to_s. Otherwise, enter does nothing. This
28 ##  is how non-inlineable attachments work.
29 ##
30 ## Independent of all that, a chunk can be quotable, in which case it's
31 ## included as quoted text during a reply. Text, Quotes, and mime-parsed
32 ## attachments are quotable; Signatures are not.
33
34 ## monkey-patch time: make temp files have the right extension
35 class Tempfile
36   def make_tmpname basename, n
37     sprintf '%d-%d-%s', $$, n, basename
38   end
39 end
40
41
42 module Redwood
43 module Chunk
44   class Attachment
45     HookManager.register "mime-decode", <<EOS
46 Executes when decoding a MIME attachment.
47 Variables:
48    content_type: the content-type of the message
49        filename: the filename of the attachment as saved to disk
50   sibling_types: if this attachment is part of a multipart MIME attachment,
51                  an array of content-types for all attachments. Otherwise,
52                  the empty array.
53 Return value:
54   The decoded text of the attachment, or nil if not decoded.
55 EOS
56 #' stupid ruby-mode
57
58     ## raw_content is the post-MIME-decode content. this is used for
59     ## saving the attachment to disk.
60     attr_reader :content_type, :filename, :lines, :raw_content
61     bool_reader :quotable
62
63     def initialize content_type, filename, encoded_content, sibling_types
64       @content_type = content_type
65       @filename = filename
66       @quotable = false # changed to true if we can parse it through the
67                         # mime-decode hook, or if it's plain text
68       @raw_content =
69         if encoded_content.body
70           encoded_content.decode
71         else
72           "For some bizarre reason, RubyMail was unable to parse this attachment.\n"
73         end
74
75       text =
76         case @content_type
77         when /^text\/plain\b/
78           Message.convert_from @raw_content, encoded_content.charset
79         else
80           HookManager.run "mime-decode", :content_type => content_type,
81                           :filename => lambda { write_to_disk },
82                           :sibling_types => sibling_types
83         end
84
85       @lines = nil
86       if text
87         @lines = text.gsub("\r\n", "\n").gsub(/\t/, "        ").gsub(/\r/, "").split("\n")
88         @quotable = true
89       end
90     end
91
92     def color; :none end
93     def patina_color; :attachment_color end
94     def patina_text
95       if expandable?
96         "Attachment: #{filename} (#{lines.length} lines)"
97       else
98         "Attachment: #{filename} (#{content_type})"
99       end
100     end
101
102     ## an attachment is exapndable if we've managed to decode it into
103     ## something we can display inline. otherwise, it's viewable.
104     def inlineable?; false end
105     def expandable?; !viewable? end
106     def initial_state; :open end
107     def viewable?; @lines.nil? end
108     def view!
109       path = write_to_disk
110       system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} > /dev/null 2> /dev/null"
111       $? == 0
112     end
113
114     def write_to_disk
115       file = Tempfile.new(@filename || "sup-attachment")
116       file.print @raw_content
117       file.close
118       file.path
119     end
120
121     ## used when viewing the attachment as text
122     def to_s
123       @lines || @raw_content
124     end
125   end
126
127   class Text
128     WRAP_LEN = 80 # wrap at this width
129
130     attr_reader :lines
131     def initialize lines
132       @lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
133
134       ## trim off all empty lines except one
135       @lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
136     end
137
138     def inlineable?; true end
139     def quotable?; true end
140     def expandable?; false end
141     def viewable?; false end
142     def color; :none end
143   end
144
145   class Quote
146     attr_reader :lines
147     def initialize lines
148       @lines = lines
149     end
150     
151     def inlineable?; @lines.length == 1 end
152     def quotable?; true end
153     def expandable?; !inlineable? end
154     def viewable?; false end
155
156     def patina_color; :quote_patina_color end
157     def patina_text; "(#{lines.length} quoted lines)" end
158     def color; :quote_color end
159   end
160
161   class Signature
162     attr_reader :lines
163     def initialize lines
164       @lines = lines
165     end
166
167     def inlineable?; @lines.length == 1 end
168     def quotable?; false end
169     def expandable?; !inlineable? end
170     def viewable?; false end
171
172     def patina_color; :sig_patina_color end
173     def patina_text; "(#{lines.length}-line signature)" end
174     def color; :sig_color end
175   end
176
177   class EnclosedMessage
178     attr_reader :lines
179     def initialize from, body
180       @from = from
181       @lines = body.split "\n"
182     end
183
184     def from
185       @from ? @from.longname : "unknown sender"
186     end
187
188     def inlineable?; false end
189     def quotable?; false end
190     def expandable?; true end
191     def initial_state; :open end
192     def viewable?; false end
193
194     def patina_color; :generic_notice_patina_color end
195     def patina_text; "Begin enclosed message from #{from} (#{@lines.length} lines)" end
196
197     def color; :quote_color end
198   end
199
200   class CryptoNotice
201     attr_reader :lines, :status, :patina_text
202
203     def initialize status, description, lines=[]
204       @status = status
205       @patina_text = description
206       @lines = lines
207     end
208
209     def patina_color
210       case status
211       when :valid: :cryptosig_valid_color
212       when :invalid: :cryptosig_invalid_color
213       else :cryptosig_unknown_color
214       end
215     end
216     def color; patina_color end
217
218     def inlineable?; false end
219     def quotable?; false end
220     def expandable?; !@lines.empty? end
221     def viewable?; false end
222   end
223 end
224 end