removing log dir from .gitignore
[monkeycharger.git] / vendor / rails / actionmailer / lib / action_mailer / vendor / tmail / header.rb
blobbe97803def6e3df1eed3b427e8c11d803cb36666
2 # header.rb
4 #--
5 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27 # with permission of Minero Aoki.
28 #++
30 require 'tmail/encode'
31 require 'tmail/address'
32 require 'tmail/parser'
33 require 'tmail/config'
34 require 'tmail/utils'
37 module TMail
39   class HeaderField
41     include TextUtils
43     class << self
45       alias newobj new
47       def new( name, body, conf = DEFAULT_CONFIG )
48         klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
49         klass.newobj body, conf
50       end
52       def new_from_port( port, name, conf = DEFAULT_CONFIG )
53         re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
54         str = nil
55         port.ropen {|f|
56             f.each do |line|
57               if m = re.match(line)            then str = m.post_match.strip
58               elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
59               elsif /\A-*\s*\z/ === line       then break
60               elsif str                        then break
61               end
62             end
63         }
64         new(name, str, Config.to_config(conf))
65       end
67       def internal_new( name, conf )
68         FNAME_TO_CLASS[name].newobj('', conf, true)
69       end
71     end   # class << self
73     def initialize( body, conf, intern = false )
74       @body = body
75       @config = conf
77       @illegal = false
78       @parsed = false
79       if intern
80         @parsed = true
81         parse_init
82       end
83     end
85     def inspect
86       "#<#{self.class} #{@body.inspect}>"
87     end
89     def illegal?
90       @illegal
91     end
93     def empty?
94       ensure_parsed
95       return true if @illegal
96       isempty?
97     end
99     private
101     def ensure_parsed
102       return if @parsed
103       @parsed = true
104       parse
105     end
107     # defabstract parse
108     # end
110     def clear_parse_status
111       @parsed = false
112       @illegal = false
113     end
115     public
117     def body
118       ensure_parsed
119       v = Decoder.new(s = '')
120       do_accept v
121       v.terminate
122       s
123     end
125     def body=( str )
126       @body = str
127       clear_parse_status
128     end
130     include StrategyInterface
132     def accept( strategy, dummy1 = nil, dummy2 = nil )
133       ensure_parsed
134       do_accept strategy
135       strategy.terminate
136     end
138     # abstract do_accept
140   end
143   class UnstructuredHeader < HeaderField
145     def body
146       ensure_parsed
147       @body
148     end
150     def body=( arg )
151       ensure_parsed
152       @body = arg
153     end
155     private
157     def parse_init
158     end
160     def parse
161       @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
162     end
164     def isempty?
165       not @body
166     end
168     def do_accept( strategy )
169       strategy.text @body
170     end
172   end
175   class StructuredHeader < HeaderField
177     def comments
178       ensure_parsed
179       @comments
180     end
182     private
184     def parse
185       save = nil
187       begin
188         parse_init
189         do_parse
190       rescue SyntaxError
191         if not save and mime_encoded? @body
192           save = @body
193           @body = Decoder.decode(save)
194           retry
195         elsif save
196           @body = save
197         end
199         @illegal = true
200         raise if @config.strict_parse?
201       end
202     end
204     def parse_init
205       @comments = []
206       init
207     end
209     def do_parse
210       obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
211       set obj if obj
212     end
214   end
217   class DateTimeHeader < StructuredHeader
219     PARSE_TYPE = :DATETIME
221     def date
222       ensure_parsed
223       @date
224     end
226     def date=( arg )
227       ensure_parsed
228       @date = arg
229     end
231     private
233     def init
234       @date = nil
235     end
237     def set( t )
238       @date = t
239     end
241     def isempty?
242       not @date
243     end
245     def do_accept( strategy )
246       strategy.meta time2str(@date)
247     end
249   end
252   class AddressHeader < StructuredHeader
254     PARSE_TYPE = :MADDRESS
256     def addrs
257       ensure_parsed
258       @addrs
259     end
261     private
263     def init
264       @addrs = []
265     end
267     def set( a )
268       @addrs = a
269     end
271     def isempty?
272       @addrs.empty?
273     end
275     def do_accept( strategy )
276       first = true
277       @addrs.each do |a|
278         if first
279           first = false
280         else
281           strategy.meta ','
282           strategy.space
283         end
284         a.accept strategy
285       end
287       @comments.each do |c|
288         strategy.space
289         strategy.meta '('
290         strategy.text c
291         strategy.meta ')'
292       end
293     end
295   end
298   class ReturnPathHeader < AddressHeader
300     PARSE_TYPE = :RETPATH
302     def addr
303       addrs()[0]
304     end
306     def spec
307       a = addr() or return nil
308       a.spec
309     end
311     def routes
312       a = addr() or return nil
313       a.routes
314     end
316     private
318     def do_accept( strategy )
319       a = addr()
321       strategy.meta '<'
322       unless a.routes.empty?
323         strategy.meta a.routes.map {|i| '@' + i }.join(',')
324         strategy.meta ':'
325       end
326       spec = a.spec
327       strategy.meta spec if spec
328       strategy.meta '>'
329     end
331   end
334   class SingleAddressHeader < AddressHeader
336     def addr
337       addrs()[0]
338     end
340     private
342     def do_accept( strategy )
343       a = addr()
344       a.accept strategy
345       @comments.each do |c|
346         strategy.space
347         strategy.meta '('
348         strategy.text c
349         strategy.meta ')'
350       end
351     end
353   end
356   class MessageIdHeader < StructuredHeader
358     def id
359       ensure_parsed
360       @id
361     end
363     def id=( arg )
364       ensure_parsed
365       @id = arg
366     end
368     private
370     def init
371       @id = nil
372     end
374     def isempty?
375       not @id
376     end
378     def do_parse
379       @id = @body.slice(MESSAGE_ID) or
380               raise SyntaxError, "wrong Message-ID format: #{@body}"
381     end
383     def do_accept( strategy )
384       strategy.meta @id
385     end
387   end
390   class ReferencesHeader < StructuredHeader
392     def refs
393       ensure_parsed
394       @refs
395     end
397     def each_id
398       self.refs.each do |i|
399         yield i if MESSAGE_ID === i
400       end
401     end
403     def ids
404       ensure_parsed
405       @ids
406     end
408     def each_phrase
409       self.refs.each do |i|
410         yield i unless MESSAGE_ID === i
411       end
412     end
414     def phrases
415       ret = []
416       each_phrase {|i| ret.push i }
417       ret
418     end
420     private
422     def init
423       @refs = []
424       @ids = []
425     end
427     def isempty?
428       @ids.empty?
429     end
431     def do_parse
432       str = @body
433       while m = MESSAGE_ID.match(str)
434         pre = m.pre_match.strip
435         @refs.push pre unless pre.empty?
436         @refs.push s = m[0]
437         @ids.push s
438         str = m.post_match
439       end
440       str = str.strip
441       @refs.push str unless str.empty?
442     end
444     def do_accept( strategy )
445       first = true
446       @ids.each do |i|
447         if first
448           first = false
449         else
450           strategy.space
451         end
452         strategy.meta i
453       end
454     end
456   end
459   class ReceivedHeader < StructuredHeader
461     PARSE_TYPE = :RECEIVED
463     def from
464       ensure_parsed
465       @from
466     end
468     def from=( arg )
469       ensure_parsed
470       @from = arg
471     end
473     def by
474       ensure_parsed
475       @by
476     end
478     def by=( arg )
479       ensure_parsed
480       @by = arg
481     end
483     def via
484       ensure_parsed
485       @via
486     end
488     def via=( arg )
489       ensure_parsed
490       @via = arg
491     end
493     def with
494       ensure_parsed
495       @with
496     end
498     def id
499       ensure_parsed
500       @id
501     end
503     def id=( arg )
504       ensure_parsed
505       @id = arg
506     end
508     def _for
509       ensure_parsed
510       @_for
511     end
513     def _for=( arg )
514       ensure_parsed
515       @_for = arg
516     end
518     def date
519       ensure_parsed
520       @date
521     end
523     def date=( arg )
524       ensure_parsed
525       @date = arg
526     end
528     private
530     def init
531       @from = @by = @via = @with = @id = @_for = nil
532       @with = []
533       @date = nil
534     end
536     def set( args )
537       @from, @by, @via, @with, @id, @_for, @date = *args
538     end
540     def isempty?
541       @with.empty? and not (@from or @by or @via or @id or @_for or @date)
542     end
544     def do_accept( strategy )
545       list = []
546       list.push 'from '  + @from       if @from
547       list.push 'by '    + @by         if @by
548       list.push 'via '   + @via        if @via
549       @with.each do |i|
550         list.push 'with ' + i
551       end
552       list.push 'id '    + @id         if @id
553       list.push 'for <'  + @_for + '>' if @_for
555       first = true
556       list.each do |i|
557         strategy.space unless first
558         strategy.meta i
559         first = false
560       end
561       if @date
562         strategy.meta ';'
563         strategy.space
564         strategy.meta time2str(@date)
565       end
566     end
568   end
571   class KeywordsHeader < StructuredHeader
573     PARSE_TYPE = :KEYWORDS
575     def keys
576       ensure_parsed
577       @keys
578     end
580     private
582     def init
583       @keys = []
584     end
586     def set( a )
587       @keys = a
588     end
590     def isempty?
591       @keys.empty?
592     end
594     def do_accept( strategy )
595       first = true
596       @keys.each do |i|
597         if first
598           first = false
599         else
600           strategy.meta ','
601         end
602         strategy.meta i
603       end
604     end
606   end
609   class EncryptedHeader < StructuredHeader
611     PARSE_TYPE = :ENCRYPTED
613     def encrypter
614       ensure_parsed
615       @encrypter
616     end
618     def encrypter=( arg )
619       ensure_parsed
620       @encrypter = arg
621     end
623     def keyword
624       ensure_parsed
625       @keyword
626     end
628     def keyword=( arg )
629       ensure_parsed
630       @keyword = arg
631     end
633     private
635     def init
636       @encrypter = nil
637       @keyword = nil
638     end
640     def set( args )
641       @encrypter, @keyword = args
642     end
644     def isempty?
645       not (@encrypter or @keyword)
646     end
648     def do_accept( strategy )
649       if @key
650         strategy.meta @encrypter + ','
651         strategy.space
652         strategy.meta @keyword
653       else
654         strategy.meta @encrypter
655       end
656     end
658   end
661   class MimeVersionHeader < StructuredHeader
663     PARSE_TYPE = :MIMEVERSION
665     def major
666       ensure_parsed
667       @major
668     end
670     def major=( arg )
671       ensure_parsed
672       @major = arg
673     end
675     def minor
676       ensure_parsed
677       @minor
678     end
680     def minor=( arg )
681       ensure_parsed
682       @minor = arg
683     end
685     def version
686       sprintf('%d.%d', major, minor)
687     end
689     private
691     def init
692       @major = nil
693       @minor = nil
694     end
696     def set( args )
697       @major, @minor = *args
698     end
700     def isempty?
701       not (@major or @minor)
702     end
704     def do_accept( strategy )
705       strategy.meta sprintf('%d.%d', @major, @minor)
706     end
708   end
711   class ContentTypeHeader < StructuredHeader
713     PARSE_TYPE = :CTYPE
715     def main_type
716       ensure_parsed
717       @main
718     end
720     def main_type=( arg )
721       ensure_parsed
722       @main = arg.downcase
723     end
725     def sub_type
726       ensure_parsed
727       @sub
728     end
730     def sub_type=( arg )
731       ensure_parsed
732       @sub = arg.downcase
733     end
735     def content_type
736       ensure_parsed
737       @sub ? sprintf('%s/%s', @main, @sub) : @main
738     end
740     def params
741       ensure_parsed
742       @params
743     end
745     def []( key )
746       ensure_parsed
747       @params and @params[key]
748     end
750     def []=( key, val )
751       ensure_parsed
752       (@params ||= {})[key] = val
753     end
755     private
757     def init
758       @main = @sub = @params = nil
759     end
761     def set( args )
762       @main, @sub, @params = *args
763     end
765     def isempty?
766       not (@main or @sub)
767     end
769     def do_accept( strategy )
770       if @sub
771         strategy.meta sprintf('%s/%s', @main, @sub)
772       else
773         strategy.meta @main
774       end
775       @params.each do |k,v|
776         if v
777           strategy.meta ';'
778           strategy.space
779           strategy.kv_pair k, v
780         end
781       end
782     end
784   end
787   class ContentTransferEncodingHeader < StructuredHeader
789     PARSE_TYPE = :CENCODING
791     def encoding
792       ensure_parsed
793       @encoding
794     end
796     def encoding=( arg )
797       ensure_parsed
798       @encoding = arg
799     end
801     private
803     def init
804       @encoding = nil
805     end
807     def set( s )
808       @encoding = s
809     end
811     def isempty?
812       not @encoding
813     end
815     def do_accept( strategy )
816       strategy.meta @encoding.capitalize
817     end
819   end
822   class ContentDispositionHeader < StructuredHeader
824     PARSE_TYPE = :CDISPOSITION
826     def disposition
827       ensure_parsed
828       @disposition
829     end
831     def disposition=( str )
832       ensure_parsed
833       @disposition = str.downcase
834     end
836     def params
837       ensure_parsed
838       @params
839     end
841     def []( key )
842       ensure_parsed
843       @params and @params[key]
844     end
846     def []=( key, val )
847       ensure_parsed
848       (@params ||= {})[key] = val
849     end
851     private
853     def init
854       @disposition = @params = nil
855     end
857     def set( args )
858       @disposition, @params = *args
859     end
861     def isempty?
862       not @disposition and (not @params or @params.empty?)
863     end
865     def do_accept( strategy )
866       strategy.meta @disposition
867       @params.each do |k,v|
868         strategy.meta ';'
869         strategy.space
870         strategy.kv_pair k, v
871       end
872     end
873       
874   end
877   class HeaderField   # redefine
879     FNAME_TO_CLASS = {
880       'date'                      => DateTimeHeader,
881       'resent-date'               => DateTimeHeader,
882       'to'                        => AddressHeader,
883       'cc'                        => AddressHeader,
884       'bcc'                       => AddressHeader,
885       'from'                      => AddressHeader,
886       'reply-to'                  => AddressHeader,
887       'resent-to'                 => AddressHeader,
888       'resent-cc'                 => AddressHeader,
889       'resent-bcc'                => AddressHeader,
890       'resent-from'               => AddressHeader,
891       'resent-reply-to'           => AddressHeader,
892       'sender'                    => SingleAddressHeader,
893       'resent-sender'             => SingleAddressHeader,
894       'return-path'               => ReturnPathHeader,
895       'message-id'                => MessageIdHeader,
896       'resent-message-id'         => MessageIdHeader,
897       'in-reply-to'               => ReferencesHeader,
898       'received'                  => ReceivedHeader,
899       'references'                => ReferencesHeader,
900       'keywords'                  => KeywordsHeader,
901       'encrypted'                 => EncryptedHeader,
902       'mime-version'              => MimeVersionHeader,
903       'content-type'              => ContentTypeHeader,
904       'content-transfer-encoding' => ContentTransferEncodingHeader,
905       'content-disposition'       => ContentDispositionHeader,
906       'content-id'                => MessageIdHeader,
907       'subject'                   => UnstructuredHeader,
908       'comments'                  => UnstructuredHeader,
909       'content-description'       => UnstructuredHeader
910     }
912   end
914 end   # module TMail