actionmailer 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

Files changed (34) hide show
  1. data/CHANGELOG +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +102 -0
  4. data/install.rb +61 -0
  5. data/lib/action_mailer.rb +44 -0
  6. data/lib/action_mailer/base.rb +111 -0
  7. data/lib/action_mailer/mail_helper.rb +17 -0
  8. data/lib/action_mailer/vendor/text/format.rb +1447 -0
  9. data/lib/action_mailer/vendor/tmail.rb +4 -0
  10. data/lib/action_mailer/vendor/tmail/address.rb +223 -0
  11. data/lib/action_mailer/vendor/tmail/base64.rb +52 -0
  12. data/lib/action_mailer/vendor/tmail/config.rb +50 -0
  13. data/lib/action_mailer/vendor/tmail/encode.rb +447 -0
  14. data/lib/action_mailer/vendor/tmail/facade.rb +531 -0
  15. data/lib/action_mailer/vendor/tmail/header.rb +893 -0
  16. data/lib/action_mailer/vendor/tmail/info.rb +16 -0
  17. data/lib/action_mailer/vendor/tmail/loader.rb +1 -0
  18. data/lib/action_mailer/vendor/tmail/mail.rb +420 -0
  19. data/lib/action_mailer/vendor/tmail/mailbox.rb +414 -0
  20. data/lib/action_mailer/vendor/tmail/mbox.rb +1 -0
  21. data/lib/action_mailer/vendor/tmail/net.rb +261 -0
  22. data/lib/action_mailer/vendor/tmail/obsolete.rb +116 -0
  23. data/lib/action_mailer/vendor/tmail/parser.rb +1503 -0
  24. data/lib/action_mailer/vendor/tmail/port.rb +358 -0
  25. data/lib/action_mailer/vendor/tmail/scanner.rb +22 -0
  26. data/lib/action_mailer/vendor/tmail/scanner_r.rb +244 -0
  27. data/lib/action_mailer/vendor/tmail/stringio.rb +260 -0
  28. data/lib/action_mailer/vendor/tmail/tmail.rb +1 -0
  29. data/lib/action_mailer/vendor/tmail/utils.rb +215 -0
  30. data/rakefile +99 -0
  31. data/test/fixtures/templates/signed_up.rhtml +3 -0
  32. data/test/fixtures/test_mailer/signed_up.rhtml +3 -0
  33. data/test/mail_service_test.rb +53 -0
  34. metadata +86 -0
