]> git.cworth.org Git - sup/blob - lib/sup/message-chunks.rb
fix write_to_disk error in attachment hook handling
[sup] / lib / sup / message-chunks.rb
1 ## Here we define all the "chunks" that a message is parsed
2 ## into. Chunks are used by ThreadViewMode to render a message. Chunks
3 ## are used for both MIME stuff like attachments, for Sup's parsing of
4 ## the message body into text, quote, and signature regions, and for
5 ## notices like "this message was decrypted" or "this message contains
6 ## a valid signature"---basically, anything we want to differentiate
7 ## at display time.
8
9 ## A chunk can be inlineable, expandable, or viewable. If it's
10 ## inlineable, #color and #lines are called and the output is treated
11 ## as part of the message text. This is how Text and one-line Quotes
12 ## and Signatures work.
13
14 ## If it's expandable, #patina_color and #patina_text are called to
15 ## generate a "patina" (a one-line widget, basically), and the user
16 ## can press enter to toggle the display of the chunk content, which
17 ## is generated from #color and #lines). This is how Quote, Signature,
18 ## and most widgets work.
19
20 ## If it's viewable, a patina is displayed using #patina_color and
21 ## #patina_text, but no toggling is allowed. Instead, if #view! is
22 ## defined, pressing enter on the widget calls view! and (if that
23 ## returns false) #to_s. Otherwise enter does nothing. This is how
24 ## non-inlineable attachments work.
25
26 module Redwood
27 module Chunk
28   class Attachment
29     HookManager.register "mime-decode", <<EOS
30 Executes when decoding a MIME attachment.
31 Variables:
32    content_type: the content-type of the message
33        filename: the filename of the attachment as saved to disk (generated
34                  on the fly, so don't call more than once)
35   sibling_types: if this attachment is part of a multipart MIME attachment,
36                  an array of content-types for all attachments. Otherwise,
37                  the empty array.
38 Return value:
39   The decoded text of the attachment, or nil if not decoded.
40 EOS
41 #' stupid ruby-mode
42
43     ## raw_content is the post-MIME-decode content. this is used for
44     ## saving the attachment to disk.
45     attr_reader :content_type, :filename, :lines, :raw_content
46
47     def initialize content_type, filename, encoded_content, sibling_types
48       @content_type = content_type
49       @filename = filename
50       @raw_content = encoded_content.decode
51
52       @lines =
53         case @content_type
54         when /^text\/plain\b/
55           Message.convert_from(@raw_content, encoded_content.charset).split("\n")
56         else
57           text = HookManager.run "mime-decode", :content_type => content_type,
58                                  :filename => lambda { write_to_disk },
59                                  :sibling_types => sibling_types
60           text.split("\n") if text
61         end
62     end
63
64     def color; :none end
65     def patina_color; :attachment_color end
66     def patina_text
67       if expandable?
68         "Attachment: #{filename} (#{lines.length} lines)"
69       else
70         "Attachment: #{filename} (#{content_type})"
71       end
72     end
73
74     ## an attachment is exapndable if we've managed to decode it into
75     ## something we can display inline. otherwise, it's viewable.
76     def inlineable?; false end
77     def expandable?; !viewable? end
78     def viewable?; @lines.nil? end
79     def view!
80       path = write_to_disk
81       system "/usr/bin/run-mailcap --action=view #{@content_type}:#{path} >& /dev/null"
82       $? == 0
83     end
84
85     def write_to_disk
86       file = Tempfile.new "redwood.attachment"
87       file.print @raw_content
88       file.close
89       file.path
90     end
91
92     ## used when viewing the attachment as text
93     def to_s
94       @lines || @raw_content
95     end
96   end
97
98   class Text
99     WRAP_LEN = 80 # wrap at this width
100
101     attr_reader :lines
102     def initialize lines
103       @lines = lines.map { |l| l.chomp.wrap WRAP_LEN }.flatten # wrap
104
105       ## trim off all empty lines except one
106       lines.pop while lines.last =~ /^\s*$/ 
107     end
108
109     def color; :none end
110     def inlineable?; true end
111   end
112
113   class Quote
114     attr_reader :lines
115     def initialize lines
116       @lines = lines
117     end
118     
119     def inlineable?; @lines.length == 1 end
120     def expandable?; !inlineable? end
121     def patina_color; :quote_patina_color end
122     def patina_text; "(#{lines.length} quoted lines)" end
123     def color; :quote_color end
124   end
125
126   class Signature
127     attr_reader :lines
128     def initialize lines
129       @lines = lines
130     end
131
132     def inlineable?; @lines.length == 1 end
133     def expandable?; !inlineable? end
134     def patina_color; :sig_patina_color end
135     def patina_text; "(#{lines.length}-line signature)" end
136     def color; :sig_color end
137   end
138
139   class EnclosedMessageNotice
140     attr_reader :from
141     def initialize from
142       @from = from
143     end
144
145     def to_s
146       "Begin enclosed message from #{@from.longname}"
147     end
148   end
149
150   class CryptoNotice
151     attr_reader :lines, :status, :patina_text
152
153     def initialize status, description, lines=[]
154       @status = status
155       @patina_text = description
156       @lines = lines
157     end
158
159     def patina_color
160       case status
161       when :valid: :cryptosig_valid_color
162       when :invalid: :cryptosig_invalid_color
163       else :cryptosig_unknown_color
164       end
165     end
166     def color; patina_color end
167
168     def inlineable?; false end
169     def expandable?; !@lines.empty? end
170     def viewable?; false end
171   end
172 end
173 end