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.
30 require 'tmail/encode'
31 require 'tmail/address'
32 require 'tmail/parser'
33 require 'tmail/config'
47 def new( name, body, conf = DEFAULT_CONFIG )
48 klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
49 klass.newobj body, conf
52 def new_from_port( port, name, conf = DEFAULT_CONFIG )
53 re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
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
64 new(name, str, Config.to_config(conf))
67 def internal_new( name, conf )
68 FNAME_TO_CLASS[name].newobj('', conf, true)
73 def initialize( body, conf, intern = false )
86 "#<#{self.class} #{@body.inspect}>"
95 return true if @illegal
110 def clear_parse_status
119 v = Decoder.new(s = '')
130 include StrategyInterface
132 def accept( strategy, dummy1 = nil, dummy2 = nil )
143 class UnstructuredHeader < HeaderField
161 @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
168 def do_accept( strategy )
175 class StructuredHeader < HeaderField
191 if not save and mime_encoded? @body
193 @body = Decoder.decode(save)
200 raise if @config.strict_parse?
210 obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
217 class DateTimeHeader < StructuredHeader
219 PARSE_TYPE = :DATETIME
245 def do_accept( strategy )
246 strategy.meta time2str(@date)
252 class AddressHeader < StructuredHeader
254 PARSE_TYPE = :MADDRESS
275 def do_accept( strategy )
287 @comments.each do |c|
298 class ReturnPathHeader < AddressHeader
300 PARSE_TYPE = :RETPATH
307 a = addr() or return nil
312 a = addr() or return nil
318 def do_accept( strategy )
322 unless a.routes.empty?
323 strategy.meta a.routes.map {|i| '@' + i }.join(',')
327 strategy.meta spec if spec
334 class SingleAddressHeader < AddressHeader
342 def do_accept( strategy )
345 @comments.each do |c|
356 class MessageIdHeader < StructuredHeader
379 @id = @body.slice(MESSAGE_ID) or
380 raise SyntaxError, "wrong Message-ID format: #{@body}"
383 def do_accept( strategy )
390 class ReferencesHeader < StructuredHeader
398 self.refs.each do |i|
399 yield i if MESSAGE_ID === i
409 self.refs.each do |i|
410 yield i unless MESSAGE_ID === i
416 each_phrase {|i| ret.push i }
433 while m = MESSAGE_ID.match(str)
434 pre = m.pre_match.strip
435 @refs.push pre unless pre.empty?
441 @refs.push str unless str.empty?
444 def do_accept( strategy )
459 class ReceivedHeader < StructuredHeader
461 PARSE_TYPE = :RECEIVED
531 @from = @by = @via = @with = @id = @_for = nil
537 @from, @by, @via, @with, @id, @_for, @date = *args
541 @with.empty? and not (@from or @by or @via or @id or @_for or @date)
544 def do_accept( strategy )
546 list.push 'from ' + @from if @from
547 list.push 'by ' + @by if @by
548 list.push 'via ' + @via if @via
550 list.push 'with ' + i
552 list.push 'id ' + @id if @id
553 list.push 'for <' + @_for + '>' if @_for
557 strategy.space unless first
564 strategy.meta time2str(@date)
571 class KeywordsHeader < StructuredHeader
573 PARSE_TYPE = :KEYWORDS
594 def do_accept( strategy )
609 class EncryptedHeader < StructuredHeader
611 PARSE_TYPE = :ENCRYPTED
618 def encrypter=( arg )
641 @encrypter, @keyword = args
645 not (@encrypter or @keyword)
648 def do_accept( strategy )
650 strategy.meta @encrypter + ','
652 strategy.meta @keyword
654 strategy.meta @encrypter
661 class MimeVersionHeader < StructuredHeader
663 PARSE_TYPE = :MIMEVERSION
686 sprintf('%d.%d', major, minor)
697 @major, @minor = *args
701 not (@major or @minor)
704 def do_accept( strategy )
705 strategy.meta sprintf('%d.%d', @major, @minor)
711 class ContentTypeHeader < StructuredHeader
720 def main_type=( arg )
737 @sub ? sprintf('%s/%s', @main, @sub) : @main
747 @params and @params[key]
752 (@params ||= {})[key] = val
758 @main = @sub = @params = nil
762 @main, @sub, @params = *args
769 def do_accept( strategy )
771 strategy.meta sprintf('%s/%s', @main, @sub)
775 @params.each do |k,v|
779 strategy.kv_pair k, v
787 class ContentTransferEncodingHeader < StructuredHeader
789 PARSE_TYPE = :CENCODING
815 def do_accept( strategy )
816 strategy.meta @encoding.capitalize
822 class ContentDispositionHeader < StructuredHeader
824 PARSE_TYPE = :CDISPOSITION
831 def disposition=( str )
833 @disposition = str.downcase
843 @params and @params[key]
848 (@params ||= {})[key] = val
854 @disposition = @params = nil
858 @disposition, @params = *args
862 not @disposition and (not @params or @params.empty?)
865 def do_accept( strategy )
866 strategy.meta @disposition
867 @params.each do |k,v|
870 strategy.kv_pair k, v
877 class HeaderField # redefine
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