lonbaker-tmail 1.2.3.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 (97) hide show
  1. data/CHANGES +74 -0
  2. data/LICENSE +21 -0
  3. data/NOTES +7 -0
  4. data/README +169 -0
  5. data/ext/Makefile +20 -0
  6. data/ext/tmailscanner/tmail/MANIFEST +4 -0
  7. data/ext/tmailscanner/tmail/depend +1 -0
  8. data/ext/tmailscanner/tmail/extconf.rb +33 -0
  9. data/ext/tmailscanner/tmail/tmailscanner.c +583 -0
  10. data/lib/tmail.rb +5 -0
  11. data/lib/tmail/Makefile +18 -0
  12. data/lib/tmail/address.rb +426 -0
  13. data/lib/tmail/attachments.rb +46 -0
  14. data/lib/tmail/base64.rb +46 -0
  15. data/lib/tmail/compat.rb +41 -0
  16. data/lib/tmail/config.rb +67 -0
  17. data/lib/tmail/core_extensions.rb +63 -0
  18. data/lib/tmail/encode.rb +590 -0
  19. data/lib/tmail/header.rb +962 -0
  20. data/lib/tmail/index.rb +9 -0
  21. data/lib/tmail/interface.rb +1130 -0
  22. data/lib/tmail/loader.rb +3 -0
  23. data/lib/tmail/mail.rb +580 -0
  24. data/lib/tmail/mailbox.rb +496 -0
  25. data/lib/tmail/main.rb +6 -0
  26. data/lib/tmail/mbox.rb +3 -0
  27. data/lib/tmail/net.rb +248 -0
  28. data/lib/tmail/obsolete.rb +132 -0
  29. data/lib/tmail/parser.rb +1476 -0
  30. data/lib/tmail/parser.y +381 -0
  31. data/lib/tmail/port.rb +379 -0
  32. data/lib/tmail/quoting.rb +118 -0
  33. data/lib/tmail/require_arch.rb +58 -0
  34. data/lib/tmail/scanner.rb +49 -0
  35. data/lib/tmail/scanner_r.rb +261 -0
  36. data/lib/tmail/stringio.rb +280 -0
  37. data/lib/tmail/utils.rb +351 -0
  38. data/lib/tmail/version.rb +39 -0
  39. data/meta/MANIFEST +128 -0
  40. data/meta/project.yaml +30 -0
  41. data/meta/unixname +1 -0
  42. data/sample/bench_base64.rb +48 -0
  43. data/sample/data/multipart +23 -0
  44. data/sample/data/normal +29 -0
  45. data/sample/data/sendtest +5 -0
  46. data/sample/data/simple +14 -0
  47. data/sample/data/test +27 -0
  48. data/sample/extract-attachements.rb +33 -0
  49. data/sample/from-check.rb +26 -0
  50. data/sample/multipart.rb +26 -0
  51. data/sample/parse-bench.rb +68 -0
  52. data/sample/parse-test.rb +19 -0
  53. data/sample/sendmail.rb +94 -0
  54. data/test/extctrl.rb +6 -0
  55. data/test/fixtures/mailbox +414 -0
  56. data/test/fixtures/mailbox_without_any_from_or_sender +10 -0
  57. data/test/fixtures/mailbox_without_from +11 -0
  58. data/test/fixtures/mailbox_without_return_path +12 -0
  59. data/test/fixtures/raw_base64_decoded_string +0 -0
  60. data/test/fixtures/raw_base64_email +83 -0
  61. data/test/fixtures/raw_base64_encoded_string +1 -0
  62. data/test/fixtures/raw_email +14 -0
  63. data/test/fixtures/raw_email10 +20 -0
  64. data/test/fixtures/raw_email11 +34 -0
  65. data/test/fixtures/raw_email12 +32 -0
  66. data/test/fixtures/raw_email13 +29 -0
  67. data/test/fixtures/raw_email2 +114 -0
  68. data/test/fixtures/raw_email3 +70 -0
  69. data/test/fixtures/raw_email4 +59 -0
  70. data/test/fixtures/raw_email5 +19 -0
  71. data/test/fixtures/raw_email6 +20 -0
  72. data/test/fixtures/raw_email7 +66 -0
  73. data/test/fixtures/raw_email8 +47 -0
  74. data/test/fixtures/raw_email9 +28 -0
  75. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  76. data/test/fixtures/raw_email_reply +32 -0
  77. data/test/fixtures/raw_email_simple +11 -0
  78. data/test/fixtures/raw_email_with_bad_date +48 -0
  79. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  80. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  81. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  82. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  83. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  84. data/test/kcode.rb +14 -0
  85. data/test/test_address.rb +1211 -0
  86. data/test/test_attachments.rb +60 -0
  87. data/test/test_base64.rb +64 -0
  88. data/test/test_encode.rb +85 -0
  89. data/test/test_header.rb +969 -0
  90. data/test/test_helper.rb +9 -0
  91. data/test/test_mail.rb +753 -0
  92. data/test/test_mbox.rb +184 -0
  93. data/test/test_port.rb +436 -0
  94. data/test/test_quote.rb +98 -0
  95. data/test/test_scanner.rb +209 -0
  96. data/test/test_utils.rb +36 -0
  97. metadata +159 -0
