rumbster 1.0.0

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.
@@ -0,0 +1,895 @@
1
+ #
2
+ # header.rb
3
+ #
4
+ # Copyright (c) 1998-2004 Minero Aoki
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.1.
9
+ #
10
+
11
+ require 'tmail/encode'
12
+ require 'tmail/address'
13
+ require 'tmail/parser'
14
+ require 'tmail/config'
15
+ require 'tmail/textutils'
16
+
17
+
18
+ module TMail
19
+
20
+ class HeaderField
21
+
22
+ include TextUtils
23
+
24
+ class << self
25
+
26
+ alias newobj new
27
+
28
+ def new(name, body, conf = DEFAULT_CONFIG)
29
+ klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
30
+ klass.newobj body, conf
31
+ end
32
+
33
+ def new_from_port(port, name, conf = DEFAULT_CONFIG)
34
+ re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i')
35
+ str = nil
36
+ port.ropen {|f|
37
+ f.each do |line|
38
+ if m = re.match(line) then str = m.post_match.strip
39
+ elsif str and /\A[\t ]/ =~ line then str << ' ' << line.strip
40
+ elsif /\A-*\s*\z/ =~ line then break
41
+ elsif str then break
42
+ end
43
+ end
44
+ }
45
+ new(name, str, Config.to_config(conf))
46
+ end
47
+
48
+ def internal_new(name, conf)
49
+ FNAME_TO_CLASS[name].newobj('', conf, true)
50
+ end
51
+
52
+ end # class << self
53
+
54
+ def initialize(body, conf, intern = false)
55
+ @body = body
56
+ @config = conf
57
+
58
+ @illegal = false
59
+ @parsed = false
60
+ if intern
61
+ @parsed = true
62
+ parse_init
63
+ end
64
+ end
65
+
66
+ def inspect
67
+ "#<#{self.class} #{@body.inspect}>"
68
+ end
69
+
70
+ def illegal?
71
+ @illegal
72
+ end
73
+
74
+ def empty?
75
+ ensure_parsed
76
+ return true if @illegal
77
+ isempty?
78
+ end
79
+
80
+ private
81
+
82
+ def ensure_parsed
83
+ return if @parsed
84
+ @parsed = true
85
+ parse
86
+ end
87
+
88
+ # defabstract parse
89
+ # end
90
+
91
+ def clear_parse_status
92
+ @parsed = false
93
+ @illegal = false
94
+ end
95
+
96
+ public
97
+
98
+ def body
99
+ ensure_parsed
100
+ v = Decoder.new(s = '')
101
+ do_accept v
102
+ v.terminate
103
+ s
104
+ end
105
+
106
+ def body=(str)
107
+ @body = str
108
+ clear_parse_status
109
+ end
110
+
111
+ include StrategyInterface
112
+
113
+ def accept(strategy, dummy1 = nil, dummy2 = nil)
114
+ ensure_parsed
115
+ do_accept strategy
116
+ strategy.terminate
117
+ end
118
+
119
+ # abstract do_accept
120
+
121
+ end
122
+
123
+
124
+ class UnstructuredHeader < HeaderField
125
+
126
+ def body
127
+ ensure_parsed
128
+ @body
129
+ end
130
+
131
+ def body=(arg)
132
+ ensure_parsed
133
+ @body = arg
134
+ end
135
+
136
+ private
137
+
138
+ def parse_init
139
+ end
140
+
141
+ def parse
142
+ @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
143
+ end
144
+
145
+ def isempty?
146
+ not @body
147
+ end
148
+
149
+ def do_accept(strategy)
150
+ strategy.text @body
151
+ end
152
+
153
+ end
154
+
155
+
156
+ class StructuredHeader < HeaderField
157
+
158
+ def comments
159
+ ensure_parsed
160
+ @comments
161
+ end
162
+
163
+ private
164
+
165
+ def parse
166
+ save = nil
167
+
168
+ begin
169
+ parse_init
170
+ do_parse
171
+ rescue SyntaxError
172
+ if not save and mime_encoded? @body
173
+ save = @body
174
+ @body = Decoder.decode(save)
175
+ retry
176
+ elsif save
177
+ @body = save
178
+ end
179
+
180
+ @illegal = true
181
+ raise if @config.strict_parse?
182
+ end
183
+ end
184
+
185
+ def parse_init
186
+ @comments = []
187
+ init
188
+ end
189
+
190
+ def do_parse
191
+ obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
192
+ set obj if obj
193
+ end
194
+
195
+ end
196
+
197
+
198
+ class DateTimeHeader < StructuredHeader
199
+
200
+ PARSE_TYPE = :DATETIME
201
+
202
+ def date
203
+ ensure_parsed
204
+ @date
205
+ end
206
+
207
+ def date=(arg)
208
+ ensure_parsed
209
+ @date = arg
210
+ end
211
+
212
+ private
213
+
214
+ def init
215
+ @date = nil
216
+ end
217
+
218
+ def set(t)
219
+ @date = t
220
+ end
221
+
222
+ def isempty?
223
+ not @date
224
+ end
225
+
226
+ def do_accept(strategy)
227
+ strategy.meta time2str(@date)
228
+ end
229
+
230
+ end
231
+
232
+
233
+ class AddressHeader < StructuredHeader
234
+
235
+ PARSE_TYPE = :MADDRESS
236
+
237
+ def addrs
238
+ ensure_parsed
239
+ @addrs
240
+ end
241
+
242
+ private
243
+
244
+ def init
245
+ @addrs = []
246
+ end
247
+
248
+ def set(a)
249
+ @addrs = a
250
+ end
251
+
252
+ def isempty?
253
+ @addrs.empty?
254
+ end
255
+
256
+ def do_accept(strategy)
257
+ first = true
258
+ @addrs.each do |a|
259
+ if first
260
+ first = false
261
+ else
262
+ strategy.meta ','
263
+ strategy.space
264
+ end
265
+ a.accept strategy
266
+ end
267
+
268
+ @comments.each do |c|
269
+ strategy.space
270
+ strategy.meta '('
271
+ strategy.text c
272
+ strategy.meta ')'
273
+ end
274
+ end
275
+
276
+ end
277
+
278
+
279
+ class ReturnPathHeader < AddressHeader
280
+
281
+ PARSE_TYPE = :RETPATH
282
+
283
+ def addr
284
+ addrs()[0]
285
+ end
286
+
287
+ def spec
288
+ a = addr() or return nil
289
+ a.spec
290
+ end
291
+
292
+ def routes
293
+ a = addr() or return nil
294
+ a.routes
295
+ end
296
+
297
+ private
298
+
299
+ def do_accept(strategy)
300
+ a = addr()
301
+
302
+ strategy.meta '<'
303
+ unless a.routes.empty?
304
+ strategy.meta a.routes.map {|i| '@' + i }.join(',')
305
+ strategy.meta ':'
306
+ end
307
+ spec = a.spec
308
+ strategy.meta spec if spec
309
+ strategy.meta '>'
310
+ end
311
+
312
+ end
313
+
314
+
315
+ class SingleAddressHeader < AddressHeader
316
+
317
+ def addr
318
+ addrs()[0]
319
+ end
320
+
321
+ private
322
+
323
+ def do_accept(strategy)
324
+ a = addr()
325
+ a.accept strategy
326
+ @comments.each do |c|
327
+ strategy.space
328
+ strategy.meta '('
329
+ strategy.text c
330
+ strategy.meta ')'
331
+ end
332
+ end
333
+
334
+ end
335
+
336
+
337
+ class MessageIdHeader < StructuredHeader
338
+
339
+ def id
340
+ ensure_parsed
341
+ @id
342
+ end
343
+
344
+ def id=(arg)
345
+ ensure_parsed
346
+ @id = arg
347
+ end
348
+
349
+ private
350
+
351
+ def init
352
+ @id = nil
353
+ end
354
+
355
+ def isempty?
356
+ not @id
357
+ end
358
+
359
+ def do_parse
360
+ @id = @body.slice(MESSAGE_ID) or
361
+ raise SyntaxError, "wrong Message-ID format: #{@body}"
362
+ end
363
+
364
+ def do_accept(strategy)
365
+ strategy.meta @id
366
+ end
367
+
368
+ end
369
+
370
+
371
+ class ReferencesHeader < StructuredHeader
372
+
373
+ def refs
374
+ ensure_parsed
375
+ @refs
376
+ end
377
+
378
+ def each_id
379
+ self.refs.each do |i|
380
+ yield i if MESSAGE_ID =~ i
381
+ end
382
+ end
383
+
384
+ def ids
385
+ ensure_parsed
386
+ @ids
387
+ end
388
+
389
+ def each_phrase
390
+ self.refs.each do |i|
391
+ yield i unless MESSAGE_ID =~ i
392
+ end
393
+ end
394
+
395
+ def phrases
396
+ result = []
397
+ each_phrase do |i|
398
+ result.push i
399
+ end
400
+ result
401
+ end
402
+
403
+ private
404
+
405
+ def init
406
+ @refs = []
407
+ @ids = []
408
+ end
409
+
410
+ def isempty?
411
+ @ids.empty?
412
+ end
413
+
414
+ def do_parse
415
+ str = @body
416
+ while m = MESSAGE_ID.match(str)
417
+ pre = m.pre_match.strip
418
+ @refs.push pre unless pre.empty?
419
+ @refs.push s = m[0]
420
+ @ids.push s
421
+ str = m.post_match
422
+ end
423
+ str = str.strip
424
+ @refs.push str unless str.empty?
425
+ end
426
+
427
+ def do_accept(strategy)
428
+ first = true
429
+ @ids.each do |i|
430
+ if first
431
+ first = false
432
+ else
433
+ strategy.space
434
+ end
435
+ strategy.meta i
436
+ end
437
+ end
438
+
439
+ end
440
+
441
+
442
+ class ReceivedHeader < StructuredHeader
443
+
444
+ PARSE_TYPE = :RECEIVED
445
+
446
+ def from
447
+ ensure_parsed
448
+ @from
449
+ end
450
+
451
+ def from=(arg)
452
+ ensure_parsed
453
+ @from = arg
454
+ end
455
+
456
+ def by
457
+ ensure_parsed
458
+ @by
459
+ end
460
+
461
+ def by=(arg)
462
+ ensure_parsed
463
+ @by = arg
464
+ end
465
+
466
+ def via
467
+ ensure_parsed
468
+ @via
469
+ end
470
+
471
+ def via=(arg)
472
+ ensure_parsed
473
+ @via = arg
474
+ end
475
+
476
+ def with
477
+ ensure_parsed
478
+ @with
479
+ end
480
+
481
+ def id
482
+ ensure_parsed
483
+ @id
484
+ end
485
+
486
+ def id=(arg)
487
+ ensure_parsed
488
+ @id = arg
489
+ end
490
+
491
+ def _for
492
+ ensure_parsed
493
+ @_for
494
+ end
495
+
496
+ def _for=(arg)
497
+ ensure_parsed
498
+ @_for = arg
499
+ end
500
+
501
+ def date
502
+ ensure_parsed
503
+ @date
504
+ end
505
+
506
+ def date=(arg)
507
+ ensure_parsed
508
+ @date = arg
509
+ end
510
+
511
+ private
512
+
513
+ def init
514
+ @from = @by = @via = @with = @id = @_for = nil
515
+ @with = []
516
+ @date = nil
517
+ end
518
+
519
+ def set(args)
520
+ @from, @by, @via, @with, @id, @_for, @date = *args
521
+ end
522
+
523
+ def isempty?
524
+ @with.empty? and not (@from or @by or @via or @id or @_for or @date)
525
+ end
526
+
527
+ def do_accept(strategy)
528
+ list = []
529
+ list.push 'from ' + @from if @from
530
+ list.push 'by ' + @by if @by
531
+ list.push 'via ' + @via if @via
532
+ @with.each do |i|
533
+ list.push 'with ' + i
534
+ end
535
+ list.push 'id ' + @id if @id
536
+ list.push 'for <' + @_for + '>' if @_for
537
+
538
+ first = true
539
+ list.each do |i|
540
+ strategy.space unless first
541
+ strategy.meta i
542
+ first = false
543
+ end
544
+ if @date
545
+ strategy.meta ';'
546
+ strategy.space
547
+ strategy.meta time2str(@date)
548
+ end
549
+ end
550
+
551
+ end
552
+
553
+
554
+ class KeywordsHeader < StructuredHeader
555
+
556
+ PARSE_TYPE = :KEYWORDS
557
+
558
+ def keys
559
+ ensure_parsed
560
+ @keys
561
+ end
562
+
563
+ private
564
+
565
+ def init
566
+ @keys = []
567
+ end
568
+
569
+ def set(a)
570
+ @keys = a
571
+ end
572
+
573
+ def isempty?
574
+ @keys.empty?
575
+ end
576
+
577
+ def do_accept(strategy)
578
+ first = true
579
+ @keys.each do |i|
580
+ if first
581
+ first = false
582
+ else
583
+ strategy.meta ','
584
+ end
585
+ strategy.meta i
586
+ end
587
+ end
588
+
589
+ end
590
+
591
+
592
+ class EncryptedHeader < StructuredHeader
593
+
594
+ PARSE_TYPE = :ENCRYPTED
595
+
596
+ def encrypter
597
+ ensure_parsed
598
+ @encrypter
599
+ end
600
+
601
+ def encrypter=(arg)
602
+ ensure_parsed
603
+ @encrypter = arg
604
+ end
605
+
606
+ def keyword
607
+ ensure_parsed
608
+ @keyword
609
+ end
610
+
611
+ def keyword=(arg)
612
+ ensure_parsed
613
+ @keyword = arg
614
+ end
615
+
616
+ private
617
+
618
+ def init
619
+ @encrypter = nil
620
+ @keyword = nil
621
+ end
622
+
623
+ def set(args)
624
+ @encrypter, @keyword = args
625
+ end
626
+
627
+ def isempty?
628
+ not (@encrypter or @keyword)
629
+ end
630
+
631
+ def do_accept(strategy)
632
+ if @key
633
+ strategy.meta @encrypter + ','
634
+ strategy.space
635
+ strategy.meta @keyword
636
+ else
637
+ strategy.meta @encrypter
638
+ end
639
+ end
640
+
641
+ end
642
+
643
+
644
+ class MimeVersionHeader < StructuredHeader
645
+
646
+ PARSE_TYPE = :MIMEVERSION
647
+
648
+ def major
649
+ ensure_parsed
650
+ @major
651
+ end
652
+
653
+ def major=(arg)
654
+ ensure_parsed
655
+ @major = arg
656
+ end
657
+
658
+ def minor
659
+ ensure_parsed
660
+ @minor
661
+ end
662
+
663
+ def minor=(arg)
664
+ ensure_parsed
665
+ @minor = arg
666
+ end
667
+
668
+ def version
669
+ sprintf('%d.%d', major, minor)
670
+ end
671
+
672
+ private
673
+
674
+ def init
675
+ @major = nil
676
+ @minor = nil
677
+ end
678
+
679
+ def set(args)
680
+ @major, @minor = *args
681
+ end
682
+
683
+ def isempty?
684
+ not (@major or @minor)
685
+ end
686
+
687
+ def do_accept(strategy)
688
+ strategy.meta sprintf('%d.%d', @major, @minor)
689
+ end
690
+
691
+ end
692
+
693
+
694
+ class ContentTypeHeader < StructuredHeader
695
+
696
+ PARSE_TYPE = :CTYPE
697
+
698
+ def main_type
699
+ ensure_parsed
700
+ @main
701
+ end
702
+
703
+ def main_type=(arg)
704
+ ensure_parsed
705
+ @main = arg.downcase
706
+ end
707
+
708
+ def sub_type
709
+ ensure_parsed
710
+ @sub
711
+ end
712
+
713
+ def sub_type=(arg)
714
+ ensure_parsed
715
+ @sub = arg.downcase
716
+ end
717
+
718
+ def content_type
719
+ ensure_parsed
720
+ @sub ? sprintf('%s/%s', @main, @sub) : @main
721
+ end
722
+
723
+ def params
724
+ ensure_parsed
725
+ @params
726
+ end
727
+
728
+ def [](key)
729
+ ensure_parsed
730
+ @params and @params[key]
731
+ end
732
+
733
+ def []=(key, val)
734
+ ensure_parsed
735
+ (@params ||= {})[key] = val
736
+ end
737
+
738
+ private
739
+
740
+ def init
741
+ @main = @sub = @params = nil
742
+ end
743
+
744
+ def set(args)
745
+ @main, @sub, @params = *args
746
+ end
747
+
748
+ def isempty?
749
+ not (@main or @sub)
750
+ end
751
+
752
+ def do_accept(strategy)
753
+ if @sub
754
+ strategy.meta sprintf('%s/%s', @main, @sub)
755
+ else
756
+ strategy.meta @main
757
+ end
758
+ @params.each do |k,v|
759
+ strategy.meta ';'
760
+ strategy.space
761
+ strategy.kv_pair k, v
762
+ end
763
+ end
764
+
765
+ end
766
+
767
+
768
+ class ContentTransferEncodingHeader < StructuredHeader
769
+
770
+ PARSE_TYPE = :CENCODING
771
+
772
+ def encoding
773
+ ensure_parsed
774
+ @encoding
775
+ end
776
+
777
+ def encoding=(arg)
778
+ ensure_parsed
779
+ @encoding = arg
780
+ end
781
+
782
+ private
783
+
784
+ def init
785
+ @encoding = nil
786
+ end
787
+
788
+ def set(s)
789
+ @encoding = s
790
+ end
791
+
792
+ def isempty?
793
+ not @encoding
794
+ end
795
+
796
+ def do_accept(strategy)
797
+ strategy.meta @encoding.capitalize
798
+ end
799
+
800
+ end
801
+
802
+
803
+ class ContentDispositionHeader < StructuredHeader
804
+
805
+ PARSE_TYPE = :CDISPOSITION
806
+
807
+ def disposition
808
+ ensure_parsed
809
+ @disposition
810
+ end
811
+
812
+ def disposition=(str)
813
+ ensure_parsed
814
+ @disposition = str.downcase
815
+ end
816
+
817
+ def params
818
+ ensure_parsed
819
+ @params
820
+ end
821
+
822
+ def [](key)
823
+ ensure_parsed
824
+ @params and @params[key]
825
+ end
826
+
827
+ def []=(key, val)
828
+ ensure_parsed
829
+ (@params ||= {})[key] = val
830
+ end
831
+
832
+ private
833
+
834
+ def init
835
+ @disposition = @params = nil
836
+ end
837
+
838
+ def set(args)
839
+ @disposition, @params = *args
840
+ end
841
+
842
+ def isempty?
843
+ not @disposition and (not @params or @params.empty?)
844
+ end
845
+
846
+ def do_accept(strategy)
847
+ strategy.meta @disposition
848
+ @params.each do |k,v|
849
+ strategy.meta ';'
850
+ strategy.space
851
+ strategy.kv_pair k, v
852
+ end
853
+ end
854
+
855
+ end
856
+
857
+
858
+ class HeaderField # redefine
859
+
860
+ FNAME_TO_CLASS = {
861
+ 'date' => DateTimeHeader,
862
+ 'resent-date' => DateTimeHeader,
863
+ 'to' => AddressHeader,
864
+ 'cc' => AddressHeader,
865
+ 'bcc' => AddressHeader,
866
+ 'from' => AddressHeader,
867
+ 'reply-to' => AddressHeader,
868
+ 'resent-to' => AddressHeader,
869
+ 'resent-cc' => AddressHeader,
870
+ 'resent-bcc' => AddressHeader,
871
+ 'resent-from' => AddressHeader,
872
+ 'resent-reply-to' => AddressHeader,
873
+ 'sender' => SingleAddressHeader,
874
+ 'resent-sender' => SingleAddressHeader,
875
+ 'return-path' => ReturnPathHeader,
876
+ 'message-id' => MessageIdHeader,
877
+ 'resent-message-id' => MessageIdHeader,
878
+ 'in-reply-to' => ReferencesHeader,
879
+ 'received' => ReceivedHeader,
880
+ 'references' => ReferencesHeader,
881
+ 'keywords' => KeywordsHeader,
882
+ 'encrypted' => EncryptedHeader,
883
+ 'mime-version' => MimeVersionHeader,
884
+ 'content-type' => ContentTypeHeader,
885
+ 'content-transfer-encoding' => ContentTransferEncodingHeader,
886
+ 'content-disposition' => ContentDispositionHeader,
887
+ 'content-id' => MessageIdHeader,
888
+ 'subject' => UnstructuredHeader,
889
+ 'comments' => UnstructuredHeader,
890
+ 'content-description' => UnstructuredHeader
891
+ }
892
+
893
+ end
894
+
895
+ end # module TMail