hermeneutics 1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|