@@ -0,0 +1,962 @@
1
+ #--
2
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
24
+ # with permission of Minero Aoki.
25
+ #++
26
+
27
+ require 'tmail/encode'
28
+ require 'tmail/address'
29
+ require 'tmail/parser'
30
+ require 'tmail/config'
31
+ require 'tmail/utils'
32
+
33
+ #:startdoc:
34
+ module TMail
35
+
36
+ # Provides methods to handle and manipulate headers in the email
37
+ class HeaderField
38
+
39
+ include TextUtils
40
+
41
+ class << self
42
+
43
+ alias newobj new
44
+
45
+ def new( name, body, conf = DEFAULT_CONFIG )
46
+ klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
47
+ klass.newobj body, conf
48
+ end
49
+
50
+ # Returns a HeaderField object matching the header you specify in the "name" param.
51
+ # Requires an initialized TMail::Port to be passed in.
52
+ #
53
+ # The method searches the header of the Port you pass into it to find a match on
54
+ # the header line you pass. Once a match is found, it will unwrap the matching line
55
+ # as needed to return an initialized HeaderField object.
56
+ #
57
+ # If you want to get the Envelope sender of the email object, pass in "EnvelopeSender",
58
+ # if you want the From address of the email itself, pass in 'From'.
59
+ #
60
+ # This is because a mailbox doesn't have the : after the From that designates the
61
+ # beginning of the envelope sender (which can be different to the from address of
62
+ # the email)
63
+ #
64
+ # Other fields can be passed as normal, "Reply-To", "Received" etc.
65
+ #
66
+ # Note: Change of behaviour in 1.2.1 => returns nil if it does not find the specified
67
+ # header field, otherwise returns an instantiated object of the correct header class
68
+ #
69
+ # For example:
70
+ # port = TMail::FilePort.new("/test/fixtures/raw_email_simple")
71
+ # h = TMail::HeaderField.new_from_port(port, "From")
72
+ # h.addrs.to_s #=> "Mikel Lindsaar <mikel@nowhere.com>"
73
+ # h = TMail::HeaderField.new_from_port(port, "EvelopeSender")
74
+ # h.addrs.to_s #=> "mike@anotherplace.com.au"
75
+ # h = TMail::HeaderField.new_from_port(port, "SomeWeirdHeaderField")
76
+ # h #=> nil
77
+ def new_from_port( port, name, conf = DEFAULT_CONFIG )
78
+ if name == "EnvelopeSender"
79
+ name = "From"
80
+ re = Regexp.new('\A(From) ', 'i')
81
+ else
82
+ re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i')
83
+ end
84
+ str = nil
85
+ port.ropen {|f|
86
+ f.each do |line|
87
+ if m = re.match(line) then str = m.post_match.strip
88
+ elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
89
+ elsif /\A-*\s*\z/ === line then break
90
+ elsif str then break
91
+ end
92
+ end
93
+ }
94
+ new(name, str, Config.to_config(conf)) if str
95
+ end
96
+
97
+ def internal_new( name, conf )
98
+ FNAME_TO_CLASS[name].newobj('', conf, true)
99
+ end
100
+
101
+ end # class << self
102
+
103
+ def initialize( body, conf, intern = false )
104
+ @body = body
105
+ @config = conf
106
+
107
+ @illegal = false
108
+ @parsed = false
109
+
110
+ if intern
111
+ @parsed = true
112
+ parse_init
113
+ end
114
+ end
115
+
116
+ def inspect
117
+ "#<#{self.class} #{@body.inspect}>"
118
+ end
119
+
120
+ def illegal?
121
+ @illegal
122
+ end
123
+
124
+ def empty?
125
+ ensure_parsed
126
+ return true if @illegal
127
+ isempty?
128
+ end
129
+
130
+ private
131
+
132
+ def ensure_parsed
133
+ return if @parsed
134
+ @parsed = true
135
+ parse
136
+ end
137
+
138
+ # defabstract parse
139
+ # end
140
+
141
+ def clear_parse_status
142
+ @parsed = false
143
+ @illegal = false
144
+ end
145
+
146
+ public
147
+
148
+ def body
149
+ ensure_parsed
150
+ v = Decoder.new(s = '')
151
+ do_accept v
152
+ v.terminate
153
+ s
154
+ end
155
+
156
+ def body=( str )
157
+ @body = str
158
+ clear_parse_status
159
+ end
160
+
161
+ include StrategyInterface
162
+
163
+ def accept( strategy )
164
+ ensure_parsed
165
+ do_accept strategy
166
+ strategy.terminate
167
+ end
168
+
169
+ # abstract do_accept
170
+
171
+ end
172
+
173
+
174
+ class UnstructuredHeader < HeaderField
175
+
176
+ def body
177
+ ensure_parsed
178
+ @body
179
+ end
180
+
181
+ def body=( arg )
182
+ ensure_parsed
183
+ @body = arg
184
+ end
185
+
186
+ private
187
+
188
+ def parse_init
189
+ end
190
+
191
+ def parse
192
+ @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
193
+ end
194
+
195
+ def isempty?
196
+ not @body
197
+ end
198
+
199
+ def do_accept( strategy )
200
+ strategy.text @body
201
+ end
202
+
203
+ end
204
+
205
+
206
+ class StructuredHeader < HeaderField
207
+
208
+ def comments
209
+ ensure_parsed
210
+ if @comments[0]
211
+ [Decoder.decode(@comments[0])]
212
+ else
213
+ @comments
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ def parse
220
+ save = nil
221
+
222
+ begin
223
+ parse_init
224
+ do_parse
225
+ rescue SyntaxError
226
+ if not save and mime_encoded? @body
227
+ save = @body
228
+ @body = Decoder.decode(save)
229
+ retry
230
+ elsif save
231
+ @body = save
232
+ end
233
+
234
+ @illegal = true
235
+ raise if @config.strict_parse?
236
+ end
237
+ end
238
+
239
+ def parse_init
240
+ @comments = []
241
+ init
242
+ end
243
+
244
+ def do_parse
245
+ quote_boundary
246
+ obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
247
+ set obj if obj
248
+ end
249
+
250
+ end
251
+
252
+
253
+ class DateTimeHeader < StructuredHeader
254
+
255
+ PARSE_TYPE = :DATETIME
256
+
257
+ def date
258
+ ensure_parsed
259
+ @date
260
+ end
261
+
262
+ def date=( arg )
263
+ ensure_parsed
264
+ @date = arg
265
+ end
266
+
267
+ private
268
+
269
+ def init
270
+ clean_date
271
+ @date = nil
272
+ end
273
+
274
+ def set( t )
275
+ @date = t
276
+ end
277
+
278
+ def isempty?
279
+ not @date
280
+ end
281
+
282
+ def do_accept( strategy )
283
+ strategy.meta time2str(@date)
284
+ end
285
+
286
+ end
287
+
288
+
289
+ class AddressHeader < StructuredHeader
290
+
291
+ PARSE_TYPE = :MADDRESS
292
+
293
+ def addrs
294
+ ensure_parsed
295
+ @addrs
296
+ end
297
+
298
+ private
299
+
300
+ def init
301
+ @addrs = []
302
+ end
303
+
304
+ def set( a )
305
+ @addrs = a
306
+ end
307
+
308
+ def isempty?
309
+ @addrs.empty?
310
+ end
311
+
312
+ def do_accept( strategy )
313
+ first = true
314
+ @addrs.each do |a|
315
+ if first
316
+ first = false
317
+ else
318
+ strategy.puts_meta ','
319
+ strategy.space
320
+ end
321
+ a.accept strategy
322
+ end
323
+
324
+ @comments.each do |c|
325
+ strategy.space
326
+ strategy.meta '('
327
+ strategy.text c
328
+ strategy.meta ')'
329
+ end
330
+ end
331
+
332
+ end
333
+
334
+
335
+ class ReturnPathHeader < AddressHeader
336
+
337
+ PARSE_TYPE = :RETPATH
338
+
339
+ def addr
340
+ addrs()[0]
341
+ end
342
+
343
+ def spec
344
+ a = addr() or return nil
345
+ a.spec
346
+ end
347
+
348
+ def routes
349
+ a = addr() or return nil
350
+ a.routes
351
+ end
352
+
353
+ private
354
+
355
+ def do_accept( strategy )
356
+ a = addr()
357
+
358
+ strategy.meta '<'
359
+ unless a.routes.empty?
360
+ strategy.meta a.routes.map {|i| '@' + i }.join(',')
361
+ strategy.meta ':'
362
+ end
363
+ spec = a.spec
364
+ strategy.meta spec if spec
365
+ strategy.meta '>'
366
+ end
367
+
368
+ end
369
+
370
+
371
+ class SingleAddressHeader < AddressHeader
372
+
373
+ def addr
374
+ addrs()[0]
375
+ end
376
+
377
+ private
378
+
379
+ def do_accept( strategy )
380
+ a = addr()
381
+ a.accept strategy
382
+ @comments.each do |c|
383
+ strategy.space
384
+ strategy.meta '('
385
+ strategy.text c
386
+ strategy.meta ')'
387
+ end
388
+ end
389
+
390
+ end
391
+
392
+
393
+ class MessageIdHeader < StructuredHeader
394
+
395
+ def id
396
+ ensure_parsed
397
+ @id
398
+ end
399
+
400
+ def id=( arg )
401
+ ensure_parsed
402
+ @id = arg
403
+ end
404
+
405
+ private
406
+
407
+ def init
408
+ @id = nil
409
+ end
410
+
411
+ def isempty?
412
+ not @id
413
+ end
414
+
415
+ def do_parse
416
+ @id = @body.slice(MESSAGE_ID) or
417
+ raise SyntaxError, "wrong Message-ID format: #{@body}"
418
+ end
419
+
420
+ def do_accept( strategy )
421
+ strategy.meta @id
422
+ end
423
+
424
+ end
425
+
426
+
427
+ class ReferencesHeader < StructuredHeader
428
+
429
+ def refs
430
+ ensure_parsed
431
+ @refs
432
+ end
433
+
434
+ def each_id
435
+ self.refs.each do |i|
436
+ yield i if MESSAGE_ID === i
437
+ end
438
+ end
439
+
440
+ def ids
441
+ ensure_parsed
442
+ @ids
443
+ end
444
+
445
+ def each_phrase
446
+ self.refs.each do |i|
447
+ yield i unless MESSAGE_ID === i
448
+ end
449
+ end
450
+
451
+ def phrases
452
+ ret = []
453
+ each_phrase {|i| ret.push i }
454
+ ret
455
+ end
456
+
457
+ private
458
+
459
+ def init
460
+ @refs = []
461
+ @ids = []
462
+ end
463
+
464
+ def isempty?
465
+ @ids.empty?
466
+ end
467
+
468
+ def do_parse
469
+ str = @body
470
+ while m = MESSAGE_ID.match(str)
471
+ pre = m.pre_match.strip
472
+ @refs.push pre unless pre.empty?
473
+ @refs.push s = m[0]
474
+ @ids.push s
475
+ str = m.post_match
476
+ end
477
+ str = str.strip
478
+ @refs.push str unless str.empty?
479
+ end
480
+
481
+ def do_accept( strategy )
482
+ first = true
483
+ @ids.each do |i|
484
+ if first
485
+ first = false
486
+ else
487
+ strategy.space
488
+ end
489
+ strategy.meta i
490
+ end
491
+ end
492
+
493
+ end
494
+
495
+
496
+ class ReceivedHeader < StructuredHeader
497
+
498
+ PARSE_TYPE = :RECEIVED
499
+
500
+ def from
501
+ ensure_parsed
502
+ @from
503
+ end
504
+
505
+ def from=( arg )
506
+ ensure_parsed
507
+ @from = arg
508
+ end
509
+
510
+ def by
511
+ ensure_parsed
512
+ @by
513
+ end
514
+
515
+ def by=( arg )
516
+ ensure_parsed
517
+ @by = arg
518
+ end
519
+
520
+ def via
521
+ ensure_parsed
522
+ @via
523
+ end
524
+
525
+ def via=( arg )
526
+ ensure_parsed
527
+ @via = arg
528
+ end
529
+
530
+ def with
531
+ ensure_parsed
532
+ @with
533
+ end
534
+
535
+ def id
536
+ ensure_parsed
537
+ @id
538
+ end
539
+
540
+ def id=( arg )
541
+ ensure_parsed
542
+ @id = arg
543
+ end
544
+
545
+ def _for
546
+ ensure_parsed
547
+ @_for
548
+ end
549
+
550
+ def _for=( arg )
551
+ ensure_parsed
552
+ @_for = arg
553
+ end
554
+
555
+ def date
556
+ ensure_parsed
557
+ @date
558
+ end
559
+
560
+ def date=( arg )
561
+ ensure_parsed
562
+ @date = arg
563
+ end
564
+
565
+ private
566
+
567
+ def init
568
+ quote_parentheses
569
+ @from = @by = @via = @with = @id = @_for = nil
570
+ @with = []
571
+ @date = nil
572
+ end
573
+
574
+ def set( args )
575
+ @from, @by, @via, @with, @id, @_for, @date = *args
576
+ end
577
+
578
+ def isempty?
579
+ @with.empty? and not (@from or @by or @via or @id or @_for or @date)
580
+ end
581
+
582
+ def do_accept( strategy )
583
+ list = []
584
+ list.push 'from ' + @from if @from
585
+ list.push 'by ' + @by if @by
586
+ list.push 'via ' + @via if @via
587
+ @with.each do |i|
588
+ list.push 'with ' + i
589
+ end
590
+ list.push 'id ' + @id if @id
591
+ list.push 'for <' + @_for + '>' if @_for
592
+
593
+ first = true
594
+ list.each do |i|
595
+ strategy.space unless first
596
+ strategy.meta i
597
+ first = false
598
+ end
599
+ if @date
600
+ strategy.meta ';'
601
+ strategy.space
602
+ strategy.meta time2str(@date)
603
+ end
604
+ end
605
+
606
+ end
607
+
608
+
609
+ class KeywordsHeader < StructuredHeader
610
+
611
+ PARSE_TYPE = :KEYWORDS
612
+
613
+ def keys
614
+ ensure_parsed
615
+ @keys
616
+ end
617
+
618
+ private
619
+
620
+ def init
621
+ @keys = []
622
+ end
623
+
624
+ def set( a )
625
+ @keys = a
626
+ end
627
+
628
+ def isempty?
629
+ @keys.empty?
630
+ end
631
+
632
+ def do_accept( strategy )
633
+ first = true
634
+ @keys.each do |i|
635
+ if first
636
+ first = false
637
+ else
638
+ strategy.meta ','
639
+ end
640
+ strategy.meta i
641
+ end
642
+ end
643
+
644
+ end
645
+
646
+
647
+ class EncryptedHeader < StructuredHeader
648
+
649
+ PARSE_TYPE = :ENCRYPTED
650
+
651
+ def encrypter
652
+ ensure_parsed
653
+ @encrypter
654
+ end
655
+
656
+ def encrypter=( arg )
657
+ ensure_parsed
658
+ @encrypter = arg
659
+ end
660
+
661
+ def keyword
662
+ ensure_parsed
663
+ @keyword
664
+ end
665
+
666
+ def keyword=( arg )
667
+ ensure_parsed
668
+ @keyword = arg
669
+ end
670
+
671
+ private
672
+
673
+ def init
674
+ @encrypter = nil
675
+ @keyword = nil
676
+ end
677
+
678
+ def set( args )
679
+ @encrypter, @keyword = args
680
+ end
681
+
682
+ def isempty?
683
+ not (@encrypter or @keyword)
684
+ end
685
+
686
+ def do_accept( strategy )
687
+ if @key
688
+ strategy.meta @encrypter + ','
689
+ strategy.space
690
+ strategy.meta @keyword
691
+ else
692
+ strategy.meta @encrypter
693
+ end
694
+ end
695
+
696
+ end
697
+
698
+
699
+ class MimeVersionHeader < StructuredHeader
700
+
701
+ PARSE_TYPE = :MIMEVERSION
702
+
703
+ def major
704
+ ensure_parsed
705
+ @major
706
+ end
707
+
708
+ def major=( arg )
709
+ ensure_parsed
710
+ @major = arg
711
+ end
712
+
713
+ def minor
714
+ ensure_parsed
715
+ @minor
716
+ end
717
+
718
+ def minor=( arg )
719
+ ensure_parsed
720
+ @minor = arg
721
+ end
722
+
723
+ def version
724
+ sprintf('%d.%d', major, minor)
725
+ end
726
+
727
+ private
728
+
729
+ def init
730
+ @major = nil
731
+ @minor = nil
732
+ end
733
+
734
+ def set( args )
735
+ @major, @minor = *args
736
+ end
737
+
738
+ def isempty?
739
+ not (@major or @minor)
740
+ end
741
+
742
+ def do_accept( strategy )
743
+ strategy.meta sprintf('%d.%d', @major, @minor)
744
+ end
745
+
746
+ end
747
+
748
+
749
+ class ContentTypeHeader < StructuredHeader
750
+
751
+ PARSE_TYPE = :CTYPE
752
+
753
+ def main_type
754
+ ensure_parsed
755
+ @main
756
+ end
757
+
758
+ def main_type=( arg )
759
+ ensure_parsed
760
+ @main = arg.downcase
761
+ end
762
+
763
+ def sub_type
764
+ ensure_parsed
765
+ @sub
766
+ end
767
+
768
+ def sub_type=( arg )
769
+ ensure_parsed
770
+ @sub = arg.downcase
771
+ end
772
+
773
+ def content_type
774
+ ensure_parsed
775
+ @sub ? sprintf('%s/%s', @main, @sub) : @main
776
+ end
777
+
778
+ def params
779
+ ensure_parsed
780
+ unless @params.blank?
781
+ @params.each do |k, v|
782
+ @params[k] = unquote(v)
783
+ end
784
+ end
785
+ @params
786
+ end
787
+
788
+ def []( key )
789
+ ensure_parsed
790
+ @params and unquote(@params[key])
791
+ end
792
+
793
+ def []=( key, val )
794
+ ensure_parsed
795
+ (@params ||= {})[key] = val
796
+ end
797
+
798
+ private
799
+
800
+ def init
801
+ @main = @sub = @params = nil
802
+ end
803
+
804
+ def set( args )
805
+ @main, @sub, @params = *args
806
+ end
807
+
808
+ def isempty?
809
+ not (@main or @sub)
810
+ end
811
+
812
+ def do_accept( strategy )
813
+ if @sub
814
+ strategy.meta sprintf('%s/%s', @main, @sub)
815
+ else
816
+ strategy.meta @main
817
+ end
818
+ @params.each do |k,v|
819
+ if v
820
+ strategy.meta ';'
821
+ strategy.space
822
+ strategy.kv_pair k, v
823
+ end
824
+ end
825
+ end
826
+
827
+ end
828
+
829
+
830
+ class ContentTransferEncodingHeader < StructuredHeader
831
+
832
+ PARSE_TYPE = :CENCODING
833
+
834
+ def encoding
835
+ ensure_parsed
836
+ @encoding
837
+ end
838
+
839
+ def encoding=( arg )
840
+ ensure_parsed
841
+ @encoding = arg
842
+ end
843
+
844
+ private
845
+
846
+ def init
847
+ @encoding = nil
848
+ end
849
+
850
+ def set( s )
851
+ @encoding = s
852
+ end
853
+
854
+ def isempty?
855
+ not @encoding
856
+ end
857
+
858
+ def do_accept( strategy )
859
+ strategy.meta @encoding.capitalize
860
+ end
861
+
862
+ end
863
+
864
+
865
+ class ContentDispositionHeader < StructuredHeader
866
+
867
+ PARSE_TYPE = :CDISPOSITION
868
+
869
+ def disposition
870
+ ensure_parsed
871
+ @disposition
872
+ end
873
+
874
+ def disposition=( str )
875
+ ensure_parsed
876
+ @disposition = str.downcase
877
+ end
878
+
879
+ def params
880
+ ensure_parsed
881
+ unless @params.blank?
882
+ @params.each do |k, v|
883
+ @params[k] = unquote(v)
884
+ end
885
+ end
886
+ @params
887
+ end
888
+
889
+ def []( key )
890
+ ensure_parsed
891
+ @params and unquote(@params[key])
892
+ end
893
+
894
+ def []=( key, val )
895
+ ensure_parsed
896
+ (@params ||= {})[key] = val
897
+ end
898
+
899
+ private
900
+
901
+ def init
902
+ @disposition = @params = nil
903
+ end
904
+
905
+ def set( args )
906
+ @disposition, @params = *args
907
+ end
908
+
909
+ def isempty?
910
+ not @disposition and (not @params or @params.empty?)
911
+ end
912
+
913
+ def do_accept( strategy )
914
+ strategy.meta @disposition
915
+ @params.each do |k,v|
916
+ strategy.meta ';'
917
+ strategy.space
918
+ strategy.kv_pair k, unquote(v)
919
+ end
920
+ end
921
+
922
+ end
923
+
924
+
925
+ class HeaderField # redefine
926
+
927
+ FNAME_TO_CLASS = {
928
+ 'date' => DateTimeHeader,
929
+ 'resent-date' => DateTimeHeader,
930
+ 'to' => AddressHeader,
931
+ 'cc' => AddressHeader,
932
+ 'bcc' => AddressHeader,
933
+ 'from' => AddressHeader,
934
+ 'reply-to' => AddressHeader,
935
+ 'resent-to' => AddressHeader,
936
+ 'resent-cc' => AddressHeader,
937
+ 'resent-bcc' => AddressHeader,
938
+ 'resent-from' => AddressHeader,
939
+ 'resent-reply-to' => AddressHeader,
940
+ 'sender' => SingleAddressHeader,
941
+ 'resent-sender' => SingleAddressHeader,
942
+ 'return-path' => ReturnPathHeader,
943
+ 'message-id' => MessageIdHeader,
944
+ 'resent-message-id' => MessageIdHeader,
945
+ 'in-reply-to' => ReferencesHeader,
946
+ 'received' => ReceivedHeader,
947
+ 'references' => ReferencesHeader,
948
+ 'keywords' => KeywordsHeader,
949
+ 'encrypted' => EncryptedHeader,
950
+ 'mime-version' => MimeVersionHeader,
951
+ 'content-type' => ContentTypeHeader,
952
+ 'content-transfer-encoding' => ContentTransferEncodingHeader,
953
+ 'content-disposition' => ContentDispositionHeader,
954
+ 'content-id' => UnstructuredHeader,
955
+ 'subject' => UnstructuredHeader,
956
+ 'comments' => UnstructuredHeader,
957
+ 'content-description' => UnstructuredHeader
958
+ }
959
+
960
+ end
961
+
962
+ end # module TMail