@@ -0,0 +1,4 @@
1
+ require 'tmail/info'
2
+ require 'tmail/mail'
3
+ require 'tmail/mailbox'
4
+ require 'tmail/obsolete'
@@ -0,0 +1,223 @@
1
+ #
2
+ # address.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ require 'tmail/encode'
12
+ require 'tmail/parser'
13
+
14
+
15
+ module TMail
16
+
17
+ class Address
18
+
19
+ include TextUtils
20
+
21
+ def Address.parse( str )
22
+ Parser.parse :ADDRESS, str
23
+ end
24
+
25
+ def address_group?
26
+ false
27
+ end
28
+
29
+ def initialize( local, domain )
30
+ if domain
31
+ domain.each do |s|
32
+ raise SyntaxError, 'empty word in domain' if s.empty?
33
+ end
34
+ end
35
+ @local = local
36
+ @domain = domain
37
+ @name = nil
38
+ @routes = []
39
+ end
40
+
41
+ attr_reader :name
42
+
43
+ def name=( str )
44
+ @name = str
45
+ @name = nil if str and str.empty?
46
+ end
47
+
48
+ alias phrase name
49
+ alias phrase= name=
50
+
51
+ attr_reader :routes
52
+
53
+ def inspect
54
+ "#<#{self.class} #{address()}>"
55
+ end
56
+
57
+ def local
58
+ return nil unless @local
59
+ return '""' if @local.size == 1 and @local[0].empty?
60
+ @local.map {|i| quote_atom(i) }.join('.')
61
+ end
62
+
63
+ def domain
64
+ return nil unless @domain
65
+ join_domain(@domain)
66
+ end
67
+
68
+ def spec
69
+ s = self.local
70
+ d = self.domain
71
+ if s and d
72
+ s + '@' + d
73
+ else
74
+ s
75
+ end
76
+ end
77
+
78
+ alias address spec
79
+
80
+
81
+ def ==( other )
82
+ other.respond_to? :spec and self.spec == other.spec
83
+ end
84
+
85
+ alias eql? ==
86
+
87
+ def hash
88
+ @local.hash ^ @domain.hash
89
+ end
90
+
91
+ def dup
92
+ obj = self.class.new(@local.dup, @domain.dup)
93
+ obj.name = @name.dup if @name
94
+ obj.routes.replace @routes
95
+ obj
96
+ end
97
+
98
+ include StrategyInterface
99
+
100
+ def accept( strategy, dummy1 = nil, dummy2 = nil )
101
+ unless @local
102
+ strategy.meta '<>' # empty return-path
103
+ return
104
+ end
105
+
106
+ spec_p = (not @name and @routes.empty?)
107
+ if @name
108
+ strategy.phrase @name
109
+ strategy.space
110
+ end
111
+ tmp = spec_p ? '' : '<'
112
+ unless @routes.empty?
113
+ tmp << @routes.map {|i| '@' + i }.join(',') << ':'
114
+ end
115
+ tmp << self.spec
116
+ tmp << '>' unless spec_p
117
+ strategy.meta tmp
118
+ strategy.lwsp ''
119
+ end
120
+
121
+ end
122
+
123
+
124
+ class AddressGroup
125
+
126
+ include Enumerable
127
+
128
+ def address_group?
129
+ true
130
+ end
131
+
132
+ def initialize( name, addrs )
133
+ @name = name
134
+ @addresses = addrs
135
+ end
136
+
137
+ attr_reader :name
138
+
139
+ def ==( other )
140
+ other.respond_to? :to_a and @addresses == other.to_a
141
+ end
142
+
143
+ alias eql? ==
144
+
145
+ def hash
146
+ map {|i| i.hash }.hash
147
+ end
148
+
149
+ def []( idx )
150
+ @addresses[idx]
151
+ end
152
+
153
+ def size
154
+ @addresses.size
155
+ end
156
+
157
+ def empty?
158
+ @addresses.empty?
159
+ end
160
+
161
+ def each( &block )
162
+ @addresses.each(&block)
163
+ end
164
+
165
+ def to_a
166
+ @addresses.dup
167
+ end
168
+
169
+ alias to_ary to_a
170
+
171
+ def include?( a )
172
+ @addresses.include? a
173
+ end
174
+
175
+ def flatten
176
+ set = []
177
+ @addresses.each do |a|
178
+ if a.respond_to? :flatten
179
+ set.concat a.flatten
180
+ else
181
+ set.push a
182
+ end
183
+ end
184
+ set
185
+ end
186
+
187
+ def each_address( &block )
188
+ flatten.each(&block)
189
+ end
190
+
191
+ def add( a )
192
+ @addresses.push a
193
+ end
194
+
195
+ alias push add
196
+
197
+ def delete( a )
198
+ @addresses.delete a
199
+ end
200
+
201
+ include StrategyInterface
202
+
203
+ def accept( strategy, dummy1 = nil, dummy2 = nil )
204
+ strategy.phrase @name
205
+ strategy.meta ':'
206
+ strategy.space
207
+ first = true
208
+ each do |mbox|
209
+ if first
210
+ first = false
211
+ else
212
+ strategy.meta ','
213
+ end
214
+ strategy.space
215
+ mbox.accept strategy
216
+ end
217
+ strategy.meta ';'
218
+ strategy.lwsp ''
219
+ end
220
+
221
+ end
222
+
223
+ end # module TMail
@@ -0,0 +1,52 @@
1
+ #
2
+ # base64.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ module TMail
12
+
13
+ module Base64
14
+
15
+ module_function
16
+
17
+ def rb_folding_encode( str, eol = "\n", limit = 60 )
18
+ [str].pack('m')
19
+ end
20
+
21
+ def rb_encode( str )
22
+ [str].pack('m').tr( "\r\n", '' )
23
+ end
24
+
25
+ def rb_decode( str, strict = false )
26
+ str.unpack('m')
27
+ end
28
+
29
+ begin
30
+ require 'tmail/base64.so'
31
+ alias folding_encode c_folding_encode
32
+ alias encode c_encode
33
+ alias decode c_decode
34
+ class << self
35
+ alias folding_encode c_folding_encode
36
+ alias encode c_encode
37
+ alias decode c_decode
38
+ end
39
+ rescue LoadError
40
+ alias folding_encode rb_folding_encode
41
+ alias encode rb_encode
42
+ alias decode rb_decode
43
+ class << self
44
+ alias folding_encode rb_folding_encode
45
+ alias encode rb_encode
46
+ alias decode rb_decode
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,50 @@
1
+ #
2
+ # config.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ module TMail
12
+
13
+ class Config
14
+
15
+ def initialize( strict )
16
+ @strict_parse = strict
17
+ @strict_base64decode = strict
18
+ end
19
+
20
+ def strict_parse?
21
+ @strict_parse
22
+ end
23
+
24
+ attr_writer :strict_parse
25
+
26
+ def strict_base64decode?
27
+ @strict_base64decode
28
+ end
29
+
30
+ attr_writer :strict_base64decode
31
+
32
+ def new_body_port( mail )
33
+ StringPort.new
34
+ end
35
+
36
+ alias new_preamble_port new_body_port
37
+ alias new_part_port new_body_port
38
+
39
+ end
40
+
41
+ DEFAULT_CONFIG = Config.new(false)
42
+ DEFAULT_STRICT_CONFIG = Config.new(true)
43
+
44
+ def Config.to_config( arg )
45
+ return DEFAULT_STRICT_CONFIG if arg == true
46
+ return DEFAULT_CONFIG if arg == false
47
+ arg or DEFAULT_CONFIG
48
+ end
49
+
50
+ end
@@ -0,0 +1,447 @@
1
+ #
2
+ # encode.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ require 'nkf'
12
+ require 'tmail/base64.rb'
13
+ require 'tmail/stringio'
14
+ require 'tmail/utils'
15
+
16
+
17
+ module TMail
18
+
19
+ module StrategyInterface
20
+
21
+ def create_dest( obj )
22
+ case obj
23
+ when nil
24
+ StringOutput.new
25
+ when String
26
+ StringOutput.new(obj)
27
+ when IO, StringOutput
28
+ obj
29
+ else
30
+ raise TypeError, 'cannot handle this type of object for dest'
31
+ end
32
+ end
33
+ module_function :create_dest
34
+
35
+ def encoded( eol = "\r\n", charset = 'j', dest = nil )
36
+ accept_strategy Encoder, eol, charset, dest
37
+ end
38
+
39
+ def decoded( eol = "\n", charset = 'e', dest = nil )
40
+ accept_strategy Decoder, eol, charset, dest
41
+ end
42
+
43
+ alias to_s decoded
44
+
45
+ def accept_strategy( klass, eol, charset, dest = nil )
46
+ dest ||= ''
47
+ accept klass.new(create_dest(dest), charset, eol)
48
+ dest
49
+ end
50
+
51
+ end
52
+
53
+
54
+ ###
55
+ ### MIME B encoding decoder
56
+ ###
57
+
58
+ class Decoder
59
+
60
+ include TextUtils
61
+
62
+ encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
63
+ ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
64
+
65
+ OUTPUT_ENCODING = {
66
+ 'EUC' => 'e',
67
+ 'SJIS' => 's',
68
+ }
69
+
70
+ def self.decode( str, encoding = nil )
71
+ encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
72
+ opt = '-m' + encoding
73
+ str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
74
+ end
75
+
76
+ def initialize( dest, encoding = nil, eol = "\n" )
77
+ @f = StrategyInterface.create_dest(dest)
78
+ @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
79
+ @eol = eol
80
+ end
81
+
82
+ def decode( str )
83
+ self.class.decode(str, @encoding)
84
+ end
85
+ private :decode
86
+
87
+ def terminate
88
+ end
89
+
90
+ def header_line( str )
91
+ @f << decode(str)
92
+ end
93
+
94
+ def header_name( nm )
95
+ @f << nm << ': '
96
+ end
97
+
98
+ def header_body( str )
99
+ @f << decode(str)
100
+ end
101
+
102
+ def space
103
+ @f << ' '
104
+ end
105
+
106
+ alias spc space
107
+
108
+ def lwsp( str )
109
+ @f << str
110
+ end
111
+
112
+ def meta( str )
113
+ @f << str
114
+ end
115
+
116
+ def text( str )
117
+ @f << decode(str)
118
+ end
119
+
120
+ def phrase( str )
121
+ @f << quote_phrase(decode(str))
122
+ end
123
+
124
+ def kv_pair( k, v )
125
+ @f << k << '=' << v
126
+ end
127
+
128
+ def puts( str = nil )
129
+ @f << str if str
130
+ @f << @eol
131
+ end
132
+
133
+ def write( str )
134
+ @f << str
135
+ end
136
+
137
+ end
138
+
139
+
140
+ ###
141
+ ### MIME B-encoding encoder
142
+ ###
143
+
144
+ #
145
+ # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
146
+ #
147
+ class Encoder
148
+
149
+ include TextUtils
150
+
151
+ BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
152
+
153
+ def Encoder.encode( str )
154
+ e = new()
155
+ e.header_body str
156
+ e.terminate
157
+ e.dest.string
158
+ end
159
+
160
+ SPACER = "\t"
161
+ MAX_LINE_LEN = 70
162
+
163
+ OPTIONS = {
164
+ 'EUC' => '-Ej -m0',
165
+ 'SJIS' => '-Sj -m0',
166
+ 'UTF8' => nil, # FIXME
167
+ 'NONE' => nil
168
+ }
169
+
170
+ def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
171
+ @f = StrategyInterface.create_dest(dest)
172
+ @opt = OPTIONS[$KCODE]
173
+ @eol = eol
174
+ reset
175
+ end
176
+
177
+ def normalize_encoding( str )
178
+ if @opt
179
+ then NKF.nkf(@opt, str)
180
+ else str
181
+ end
182
+ end
183
+
184
+ def reset
185
+ @text = ''
186
+ @lwsp = ''
187
+ @curlen = 0
188
+ end
189
+
190
+ def terminate
191
+ add_lwsp ''
192
+ reset
193
+ end
194
+
195
+ def dest
196
+ @f
197
+ end
198
+
199
+ def puts( str = nil )
200
+ @f << str if str
201
+ @f << @eol
202
+ end
203
+
204
+ def write( str )
205
+ @f << str
206
+ end
207
+
208
+ #
209
+ # add
210
+ #
211
+
212
+ def header_line( line )
213
+ scanadd line
214
+ end
215
+
216
+ def header_name( name )
217
+ add_text name.split(/-/).map {|i| i.capitalize }.join('-')
218
+ add_text ':'
219
+ add_lwsp ' '
220
+ end
221
+
222
+ def header_body( str )
223
+ scanadd normalize_encoding(str)
224
+ end
225
+
226
+ def space
227
+ add_lwsp ' '
228
+ end
229
+
230
+ alias spc space
231
+
232
+ def lwsp( str )
233
+ add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
234
+ end
235
+
236
+ def meta( str )
237
+ add_text str
238
+ end
239
+
240
+ def text( str )
241
+ scanadd normalize_encoding(str)
242
+ end
243
+
244
+ def phrase( str )
245
+ str = normalize_encoding(str)
246
+ if CONTROL_CHAR === str
247
+ scanadd str
248
+ else
249
+ add_text quote_phrase(str)
250
+ end
251
+ end
252
+
253
+ # FIXME: implement line folding
254
+ #
255
+ def kv_pair( k, v )
256
+ v = normalize_encoding(v)
257
+ if token_safe?(v)
258
+ add_text k + '=' + v
259
+ elsif not CONTROL_CHAR === v
260
+ add_text k + '=' + quote_token(v)
261
+ else
262
+ # apply RFC2231 encoding
263
+ kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
264
+ add_text kv
265
+ end
266
+ end
267
+
268
+ def encode_value( str )
269
+ str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
270
+ end
271
+
272
+ private
273
+
274
+ def scanadd( str, force = false )
275
+ types = ''
276
+ strs = []
277
+
278
+ until str.empty?
279
+ if m = /\A[^\e\t\r\n ]+/.match(str)
280
+ types << (force ? 'j' : 'a')
281
+ strs.push m[0]
282
+
283
+ elsif m = /\A[\t\r\n ]+/.match(str)
284
+ types << 's'
285
+ strs.push m[0]
286
+
287
+ elsif m = /\A\e../.match(str)
288
+ esc = m[0]
289
+ str = m.post_match
290
+ if esc != "\e(B" and m = /\A[^\e]+/.match(str)
291
+ types << 'j'
292
+ strs.push m[0]
293
+ end
294
+
295
+ else
296
+ raise 'TMail FATAL: encoder scan fail'
297
+ end
298
+ str = m.post_match
299
+ end
300
+
301
+ do_encode types, strs
302
+ end
303
+
304
+ def do_encode( types, strs )
305
+ #
306
+ # result : (A|E)(S(A|E))*
307
+ # E : W(SW)*
308
+ # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
309
+ # A : <<A character string not to be encoded>>
310
+ # J : <<A character string to be encoded>>
311
+ # S : <<LWSP>>
312
+ #
313
+ # An encoding unit is `E'.
314
+ # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
315
+ #
316
+ if BENCODE_DEBUG
317
+ puts
318
+ puts '-- do_encode ------------'
319
+ puts types.split(//).join(' ')
320
+ p strs
321
+ end
322
+
323
+ e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
324
+
325
+ while m = e.match(types)
326
+ pre = m.pre_match
327
+ concat_A_S pre, strs[0, pre.size] unless pre.empty?
328
+ concat_E m[0], strs[m.begin(0) ... m.end(0)]
329
+ types = m.post_match
330
+ strs.slice! 0, m.end(0)
331
+ end
332
+ concat_A_S types, strs
333
+ end
334
+
335
+ def concat_A_S( types, strs )
336
+ i = 0
337
+ types.each_byte do |t|
338
+ case t
339
+ when ?a then add_text strs[i]
340
+ when ?s then add_lwsp strs[i]
341
+ else
342
+ raise "TMail FATAL: unknown flag: #{t.chr}"
343
+ end
344
+ i += 1
345
+ end
346
+ end
347
+
348
+ METHOD_ID = {
349
+ ?j => :extract_J,
350
+ ?e => :extract_E,
351
+ ?a => :extract_A,
352
+ ?s => :extract_S
353
+ }
354
+
355
+ def concat_E( types, strs )
356
+ if BENCODE_DEBUG
357
+ puts '---- concat_E'
358
+ puts "types=#{types.split(//).join(' ')}"
359
+ puts "strs =#{strs.inspect}"
360
+ end
361
+
362
+ flush() unless @text.empty?
363
+
364
+ chunk = ''
365
+ strs.each_with_index do |s,i|
366
+ mid = METHOD_ID[types[i]]
367
+ until s.empty?
368
+ unless c = __send__(mid, chunk.size, s)
369
+ add_with_encode chunk unless chunk.empty?
370
+ flush
371
+ chunk = ''
372
+ fold
373
+ c = __send__(mid, 0, s)
374
+ raise 'TMail FATAL: extract fail' unless c
375
+ end
376
+ chunk << c
377
+ end
378
+ end
379
+ add_with_encode chunk unless chunk.empty?
380
+ end
381
+
382
+ def extract_J( chunksize, str )
383
+ size = max_bytes(chunksize, str.size) - 6
384
+ size = (size % 2 == 0) ? (size) : (size - 1)
385
+ return nil if size <= 0
386
+ "\e$B#{str.slice!(0, size)}\e(B"
387
+ end
388
+
389
+ def extract_A( chunksize, str )
390
+ size = max_bytes(chunksize, str.size)
391
+ return nil if size <= 0
392
+ str.slice!(0, size)
393
+ end
394
+
395
+ alias extract_S extract_A
396
+
397
+ def max_bytes( chunksize, ssize )
398
+ (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
399
+ end
400
+
401
+ #
402
+ # free length buffer
403
+ #
404
+
405
+ def add_text( str )
406
+ @text << str
407
+ # puts '---- text -------------------------------------'
408
+ # puts "+ #{str.inspect}"
409
+ # puts "txt >>>#{@text.inspect}<<<"
410
+ end
411
+
412
+ def add_with_encode( str )
413
+ @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
414
+ end
415
+
416
+ def add_lwsp( lwsp )
417
+ # puts '---- lwsp -------------------------------------'
418
+ # puts "+ #{lwsp.inspect}"
419
+ fold if restsize() <= 0
420
+ flush
421
+ @lwsp = lwsp
422
+ end
423
+
424
+ def flush
425
+ # puts '---- flush ----'
426
+ # puts "spc >>>#{@lwsp.inspect}<<<"
427
+ # puts "txt >>>#{@text.inspect}<<<"
428
+ @f << @lwsp << @text
429
+ @curlen += (@lwsp.size + @text.size)
430
+ @text = ''
431
+ @lwsp = ''
432
+ end
433
+
434
+ def fold
435
+ # puts '---- fold ----'
436
+ @f << @eol
437
+ @curlen = 0
438
+ @lwsp = SPACER
439
+ end
440
+
441
+ def restsize
442
+ MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
443
+ end
444
+
445
+ end
446
+
447
+ end # module TMail