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,102 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/cli/pop.rb -- POP client
|
3
|
+
#
|
4
|
+
|
5
|
+
module Hermeneutics
|
6
|
+
|
7
|
+
module Cli
|
8
|
+
|
9
|
+
class Pop
|
10
|
+
|
11
|
+
class Keep < Exception ; end
|
12
|
+
|
13
|
+
def initialize host, port = nil
|
14
|
+
if not port and host =~ /:(\d+)\z/ then
|
15
|
+
host, port = $`, $1.to_i
|
16
|
+
end
|
17
|
+
@host, @port = host, port
|
18
|
+
require "net/pop"
|
19
|
+
end
|
20
|
+
|
21
|
+
def login user, password
|
22
|
+
do_apop do
|
23
|
+
@pop = Net::POP3.new @host, @port, @apop
|
24
|
+
do_ssl
|
25
|
+
@pop.start user, password do |pop|
|
26
|
+
@user = user
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
ensure
|
31
|
+
@user = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
@user or raise "Not logged in."
|
36
|
+
r = "#@user@#@host"
|
37
|
+
r << ":#@port" if @port
|
38
|
+
r
|
39
|
+
end
|
40
|
+
|
41
|
+
def count ; @pop.n_mails ; end
|
42
|
+
|
43
|
+
def each
|
44
|
+
@pop.mails.each do |m|
|
45
|
+
begin
|
46
|
+
yield m.pop
|
47
|
+
m.delete
|
48
|
+
rescue Keep
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def do_apop
|
56
|
+
@apop = true
|
57
|
+
begin
|
58
|
+
yield
|
59
|
+
rescue Net::POPAuthenticationError
|
60
|
+
raise unless @apop
|
61
|
+
@apop = false
|
62
|
+
retry
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def do_ssl
|
67
|
+
@pop.disable_ssl
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class Pops < Pop
|
73
|
+
|
74
|
+
def initialize host, port = nil, certs = nil
|
75
|
+
unless certs or Integer === port then
|
76
|
+
port, certs = nil, port
|
77
|
+
end
|
78
|
+
@certs = File.expand_path certs if certs
|
79
|
+
super host, port
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def do_apop
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
|
88
|
+
def do_ssl
|
89
|
+
v = if @certs then
|
90
|
+
OpenSSL::SSL::VERIFY_PEER
|
91
|
+
else
|
92
|
+
OpenSSL::SSL::VERIFY_NONE
|
93
|
+
end
|
94
|
+
@pop.enable_ssl v, @certs
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,275 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/color.rb -- Color calculation
|
3
|
+
#
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
|
7
|
+
:section: Classes definied here
|
8
|
+
|
9
|
+
Hermeneutics::Color handles 24-bit colors.
|
10
|
+
|
11
|
+
Hermeneutics::Colour is an alias for <code>Hermeneutics::Color</code>.
|
12
|
+
|
13
|
+
=end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
module Hermeneutics
|
18
|
+
|
19
|
+
# Generate HTML color values.
|
20
|
+
#
|
21
|
+
# = Examples
|
22
|
+
#
|
23
|
+
# red = Color.new 0xff, 0, 0
|
24
|
+
# black = Color.grey 0
|
25
|
+
# red.to_s #=> "#ff0000"
|
26
|
+
# red.to_fract #=> [ 1.0, 0.0, 0.0]
|
27
|
+
#
|
28
|
+
# (Color.new 0xff, 0, 0).to_hsv #=> [0.0, 1.0, 1.0]
|
29
|
+
# (Color.new 0, 0xff, 0).to_hsv #=> [120.0, 1.0, 1.0]
|
30
|
+
# (Color.new 0, 0x7f, 0x7f).to_hsv #=> [180.0, 1.0, 0.49804]
|
31
|
+
# (Color.new 0, 0x80, 0x80).to_hsv #=> [180.0, 1.0, 0.50196]
|
32
|
+
# (Color.from_hsv 180, 1, 0.5).to_s #=> "#007f7f"
|
33
|
+
#
|
34
|
+
class Color
|
35
|
+
|
36
|
+
attr_reader :r, :g, :b
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# :call-seq:
|
41
|
+
# new( r, g, b) -> clr
|
42
|
+
#
|
43
|
+
# Create a color with red, green and blue values. They are in range
|
44
|
+
# <code>0..255</code>.
|
45
|
+
#
|
46
|
+
def initialize r, *gb
|
47
|
+
if gb.any? then
|
48
|
+
@r, @g, @b = *[ r, *gb].map { |x| ff x }
|
49
|
+
else
|
50
|
+
@r = @g = @b = (ff r)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def ff x ; (Integer x) & 0xff ; end
|
55
|
+
|
56
|
+
public
|
57
|
+
|
58
|
+
def r= x ; @r = ff x ; end
|
59
|
+
def g= x ; @g = ff x ; end
|
60
|
+
def b= x ; @b = ff x ; end
|
61
|
+
|
62
|
+
# :call-seq:
|
63
|
+
# to_a() -> ary
|
64
|
+
#
|
65
|
+
# Return RGB values as an array.
|
66
|
+
#
|
67
|
+
def to_a ; [ @r, @g, @b] ; end
|
68
|
+
alias tuple to_a
|
69
|
+
|
70
|
+
def == oth
|
71
|
+
case oth
|
72
|
+
when Color then to_a == oth.to_a
|
73
|
+
when Array then to_a == oth
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class <<self
|
78
|
+
|
79
|
+
# :call-seq:
|
80
|
+
# gray( num) -> clr
|
81
|
+
#
|
82
|
+
# Create a gray color (r=b=g). <code>num</code> is in range
|
83
|
+
# <code>0..255</code>.
|
84
|
+
#
|
85
|
+
def gray i
|
86
|
+
new i, i, i
|
87
|
+
end
|
88
|
+
alias grey gray
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
# :call-seq:
|
93
|
+
# to_s() -> str
|
94
|
+
#
|
95
|
+
# Return color as an HTML tags key.
|
96
|
+
#
|
97
|
+
def to_s ; "#" + tuple.map { |x| "%02x" % x }.join ; end
|
98
|
+
|
99
|
+
def inspect ; "#<#{cls}:#{'0x%08x' % (object_id << 1)} #{to_s}>" ; end
|
100
|
+
|
101
|
+
class <<self
|
102
|
+
|
103
|
+
# :call-seq:
|
104
|
+
# from_s( str) -> clr
|
105
|
+
#
|
106
|
+
# Build a Color from an HTML tags key.
|
107
|
+
#
|
108
|
+
def from_s str
|
109
|
+
rgb = str.scan( /[0-9a-f]{2}/i).map do |x| x.to_i 0x10 end
|
110
|
+
new *rgb
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# :call-seq:
|
117
|
+
# to_fract() -> ary
|
118
|
+
#
|
119
|
+
# Return three values in range 0..1 where 1.0 means 255.
|
120
|
+
#
|
121
|
+
def to_fract
|
122
|
+
tuple.map { |x| x / 255.0 }
|
123
|
+
end
|
124
|
+
|
125
|
+
class <<self
|
126
|
+
|
127
|
+
# :call-seq:
|
128
|
+
# from_fract( rf, gf, bf) -> clr
|
129
|
+
#
|
130
|
+
# Build a Color from three values in range 0..1 where 1.0 means 255.
|
131
|
+
#
|
132
|
+
def from_fract rf, gf, bf
|
133
|
+
rgb = [rf, gf, bf].map { |x| 0xff * x }
|
134
|
+
new *rgb
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
# :call-seq:
|
141
|
+
# to_long() -> ary
|
142
|
+
#
|
143
|
+
# Extend it to a 48-bit color triple.
|
144
|
+
#
|
145
|
+
def to_long
|
146
|
+
tuple.map { |x| x * 0x100 + x }
|
147
|
+
end
|
148
|
+
|
149
|
+
class <<self
|
150
|
+
|
151
|
+
# :call-seq:
|
152
|
+
# from_fract( rl, gl, bl) -> clr
|
153
|
+
#
|
154
|
+
# Build a Color from three values in range 0..0xffff.
|
155
|
+
#
|
156
|
+
def from_long rl, gl, bl
|
157
|
+
rgb = [rl, gl, bl].map { |x| x / 0x100 }
|
158
|
+
new *rgb
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
# :call-seq:
|
165
|
+
# to_hsv() -> ary
|
166
|
+
#
|
167
|
+
# Convert it to an HSV triple.
|
168
|
+
#
|
169
|
+
def to_hsv
|
170
|
+
rgb = [ @r, @g, @b].map { |x| (Integer x) / 255.0 }
|
171
|
+
v = rgb.max
|
172
|
+
delta = v - rgb.min
|
173
|
+
unless delta > 0.0 then
|
174
|
+
h = s = 0.0
|
175
|
+
else
|
176
|
+
s = delta / v
|
177
|
+
r, g, b = rgb
|
178
|
+
case v
|
179
|
+
when r then h = (g - b) / delta ; h += 6 if h < 0
|
180
|
+
when g then h = 2 + (b - r) / delta
|
181
|
+
when b then h = 4 + (r - g) / delta
|
182
|
+
end
|
183
|
+
h *= 60
|
184
|
+
end
|
185
|
+
[ h, s, v]
|
186
|
+
end
|
187
|
+
|
188
|
+
class <<self
|
189
|
+
|
190
|
+
# :call-seq:
|
191
|
+
# from_hsv( h, s, v) -> clr
|
192
|
+
#
|
193
|
+
# Build a Color from HSV parameters.
|
194
|
+
#
|
195
|
+
# Ranges are:
|
196
|
+
#
|
197
|
+
# h 0...360
|
198
|
+
# s 0.0..1.0
|
199
|
+
# v 0.0..1.0
|
200
|
+
#
|
201
|
+
def from_hsv h, s, v
|
202
|
+
if s.nonzero? then
|
203
|
+
h /= 60.0
|
204
|
+
i = h.to_i % 6
|
205
|
+
f = h - i
|
206
|
+
rgb = []
|
207
|
+
if (i%2).zero? then
|
208
|
+
rgb.push v
|
209
|
+
rgb.push v * (1.0 - s * (1.0 - f))
|
210
|
+
else
|
211
|
+
rgb.push v * (1.0 - s * f)
|
212
|
+
rgb.push v
|
213
|
+
end
|
214
|
+
rgb.push v * (1.0 - s)
|
215
|
+
rgb.rotate! -(i/2)
|
216
|
+
from_fract *rgb
|
217
|
+
else
|
218
|
+
from_fract v, v, v
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
# :call-seq:
|
225
|
+
# edit_hsv() { |h,s,v| ... } -> clr
|
226
|
+
#
|
227
|
+
# Convert it to an HSV triple, yield that to the block and build a
|
228
|
+
# new <code>Color</code> from the blocks result.
|
229
|
+
#
|
230
|
+
def edit_hsv
|
231
|
+
hsv = yield *to_hsv
|
232
|
+
self.class.from_hsv *hsv
|
233
|
+
end
|
234
|
+
|
235
|
+
# :call-seq:
|
236
|
+
# complementary() -> clr
|
237
|
+
#
|
238
|
+
# Build the complementary color.
|
239
|
+
#
|
240
|
+
def complementary ; Color.new *(tuple.map { |x| 0xff - x }) ; end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
# Alias for class <code>Hermeneutics::Color</code> in British English.
|
245
|
+
Colour = Color
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
class Float
|
251
|
+
def to_gray
|
252
|
+
Hermeneutics::Color.gray self
|
253
|
+
end
|
254
|
+
alias to_grey to_gray
|
255
|
+
end
|
256
|
+
|
257
|
+
class String
|
258
|
+
def to_gray
|
259
|
+
(Integer self).to_gray
|
260
|
+
end
|
261
|
+
alias to_grey to_gray
|
262
|
+
def to_rgb
|
263
|
+
Hermeneutics::Color.from_s self
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class Array
|
268
|
+
def to_rgb
|
269
|
+
Hermeneutics::Color.new *self
|
270
|
+
end
|
271
|
+
def to_hsv
|
272
|
+
Hermeneutics::Color.from_hsv *self
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
@@ -0,0 +1,351 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/contents.rb -- Handle header fields like Content-Type
|
3
|
+
#
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
|
7
|
+
:section: Classes definied here
|
8
|
+
|
9
|
+
Hermeneutics::Contents is a content field parser.
|
10
|
+
|
11
|
+
Hermeneutics::ContentType parses "Content-Type" header fields.
|
12
|
+
|
13
|
+
=end
|
14
|
+
|
15
|
+
require "hermeneutics/escape"
|
16
|
+
|
17
|
+
|
18
|
+
module Hermeneutics
|
19
|
+
|
20
|
+
# A parser for header fields like DKIM-Signature
|
21
|
+
#
|
22
|
+
# === Example
|
23
|
+
#
|
24
|
+
# ds = Dictionary.new v: 1, a: "rsa-sha256", c: "relaxed/relaxed", ...
|
25
|
+
#
|
26
|
+
# ds = Dictionary.parse "v=1; a=rsa-sha256; c=relaxed/relaxed; ..."
|
27
|
+
# ds[ "a"] #=> "0123456"
|
28
|
+
#
|
29
|
+
class Dictionary
|
30
|
+
|
31
|
+
# :stopdoc:
|
32
|
+
SEP = ";"
|
33
|
+
SEA = "="
|
34
|
+
RES = /#{SEP}\s*/
|
35
|
+
REA = /((?:\*(\d+))?\*)?#{SEA}/
|
36
|
+
# :startdoc:
|
37
|
+
|
38
|
+
class <<self
|
39
|
+
|
40
|
+
# Create a +Dictionary+ object out of a string from a
|
41
|
+
# mail header field.
|
42
|
+
#
|
43
|
+
# ds = Dictionary.parse "v=1; a=rsa-sha256; c=relaxed/relaxed; ..."
|
44
|
+
# ds[ "a"] #=> "0123456"
|
45
|
+
#
|
46
|
+
def parse line
|
47
|
+
rest = line.strip
|
48
|
+
hash = parse_hash rest
|
49
|
+
new hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def urltext
|
53
|
+
@urltext ||= URLText.new mask_space: true
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_hash rest
|
59
|
+
hash = Hash.new { |h,k| h[ k] = [] }
|
60
|
+
asts = {}
|
61
|
+
while rest.notempty? do
|
62
|
+
key, rest = if rest =~ REA then
|
63
|
+
ast = $1
|
64
|
+
ord = $2.to_i if $2
|
65
|
+
[ $`, $']
|
66
|
+
else
|
67
|
+
[ rest.dup, ""]
|
68
|
+
end
|
69
|
+
key.downcase!
|
70
|
+
key = key.to_sym
|
71
|
+
asts[ key] = ast
|
72
|
+
val, rest = if not ast and rest =~ /\A"(.*?)"(?:#{SEP}\s*|\z)/ then
|
73
|
+
[ $1, $']
|
74
|
+
else
|
75
|
+
rest.split RES, 2
|
76
|
+
end
|
77
|
+
if ord then
|
78
|
+
hash[ key][ ord] = val
|
79
|
+
else
|
80
|
+
hash[ key] = val
|
81
|
+
end
|
82
|
+
end
|
83
|
+
r = URLText::Dict.new
|
84
|
+
hash.keys.each { |k|
|
85
|
+
v = hash[ k]
|
86
|
+
Array === v and v = v.join
|
87
|
+
if asts[ k] then
|
88
|
+
enc, lang, val = v.split "'"
|
89
|
+
val.force_encoding enc
|
90
|
+
v = URLText.decode val
|
91
|
+
end
|
92
|
+
r[ k] = v
|
93
|
+
}
|
94
|
+
r
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_reader :hash
|
100
|
+
alias to_hash hash
|
101
|
+
alias to_h to_hash
|
102
|
+
|
103
|
+
# Create a +Dictionary+ object from a value and a hash.
|
104
|
+
#
|
105
|
+
# ds = Dictionary.new :v => 1, :a => "rsa-sha256",
|
106
|
+
# :c => "relaxed/relaxed", ...
|
107
|
+
#
|
108
|
+
def initialize hash = nil
|
109
|
+
case hash
|
110
|
+
when URLText::Dict then
|
111
|
+
@hash = hash
|
112
|
+
else
|
113
|
+
@hash = URLText::Dict.new
|
114
|
+
@hash.merge! hash if hash
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# :call-seq:
|
119
|
+
# []( key) -> str or nil
|
120
|
+
#
|
121
|
+
# Find value of +key+.
|
122
|
+
#
|
123
|
+
def [] key ; @hash[ key.to_sym] ; end
|
124
|
+
alias field []
|
125
|
+
|
126
|
+
def method_missing sym, *args
|
127
|
+
if sym =~ /[^a-z_]/ or args.any? then
|
128
|
+
super
|
129
|
+
else
|
130
|
+
field sym
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# :call-seq:
|
135
|
+
# keys() -> ary
|
136
|
+
#
|
137
|
+
# Returns a list of all contained keys
|
138
|
+
#
|
139
|
+
# c = Contents.new "text/html; boundary=0123456"
|
140
|
+
# c.keys #=> [ :boundary]
|
141
|
+
#
|
142
|
+
def keys ; @hash.keys ; end
|
143
|
+
|
144
|
+
|
145
|
+
# :stopdoc:
|
146
|
+
TSPECIAL = %r_[()<>@,;:\\"\[\]/?=]_ # RFC 1521
|
147
|
+
# :startdoc:
|
148
|
+
|
149
|
+
# Show the line as readable text.
|
150
|
+
#
|
151
|
+
def to_s
|
152
|
+
quoted_parts.join "#{SEP} "
|
153
|
+
end
|
154
|
+
alias quote to_s
|
155
|
+
|
156
|
+
private
|
157
|
+
def quoted_parts
|
158
|
+
@hash.map { |k,v|
|
159
|
+
case v
|
160
|
+
when true then v = k
|
161
|
+
when false then v = nil
|
162
|
+
when TSPECIAL, /\s/, /[\0-\x1f\x7f]/ then v = v.inspect
|
163
|
+
end
|
164
|
+
"#{k}=#{v}"
|
165
|
+
}
|
166
|
+
end
|
167
|
+
public
|
168
|
+
|
169
|
+
# Encode it for a mail header field.
|
170
|
+
#
|
171
|
+
def encode
|
172
|
+
f, *rest = encoded_parts
|
173
|
+
if f then
|
174
|
+
r = [ f]
|
175
|
+
rest.each { |e|
|
176
|
+
r.last << SEP
|
177
|
+
r.push e
|
178
|
+
}
|
179
|
+
end
|
180
|
+
r
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
def encoded_parts
|
185
|
+
r = @hash.map { |k,v|
|
186
|
+
case v
|
187
|
+
when nil then next
|
188
|
+
when true then v = k
|
189
|
+
when false then v = ""
|
190
|
+
when String then nil
|
191
|
+
else v = v.to_s
|
192
|
+
end
|
193
|
+
if not v.ascii_only? or v =~ /[=;"]/ then
|
194
|
+
enc = v.encoding
|
195
|
+
if (l = ENV[ "LANG"]) then
|
196
|
+
l, = l.split /\W/, 2
|
197
|
+
lang = l.gsub "_", "-"
|
198
|
+
end
|
199
|
+
v = [ enc, lang, (Dictionary.urltext.encode v)].join "'"
|
200
|
+
end
|
201
|
+
"#{k}=#{v}"
|
202
|
+
}
|
203
|
+
r.compact!
|
204
|
+
r
|
205
|
+
end
|
206
|
+
public
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# A parser for header fields in Content-Type style
|
211
|
+
#
|
212
|
+
# === Example
|
213
|
+
#
|
214
|
+
# content_disposition = Contents.new "form-data", :name => "mycontrol"
|
215
|
+
#
|
216
|
+
# content_type = Contents.parse "text/html; boundary=0123456"
|
217
|
+
# content_type.caption #=> "text/html"
|
218
|
+
# content_type[ :boundary] #=> "0123456"
|
219
|
+
# # (Subclass ContentType even splits the caption into type/subtype.)
|
220
|
+
#
|
221
|
+
class Contents < Dictionary
|
222
|
+
|
223
|
+
class <<self
|
224
|
+
|
225
|
+
# Create a +Contents+ object out of a string from a
|
226
|
+
# mail header field.
|
227
|
+
#
|
228
|
+
# c = Contents.parse "text/html; boundary=0123456"
|
229
|
+
# c.caption #=> "text/html"
|
230
|
+
# c[ :boundary] #=> "0123456"
|
231
|
+
#
|
232
|
+
def parse line
|
233
|
+
rest = line.strip
|
234
|
+
caption, rest = rest.split Dictionary::RES, 2
|
235
|
+
hash = parse_hash rest
|
236
|
+
new caption, hash
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
attr_reader :caption
|
242
|
+
|
243
|
+
# Create a +Contents+ object from a value and a hash.
|
244
|
+
#
|
245
|
+
# c = Contents.new "text/html", :boundary => "0123456"
|
246
|
+
#
|
247
|
+
def initialize caption, hash = nil
|
248
|
+
if caption =~ RES or caption =~ REA then
|
249
|
+
raise "Invalid content caption '#{caption}'."
|
250
|
+
end
|
251
|
+
@caption = caption.new_string
|
252
|
+
super hash
|
253
|
+
end
|
254
|
+
|
255
|
+
def =~ re
|
256
|
+
@caption =~ re
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
def quoted_parts
|
261
|
+
r = [ "#@caption"]
|
262
|
+
r.concat super
|
263
|
+
r
|
264
|
+
end
|
265
|
+
|
266
|
+
def encoded_parts
|
267
|
+
r = [ "#@caption"]
|
268
|
+
r.concat super
|
269
|
+
r
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
class ContentType < Contents
|
275
|
+
|
276
|
+
# :call-seq:
|
277
|
+
# new( str) -> cts
|
278
|
+
#
|
279
|
+
# Create a +ContentType+ object either out of a string from an
|
280
|
+
# E-Mail header field or from a value and a hash.
|
281
|
+
#
|
282
|
+
# c = ContentType.parse "text/html; boundary=0123456"
|
283
|
+
# c = ContentType.new "text/html", :boundary => "0123456"
|
284
|
+
#
|
285
|
+
def initialize line, sf = nil
|
286
|
+
line = line.join "/" if Array === line
|
287
|
+
super
|
288
|
+
end
|
289
|
+
|
290
|
+
# :call-seq:
|
291
|
+
# split_type -> str
|
292
|
+
#
|
293
|
+
# Find caption value of Content-Type style header field as an array
|
294
|
+
#
|
295
|
+
# c = ContentType.new "text/html; boundary=0123456"
|
296
|
+
# c.split_type #=> [ "text", "html"]
|
297
|
+
# c.type #=> "text"
|
298
|
+
# c.subtype #=> "html"
|
299
|
+
#
|
300
|
+
def split_type ; @split ||= (@caption.split "/", 2) ; end
|
301
|
+
|
302
|
+
# :call-seq:
|
303
|
+
# fulltype -> str
|
304
|
+
#
|
305
|
+
# Find caption value of Content-Type style header field.
|
306
|
+
#
|
307
|
+
# c = ContentType.new "text/html; boundary=0123456"
|
308
|
+
# c.fulltype #=> "text/html"
|
309
|
+
# c.type #=> "text"
|
310
|
+
# c.subtype #=> "html"
|
311
|
+
#
|
312
|
+
def fulltype ; caption ; end
|
313
|
+
|
314
|
+
# :call-seq:
|
315
|
+
# type -> str
|
316
|
+
#
|
317
|
+
# See +fulltype+ or +split_type+.
|
318
|
+
#
|
319
|
+
def type ; split_type.first ; end
|
320
|
+
|
321
|
+
# :call-seq:
|
322
|
+
# subtype -> str
|
323
|
+
#
|
324
|
+
# See +fulltype+ or +split_type+.
|
325
|
+
#
|
326
|
+
def subtype ; split_type.last ; end
|
327
|
+
|
328
|
+
def parse_mime input
|
329
|
+
m = Mime.find @caption
|
330
|
+
m and m.parse input, @hash
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
class Mime
|
336
|
+
@types = []
|
337
|
+
class <<self
|
338
|
+
def inherited cls
|
339
|
+
Mime.types.push cls
|
340
|
+
end
|
341
|
+
protected
|
342
|
+
attr_reader :types
|
343
|
+
public
|
344
|
+
def find type
|
345
|
+
Mime.types.find { |t| t::MIME === type }
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|