hermeneutics 1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +29 -0
- data/bin/hermesmail +262 -0
- data/etc/exim.conf +34 -0
- data/lib/hermeneutics/addrs.rb +687 -0
- data/lib/hermeneutics/boxes.rb +321 -0
- data/lib/hermeneutics/cgi.rb +253 -0
- data/lib/hermeneutics/cli/pop.rb +102 -0
- data/lib/hermeneutics/color.rb +275 -0
- data/lib/hermeneutics/contents.rb +351 -0
- data/lib/hermeneutics/css.rb +261 -0
- data/lib/hermeneutics/escape.rb +826 -0
- data/lib/hermeneutics/html.rb +462 -0
- data/lib/hermeneutics/mail.rb +105 -0
- data/lib/hermeneutics/message.rb +626 -0
- data/lib/hermeneutics/tags.rb +317 -0
- data/lib/hermeneutics/transports.rb +230 -0
- data/lib/hermeneutics/types.rb +137 -0
- data/lib/hermeneutics/version.rb +32 -0
- metadata +83 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/mail.rb -- A mail
|
3
|
+
#
|
4
|
+
|
5
|
+
require "hermeneutics/message"
|
6
|
+
|
7
|
+
module Hermeneutics
|
8
|
+
|
9
|
+
class Mail < Message
|
10
|
+
|
11
|
+
# :stopdoc:
|
12
|
+
class FromReader
|
13
|
+
class <<self
|
14
|
+
def open file
|
15
|
+
i = new file
|
16
|
+
yield i
|
17
|
+
end
|
18
|
+
private :new
|
19
|
+
end
|
20
|
+
attr_reader :from
|
21
|
+
def initialize file
|
22
|
+
@file = file
|
23
|
+
@file.eat_lines { |l|
|
24
|
+
l =~ /^From .*/ rescue nil
|
25
|
+
if $& then
|
26
|
+
@from = l
|
27
|
+
@from.chomp!
|
28
|
+
else
|
29
|
+
@first = l
|
30
|
+
end
|
31
|
+
break
|
32
|
+
}
|
33
|
+
end
|
34
|
+
def eat_lines &block
|
35
|
+
if @first then
|
36
|
+
yield @first
|
37
|
+
@first = nil
|
38
|
+
end
|
39
|
+
@file.eat_lines &block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# :startdoc:
|
43
|
+
|
44
|
+
class <<self
|
45
|
+
|
46
|
+
def parse input
|
47
|
+
FromReader.open input do |fr|
|
48
|
+
parse_hb fr do |h,b|
|
49
|
+
new fr.from, h, b
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create
|
55
|
+
new nil, nil, nil
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize from, headers, body
|
61
|
+
super headers, body
|
62
|
+
@from = from
|
63
|
+
end
|
64
|
+
|
65
|
+
# String representation with "From " line.
|
66
|
+
# Mails reside in mbox files etc. and so have to end in a newline.
|
67
|
+
def to_s
|
68
|
+
set_unix_from
|
69
|
+
r = ""
|
70
|
+
r << @from << $/ << super
|
71
|
+
r.ends_with? $/ or r << $/
|
72
|
+
r
|
73
|
+
end
|
74
|
+
|
75
|
+
def receivers
|
76
|
+
addresses_of :to, :cc, :bcc
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def addresses_of *args
|
82
|
+
l = args.map { |f| @headers.field f }
|
83
|
+
AddrList.new *l
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_unix_from
|
87
|
+
return if @from
|
88
|
+
# Common MTA's will issue a proper "From" line; some MDA's
|
89
|
+
# won't. Then, build it using the "From:" header.
|
90
|
+
addr = nil
|
91
|
+
l = addresses_of :from, :return_path
|
92
|
+
# Prefer the non-local version if present.
|
93
|
+
l.each { |a|
|
94
|
+
if not addr or addr !~ /@/ then
|
95
|
+
addr = a
|
96
|
+
end
|
97
|
+
}
|
98
|
+
addr or raise ArgumentError, "No From: field present."
|
99
|
+
@from = "From #{addr.plain} #{Time.now.gmtime.asctime}"
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,626 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/message.rb -- a message as in mails or in HTTP communication
|
3
|
+
#
|
4
|
+
|
5
|
+
require "hermeneutics/types"
|
6
|
+
require "hermeneutics/contents"
|
7
|
+
require "hermeneutics/addrs"
|
8
|
+
|
9
|
+
|
10
|
+
class NilClass
|
11
|
+
def eat_lines
|
12
|
+
end
|
13
|
+
def rewind
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class String
|
17
|
+
def eat_lines
|
18
|
+
@pos ||= 0
|
19
|
+
while @pos < length do
|
20
|
+
p = index /.*\n?/, @pos
|
21
|
+
l = $&.length
|
22
|
+
begin
|
23
|
+
yield self[ @pos, l]
|
24
|
+
ensure
|
25
|
+
@pos += l
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
def rewind
|
30
|
+
@pos = 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class Array
|
34
|
+
def eat_lines &block
|
35
|
+
@pos ||= 0
|
36
|
+
while @pos < length do
|
37
|
+
begin
|
38
|
+
self[ @pos].eat_lines &block
|
39
|
+
ensure
|
40
|
+
@pos += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def rewind
|
45
|
+
each { |e| e.rewind }
|
46
|
+
@pos = 0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
class IO
|
50
|
+
def eat_lines &block
|
51
|
+
each_line &block
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
def to_s
|
55
|
+
rewind
|
56
|
+
read
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
module Hermeneutics
|
62
|
+
|
63
|
+
class Multipart < Mime
|
64
|
+
|
65
|
+
MIME = /^multipart\//
|
66
|
+
|
67
|
+
class IllegalBoundary < StandardError ; end
|
68
|
+
class ParseError < StandardError ; end
|
69
|
+
|
70
|
+
# :stopdoc:
|
71
|
+
class PartFile
|
72
|
+
class <<self
|
73
|
+
def open file, sep
|
74
|
+
i = new file, sep
|
75
|
+
yield i
|
76
|
+
end
|
77
|
+
private :new
|
78
|
+
end
|
79
|
+
public
|
80
|
+
attr_reader :prolog, :epilog
|
81
|
+
def initialize file, sep
|
82
|
+
@file = file
|
83
|
+
@sep = /^--#{Regexp.quote sep}(--)?/
|
84
|
+
read_part
|
85
|
+
@prolog = norm_nl @a
|
86
|
+
end
|
87
|
+
def next_part
|
88
|
+
return if @epilog
|
89
|
+
read_part
|
90
|
+
@a.first.chomp!
|
91
|
+
true
|
92
|
+
end
|
93
|
+
def eat_lines
|
94
|
+
yield @a.pop while @a.any?
|
95
|
+
end
|
96
|
+
private
|
97
|
+
def read_part
|
98
|
+
@a = []
|
99
|
+
e = nil
|
100
|
+
@file.eat_lines { |l|
|
101
|
+
l =~ @sep rescue nil
|
102
|
+
if $& then
|
103
|
+
e = [ $'] if $1
|
104
|
+
@a.reverse!
|
105
|
+
return
|
106
|
+
end
|
107
|
+
@a.push l
|
108
|
+
}
|
109
|
+
raise ParseError, "Missing separator #@sep"
|
110
|
+
ensure
|
111
|
+
if e then
|
112
|
+
@file.eat_lines { |l| e.push l }
|
113
|
+
e.reverse!
|
114
|
+
@epilog = norm_nl e
|
115
|
+
end
|
116
|
+
end
|
117
|
+
def norm_nl a
|
118
|
+
r = ""
|
119
|
+
while a.any? do
|
120
|
+
l = a.pop
|
121
|
+
l.chomp! and l << $/
|
122
|
+
r << l
|
123
|
+
end
|
124
|
+
r
|
125
|
+
end
|
126
|
+
end
|
127
|
+
# :startdoc:
|
128
|
+
|
129
|
+
public
|
130
|
+
|
131
|
+
class <<self
|
132
|
+
|
133
|
+
def parse input, parameters
|
134
|
+
b = parameters[ :boundary]
|
135
|
+
b or raise ParseError, "Missing boundary parameter."
|
136
|
+
PartFile.open input, b do |partfile|
|
137
|
+
list = []
|
138
|
+
while partfile.next_part do
|
139
|
+
m = Message.parse partfile
|
140
|
+
list.push m
|
141
|
+
end
|
142
|
+
new b, partfile.prolog, list, partfile.epilog
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
BOUNDARY_CHARS_STD = [ [*"0".."9"], [*"A".."Z"], [*"a".."z"]].join
|
149
|
+
BOUNDARY_CHARS = BOUNDARY_CHARS_STD + "+_./:=-" # "'()+_,-./:=?"
|
150
|
+
|
151
|
+
attr_reader :boundary, :prolog, :list, :epilog
|
152
|
+
|
153
|
+
def initialize boundary, prolog, list, epilog
|
154
|
+
@boundary = boundary.notempty?
|
155
|
+
@prolog, @list, @epilog = prolog, list, epilog
|
156
|
+
end
|
157
|
+
|
158
|
+
def boundary!
|
159
|
+
b = BOUNDARY_CHARS_STD.length
|
160
|
+
r = Time.now.strftime "%Y%m%d%H%M%S."
|
161
|
+
16.times { r << BOUNDARY_CHARS_STD[ (rand b)].chr }
|
162
|
+
@boundary = r
|
163
|
+
end
|
164
|
+
|
165
|
+
def inspect
|
166
|
+
r = ""
|
167
|
+
r << "#<#{cls}:"
|
168
|
+
r << "0x%x" % (object_id<<1)
|
169
|
+
r << " n=#{@list.length}"
|
170
|
+
r << ">"
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
@boundary or raise IllegalBoundary
|
175
|
+
r = ""
|
176
|
+
splitter = "--#@boundary"
|
177
|
+
re = /#{Regexp.quote @boundary}/
|
178
|
+
@prolog =~ re and raise IllegalBoundary
|
179
|
+
r << @prolog
|
180
|
+
@list.each { |p|
|
181
|
+
s = p.to_s
|
182
|
+
s =~ re rescue nil
|
183
|
+
$& and raise IllegalBoundary
|
184
|
+
r << splitter << $/ << s << $/
|
185
|
+
}
|
186
|
+
@epilog =~ re and raise IllegalBoundary
|
187
|
+
r << splitter << "--" << @epilog
|
188
|
+
rescue IllegalBoundary
|
189
|
+
boundary!
|
190
|
+
retry
|
191
|
+
end
|
192
|
+
|
193
|
+
def [] num
|
194
|
+
@list[ num]
|
195
|
+
end
|
196
|
+
|
197
|
+
def each &block
|
198
|
+
@list.each &block
|
199
|
+
end
|
200
|
+
|
201
|
+
def length ; @list.length ; end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
class Message < Mime
|
207
|
+
|
208
|
+
MIME = "message/rfc822"
|
209
|
+
|
210
|
+
class ParseError < StandardError ; end
|
211
|
+
|
212
|
+
class Headers
|
213
|
+
|
214
|
+
class Entry
|
215
|
+
LINE_LENGTH = 78
|
216
|
+
INDENT = " "
|
217
|
+
class <<self
|
218
|
+
private :new
|
219
|
+
def parse str
|
220
|
+
str =~ /:\s*/ or
|
221
|
+
raise ParseError, "Header line without a colon: #{str}"
|
222
|
+
data = $'
|
223
|
+
new $`, $&, data
|
224
|
+
end
|
225
|
+
def create name, *contents
|
226
|
+
name = build_name name
|
227
|
+
i = new name.to_s, ": ", nil
|
228
|
+
i.set *contents
|
229
|
+
end
|
230
|
+
def build_name name
|
231
|
+
n = name.to_s
|
232
|
+
unless n.equal? name then
|
233
|
+
n.gsub! /_/, "-"
|
234
|
+
n.gsub! /\b[a-z]/ do |c| c.upcase end
|
235
|
+
end
|
236
|
+
n
|
237
|
+
end
|
238
|
+
end
|
239
|
+
attr_reader :name, :sep, :data
|
240
|
+
def initialize name, sep, data
|
241
|
+
@name, @sep, @data, @contents = name, sep, data
|
242
|
+
end
|
243
|
+
def to_s
|
244
|
+
"#@name#@sep#@data"
|
245
|
+
end
|
246
|
+
def contents type
|
247
|
+
if type then
|
248
|
+
unless @contents and @contents.is_a? type then
|
249
|
+
@contents = type.parse @data
|
250
|
+
end
|
251
|
+
@contents
|
252
|
+
else
|
253
|
+
@data
|
254
|
+
end
|
255
|
+
end
|
256
|
+
def name_is? name
|
257
|
+
(@name.casecmp name).zero?
|
258
|
+
end
|
259
|
+
def set *contents
|
260
|
+
type, *args = *contents
|
261
|
+
d = case type
|
262
|
+
when Class then
|
263
|
+
@contents = type.new *args
|
264
|
+
case (e = @contents.encode)
|
265
|
+
when Array then e
|
266
|
+
when nil then []
|
267
|
+
else [ e]
|
268
|
+
end
|
269
|
+
when nil then
|
270
|
+
@contents = nil
|
271
|
+
split_args args
|
272
|
+
else
|
273
|
+
@contents = nil
|
274
|
+
split_args contents
|
275
|
+
end
|
276
|
+
@data = mk_lines d
|
277
|
+
self
|
278
|
+
end
|
279
|
+
def reset type
|
280
|
+
if type then
|
281
|
+
c = contents type
|
282
|
+
@data = mk_lines c.encode if c
|
283
|
+
end
|
284
|
+
self
|
285
|
+
end
|
286
|
+
private
|
287
|
+
def mk_lines strs
|
288
|
+
m = LINE_LENGTH - @name.length - @sep.length
|
289
|
+
data = ""
|
290
|
+
strs.each { |e|
|
291
|
+
unless data.empty? then
|
292
|
+
if 1 + e.length <= m then
|
293
|
+
data << " "
|
294
|
+
m -= 1
|
295
|
+
else
|
296
|
+
data << $/ << INDENT
|
297
|
+
m = LINE_LENGTH - INDENT.length
|
298
|
+
end
|
299
|
+
end
|
300
|
+
data << e
|
301
|
+
m -= e.length
|
302
|
+
}
|
303
|
+
data
|
304
|
+
end
|
305
|
+
def split_args ary
|
306
|
+
r = []
|
307
|
+
ary.each { |a|
|
308
|
+
r.concat case a
|
309
|
+
when Array then split_args a
|
310
|
+
else a.to_s.split
|
311
|
+
end
|
312
|
+
}
|
313
|
+
r
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
@types = {
|
318
|
+
"Content-Type" => ContentType,
|
319
|
+
"To" => AddrList,
|
320
|
+
"Cc" => AddrList,
|
321
|
+
"Bcc" => AddrList,
|
322
|
+
"From" => AddrList,
|
323
|
+
"Subject" => PlainText,
|
324
|
+
"Content-Disposition" => Contents,
|
325
|
+
"Sender" => AddrList,
|
326
|
+
"Content-Transfer-Encoding" => Contents,
|
327
|
+
"User-Agent" => PlainText,
|
328
|
+
"Date" => Timestamp,
|
329
|
+
"Delivery-Date" => Timestamp,
|
330
|
+
"Message-ID" => Id,
|
331
|
+
"List-ID" => Id,
|
332
|
+
"References" => IdList,
|
333
|
+
"In-Reply-To" => Id,
|
334
|
+
"Reply-To" => AddrList,
|
335
|
+
"Content-Length" => Count,
|
336
|
+
"Lines" => Count,
|
337
|
+
"Return-Path" => AddrList,
|
338
|
+
"Envelope-To" => AddrList,
|
339
|
+
"DKIM-Signature" => Dictionary,
|
340
|
+
"DomainKey-Signature" => Dictionary,
|
341
|
+
|
342
|
+
"Set-Cookie" => Dictionary,
|
343
|
+
"Cookie" => Dictionary,
|
344
|
+
}
|
345
|
+
|
346
|
+
class <<self
|
347
|
+
def set_field_type name, type
|
348
|
+
e = Entry.create name
|
349
|
+
if type then
|
350
|
+
@types[ e.name] = type
|
351
|
+
else
|
352
|
+
@types.delete e.name
|
353
|
+
end
|
354
|
+
end
|
355
|
+
def find_type entry
|
356
|
+
@types.each { |k,v|
|
357
|
+
return v if entry.name_is? k
|
358
|
+
}
|
359
|
+
nil
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
class <<self
|
364
|
+
private :new
|
365
|
+
def parse *list
|
366
|
+
list.flatten!
|
367
|
+
list.map! { |h| Entry.parse h }
|
368
|
+
new list
|
369
|
+
end
|
370
|
+
def create
|
371
|
+
new []
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def initialize list
|
376
|
+
@list = list
|
377
|
+
end
|
378
|
+
|
379
|
+
def length
|
380
|
+
@list.length
|
381
|
+
end
|
382
|
+
alias size length
|
383
|
+
|
384
|
+
def to_s
|
385
|
+
@list.map { |e| "#{e}#$/" }.join
|
386
|
+
end
|
387
|
+
|
388
|
+
def each
|
389
|
+
@list.each { |e|
|
390
|
+
type = Headers.find_type e
|
391
|
+
c = e.contents type
|
392
|
+
yield e.name, c
|
393
|
+
}
|
394
|
+
self
|
395
|
+
end
|
396
|
+
|
397
|
+
def raw name
|
398
|
+
e = find_entry name
|
399
|
+
e.data if e
|
400
|
+
end
|
401
|
+
|
402
|
+
def field name, type = nil
|
403
|
+
e = find_entry name
|
404
|
+
if e then
|
405
|
+
type ||= Headers.find_type e
|
406
|
+
e.contents type
|
407
|
+
end
|
408
|
+
end
|
409
|
+
def [] name, type = nil
|
410
|
+
case name
|
411
|
+
when Integer then raise "Not a field name: #{name}"
|
412
|
+
else field name, type
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def method_missing sym, *args
|
417
|
+
if args.empty? and not sym =~ /[!?=]\z/ then
|
418
|
+
field sym, *args
|
419
|
+
else
|
420
|
+
super
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def add name, *contents
|
425
|
+
e = build_entry name, *contents
|
426
|
+
add_entry e
|
427
|
+
self
|
428
|
+
end
|
429
|
+
|
430
|
+
def replace name, *contents
|
431
|
+
e = build_entry name, *contents
|
432
|
+
remove_entries e
|
433
|
+
add_entry e
|
434
|
+
self
|
435
|
+
end
|
436
|
+
|
437
|
+
def remove name
|
438
|
+
e = Entry.create name
|
439
|
+
remove_entries e
|
440
|
+
self
|
441
|
+
end
|
442
|
+
alias delete remove
|
443
|
+
|
444
|
+
def recode name, type = nil
|
445
|
+
n = Entry.build_name name
|
446
|
+
@list.each { |e|
|
447
|
+
next unless e.name_is? n
|
448
|
+
type ||= Headers.find_type e
|
449
|
+
e.reset type
|
450
|
+
}
|
451
|
+
self
|
452
|
+
end
|
453
|
+
|
454
|
+
def inspect
|
455
|
+
r = ""
|
456
|
+
r << "#<#{cls}:"
|
457
|
+
r << "0x%x" % (object_id<<1)
|
458
|
+
r << " (#{length})"
|
459
|
+
r << ">"
|
460
|
+
end
|
461
|
+
|
462
|
+
private
|
463
|
+
|
464
|
+
def find_entry name
|
465
|
+
e = Entry.build_name name
|
466
|
+
@list.find { |x| x.name_is? e }
|
467
|
+
end
|
468
|
+
|
469
|
+
def build_entry name, *contents
|
470
|
+
e = Entry.create name
|
471
|
+
type, = *contents
|
472
|
+
case type
|
473
|
+
when Class then
|
474
|
+
e.set *contents
|
475
|
+
else
|
476
|
+
type = Headers.find_type e
|
477
|
+
e.set type, *contents
|
478
|
+
end
|
479
|
+
e
|
480
|
+
end
|
481
|
+
|
482
|
+
def add_entry entry
|
483
|
+
@list.unshift entry
|
484
|
+
end
|
485
|
+
|
486
|
+
def remove_entries entry
|
487
|
+
@list.reject! { |e| e.name_is? entry.name }
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
491
|
+
|
492
|
+
class <<self
|
493
|
+
|
494
|
+
private :new
|
495
|
+
|
496
|
+
def parse input, parameters = nil
|
497
|
+
parse_hb input do |h,b|
|
498
|
+
new h, b
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def create
|
503
|
+
new nil, nil
|
504
|
+
end
|
505
|
+
|
506
|
+
private
|
507
|
+
|
508
|
+
def parse_hb input
|
509
|
+
h = parse_headers input
|
510
|
+
c = h.content_type
|
511
|
+
b = c.parse_mime input if c
|
512
|
+
unless b then
|
513
|
+
b = ""
|
514
|
+
input.eat_lines { |l| b << l }
|
515
|
+
b
|
516
|
+
end
|
517
|
+
yield h, b
|
518
|
+
end
|
519
|
+
|
520
|
+
def parse_headers input
|
521
|
+
h = []
|
522
|
+
input.eat_lines { |l|
|
523
|
+
l.chomp!
|
524
|
+
case l
|
525
|
+
when /^$/ then
|
526
|
+
break
|
527
|
+
when /^\s+/ then
|
528
|
+
h.last or
|
529
|
+
raise ParseError, "First line may not be a continuation."
|
530
|
+
h.last << $/ << l
|
531
|
+
else
|
532
|
+
h.push l
|
533
|
+
end
|
534
|
+
}
|
535
|
+
Headers.parse h
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
attr_reader :headers, :body
|
541
|
+
|
542
|
+
def initialize headers, body
|
543
|
+
@headers, @body = headers, body
|
544
|
+
@headers ||= Headers.create
|
545
|
+
end
|
546
|
+
|
547
|
+
def method_missing sym, *args, &block
|
548
|
+
case sym
|
549
|
+
when /h_(.*)/, /header_(.*)/ then
|
550
|
+
@headers.field $1.to_sym, *args
|
551
|
+
else
|
552
|
+
@headers.field sym, *args or super
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def [] name, type = nil
|
557
|
+
@headers[ name, type]
|
558
|
+
end
|
559
|
+
|
560
|
+
def is_multipart?
|
561
|
+
Multipart === @body
|
562
|
+
end
|
563
|
+
alias mp? is_multipart?
|
564
|
+
|
565
|
+
def inspect
|
566
|
+
r = ""
|
567
|
+
r << "#<#{cls}:"
|
568
|
+
r << "0x%x" % (object_id<<1)
|
569
|
+
r << " headers:#{@headers.length}"
|
570
|
+
r << " multipart" if is_multipart?
|
571
|
+
r << ">"
|
572
|
+
end
|
573
|
+
|
574
|
+
def to_s
|
575
|
+
r = ""
|
576
|
+
if is_multipart? then
|
577
|
+
c = @headers.field :content_type
|
578
|
+
u = @body.boundary
|
579
|
+
if c[ :boundary] != u then
|
580
|
+
@headers.replace :content_type, c.fulltype, :boundary => u
|
581
|
+
end
|
582
|
+
end
|
583
|
+
r << @headers.to_s << $/ << @body.to_s
|
584
|
+
r
|
585
|
+
end
|
586
|
+
|
587
|
+
def transfer_encoding
|
588
|
+
c = @headers[ :content_transfer_encoding]
|
589
|
+
c.caption if c
|
590
|
+
end
|
591
|
+
|
592
|
+
def body_decoded
|
593
|
+
r = case transfer_encoding
|
594
|
+
when "quoted-printable" then
|
595
|
+
(@body.unpack "M").join
|
596
|
+
when "base64" then
|
597
|
+
(@body.unpack "m").join
|
598
|
+
else
|
599
|
+
@body.new_string
|
600
|
+
end
|
601
|
+
if (c = @headers.content_type) and (s = c[ :charset]) then
|
602
|
+
r.force_encoding s
|
603
|
+
end
|
604
|
+
r
|
605
|
+
end
|
606
|
+
|
607
|
+
def body_text= body
|
608
|
+
body = body.to_s
|
609
|
+
@headers.replace :content_type, "text/plain", charset: body.encoding
|
610
|
+
@headers.replace :content_transfer_encoding, "quoted-printable"
|
611
|
+
@body = [ body].pack "M*"
|
612
|
+
end
|
613
|
+
|
614
|
+
def body_binary= body
|
615
|
+
@headers.replace :content_transfer_encoding, "base64"
|
616
|
+
@body = [ body].pack "m*"
|
617
|
+
end
|
618
|
+
|
619
|
+
def body= body
|
620
|
+
@body = body
|
621
|
+
end
|
622
|
+
|
623
|
+
end
|
624
|
+
|
625
|
+
end
|
626
|
+
|