Froze rails gems
[depot.git] / vendor / rails / actionmailer / lib / action_mailer / vendor / tmail-1.2.3 / tmail / header.rb
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 @date = nil
271 end
272
273 def set( t )
274 @date = t
275 end
276
277 def isempty?
278 not @date
279 end
280
281 def do_accept( strategy )
282 strategy.meta time2str(@date)
283 end
284
285 end
286
287
288 class AddressHeader < StructuredHeader
289
290 PARSE_TYPE = :MADDRESS
291
292 def addrs
293 ensure_parsed
294 @addrs
295 end
296
297 private
298
299 def init
300 @addrs = []
301 end
302
303 def set( a )
304 @addrs = a
305 end
306
307 def isempty?
308 @addrs.empty?
309 end
310
311 def do_accept( strategy )
312 first = true
313 @addrs.each do |a|
314 if first
315 first = false
316 else
317 strategy.meta ','
318 strategy.space
319 end
320 a.accept strategy
321 end
322
323 @comments.each do |c|
324 strategy.space
325 strategy.meta '('
326 strategy.text c
327 strategy.meta ')'
328 end
329 end
330
331 end
332
333
334 class ReturnPathHeader < AddressHeader
335
336 PARSE_TYPE = :RETPATH
337
338 def addr
339 addrs()[0]
340 end
341
342 def spec
343 a = addr() or return nil
344 a.spec
345 end
346
347 def routes
348 a = addr() or return nil
349 a.routes
350 end
351
352 private
353
354 def do_accept( strategy )
355 a = addr()
356
357 strategy.meta '<'
358 unless a.routes.empty?
359 strategy.meta a.routes.map {|i| '@' + i }.join(',')
360 strategy.meta ':'
361 end
362 spec = a.spec
363 strategy.meta spec if spec
364 strategy.meta '>'
365 end
366
367 end
368
369
370 class SingleAddressHeader < AddressHeader
371
372 def addr
373 addrs()[0]
374 end
375
376 private
377
378 def do_accept( strategy )
379 a = addr()
380 a.accept strategy
381 @comments.each do |c|
382 strategy.space
383 strategy.meta '('
384 strategy.text c
385 strategy.meta ')'
386 end
387 end
388
389 end
390
391
392 class MessageIdHeader < StructuredHeader
393
394 def id
395 ensure_parsed
396 @id
397 end
398
399 def id=( arg )
400 ensure_parsed
401 @id = arg
402 end
403
404 private
405
406 def init
407 @id = nil
408 end
409
410 def isempty?
411 not @id
412 end
413
414 def do_parse
415 @id = @body.slice(MESSAGE_ID) or
416 raise SyntaxError, "wrong Message-ID format: #{@body}"
417 end
418
419 def do_accept( strategy )
420 strategy.meta @id
421 end
422
423 end
424
425
426 class ReferencesHeader < StructuredHeader
427
428 def refs
429 ensure_parsed
430 @refs
431 end
432
433 def each_id
434 self.refs.each do |i|
435 yield i if MESSAGE_ID === i
436 end
437 end
438
439 def ids
440 ensure_parsed
441 @ids
442 end
443
444 def each_phrase
445 self.refs.each do |i|
446 yield i unless MESSAGE_ID === i
447 end
448 end
449
450 def phrases
451 ret = []
452 each_phrase {|i| ret.push i }
453 ret
454 end
455
456 private
457
458 def init
459 @refs = []
460 @ids = []
461 end
462
463 def isempty?
464 @ids.empty?
465 end
466
467 def do_parse
468 str = @body
469 while m = MESSAGE_ID.match(str)
470 pre = m.pre_match.strip
471 @refs.push pre unless pre.empty?
472 @refs.push s = m[0]
473 @ids.push s
474 str = m.post_match
475 end
476 str = str.strip
477 @refs.push str unless str.empty?
478 end
479
480 def do_accept( strategy )
481 first = true
482 @ids.each do |i|
483 if first
484 first = false
485 else
486 strategy.space
487 end
488 strategy.meta i
489 end
490 end
491
492 end
493
494
495 class ReceivedHeader < StructuredHeader
496
497 PARSE_TYPE = :RECEIVED
498
499 def from
500 ensure_parsed
501 @from
502 end
503
504 def from=( arg )
505 ensure_parsed
506 @from = arg
507 end
508
509 def by
510 ensure_parsed
511 @by
512 end
513
514 def by=( arg )
515 ensure_parsed
516 @by = arg
517 end
518
519 def via
520 ensure_parsed
521 @via
522 end
523
524 def via=( arg )
525 ensure_parsed
526 @via = arg
527 end
528
529 def with
530 ensure_parsed
531 @with
532 end
533
534 def id
535 ensure_parsed
536 @id
537 end
538
539 def id=( arg )
540 ensure_parsed
541 @id = arg
542 end
543
544 def _for
545 ensure_parsed
546 @_for
547 end
548
549 def _for=( arg )
550 ensure_parsed
551 @_for = arg
552 end
553
554 def date
555 ensure_parsed
556 @date
557 end
558
559 def date=( arg )
560 ensure_parsed
561 @date = arg
562 end
563
564 private
565
566 def init
567 @from = @by = @via = @with = @id = @_for = nil
568 @with = []
569 @date = nil
570 end
571
572 def set( args )
573 @from, @by, @via, @with, @id, @_for, @date = *args
574 end
575
576 def isempty?
577 @with.empty? and not (@from or @by or @via or @id or @_for or @date)
578 end
579
580 def do_accept( strategy )
581 list = []
582 list.push 'from ' + @from if @from
583 list.push 'by ' + @by if @by
584 list.push 'via ' + @via if @via
585 @with.each do |i|
586 list.push 'with ' + i
587 end
588 list.push 'id ' + @id if @id
589 list.push 'for <' + @_for + '>' if @_for
590
591 first = true
592 list.each do |i|
593 strategy.space unless first
594 strategy.meta i
595 first = false
596 end
597 if @date
598 strategy.meta ';'
599 strategy.space
600 strategy.meta time2str(@date)
601 end
602 end
603
604 end
605
606
607 class KeywordsHeader < StructuredHeader
608
609 PARSE_TYPE = :KEYWORDS
610
611 def keys
612 ensure_parsed
613 @keys
614 end
615
616 private
617
618 def init
619 @keys = []
620 end
621
622 def set( a )
623 @keys = a
624 end
625
626 def isempty?
627 @keys.empty?
628 end
629
630 def do_accept( strategy )
631 first = true
632 @keys.each do |i|
633 if first
634 first = false
635 else
636 strategy.meta ','
637 end
638 strategy.meta i
639 end
640 end
641
642 end
643
644
645 class EncryptedHeader < StructuredHeader
646
647 PARSE_TYPE = :ENCRYPTED
648
649 def encrypter
650 ensure_parsed
651 @encrypter
652 end
653
654 def encrypter=( arg )
655 ensure_parsed
656 @encrypter = arg
657 end
658
659 def keyword
660 ensure_parsed
661 @keyword
662 end
663
664 def keyword=( arg )
665 ensure_parsed
666 @keyword = arg
667 end
668
669 private
670
671 def init
672 @encrypter = nil
673 @keyword = nil
674 end
675
676 def set( args )
677 @encrypter, @keyword = args
678 end
679
680 def isempty?
681 not (@encrypter or @keyword)
682 end
683
684 def do_accept( strategy )
685 if @key
686 strategy.meta @encrypter + ','
687 strategy.space
688 strategy.meta @keyword
689 else
690 strategy.meta @encrypter
691 end
692 end
693
694 end
695
696
697 class MimeVersionHeader < StructuredHeader
698
699 PARSE_TYPE = :MIMEVERSION
700
701 def major
702 ensure_parsed
703 @major
704 end
705
706 def major=( arg )
707 ensure_parsed
708 @major = arg
709 end
710
711 def minor
712 ensure_parsed
713 @minor
714 end
715
716 def minor=( arg )
717 ensure_parsed
718 @minor = arg
719 end
720
721 def version
722 sprintf('%d.%d', major, minor)
723 end
724
725 private
726
727 def init
728 @major = nil
729 @minor = nil
730 end
731
732 def set( args )
733 @major, @minor = *args
734 end
735
736 def isempty?
737 not (@major or @minor)
738 end
739
740 def do_accept( strategy )
741 strategy.meta sprintf('%d.%d', @major, @minor)
742 end
743
744 end
745
746
747 class ContentTypeHeader < StructuredHeader
748
749 PARSE_TYPE = :CTYPE
750
751 def main_type
752 ensure_parsed
753 @main
754 end
755
756 def main_type=( arg )
757 ensure_parsed
758 @main = arg.downcase
759 end
760
761 def sub_type
762 ensure_parsed
763 @sub
764 end
765
766 def sub_type=( arg )
767 ensure_parsed
768 @sub = arg.downcase
769 end
770
771 def content_type
772 ensure_parsed
773 @sub ? sprintf('%s/%s', @main, @sub) : @main
774 end
775
776 def params
777 ensure_parsed
778 unless @params.blank?
779 @params.each do |k, v|
780 @params[k] = unquote(v)
781 end
782 end
783 @params
784 end
785
786 def []( key )
787 ensure_parsed
788 @params and unquote(@params[key])
789 end
790
791 def []=( key, val )
792 ensure_parsed
793 (@params ||= {})[key] = val
794 end
795
796 private
797
798 def init
799 @main = @sub = @params = nil
800 end
801
802 def set( args )
803 @main, @sub, @params = *args
804 end
805
806 def isempty?
807 not (@main or @sub)
808 end
809
810 def do_accept( strategy )
811 if @sub
812 strategy.meta sprintf('%s/%s', @main, @sub)
813 else
814 strategy.meta @main
815 end
816 @params.each do |k,v|
817 if v
818 strategy.meta ';'
819 strategy.space
820 strategy.kv_pair k, v
821 end
822 end
823 end
824
825 end
826
827
828 class ContentTransferEncodingHeader < StructuredHeader
829
830 PARSE_TYPE = :CENCODING
831
832 def encoding
833 ensure_parsed
834 @encoding
835 end
836
837 def encoding=( arg )
838 ensure_parsed
839 @encoding = arg
840 end
841
842 private
843
844 def init
845 @encoding = nil
846 end
847
848 def set( s )
849 @encoding = s
850 end
851
852 def isempty?
853 not @encoding
854 end
855
856 def do_accept( strategy )
857 strategy.meta @encoding.capitalize
858 end
859
860 end
861
862
863 class ContentDispositionHeader < StructuredHeader
864
865 PARSE_TYPE = :CDISPOSITION
866
867 def disposition
868 ensure_parsed
869 @disposition
870 end
871
872 def disposition=( str )
873 ensure_parsed
874 @disposition = str.downcase
875 end
876
877 def params
878 ensure_parsed
879 unless @params.blank?
880 @params.each do |k, v|
881 @params[k] = unquote(v)
882 end
883 end
884 @params
885 end
886
887 def []( key )
888 ensure_parsed
889 @params and unquote(@params[key])
890 end
891
892 def []=( key, val )
893 ensure_parsed
894 (@params ||= {})[key] = val
895 end
896
897 private
898
899 def init
900 @disposition = @params = nil
901 end
902
903 def set( args )
904 @disposition, @params = *args
905 end
906
907 def isempty?
908 not @disposition and (not @params or @params.empty?)
909 end
910
911 def do_accept( strategy )
912 strategy.meta @disposition
913 @params.each do |k,v|
914 strategy.meta ';'
915 strategy.space
916 strategy.kv_pair k, unquote(v)
917 end
918 end
919
920 end
921
922
923 class HeaderField # redefine
924
925 FNAME_TO_CLASS = {
926 'date' => DateTimeHeader,
927 'resent-date' => DateTimeHeader,
928 'to' => AddressHeader,
929 'cc' => AddressHeader,
930 'bcc' => AddressHeader,
931 'from' => AddressHeader,
932 'reply-to' => AddressHeader,
933 'resent-to' => AddressHeader,
934 'resent-cc' => AddressHeader,
935 'resent-bcc' => AddressHeader,
936 'resent-from' => AddressHeader,
937 'resent-reply-to' => AddressHeader,
938 'sender' => SingleAddressHeader,
939 'resent-sender' => SingleAddressHeader,
940 'return-path' => ReturnPathHeader,
941 'message-id' => MessageIdHeader,
942 'resent-message-id' => MessageIdHeader,
943 'in-reply-to' => ReferencesHeader,
944 'received' => ReceivedHeader,
945 'references' => ReferencesHeader,
946 'keywords' => KeywordsHeader,
947 'encrypted' => EncryptedHeader,
948 'mime-version' => MimeVersionHeader,
949 'content-type' => ContentTypeHeader,
950 'content-transfer-encoding' => ContentTransferEncodingHeader,
951 'content-disposition' => ContentDispositionHeader,
952 'content-id' => MessageIdHeader,
953 'subject' => UnstructuredHeader,
954 'comments' => UnstructuredHeader,
955 'content-description' => UnstructuredHeader
956 }
957
958 end
959
960 end # module TMail