tmail 1.1.1

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.
Files changed (102) hide show
  1. data/LICENSE +21 -0
  2. data/README +157 -0
  3. data/bat/changelog +19 -0
  4. data/bat/clobber/package +10 -0
  5. data/bat/compile +42 -0
  6. data/bat/config.yaml +8 -0
  7. data/bat/prepare +8 -0
  8. data/bat/publish +51 -0
  9. data/bat/rdoc +42 -0
  10. data/bat/release +12 -0
  11. data/bat/setup +1616 -0
  12. data/bat/stats +138 -0
  13. data/bat/tag +25 -0
  14. data/bat/test +25 -0
  15. data/ext/tmail/Makefile +25 -0
  16. data/ext/tmail/base64/MANIFEST +4 -0
  17. data/ext/tmail/base64/base64.c +264 -0
  18. data/ext/tmail/base64/depend +1 -0
  19. data/ext/tmail/base64/extconf.rb +38 -0
  20. data/ext/tmail/scanner_c/MANIFEST +4 -0
  21. data/ext/tmail/scanner_c/depend +1 -0
  22. data/ext/tmail/scanner_c/extconf.rb +38 -0
  23. data/ext/tmail/scanner_c/scanner_c.c +582 -0
  24. data/lib/tmail.rb +4 -0
  25. data/lib/tmail/Makefile +19 -0
  26. data/lib/tmail/address.rb +245 -0
  27. data/lib/tmail/attachments.rb +47 -0
  28. data/lib/tmail/base64.rb +75 -0
  29. data/lib/tmail/compat.rb +39 -0
  30. data/lib/tmail/config.rb +71 -0
  31. data/lib/tmail/core_extensions.rb +67 -0
  32. data/lib/tmail/encode.rb +524 -0
  33. data/lib/tmail/header.rb +931 -0
  34. data/lib/tmail/index.rb +8 -0
  35. data/lib/tmail/interface.rb +540 -0
  36. data/lib/tmail/loader.rb +1 -0
  37. data/lib/tmail/mail.rb +507 -0
  38. data/lib/tmail/mailbox.rb +435 -0
  39. data/lib/tmail/mbox.rb +1 -0
  40. data/lib/tmail/net.rb +282 -0
  41. data/lib/tmail/obsolete.rb +137 -0
  42. data/lib/tmail/parser.rb +1475 -0
  43. data/lib/tmail/parser.y +381 -0
  44. data/lib/tmail/port.rb +379 -0
  45. data/lib/tmail/quoting.rb +142 -0
  46. data/lib/tmail/require_arch.rb +56 -0
  47. data/lib/tmail/scanner.rb +44 -0
  48. data/lib/tmail/scanner_r.rb +263 -0
  49. data/lib/tmail/stringio.rb +279 -0
  50. data/lib/tmail/tmail.rb +1 -0
  51. data/lib/tmail/utils.rb +281 -0
  52. data/lib/tmail/version.rb +38 -0
  53. data/meta/icli.yaml +16 -0
  54. data/meta/tmail-1.1.1.roll +24 -0
  55. data/sample/data/multipart +23 -0
  56. data/sample/data/normal +29 -0
  57. data/sample/data/sendtest +5 -0
  58. data/sample/data/simple +14 -0
  59. data/sample/data/test +27 -0
  60. data/sample/extract-attachements.rb +33 -0
  61. data/sample/from-check.rb +26 -0
  62. data/sample/multipart.rb +26 -0
  63. data/sample/parse-bench.rb +68 -0
  64. data/sample/parse-test.rb +19 -0
  65. data/sample/sendmail.rb +94 -0
  66. data/test/extctrl.rb +6 -0
  67. data/test/fixtures/raw_base64_decoded_string +0 -0
  68. data/test/fixtures/raw_base64_email +83 -0
  69. data/test/fixtures/raw_base64_encoded_string +1 -0
  70. data/test/fixtures/raw_email +14 -0
  71. data/test/fixtures/raw_email10 +20 -0
  72. data/test/fixtures/raw_email11 +34 -0
  73. data/test/fixtures/raw_email12 +32 -0
  74. data/test/fixtures/raw_email13 +29 -0
  75. data/test/fixtures/raw_email2 +114 -0
  76. data/test/fixtures/raw_email3 +70 -0
  77. data/test/fixtures/raw_email4 +59 -0
  78. data/test/fixtures/raw_email5 +19 -0
  79. data/test/fixtures/raw_email6 +20 -0
  80. data/test/fixtures/raw_email7 +66 -0
  81. data/test/fixtures/raw_email8 +47 -0
  82. data/test/fixtures/raw_email9 +28 -0
  83. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  84. data/test/fixtures/raw_email_simple +11 -0
  85. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  86. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  87. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  88. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  89. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  90. data/test/kcode.rb +14 -0
  91. data/test/test_address.rb +1128 -0
  92. data/test/test_attachments.rb +35 -0
  93. data/test/test_base64.rb +63 -0
  94. data/test/test_encode.rb +77 -0
  95. data/test/test_header.rb +885 -0
  96. data/test/test_helper.rb +2 -0
  97. data/test/test_mail.rb +623 -0
  98. data/test/test_mbox.rb +126 -0
  99. data/test/test_port.rb +430 -0
  100. data/test/test_scanner.rb +209 -0
  101. data/test/test_utils.rb +37 -0
  102. metadata +205 -0
