tmail 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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 @@
1
+ require 'tmail/mailbox'
@@ -0,0 +1,507 @@
1
+ =begin rdoc
2
+
3
+ = Mail 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
+ # == TMail::Mail
32
+ # === Class Methods
33
+ #
34
+ #
35
+ #
36
+ #
37
+ #
38
+ #
39
+ #
40
+ #
41
+ #
42
+ #
43
+ #
44
+ #
45
+ #
46
+ #
47
+
48
+
49
+ require 'tmail/interface'
50
+ require 'tmail/encode'
51
+ require 'tmail/header'
52
+ require 'tmail/port'
53
+ require 'tmail/config'
54
+ require 'tmail/utils'
55
+ require 'tmail/attachments'
56
+ require 'tmail/quoting'
57
+ require 'socket'
58
+
59
+ module TMail
60
+
61
+ class Mail
62
+
63
+ class << self
64
+ def load( fname )
65
+ new(FilePort.new(fname))
66
+ end
67
+
68
+ alias load_from load
69
+ alias loadfrom load
70
+
71
+ def parse( str )
72
+ new(StringPort.new(str))
73
+ end
74
+
75
+ end
76
+
77
+ def initialize( port = nil, conf = DEFAULT_CONFIG )
78
+ @port = port || StringPort.new
79
+ @config = Config.to_config(conf)
80
+
81
+ @header = {}
82
+ @body_port = nil
83
+ @body_parsed = false
84
+ @epilogue = ''
85
+ @parts = []
86
+
87
+ @port.ropen {|f|
88
+ parse_header f
89
+ parse_body f unless @port.reproducible?
90
+ }
91
+ end
92
+
93
+ attr_reader :port
94
+
95
+ def inspect
96
+ "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
97
+ end
98
+
99
+ #
100
+ # to_s interfaces
101
+ #
102
+
103
+ public
104
+
105
+ include StrategyInterface
106
+
107
+ def write_back( eol = "\n", charset = 'e' )
108
+ parse_body
109
+ @port.wopen {|stream| encoded eol, charset, stream }
110
+ end
111
+
112
+ def accept( strategy )
113
+ with_multipart_encoding(strategy) {
114
+ ordered_each do |name, field|
115
+ next if field.empty?
116
+ strategy.header_name canonical(name)
117
+ field.accept strategy
118
+ strategy.puts
119
+ end
120
+ strategy.puts
121
+ body_port().ropen {|r|
122
+ strategy.write r.read
123
+ }
124
+ }
125
+ end
126
+
127
+ private
128
+
129
+ def canonical( name )
130
+ name.split(/-/).map {|s| s.capitalize }.join('-')
131
+ end
132
+
133
+ def with_multipart_encoding( strategy )
134
+ if parts().empty? # DO NOT USE @parts
135
+ yield
136
+
137
+ else
138
+ bound = ::TMail.new_boundary
139
+ if @header.key? 'content-type'
140
+ @header['content-type'].params['boundary'] = bound
141
+ else
142
+ store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
143
+ end
144
+
145
+ yield
146
+
147
+ parts().each do |tm|
148
+ strategy.puts
149
+ strategy.puts '--' + bound
150
+ tm.accept strategy
151
+ end
152
+ strategy.puts
153
+ strategy.puts '--' + bound + '--'
154
+ strategy.write epilogue()
155
+ end
156
+ end
157
+
158
+ ###
159
+ ### header
160
+ ###
161
+
162
+ public
163
+
164
+ ALLOW_MULTIPLE = {
165
+ 'received' => true,
166
+ 'resent-date' => true,
167
+ 'resent-from' => true,
168
+ 'resent-sender' => true,
169
+ 'resent-to' => true,
170
+ 'resent-cc' => true,
171
+ 'resent-bcc' => true,
172
+ 'resent-message-id' => true,
173
+ 'comments' => true,
174
+ 'keywords' => true
175
+ }
176
+ USE_ARRAY = ALLOW_MULTIPLE
177
+
178
+ def header
179
+ @header.dup
180
+ end
181
+
182
+ # Returns a TMail::AddressHeader object of the field you are querying.
183
+ # Examples:
184
+ # @mail['from'] #=> #<TMail::AddressHeader "mikel@test.com.au">
185
+ # @mail['to'] #=> #<TMail::AddressHeader "mikel@test.com.au">
186
+ #
187
+ # You can get the string value of this by passing "to_s" to the query:
188
+ # Example:
189
+ # @mail['to'].to_s #=> "mikel@test.com.au"
190
+ def []( key )
191
+ @header[key.downcase]
192
+ end
193
+
194
+ def sub_header(key, param)
195
+ (hdr = self[key]) ? hdr[param] : nil
196
+ end
197
+
198
+ alias fetch []
199
+
200
+ # Allows you to set or delete TMail header objects at will.
201
+ # Eamples:
202
+ # @mail = TMail::Mail.new
203
+ # @mail['to'].to_s # => 'mikel@test.com.au'
204
+ # @mail['to'] = 'mikel@elsewhere.org'
205
+ # @mail['to'].to_s # => 'mikel@elsewhere.org'
206
+ # @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n"
207
+ # @mail['to'] = nil
208
+ # @mail['to'].to_s # => nil
209
+ # @mail.encoded # => "\r\n"
210
+ #
211
+ # Note: setting mail[] = nil actualy deletes the header field in question from the object,
212
+ # it does not just set the value of the hash to nil
213
+ def []=( key, val )
214
+ dkey = key.downcase
215
+
216
+ if val.nil?
217
+ @header.delete dkey
218
+ return nil
219
+ end
220
+
221
+ case val
222
+ when String
223
+ header = new_hf(key, val)
224
+ when HeaderField
225
+ ;
226
+ when Array
227
+ ALLOW_MULTIPLE.include? dkey or
228
+ raise ArgumentError, "#{key}: Header must not be multiple"
229
+ @header[dkey] = val
230
+ return val
231
+ else
232
+ header = new_hf(key, val.to_s)
233
+ end
234
+ if ALLOW_MULTIPLE.include? dkey
235
+ (@header[dkey] ||= []).push header
236
+ else
237
+ @header[dkey] = header
238
+ end
239
+
240
+ val
241
+ end
242
+
243
+ alias store []=
244
+
245
+ # Allows you to loop through each header in the TMail::Mail object in a block
246
+ # Example:
247
+ # @mail['to'] = 'mikel@elsewhere.org'
248
+ # @mail['from'] = 'me@me.com'
249
+ # @mail.each_header { |k,v| puts "#{k} = #{v}" }
250
+ # # => from = me@me.com
251
+ # # => to = mikel@elsewhere.org
252
+ def each_header
253
+ @header.each do |key, val|
254
+ [val].flatten.each {|v| yield key, v }
255
+ end
256
+ end
257
+
258
+ alias each_pair each_header
259
+
260
+ def each_header_name( &block )
261
+ @header.each_key(&block)
262
+ end
263
+
264
+ alias each_key each_header_name
265
+
266
+ def each_field( &block )
267
+ @header.values.flatten.each(&block)
268
+ end
269
+
270
+ alias each_value each_field
271
+
272
+ FIELD_ORDER = %w(
273
+ return-path received
274
+ resent-date resent-from resent-sender resent-to
275
+ resent-cc resent-bcc resent-message-id
276
+ date from sender reply-to to cc bcc
277
+ message-id in-reply-to references
278
+ subject comments keywords
279
+ mime-version content-type content-transfer-encoding
280
+ content-disposition content-description
281
+ )
282
+
283
+ def ordered_each
284
+ list = @header.keys
285
+ FIELD_ORDER.each do |name|
286
+ if list.delete(name)
287
+ [@header[name]].flatten.each {|v| yield name, v }
288
+ end
289
+ end
290
+ list.each do |name|
291
+ [@header[name]].flatten.each {|v| yield name, v }
292
+ end
293
+ end
294
+
295
+ def clear
296
+ @header.clear
297
+ end
298
+
299
+ def delete( key )
300
+ @header.delete key.downcase
301
+ end
302
+
303
+ def delete_if
304
+ @header.delete_if do |key,val|
305
+ if Array === val
306
+ val.delete_if {|v| yield key, v }
307
+ val.empty?
308
+ else
309
+ yield key, val
310
+ end
311
+ end
312
+ end
313
+
314
+ def keys
315
+ @header.keys
316
+ end
317
+
318
+ def key?( key )
319
+ @header.key? key.downcase
320
+ end
321
+
322
+ def values_at( *args )
323
+ args.map {|k| @header[k.downcase] }.flatten
324
+ end
325
+
326
+ alias indexes values_at
327
+ alias indices values_at
328
+
329
+ private
330
+
331
+ def parse_header( f )
332
+ name = field = nil
333
+ unixfrom = nil
334
+
335
+ while line = f.gets
336
+ case line
337
+ when /\A[ \t]/ # continue from prev line
338
+ raise SyntaxError, 'mail is began by space' unless field
339
+ field << ' ' << line.strip
340
+
341
+ when /\A([^\: \t]+):\s*/ # new header line
342
+ add_hf name, field if field
343
+ name = $1
344
+ field = $' #.strip
345
+
346
+ when /\A\-*\s*\z/ # end of header
347
+ add_hf name, field if field
348
+ name = field = nil
349
+ break
350
+
351
+ when /\AFrom (\S+)/
352
+ unixfrom = $1
353
+
354
+ when /^charset=.*/
355
+
356
+ else
357
+ raise SyntaxError, "wrong mail header: '#{line.inspect}'"
358
+ end
359
+ end
360
+ add_hf name, field if name
361
+
362
+ if unixfrom
363
+ add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
364
+ end
365
+ end
366
+
367
+ def add_hf( name, field )
368
+ key = name.downcase
369
+ field = new_hf(name, field)
370
+
371
+ if ALLOW_MULTIPLE.include? key
372
+ (@header[key] ||= []).push field
373
+ else
374
+ @header[key] = field
375
+ end
376
+ end
377
+
378
+ def new_hf( name, field )
379
+ HeaderField.new(name, field, @config)
380
+ end
381
+
382
+ ###
383
+ ### body
384
+ ###
385
+
386
+ public
387
+
388
+ def body_port
389
+ parse_body
390
+ @body_port
391
+ end
392
+
393
+ def each( &block )
394
+ body_port().ropen {|f| f.each(&block) }
395
+ end
396
+
397
+ def quoted_body
398
+ parse_body
399
+ @body_port.ropen {|f|
400
+ return f.read
401
+ }
402
+ end
403
+
404
+ def body=( str )
405
+ # Sets the body of the email to a new (encoded) string.
406
+ #
407
+ # We also reparses the email if the body is ever reassigned, this is a performance hit, however when
408
+ # you assign the body, you usually want to be able to make sure that you can access the attachments etc.
409
+ #
410
+ # Usage:
411
+ #
412
+ # mail.body = "Hello, this is\nthe body text"
413
+ # # => "Hello, this is\nthe body"
414
+ # mail.body
415
+ # # => "Hello, this is\nthe body"
416
+ @body_parsed = false
417
+ parse_body(StringInput.new(str))
418
+ parse_body
419
+ @body_port.wopen {|f| f.write str }
420
+ str
421
+ end
422
+
423
+ alias preamble body
424
+ alias preamble= body=
425
+
426
+ def epilogue
427
+ parse_body
428
+ @epilogue.dup
429
+ end
430
+
431
+ def epilogue=( str )
432
+ parse_body
433
+ @epilogue = str
434
+ str
435
+ end
436
+
437
+ def parts
438
+ parse_body
439
+ @parts
440
+ end
441
+
442
+ def each_part( &block )
443
+ parts().each(&block)
444
+ end
445
+
446
+ private
447
+
448
+ def parse_body( f = nil )
449
+ return if @body_parsed
450
+ if f
451
+ parse_body_0 f
452
+ else
453
+ @port.ropen {|f|
454
+ skip_header f
455
+ parse_body_0 f
456
+ }
457
+ end
458
+ @body_parsed = true
459
+ end
460
+
461
+ def skip_header( f )
462
+ while line = f.gets
463
+ return if /\A[\r\n]*\z/ === line
464
+ end
465
+ end
466
+
467
+ def parse_body_0( f )
468
+ if multipart?
469
+ read_multipart f
470
+ else
471
+ @body_port = @config.new_body_port(self)
472
+ @body_port.wopen {|w|
473
+ w.write f.read
474
+ }
475
+ end
476
+ end
477
+
478
+ def read_multipart( src )
479
+ bound = @header['content-type'].params['boundary']
480
+ is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
481
+ lastbound = "--#{bound}--"
482
+
483
+ ports = [ @config.new_preamble_port(self) ]
484
+ begin
485
+ f = ports.last.wopen
486
+ while line = src.gets
487
+ if is_sep === line
488
+ f.close
489
+ break if line.strip == lastbound
490
+ ports.push @config.new_part_port(self)
491
+ f = ports.last.wopen
492
+ else
493
+ f << line
494
+ end
495
+ end
496
+ @epilogue = (src.read || '')
497
+ ensure
498
+ f.close if f and not f.closed?
499
+ end
500
+
501
+ @body_port = ports.shift
502
+ @parts = ports.map {|p| self.class.new(p, @config) }
503
+ end
504
+
505
+ end # class Mail
506
+
507
+ end # module TMail