@@ -0,0 +1,67 @@
1
+ =begin rdoc
2
+
3
+ = Ruby on Rails Core Extensions
4
+
5
+ provides .blank?
6
+
7
+ =end
8
+ unless Object.respond_to?(:blank?) #:nodoc:
9
+ # Check first to see if we are in a Rails environment, no need to
10
+ # define these methods if we are
11
+ class Object
12
+ # An object is blank if it's nil, empty, or a whitespace string.
13
+ # For example, "", " ", nil, [], and {} are blank.
14
+ #
15
+ # This simplifies
16
+ # if !address.nil? && !address.empty?
17
+ # to
18
+ # if !address.blank?
19
+ def blank?
20
+ if respond_to?(:empty?) && respond_to?(:strip)
21
+ empty? or strip.empty?
22
+ elsif respond_to?(:empty?)
23
+ empty?
24
+ else
25
+ !self
26
+ end
27
+ end
28
+ end
29
+
30
+ class NilClass #:nodoc:
31
+ def blank?
32
+ true
33
+ end
34
+ end
35
+
36
+ class FalseClass #:nodoc:
37
+ def blank?
38
+ true
39
+ end
40
+ end
41
+
42
+ class TrueClass #:nodoc:
43
+ def blank?
44
+ false
45
+ end
46
+ end
47
+
48
+ class Array #:nodoc:
49
+ alias_method :blank?, :empty?
50
+ end
51
+
52
+ class Hash #:nodoc:
53
+ alias_method :blank?, :empty?
54
+ end
55
+
56
+ class String #:nodoc:
57
+ def blank?
58
+ empty? || strip.empty?
59
+ end
60
+ end
61
+
62
+ class Numeric #:nodoc:
63
+ def blank?
64
+ false
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,524 @@
1
+ =begin rdoc
2
+
3
+ = Text Encoding class
4
+
5
+ =end
6
+ #--
7
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29
+ # with permission of Minero Aoki.
30
+ #++
31
+
32
+ require 'nkf'
33
+ require 'tmail/base64.rb'
34
+ require 'tmail/stringio'
35
+ require 'tmail/utils'
36
+
37
+
38
+ module TMail
39
+
40
+ module StrategyInterface
41
+
42
+ def create_dest( obj )
43
+ case obj
44
+ when nil
45
+ StringOutput.new
46
+ when String
47
+ StringOutput.new(obj)
48
+ when IO, StringOutput
49
+ obj
50
+ else
51
+ raise TypeError, 'cannot handle this type of object for dest'
52
+ end
53
+ end
54
+ module_function :create_dest
55
+
56
+ def encoded( eol = "\r\n", charset = 'j', dest = nil )
57
+ accept_strategy Encoder, eol, charset, dest
58
+ end
59
+
60
+ def decoded( eol = "\n", charset = 'e', dest = nil )
61
+ # Turn the E-Mail into a string and return it with all
62
+ # encoded characters decoded. alias for to_s
63
+ accept_strategy Decoder, eol, charset, dest
64
+ end
65
+
66
+ alias to_s decoded
67
+
68
+ def accept_strategy( klass, eol, charset, dest = nil )
69
+ dest ||= ''
70
+ accept klass.new( create_dest(dest), charset, eol )
71
+ dest
72
+ end
73
+
74
+ end
75
+
76
+
77
+ ###
78
+ ### MIME B encoding decoder
79
+ ###
80
+
81
+ class Decoder
82
+
83
+ include TextUtils
84
+
85
+ encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
86
+ ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
87
+
88
+ OUTPUT_ENCODING = {
89
+ 'EUC' => 'e',
90
+ 'SJIS' => 's',
91
+ }
92
+
93
+ def self.decode( str, encoding = nil )
94
+ encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
95
+ opt = '-m' + encoding
96
+ str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
97
+ end
98
+
99
+ def initialize( dest, encoding = nil, eol = "\n" )
100
+ @f = StrategyInterface.create_dest(dest)
101
+ @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
102
+ @eol = eol
103
+ end
104
+
105
+ def decode( str )
106
+ self.class.decode(str, @encoding)
107
+ end
108
+ private :decode
109
+
110
+ def terminate
111
+ end
112
+
113
+ def header_line( str )
114
+ @f << decode(str)
115
+ end
116
+
117
+ def header_name( nm )
118
+ @f << nm << ': '
119
+ end
120
+
121
+ def header_body( str )
122
+ @f << decode(str)
123
+ end
124
+
125
+ def space
126
+ @f << ' '
127
+ end
128
+
129
+ alias spc space
130
+
131
+ def lwsp( str )
132
+ @f << str
133
+ end
134
+
135
+ def meta( str )
136
+ @f << str
137
+ end
138
+
139
+ def text( str )
140
+ @f << decode(str)
141
+ end
142
+
143
+ def phrase( str )
144
+ @f << quote_phrase(decode(str))
145
+ end
146
+
147
+ def kv_pair( k, v )
148
+ v = dquote(v) unless token_safe?(v)
149
+ @f << k << '=' << v
150
+ end
151
+
152
+ def puts( str = nil )
153
+ @f << str if str
154
+ @f << @eol
155
+ end
156
+
157
+ def write( str )
158
+ @f << str
159
+ end
160
+
161
+ end
162
+
163
+
164
+ ###
165
+ ### MIME B-encoding encoder
166
+ ###
167
+
168
+ #
169
+ # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
170
+ #
171
+ class Encoder
172
+
173
+ include TextUtils
174
+
175
+ BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
176
+
177
+ def Encoder.encode( str )
178
+ e = new()
179
+ e.header_body str
180
+ e.terminate
181
+ e.dest.string
182
+ end
183
+
184
+ SPACER = "\t"
185
+ MAX_LINE_LEN = 78
186
+ RFC_2822_MAX_LENGTH = 998
187
+
188
+ OPTIONS = {
189
+ 'EUC' => '-Ej -m0',
190
+ 'SJIS' => '-Sj -m0',
191
+ 'UTF8' => nil, # FIXME
192
+ 'NONE' => nil
193
+ }
194
+
195
+ def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
196
+ @f = StrategyInterface.create_dest(dest)
197
+ @opt = OPTIONS[$KCODE]
198
+ @eol = eol
199
+ @preserve_quotes = true
200
+ reset
201
+ end
202
+
203
+ def preserve_quotes=( bool )
204
+ @preserve_quotes
205
+ end
206
+
207
+ def preserve_quotes
208
+ @preserve_quotes
209
+ end
210
+
211
+ def normalize_encoding( str )
212
+ if @opt
213
+ then NKF.nkf(@opt, str)
214
+ else str
215
+ end
216
+ end
217
+
218
+ def reset
219
+ @text = ''
220
+ @lwsp = ''
221
+ @curlen = 0
222
+ end
223
+
224
+ def terminate
225
+ add_lwsp ''
226
+ reset
227
+ end
228
+
229
+ def dest
230
+ @f
231
+ end
232
+
233
+ def puts( str = nil )
234
+ @f << str if str
235
+ @f << @eol
236
+ end
237
+
238
+ def write( str )
239
+ @f << str
240
+ end
241
+
242
+ #
243
+ # add
244
+ #
245
+
246
+ def header_line( line )
247
+ scanadd line
248
+ end
249
+
250
+ def header_name( name )
251
+ add_text name.split(/-/).map {|i| i.capitalize }.join('-')
252
+ add_text ':'
253
+ add_lwsp ' '
254
+ end
255
+
256
+ def header_body( str )
257
+ scanadd normalize_encoding(str)
258
+ end
259
+
260
+ def space
261
+ add_lwsp ' '
262
+ end
263
+
264
+ alias spc space
265
+
266
+ def lwsp( str )
267
+ add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
268
+ end
269
+
270
+ def meta( str )
271
+ add_text str
272
+ end
273
+
274
+ def text( str )
275
+ scanadd normalize_encoding(str)
276
+ end
277
+
278
+ def phrase( str )
279
+ str = normalize_encoding(str)
280
+ if CONTROL_CHAR === str
281
+ scanadd str
282
+ else
283
+ add_text quote_phrase(str)
284
+ end
285
+ end
286
+
287
+ # FIXME: implement line folding
288
+ #
289
+ def kv_pair( k, v )
290
+ return if v.nil?
291
+ v = normalize_encoding(v)
292
+ if token_safe?(v)
293
+ add_text k + '=' + v
294
+ elsif not CONTROL_CHAR === v
295
+ add_text k + '=' + quote_token(v)
296
+ else
297
+ # apply RFC2231 encoding
298
+ kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
299
+ add_text kv
300
+ end
301
+ end
302
+
303
+ def encode_value( str )
304
+ str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
305
+ end
306
+
307
+ private
308
+
309
+ def scanadd( str, force = false )
310
+ types = ''
311
+ strs = []
312
+
313
+ until str.empty?
314
+ if m = /\A[^\e\t\r\n ]+/.match(str)
315
+ types << (force ? 'j' : 'a')
316
+ strs.push m[0]
317
+
318
+ elsif m = /\A[\t\r\n ]+/.match(str)
319
+ types << 's'
320
+ strs.push m[0]
321
+
322
+ elsif m = /\A\e../.match(str)
323
+ esc = m[0]
324
+ str = m.post_match
325
+ if esc != "\e(B" and m = /\A[^\e]+/.match(str)
326
+ types << 'j'
327
+ strs.push m[0]
328
+ end
329
+
330
+ else
331
+ raise 'TMail FATAL: encoder scan fail'
332
+ end
333
+ (str = m.post_match) unless m.nil?
334
+ end
335
+
336
+ do_encode types, strs
337
+ end
338
+
339
+ def do_encode( types, strs )
340
+ #
341
+ # result : (A|E)(S(A|E))*
342
+ # E : W(SW)*
343
+ # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
344
+ # A : <<A character string not to be encoded>>
345
+ # J : <<A character string to be encoded>>
346
+ # S : <<LWSP>>
347
+ #
348
+ # An encoding unit is `E'.
349
+ # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
350
+ #
351
+ if BENCODE_DEBUG
352
+ puts
353
+ puts '-- do_encode ------------'
354
+ puts types.split(//).join(' ')
355
+ p strs
356
+ end
357
+
358
+ e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
359
+
360
+ while m = e.match(types)
361
+ pre = m.pre_match
362
+ concat_A_S pre, strs[0, pre.size] unless pre.empty?
363
+ concat_E m[0], strs[m.begin(0) ... m.end(0)]
364
+ types = m.post_match
365
+ strs.slice! 0, m.end(0)
366
+ end
367
+ concat_A_S types, strs
368
+ end
369
+
370
+ def concat_A_S( types, strs )
371
+ i = 0
372
+ types.each_byte do |t|
373
+ case t
374
+ when ?a then add_text strs[i]
375
+ when ?s then add_lwsp strs[i]
376
+ else
377
+ raise "TMail FATAL: unknown flag: #{t.chr}"
378
+ end
379
+ i += 1
380
+ end
381
+ end
382
+
383
+ METHOD_ID = {
384
+ ?j => :extract_J,
385
+ ?e => :extract_E,
386
+ ?a => :extract_A,
387
+ ?s => :extract_S
388
+ }
389
+
390
+ def concat_E( types, strs )
391
+ if BENCODE_DEBUG
392
+ puts '---- concat_E'
393
+ puts "types=#{types.split(//).join(' ')}"
394
+ puts "strs =#{strs.inspect}"
395
+ end
396
+
397
+ flush() unless @text.empty?
398
+
399
+ chunk = ''
400
+ strs.each_with_index do |s,i|
401
+ mid = METHOD_ID[types[i]]
402
+ until s.empty?
403
+ unless c = __send__(mid, chunk.size, s)
404
+ add_with_encode chunk unless chunk.empty?
405
+ flush
406
+ chunk = ''
407
+ fold
408
+ c = __send__(mid, 0, s)
409
+ raise 'TMail FATAL: extract fail' unless c
410
+ end
411
+ chunk << c
412
+ end
413
+ end
414
+ add_with_encode chunk unless chunk.empty?
415
+ end
416
+
417
+ def extract_J( chunksize, str )
418
+ size = max_bytes(chunksize, str.size) - 6
419
+ size = (size % 2 == 0) ? (size) : (size - 1)
420
+ return nil if size <= 0
421
+ "\e$B#{str.slice!(0, size)}\e(B"
422
+ end
423
+
424
+ def extract_A( chunksize, str )
425
+ size = max_bytes(chunksize, str.size)
426
+ return nil if size <= 0
427
+ str.slice!(0, size)
428
+ end
429
+
430
+ alias extract_S extract_A
431
+
432
+ def max_bytes( chunksize, ssize )
433
+ (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
434
+ end
435
+
436
+ #
437
+ # free length buffer
438
+ #
439
+
440
+ def add_text( str )
441
+ @text << str
442
+ # puts '---- text -------------------------------------'
443
+ # puts "+ #{str.inspect}"
444
+ # puts "txt >>>#{@text.inspect}<<<"
445
+ end
446
+
447
+ def add_with_encode( str )
448
+ @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
449
+ end
450
+
451
+ def add_lwsp( lwsp )
452
+ # puts '---- lwsp -------------------------------------'
453
+ # puts "+ #{lwsp.inspect}"
454
+ fold if restsize() <= 0
455
+ flush(@folded)
456
+ @lwsp = lwsp
457
+ end
458
+
459
+ def flush(folded = false)
460
+ # puts '---- flush ----'
461
+ # puts "spc >>>#{@lwsp.inspect}<<<"
462
+ # puts "txt >>>#{@text.inspect}<<<"
463
+ @f << @lwsp << @text
464
+ if folded
465
+ @curlen = 0
466
+ else
467
+ @curlen += (@lwsp.size + @text.size)
468
+ end
469
+ @text = ''
470
+ @lwsp = ''
471
+ end
472
+
473
+ def fold
474
+ # puts '---- fold ----'
475
+ unless @f.string =~ /^.*?:$/
476
+ @f << @eol
477
+ @lwsp = SPACER
478
+ else
479
+ fold_header
480
+ @folded = true
481
+ end
482
+ @curlen = 0
483
+ end
484
+
485
+ def fold_header
486
+ # Called because line is too long - so we need to wrap.
487
+ # First look for whitespace in the text
488
+ # if it has text, fold there
489
+ # check the remaining text, if too long, fold again
490
+ # if it doesn't, then don't fold unless the line goes beyond 998 chars
491
+
492
+ # Check the text to see if there is whitespace, or if not
493
+ @wrapped_text = []
494
+ until @text == ''
495
+ fold_the_string
496
+ end
497
+ @text = @wrapped_text.join("#{@eol}#{SPACER}")
498
+ end
499
+
500
+ def fold_the_string
501
+ whitespace_location = @text =~ /\s/ || @text.length
502
+ # Is the location of the whitespace shorter than the RCF_2822_MAX_LENGTH?
503
+ # if there is no whitespace in the string, then this
504
+ unless mazsize(whitespace_location) <= 0
505
+ @wrapped_text << @text.slice!(0...whitespace_location)
506
+ # If it is not less, we have to wrap it destructively
507
+ else
508
+ slice_point = RFC_2822_MAX_LENGTH - @curlen - @lwsp.length
509
+ @wrapped_text << @text.slice!(0...slice_point)
510
+ end
511
+ end
512
+
513
+ def restsize
514
+ MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
515
+ end
516
+
517
+ def mazsize(whitespace_location)
518
+ # Per RFC2822, the maximum length of a line is 998 chars
519
+ RFC_2822_MAX_LENGTH - (@curlen + @lwsp.size + whitespace_location)
520
+ end
521
+
522
+ end
523
+
524
+ end # module TMail