diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2008-05-19 19:04:28 -0700 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2008-05-19 19:04:28 -0700 |
commit | 762295e0ae9d67450403596412bb8489893378d9 (patch) | |
tree | de5a6344505601fcb3dabbadc708018f0ed3fef2 /actionmailer/lib/action_mailer/vendor/tmail-1.2.3 | |
parent | b43309328a4a4c1f6fda6db40a4a86a3c8d643fc (diff) | |
download | rails-762295e0ae9d67450403596412bb8489893378d9.tar.gz rails-762295e0ae9d67450403596412bb8489893378d9.tar.bz2 rails-762295e0ae9d67450403596412bb8489893378d9.zip |
Bundle tmail 1.2.3
Diffstat (limited to 'actionmailer/lib/action_mailer/vendor/tmail-1.2.3')
27 files changed, 7836 insertions, 0 deletions
diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb new file mode 100644 index 0000000000..18003659a6 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb @@ -0,0 +1,5 @@ +require 'tmail/version' +require 'tmail/mail' +require 'tmail/mailbox' +require 'tmail/core_extensions' +require 'tmail/net' diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb new file mode 100644 index 0000000000..fa8e5bcd8c --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb @@ -0,0 +1,426 @@ +=begin rdoc + += Address handling class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/encode' +require 'tmail/parser' + + +module TMail + + # = Class Address + # + # Provides a complete handling library for email addresses. Can parse a string of an + # address directly or take in preformatted addresses themseleves. Allows you to add + # and remove phrases from the front of the address and provides a compare function for + # email addresses. + # + # == Parsing and Handling a Valid Address: + # + # Just pass the email address in as a string to Address.parse: + # + # email = TMail::Address.parse('Mikel Lindsaar <mikel@lindsaar.net>) + # #=> #<TMail::Address mikel@lindsaar.net> + # email.address + # #=> "mikel@lindsaar.net" + # email.local + # #=> "mikel" + # email.domain + # #=> "lindsaar.net" + # email.name # Aliased as phrase as well + # #=> "Mikel Lindsaar" + # + # == Detecting an Invalid Address + # + # If you want to check the syntactical validity of an email address, just pass it to + # Address.parse and catch any SyntaxError: + # + # begin + # TMail::Mail.parse("mikel 2@@@@@ me .com") + # rescue TMail::SyntaxError + # puts("Invalid Email Address Detected") + # else + # puts("Address is valid") + # end + # #=> "Invalid Email Address Detected" + class Address + + include TextUtils #:nodoc: + + # Sometimes you need to parse an address, TMail can do it for you and provide you with + # a fairly robust method of detecting a valid address. + # + # Takes in a string, returns a TMail::Address object. + # + # Raises a TMail::SyntaxError on invalid email format + def Address.parse( str ) + Parser.parse :ADDRESS, special_quote_address(str) + end + + def Address.special_quote_address(str) #:nodoc: + # Takes a string which is an address and adds quotation marks to special + # edge case methods that the RACC parser can not handle. + # + # Right now just handles two edge cases: + # + # Full stop as the last character of the display name: + # Mikel L. <mikel@me.com> + # Returns: + # "Mikel L." <mikel@me.com> + # + # Unquoted @ symbol in the display name: + # mikel@me.com <mikel@me.com> + # Returns: + # "mikel@me.com" <mikel@me.com> + # + # Any other address not matching these patterns just gets returned as is. + case + # This handles the missing "" in an older version of Apple Mail.app + # around the display name when the display name contains a '@' + # like 'mikel@me.com <mikel@me.com>' + # Just quotes it to: '"mikel@me.com" <mikel@me.com>' + when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/ + return "\"#{$1}\" #{$2}" + # This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing + # full stop before the address section. Just quotes it to + # '"Mikel A. <mikel@me.com>" + when str =~ /\A(.*?\.)\s(<.*?>)\Z/ + return "\"#{$1}\" #{$2}" + else + str + end + end + + def address_group? #:nodoc: + false + end + + # Address.new(local, domain) + # + # Accepts: + # + # * local - Left of the at symbol + # + # * domain - Array of the domain split at the periods. + # + # For example: + # + # Address.new("mikel", ["lindsaar", "net"]) + # #=> "#<TMail::Address mikel@lindsaar.net>" + def initialize( local, domain ) + if domain + domain.each do |s| + raise SyntaxError, 'empty word in domain' if s.empty? + end + end + + # This is to catch an unquoted "@" symbol in the local part of the + # address. Handles addresses like <"@"@me.com> and makes sure they + # stay like <"@"@me.com> (previously were becomming <@@me.com>) + if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/) + @local = "\"#{local.join}\"" + else + @local = local + end + + @domain = domain + @name = nil + @routes = [] + end + + # Provides the name or 'phrase' of the email address. + # + # For Example: + # + # email = TMail::Address.parse("Mikel Lindsaar <mikel@lindsaar.net>") + # email.name + # #=> "Mikel Lindsaar" + def name + @name + end + + # Setter method for the name or phrase of the email + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.name + # #=> nil + # email.name = "Mikel Lindsaar" + # email.to_s + # #=> "Mikel Lindsaar <mikel@me.com>" + def name=( str ) + @name = str + @name = nil if str and str.empty? + end + + #:stopdoc: + alias phrase name + alias phrase= name= + #:startdoc: + + # This is still here from RFC 822, and is now obsolete per RFC2822 Section 4. + # + # "When interpreting addresses, the route portion SHOULD be ignored." + # + # It is still here, so you can access it. + # + # Routes return the route portion at the front of the email address, if any. + # + # For Example: + # email = TMail::Address.parse( "<@sa,@another:Mikel@me.com>") + # => #<TMail::Address Mikel@me.com> + # email.to_s + # => "<@sa,@another:Mikel@me.com>" + # email.routes + # => ["sa", "another"] + def routes + @routes + end + + def inspect #:nodoc: + "#<#{self.class} #{address()}>" + end + + # Returns the local part of the email address + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.local + # #=> "mikel" + def local + return nil unless @local + return '""' if @local.size == 1 and @local[0].empty? + # Check to see if it is an array before trying to map it + if @local.respond_to?(:map) + @local.map {|i| quote_atom(i) }.join('.') + else + quote_atom(@local) + end + end + + # Returns the domain part of the email address + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.local + # #=> "lindsaar.net" + def domain + return nil unless @domain + join_domain(@domain) + end + + # Returns the full specific address itself + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.address + # #=> "mikel@lindsaar.net" + def spec + s = self.local + d = self.domain + if s and d + s + '@' + d + else + s + end + end + + alias address spec + + # Provides == function to the email. Only checks the actual address + # and ignores the name/phrase component + # + # For Example + # + # addr1 = TMail::Address.parse("My Address <mikel@lindsaar.net>") + # #=> "#<TMail::Address mikel@lindsaar.net>" + # addr2 = TMail::Address.parse("Another <mikel@lindsaar.net>") + # #=> "#<TMail::Address mikel@lindsaar.net>" + # addr1 == addr2 + # #=> true + def ==( other ) + other.respond_to? :spec and self.spec == other.spec + end + + alias eql? == + + # Provides a unique hash value for this record against the local and domain + # parts, ignores the name/phrase value + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.hash + # #=> 18767598 + def hash + @local.hash ^ @domain.hash + end + + # Duplicates a TMail::Address object returning the duplicate + # + # addr1 = TMail::Address.parse("mikel@lindsaar.net") + # addr2 = addr1.dup + # addr1.id == addr2.id + # #=> false + def dup + obj = self.class.new(@local.dup, @domain.dup) + obj.name = @name.dup if @name + obj.routes.replace @routes + obj + end + + include StrategyInterface #:nodoc: + + def accept( strategy, dummy1 = nil, dummy2 = nil ) #:nodoc: + unless @local + strategy.meta '<>' # empty return-path + return + end + + spec_p = (not @name and @routes.empty?) + if @name + strategy.phrase @name + strategy.space + end + tmp = spec_p ? '' : '<' + unless @routes.empty? + tmp << @routes.map {|i| '@' + i }.join(',') << ':' + end + tmp << self.spec + tmp << '>' unless spec_p + strategy.meta tmp + strategy.lwsp '' + end + + end + + + class AddressGroup + + include Enumerable + + def address_group? + true + end + + def initialize( name, addrs ) + @name = name + @addresses = addrs + end + + attr_reader :name + + def ==( other ) + other.respond_to? :to_a and @addresses == other.to_a + end + + alias eql? == + + def hash + map {|i| i.hash }.hash + end + + def []( idx ) + @addresses[idx] + end + + def size + @addresses.size + end + + def empty? + @addresses.empty? + end + + def each( &block ) + @addresses.each(&block) + end + + def to_a + @addresses.dup + end + + alias to_ary to_a + + def include?( a ) + @addresses.include? a + end + + def flatten + set = [] + @addresses.each do |a| + if a.respond_to? :flatten + set.concat a.flatten + else + set.push a + end + end + set + end + + def each_address( &block ) + flatten.each(&block) + end + + def add( a ) + @addresses.push a + end + + alias push add + + def delete( a ) + @addresses.delete a + end + + include StrategyInterface + + def accept( strategy, dummy1 = nil, dummy2 = nil ) + strategy.phrase @name + strategy.meta ':' + strategy.space + first = true + each do |mbox| + if first + first = false + else + strategy.meta ',' + end + strategy.space + mbox.accept strategy + end + strategy.meta ';' + strategy.lwsp '' + end + + end + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb new file mode 100644 index 0000000000..5dc5efae5e --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb @@ -0,0 +1,46 @@ +=begin rdoc + += Attachment handling file + +=end + +require 'stringio' + +module TMail + class Attachment < StringIO + attr_accessor :original_filename, :content_type + end + + class Mail + def has_attachments? + multipart? && parts.any? { |part| attachment?(part) } + end + + def attachment?(part) + part.disposition_is_attachment? || part.content_type_is_text? + end + + def attachments + if multipart? + parts.collect { |part| + if part.multipart? + part.attachments + elsif attachment?(part) + content = part.body # unquoted automatically by TMail#body + file_name = (part['content-location'] && + part['content-location'].body) || + part.sub_header("content-type", "name") || + part.sub_header("content-disposition", "filename") + + next if file_name.blank? || content.blank? + + attachment = Attachment.new(content) + attachment.original_filename = file_name.strip + attachment.content_type = part.content_type + attachment + end + }.flatten.compact + end + end + end +end diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb new file mode 100644 index 0000000000..e294c62960 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb @@ -0,0 +1,46 @@ +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +module TMail + module Base64 + + module_function + + def folding_encode( str, eol = "\n", limit = 60 ) + [str].pack('m') + end + + def encode( str ) + [str].pack('m').tr( "\r\n", '' ) + end + + def decode( str, strict = false ) + str.unpack('m').first + end + + end +end +#:startdoc: diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb new file mode 100644 index 0000000000..1275df79a6 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb @@ -0,0 +1,41 @@ +#:stopdoc: +unless Enumerable.method_defined?(:map) + module Enumerable #:nodoc: + alias map collect + end +end + +unless Enumerable.method_defined?(:select) + module Enumerable #:nodoc: + alias select find_all + end +end + +unless Enumerable.method_defined?(:reject) + module Enumerable #:nodoc: + def reject + result = [] + each do |i| + result.push i unless yield(i) + end + result + end + end +end + +unless Enumerable.method_defined?(:sort_by) + module Enumerable #:nodoc: + def sort_by + map {|i| [yield(i), i] }.sort.map {|val, i| i } + end + end +end + +unless File.respond_to?(:read) + def File.read(fname) #:nodoc: + File.open(fname) {|f| + return f.read + } + end +end +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb new file mode 100644 index 0000000000..3a876dcdbd --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb @@ -0,0 +1,67 @@ +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +module TMail + + class Config + + def initialize( strict ) + @strict_parse = strict + @strict_base64decode = strict + end + + def strict_parse? + @strict_parse + end + + attr_writer :strict_parse + + def strict_base64decode? + @strict_base64decode + end + + attr_writer :strict_base64decode + + def new_body_port( mail ) + StringPort.new + end + + alias new_preamble_port new_body_port + alias new_part_port new_body_port + + end + + DEFAULT_CONFIG = Config.new(false) + DEFAULT_STRICT_CONFIG = Config.new(true) + + def Config.to_config( arg ) + return DEFAULT_STRICT_CONFIG if arg == true + return DEFAULT_CONFIG if arg == false + arg or DEFAULT_CONFIG + end + +end +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb new file mode 100644 index 0000000000..da62c33bbf --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb @@ -0,0 +1,63 @@ +#:stopdoc: +unless Object.respond_to?(:blank?) + class Object + # Check first to see if we are in a Rails environment, no need to + # define these methods if we are + + # An object is blank if it's nil, empty, or a whitespace string. + # For example, "", " ", nil, [], and {} are blank. + # + # This simplifies + # if !address.nil? && !address.empty? + # to + # if !address.blank? + def blank? + if respond_to?(:empty?) && respond_to?(:strip) + empty? or strip.empty? + elsif respond_to?(:empty?) + empty? + else + !self + end + end + end + + class NilClass + def blank? + true + end + end + + class FalseClass + def blank? + true + end + end + + class TrueClass + def blank? + false + end + end + + class Array + alias_method :blank?, :empty? + end + + class Hash + alias_method :blank?, :empty? + end + + class String + def blank? + empty? || strip.empty? + end + end + + class Numeric + def blank? + false + end + end +end +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb new file mode 100644 index 0000000000..458dbbfe6a --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb @@ -0,0 +1,581 @@ +#-- +# = COPYRIGHT: +# +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +require 'nkf' +require 'tmail/base64' +require 'tmail/stringio' +require 'tmail/utils' +#:startdoc: + + +module TMail + + #:stopdoc: + class << self + attr_accessor :KCODE + end + self.KCODE = 'NONE' + + module StrategyInterface + + def create_dest( obj ) + case obj + when nil + StringOutput.new + when String + StringOutput.new(obj) + when IO, StringOutput + obj + else + raise TypeError, 'cannot handle this type of object for dest' + end + end + module_function :create_dest + + #:startdoc: + # Returns the TMail object encoded and ready to be sent via SMTP etc. + # You should call this before you are packaging up your email to + # correctly escape all the values that need escaping in the email, line + # wrap the email etc. + # + # It is also a good idea to call this before you marshal or serialize + # a TMail object. + # + # For Example: + # + # email = TMail::Load(my_email_file) + # email_to_send = email.encoded + def encoded( eol = "\r\n", charset = 'j', dest = nil ) + accept_strategy Encoder, eol, charset, dest + end + + # Returns the TMail object decoded and ready to be used by you, your + # program etc. + # + # You should call this before you are packaging up your email to + # correctly escape all the values that need escaping in the email, line + # wrap the email etc. + # + # For Example: + # + # email = TMail::Load(my_email_file) + # email_to_send = email.encoded + def decoded( eol = "\n", charset = 'e', dest = nil ) + # Turn the E-Mail into a string and return it with all + # encoded characters decoded. alias for to_s + accept_strategy Decoder, eol, charset, dest + end + + alias to_s decoded + + def accept_strategy( klass, eol, charset, dest = nil ) #:nodoc: + dest ||= '' + accept klass.new( create_dest(dest), charset, eol ) + dest + end + + end + + #:stopdoc: + + ### + ### MIME B encoding decoder + ### + + class Decoder + + include TextUtils + + encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?=' + ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i + + OUTPUT_ENCODING = { + 'EUC' => 'e', + 'SJIS' => 's', + } + + def self.decode( str, encoding = nil ) + encoding ||= (OUTPUT_ENCODING[TMail.KCODE] || 'j') + opt = '-mS' + encoding + str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) } + end + + def initialize( dest, encoding = nil, eol = "\n" ) + @f = StrategyInterface.create_dest(dest) + @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil + @eol = eol + end + + def decode( str ) + self.class.decode(str, @encoding) + end + private :decode + + def terminate + end + + def header_line( str ) + @f << decode(str) + end + + def header_name( nm ) + @f << nm << ': ' + end + + def header_body( str ) + @f << decode(str) + end + + def space + @f << ' ' + end + + alias spc space + + def lwsp( str ) + @f << str + end + + def meta( str ) + @f << str + end + + def text( str ) + @f << decode(str) + end + + def phrase( str ) + @f << quote_phrase(decode(str)) + end + + def kv_pair( k, v ) + v = dquote(v) unless token_safe?(v) + @f << k << '=' << v + end + + def puts( str = nil ) + @f << str if str + @f << @eol + end + + def write( str ) + @f << str + end + + end + + + ### + ### MIME B-encoding encoder + ### + + # + # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp). + # + class Encoder + + include TextUtils + + BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG) + + def Encoder.encode( str ) + e = new() + e.header_body str + e.terminate + e.dest.string + end + + SPACER = "\t" + MAX_LINE_LEN = 78 + RFC_2822_MAX_LENGTH = 998 + + OPTIONS = { + 'EUC' => '-Ej -m0', + 'SJIS' => '-Sj -m0', + 'UTF8' => nil, # FIXME + 'NONE' => nil + } + + def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil ) + @f = StrategyInterface.create_dest(dest) + @opt = OPTIONS[TMail.KCODE] + @eol = eol + @folded = false + @preserve_quotes = true + reset + end + + def preserve_quotes=( bool ) + @preserve_quotes + end + + def preserve_quotes + @preserve_quotes + end + + def normalize_encoding( str ) + if @opt + then NKF.nkf(@opt, str) + else str + end + end + + def reset + @text = '' + @lwsp = '' + @curlen = 0 + end + + def terminate + add_lwsp '' + reset + end + + def dest + @f + end + + def puts( str = nil ) + @f << str if str + @f << @eol + end + + def write( str ) + @f << str + end + + # + # add + # + + def header_line( line ) + scanadd line + end + + def header_name( name ) + add_text name.split(/-/).map {|i| i.capitalize }.join('-') + add_text ':' + add_lwsp ' ' + end + + def header_body( str ) + scanadd normalize_encoding(str) + end + + def space + add_lwsp ' ' + end + + alias spc space + + def lwsp( str ) + add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '') + end + + def meta( str ) + add_text str + end + + def text( str ) + scanadd normalize_encoding(str) + end + + def phrase( str ) + str = normalize_encoding(str) + if CONTROL_CHAR === str + scanadd str + else + add_text quote_phrase(str) + end + end + + # FIXME: implement line folding + # + def kv_pair( k, v ) + return if v.nil? + v = normalize_encoding(v) + if token_safe?(v) + add_text k + '=' + v + elsif not CONTROL_CHAR === v + add_text k + '=' + quote_token(v) + else + # apply RFC2231 encoding + kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v) + add_text kv + end + end + + def encode_value( str ) + str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] } + end + + private + + def scanadd( str, force = false ) + types = '' + strs = [] + if str.respond_to?(:encoding) + enc = str.encoding + str.force_encoding(Encoding::ASCII_8BIT) + end + until str.empty? + if m = /\A[^\e\t\r\n ]+/.match(str) + types << (force ? 'j' : 'a') + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end + elsif m = /\A[\t\r\n ]+/.match(str) + types << 's' + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end + + elsif m = /\A\e../.match(str) + esc = m[0] + str = m.post_match + if esc != "\e(B" and m = /\A[^\e]+/.match(str) + types << 'j' + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end + end + + else + raise 'TMail FATAL: encoder scan fail' + end + (str = m.post_match) unless m.nil? + end + + do_encode types, strs + end + + def do_encode( types, strs ) + # + # result : (A|E)(S(A|E))* + # E : W(SW)* + # W : (J|A)+ but must contain J # (J|A)*J(J|A)* + # A : <<A character string not to be encoded>> + # J : <<A character string to be encoded>> + # S : <<LWSP>> + # + # An encoding unit is `E'. + # Input (parameter `types') is (J|A)(J|A|S)*(J|A) + # + if BENCODE_DEBUG + puts + puts '-- do_encode ------------' + puts types.split(//).join(' ') + p strs + end + + e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/ + + while m = e.match(types) + pre = m.pre_match + concat_A_S pre, strs[0, pre.size] unless pre.empty? + concat_E m[0], strs[m.begin(0) ... m.end(0)] + types = m.post_match + strs.slice! 0, m.end(0) + end + concat_A_S types, strs + end + + def concat_A_S( types, strs ) + if RUBY_VERSION < '1.9' + a = ?a; s = ?s + else + a = 'a'.ord; s = 's'.ord + end + i = 0 + types.each_byte do |t| + case t + when a then add_text strs[i] + when s then add_lwsp strs[i] + else + raise "TMail FATAL: unknown flag: #{t.chr}" + end + i += 1 + end + end + + METHOD_ID = { + ?j => :extract_J, + ?e => :extract_E, + ?a => :extract_A, + ?s => :extract_S + } + + def concat_E( types, strs ) + if BENCODE_DEBUG + puts '---- concat_E' + puts "types=#{types.split(//).join(' ')}" + puts "strs =#{strs.inspect}" + end + + flush() unless @text.empty? + + chunk = '' + strs.each_with_index do |s,i| + mid = METHOD_ID[types[i]] + until s.empty? + unless c = __send__(mid, chunk.size, s) + add_with_encode chunk unless chunk.empty? + flush + chunk = '' + fold + c = __send__(mid, 0, s) + raise 'TMail FATAL: extract fail' unless c + end + chunk << c + end + end + add_with_encode chunk unless chunk.empty? + end + + def extract_J( chunksize, str ) + size = max_bytes(chunksize, str.size) - 6 + size = (size % 2 == 0) ? (size) : (size - 1) + return nil if size <= 0 + if str.respond_to?(:encoding) + enc = str.encoding + str.force_encoding(Encoding::ASCII_8BIT) + "\e$B#{str.slice!(0, size)}\e(B".force_encoding(enc) + else + "\e$B#{str.slice!(0, size)}\e(B" + end + end + + def extract_A( chunksize, str ) + size = max_bytes(chunksize, str.size) + return nil if size <= 0 + str.slice!(0, size) + end + + alias extract_S extract_A + + def max_bytes( chunksize, ssize ) + (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize + end + + # + # free length buffer + # + + def add_text( str ) + @text << str + # puts '---- text -------------------------------------' + # puts "+ #{str.inspect}" + # puts "txt >>>#{@text.inspect}<<<" + end + + def add_with_encode( str ) + @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?=" + end + + def add_lwsp( lwsp ) + # puts '---- lwsp -------------------------------------' + # puts "+ #{lwsp.inspect}" + fold if restsize() <= 0 + flush(@folded) + @lwsp = lwsp + end + + def flush(folded = false) + # puts '---- flush ----' + # puts "spc >>>#{@lwsp.inspect}<<<" + # puts "txt >>>#{@text.inspect}<<<" + @f << @lwsp << @text + if folded + @curlen = 0 + else + @curlen += (@lwsp.size + @text.size) + end + @text = '' + @lwsp = '' + end + + def fold + # puts '---- fold ----' + unless @f.string =~ /^.*?:$/ + @f << @eol + @lwsp = SPACER + else + fold_header + @folded = true + end + @curlen = 0 + end + + def fold_header + # Called because line is too long - so we need to wrap. + # First look for whitespace in the text + # if it has text, fold there + # check the remaining text, if too long, fold again + # if it doesn't, then don't fold unless the line goes beyond 998 chars + + # Check the text to see if there is whitespace, or if not + @wrapped_text = [] + until @text.blank? + fold_the_string + end + @text = @wrapped_text.join("#{@eol}#{SPACER}") + end + + def fold_the_string + whitespace_location = @text =~ /\s/ || @text.length + # Is the location of the whitespace shorter than the RCF_2822_MAX_LENGTH? + # if there is no whitespace in the string, then this + unless mazsize(whitespace_location) <= 0 + @text.strip! + @wrapped_text << @text.slice!(0...whitespace_location) + # If it is not less, we have to wrap it destructively + else + slice_point = RFC_2822_MAX_LENGTH - @curlen - @lwsp.length + @text.strip! + @wrapped_text << @text.slice!(0...slice_point) + end + end + + def restsize + MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size) + end + + def mazsize(whitespace_location) + # Per RFC2822, the maximum length of a line is 998 chars + RFC_2822_MAX_LENGTH - (@curlen + @lwsp.size + whitespace_location) + end + + end + #:startdoc: +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb new file mode 100644 index 0000000000..9153dcd7c6 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb @@ -0,0 +1,960 @@ +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/encode' +require 'tmail/address' +require 'tmail/parser' +require 'tmail/config' +require 'tmail/utils' + +#:startdoc: +module TMail + + # Provides methods to handle and manipulate headers in the email + class HeaderField + + include TextUtils + + class << self + + alias newobj new + + def new( name, body, conf = DEFAULT_CONFIG ) + klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader + klass.newobj body, conf + end + + # Returns a HeaderField object matching the header you specify in the "name" param. + # Requires an initialized TMail::Port to be passed in. + # + # The method searches the header of the Port you pass into it to find a match on + # the header line you pass. Once a match is found, it will unwrap the matching line + # as needed to return an initialized HeaderField object. + # + # If you want to get the Envelope sender of the email object, pass in "EnvelopeSender", + # if you want the From address of the email itself, pass in 'From'. + # + # This is because a mailbox doesn't have the : after the From that designates the + # beginning of the envelope sender (which can be different to the from address of + # the emial) + # + # Other fields can be passed as normal, "Reply-To", "Received" etc. + # + # Note: Change of behaviour in 1.2.1 => returns nil if it does not find the specified + # header field, otherwise returns an instantiated object of the correct header class + # + # For example: + # port = TMail::FilePort.new("/test/fixtures/raw_email_simple") + # h = TMail::HeaderField.new_from_port(port, "From") + # h.addrs.to_s #=> "Mikel Lindsaar <mikel@nowhere.com>" + # h = TMail::HeaderField.new_from_port(port, "EvelopeSender") + # h.addrs.to_s #=> "mike@anotherplace.com.au" + # h = TMail::HeaderField.new_from_port(port, "SomeWeirdHeaderField") + # h #=> nil + def new_from_port( port, name, conf = DEFAULT_CONFIG ) + if name == "EnvelopeSender" + name = "From" + re = Regexp.new('\A(From) ', 'i') + else + re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i') + end + str = nil + port.ropen {|f| + f.each do |line| + if m = re.match(line) then str = m.post_match.strip + elsif str and /\A[\t ]/ === line then str << ' ' << line.strip + elsif /\A-*\s*\z/ === line then break + elsif str then break + end + end + } + new(name, str, Config.to_config(conf)) if str + end + + def internal_new( name, conf ) + FNAME_TO_CLASS[name].newobj('', conf, true) + end + + end # class << self + + def initialize( body, conf, intern = false ) + @body = body + @config = conf + + @illegal = false + @parsed = false + + if intern + @parsed = true + parse_init + end + end + + def inspect + "#<#{self.class} #{@body.inspect}>" + end + + def illegal? + @illegal + end + + def empty? + ensure_parsed + return true if @illegal + isempty? + end + + private + + def ensure_parsed + return if @parsed + @parsed = true + parse + end + + # defabstract parse + # end + + def clear_parse_status + @parsed = false + @illegal = false + end + + public + + def body + ensure_parsed + v = Decoder.new(s = '') + do_accept v + v.terminate + s + end + + def body=( str ) + @body = str + clear_parse_status + end + + include StrategyInterface + + def accept( strategy ) + ensure_parsed + do_accept strategy + strategy.terminate + end + + # abstract do_accept + + end + + + class UnstructuredHeader < HeaderField + + def body + ensure_parsed + @body + end + + def body=( arg ) + ensure_parsed + @body = arg + end + + private + + def parse_init + end + + def parse + @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, '')) + end + + def isempty? + not @body + end + + def do_accept( strategy ) + strategy.text @body + end + + end + + + class StructuredHeader < HeaderField + + def comments + ensure_parsed + if @comments[0] + [Decoder.decode(@comments[0])] + else + @comments + end + end + + private + + def parse + save = nil + + begin + parse_init + do_parse + rescue SyntaxError + if not save and mime_encoded? @body + save = @body + @body = Decoder.decode(save) + retry + elsif save + @body = save + end + + @illegal = true + raise if @config.strict_parse? + end + end + + def parse_init + @comments = [] + init + end + + def do_parse + quote_boundary + obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments) + set obj if obj + end + + end + + + class DateTimeHeader < StructuredHeader + + PARSE_TYPE = :DATETIME + + def date + ensure_parsed + @date + end + + def date=( arg ) + ensure_parsed + @date = arg + end + + private + + def init + @date = nil + end + + def set( t ) + @date = t + end + + def isempty? + not @date + end + + def do_accept( strategy ) + strategy.meta time2str(@date) + end + + end + + + class AddressHeader < StructuredHeader + + PARSE_TYPE = :MADDRESS + + def addrs + ensure_parsed + @addrs + end + + private + + def init + @addrs = [] + end + + def set( a ) + @addrs = a + end + + def isempty? + @addrs.empty? + end + + def do_accept( strategy ) + first = true + @addrs.each do |a| + if first + first = false + else + strategy.meta ',' + strategy.space + end + a.accept strategy + end + + @comments.each do |c| + strategy.space + strategy.meta '(' + strategy.text c + strategy.meta ')' + end + end + + end + + + class ReturnPathHeader < AddressHeader + + PARSE_TYPE = :RETPATH + + def addr + addrs()[0] + end + + def spec + a = addr() or return nil + a.spec + end + + def routes + a = addr() or return nil + a.routes + end + + private + + def do_accept( strategy ) + a = addr() + + strategy.meta '<' + unless a.routes.empty? + strategy.meta a.routes.map {|i| '@' + i }.join(',') + strategy.meta ':' + end + spec = a.spec + strategy.meta spec if spec + strategy.meta '>' + end + + end + + + class SingleAddressHeader < AddressHeader + + def addr + addrs()[0] + end + + private + + def do_accept( strategy ) + a = addr() + a.accept strategy + @comments.each do |c| + strategy.space + strategy.meta '(' + strategy.text c + strategy.meta ')' + end + end + + end + + + class MessageIdHeader < StructuredHeader + + def id + ensure_parsed + @id + end + + def id=( arg ) + ensure_parsed + @id = arg + end + + private + + def init + @id = nil + end + + def isempty? + not @id + end + + def do_parse + @id = @body.slice(MESSAGE_ID) or + raise SyntaxError, "wrong Message-ID format: #{@body}" + end + + def do_accept( strategy ) + strategy.meta @id + end + + end + + + class ReferencesHeader < StructuredHeader + + def refs + ensure_parsed + @refs + end + + def each_id + self.refs.each do |i| + yield i if MESSAGE_ID === i + end + end + + def ids + ensure_parsed + @ids + end + + def each_phrase + self.refs.each do |i| + yield i unless MESSAGE_ID === i + end + end + + def phrases + ret = [] + each_phrase {|i| ret.push i } + ret + end + + private + + def init + @refs = [] + @ids = [] + end + + def isempty? + @ids.empty? + end + + def do_parse + str = @body + while m = MESSAGE_ID.match(str) + pre = m.pre_match.strip + @refs.push pre unless pre.empty? + @refs.push s = m[0] + @ids.push s + str = m.post_match + end + str = str.strip + @refs.push str unless str.empty? + end + + def do_accept( strategy ) + first = true + @ids.each do |i| + if first + first = false + else + strategy.space + end + strategy.meta i + end + end + + end + + + class ReceivedHeader < StructuredHeader + + PARSE_TYPE = :RECEIVED + + def from + ensure_parsed + @from + end + + def from=( arg ) + ensure_parsed + @from = arg + end + + def by + ensure_parsed + @by + end + + def by=( arg ) + ensure_parsed + @by = arg + end + + def via + ensure_parsed + @via + end + + def via=( arg ) + ensure_parsed + @via = arg + end + + def with + ensure_parsed + @with + end + + def id + ensure_parsed + @id + end + + def id=( arg ) + ensure_parsed + @id = arg + end + + def _for + ensure_parsed + @_for + end + + def _for=( arg ) + ensure_parsed + @_for = arg + end + + def date + ensure_parsed + @date + end + + def date=( arg ) + ensure_parsed + @date = arg + end + + private + + def init + @from = @by = @via = @with = @id = @_for = nil + @with = [] + @date = nil + end + + def set( args ) + @from, @by, @via, @with, @id, @_for, @date = *args + end + + def isempty? + @with.empty? and not (@from or @by or @via or @id or @_for or @date) + end + + def do_accept( strategy ) + list = [] + list.push 'from ' + @from if @from + list.push 'by ' + @by if @by + list.push 'via ' + @via if @via + @with.each do |i| + list.push 'with ' + i + end + list.push 'id ' + @id if @id + list.push 'for <' + @_for + '>' if @_for + + first = true + list.each do |i| + strategy.space unless first + strategy.meta i + first = false + end + if @date + strategy.meta ';' + strategy.space + strategy.meta time2str(@date) + end + end + + end + + + class KeywordsHeader < StructuredHeader + + PARSE_TYPE = :KEYWORDS + + def keys + ensure_parsed + @keys + end + + private + + def init + @keys = [] + end + + def set( a ) + @keys = a + end + + def isempty? + @keys.empty? + end + + def do_accept( strategy ) + first = true + @keys.each do |i| + if first + first = false + else + strategy.meta ',' + end + strategy.meta i + end + end + + end + + + class EncryptedHeader < StructuredHeader + + PARSE_TYPE = :ENCRYPTED + + def encrypter + ensure_parsed + @encrypter + end + + def encrypter=( arg ) + ensure_parsed + @encrypter = arg + end + + def keyword + ensure_parsed + @keyword + end + + def keyword=( arg ) + ensure_parsed + @keyword = arg + end + + private + + def init + @encrypter = nil + @keyword = nil + end + + def set( args ) + @encrypter, @keyword = args + end + + def isempty? + not (@encrypter or @keyword) + end + + def do_accept( strategy ) + if @key + strategy.meta @encrypter + ',' + strategy.space + strategy.meta @keyword + else + strategy.meta @encrypter + end + end + + end + + + class MimeVersionHeader < StructuredHeader + + PARSE_TYPE = :MIMEVERSION + + def major + ensure_parsed + @major + end + + def major=( arg ) + ensure_parsed + @major = arg + end + + def minor + ensure_parsed + @minor + end + + def minor=( arg ) + ensure_parsed + @minor = arg + end + + def version + sprintf('%d.%d', major, minor) + end + + private + + def init + @major = nil + @minor = nil + end + + def set( args ) + @major, @minor = *args + end + + def isempty? + not (@major or @minor) + end + + def do_accept( strategy ) + strategy.meta sprintf('%d.%d', @major, @minor) + end + + end + + + class ContentTypeHeader < StructuredHeader + + PARSE_TYPE = :CTYPE + + def main_type + ensure_parsed + @main + end + + def main_type=( arg ) + ensure_parsed + @main = arg.downcase + end + + def sub_type + ensure_parsed + @sub + end + + def sub_type=( arg ) + ensure_parsed + @sub = arg.downcase + end + + def content_type + ensure_parsed + @sub ? sprintf('%s/%s', @main, @sub) : @main + end + + def params + ensure_parsed + unless @params.blank? + @params.each do |k, v| + @params[k] = unquote(v) + end + end + @params + end + + def []( key ) + ensure_parsed + @params and unquote(@params[key]) + end + + def []=( key, val ) + ensure_parsed + (@params ||= {})[key] = val + end + + private + + def init + @main = @sub = @params = nil + end + + def set( args ) + @main, @sub, @params = *args + end + + def isempty? + not (@main or @sub) + end + + def do_accept( strategy ) + if @sub + strategy.meta sprintf('%s/%s', @main, @sub) + else + strategy.meta @main + end + @params.each do |k,v| + if v + strategy.meta ';' + strategy.space + strategy.kv_pair k, v + end + end + end + + end + + + class ContentTransferEncodingHeader < StructuredHeader + + PARSE_TYPE = :CENCODING + + def encoding + ensure_parsed + @encoding + end + + def encoding=( arg ) + ensure_parsed + @encoding = arg + end + + private + + def init + @encoding = nil + end + + def set( s ) + @encoding = s + end + + def isempty? + not @encoding + end + + def do_accept( strategy ) + strategy.meta @encoding.capitalize + end + + end + + + class ContentDispositionHeader < StructuredHeader + + PARSE_TYPE = :CDISPOSITION + + def disposition + ensure_parsed + @disposition + end + + def disposition=( str ) + ensure_parsed + @disposition = str.downcase + end + + def params + ensure_parsed + unless @params.blank? + @params.each do |k, v| + @params[k] = unquote(v) + end + end + @params + end + + def []( key ) + ensure_parsed + @params and unquote(@params[key]) + end + + def []=( key, val ) + ensure_parsed + (@params ||= {})[key] = val + end + + private + + def init + @disposition = @params = nil + end + + def set( args ) + @disposition, @params = *args + end + + def isempty? + not @disposition and (not @params or @params.empty?) + end + + def do_accept( strategy ) + strategy.meta @disposition + @params.each do |k,v| + strategy.meta ';' + strategy.space + strategy.kv_pair k, unquote(v) + end + end + + end + + + class HeaderField # redefine + + FNAME_TO_CLASS = { + 'date' => DateTimeHeader, + 'resent-date' => DateTimeHeader, + 'to' => AddressHeader, + 'cc' => AddressHeader, + 'bcc' => AddressHeader, + 'from' => AddressHeader, + 'reply-to' => AddressHeader, + 'resent-to' => AddressHeader, + 'resent-cc' => AddressHeader, + 'resent-bcc' => AddressHeader, + 'resent-from' => AddressHeader, + 'resent-reply-to' => AddressHeader, + 'sender' => SingleAddressHeader, + 'resent-sender' => SingleAddressHeader, + 'return-path' => ReturnPathHeader, + 'message-id' => MessageIdHeader, + 'resent-message-id' => MessageIdHeader, + 'in-reply-to' => ReferencesHeader, + 'received' => ReceivedHeader, + 'references' => ReferencesHeader, + 'keywords' => KeywordsHeader, + 'encrypted' => EncryptedHeader, + 'mime-version' => MimeVersionHeader, + 'content-type' => ContentTypeHeader, + 'content-transfer-encoding' => ContentTransferEncodingHeader, + 'content-disposition' => ContentDispositionHeader, + 'content-id' => MessageIdHeader, + 'subject' => UnstructuredHeader, + 'comments' => UnstructuredHeader, + 'content-description' => UnstructuredHeader + } + + end + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb new file mode 100644 index 0000000000..554e2fd696 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb @@ -0,0 +1,9 @@ +#:stopdoc: +# This is here for Rolls. +# Rolls uses this instead of lib/tmail.rb. + +require 'tmail/version' +require 'tmail/mail' +require 'tmail/mailbox' +require 'tmail/core_extensions' +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb new file mode 100644 index 0000000000..a6d428d7d6 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb @@ -0,0 +1,1130 @@ +=begin rdoc + += interface.rb Provides an interface to the TMail object + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +# TMail::Mail objects get accessed primarily through the methods in this file. +# +# + +require 'tmail/utils' + +module TMail + + class Mail + + # Allows you to query the mail object with a string to get the contents + # of the field you want. + # + # Returns a string of the exact contnts of the field + # + # mail.from = "mikel <mikel@lindsaar.net>" + # mail.header_string("From") #=> "mikel <mikel@lindsaar.net>" + def header_string( name, default = nil ) + h = @header[name.downcase] or return default + h.to_s + end + + #:stopdoc: + #-- + #== Attributes + + include TextUtils + + def set_string_array_attr( key, strs ) + strs.flatten! + if strs.empty? + @header.delete key.downcase + else + store key, strs.join(', ') + end + strs + end + private :set_string_array_attr + + def set_string_attr( key, str ) + if str + store key, str + else + @header.delete key.downcase + end + str + end + private :set_string_attr + + def set_addrfield( name, arg ) + if arg + h = HeaderField.internal_new(name, @config) + h.addrs.replace [arg].flatten + @header[name] = h + else + @header.delete name + end + arg + end + private :set_addrfield + + def addrs2specs( addrs ) + return nil unless addrs + list = addrs.map {|addr| + if addr.address_group? + then addr.map {|a| a.spec } + else addr.spec + end + }.flatten + return nil if list.empty? + list + end + private :addrs2specs + + #:startdoc: + + #== Date and Time methods + + # Returns the date of the email message as per the "date" header value or returns + # nil by default (if no date field exists). + # + # You can also pass whatever default you want into this method and it will return + # that instead of nil if there is no date already set. + def date( default = nil ) + if h = @header['date'] + h.date + else + default + end + end + + # Destructively sets the date of the mail object with the passed Time instance, + # returns a Time instance set to the date/time of the mail + # + # Example: + # + # now = Time.now + # mail.date = now + # mail.date #=> Sat Nov 03 18:47:50 +1100 2007 + # mail.date.class #=> Time + def date=( time ) + if time + store 'Date', time2str(time) + else + @header.delete 'date' + end + time + end + + # Returns the time of the mail message formatted to your taste using a + # strftime format string. If no date set returns nil by default or whatever value + # you pass as the second optional parameter. + # + # time = Time.now # (on Nov 16 2007) + # mail.date = time + # mail.strftime("%D") #=> "11/16/07" + def strftime( fmt, default = nil ) + if t = date + t.strftime(fmt) + else + default + end + end + + #== Destination methods + + # Return a TMail::Addresses instance for each entry in the "To:" field of the mail object header. + # + # If the "To:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.to_addrs #=> nil + # mail.to_addrs([]) #=> [] + # mail.to = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.to_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def to_addrs( default = nil ) + if h = @header['to'] + h.addrs + else + default + end + end + + # Return a TMail::Addresses instance for each entry in the "Cc:" field of the mail object header. + # + # If the "Cc:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc_addrs #=> nil + # mail.cc_addrs([]) #=> [] + # mail.cc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.cc_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def cc_addrs( default = nil ) + if h = @header['cc'] + h.addrs + else + default + end + end + + # Return a TMail::Addresses instance for each entry in the "Bcc:" field of the mail object header. + # + # If the "Bcc:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc_addrs #=> nil + # mail.bcc_addrs([]) #=> [] + # mail.bcc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.bcc_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def bcc_addrs( default = nil ) + if h = @header['bcc'] + h.addrs + else + default + end + end + + # Destructively set the to field of the "To:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.to_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def to_addrs=( arg ) + set_addrfield 'to', arg + end + + # Destructively set the to field of the "Cc:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.cc_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def cc_addrs=( arg ) + set_addrfield 'cc', arg + end + + # Destructively set the to field of the "Bcc:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.bcc_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def bcc_addrs=( arg ) + set_addrfield 'bcc', arg + end + + # Returns who the email is to as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.to #=> ["mikel@me.org", "mikel@you.org"] + def to( default = nil ) + addrs2specs(to_addrs(nil)) || default + end + + # Returns who the email cc'd as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.cc #=> ["mikel@me.org", "mikel@you.org"] + def cc( default = nil ) + addrs2specs(cc_addrs(nil)) || default + end + + # Returns who the email bcc'd as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.bcc #=> ["mikel@me.org", "mikel@you.org"] + def bcc( default = nil ) + addrs2specs(bcc_addrs(nil)) || default + end + + # Destructively sets the "To:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = ["mikel@abc.com", "Mikel <mikel@xyz.com>"] + # mail.to #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['to'].to_s #=> "mikel@abc.com, Mikel <mikel@xyz.com>" + def to=( *strs ) + set_string_array_attr 'To', strs + end + + # Destructively sets the "Cc:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = ["mikel@abc.com", "Mikel <mikel@xyz.com>"] + # mail.cc #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['cc'].to_s #=> "mikel@abc.com, Mikel <mikel@xyz.com>" + def cc=( *strs ) + set_string_array_attr 'Cc', strs + end + + # Destructively sets the "Bcc:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = ["mikel@abc.com", "Mikel <mikel@xyz.com>"] + # mail.bcc #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['bcc'].to_s #=> "mikel@abc.com, Mikel <mikel@xyz.com>" + def bcc=( *strs ) + set_string_array_attr 'Bcc', strs + end + + #== Originator methods + + # Return a TMail::Addresses instance for each entry in the "From:" field of the mail object header. + # + # If the "From:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.from_addrs #=> nil + # mail.from_addrs([]) #=> [] + # mail.from = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.from_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def from_addrs( default = nil ) + if h = @header['from'] + h.addrs + else + default + end + end + + # Destructively set the to value of the "From:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.from_addrs = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.from_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def from_addrs=( arg ) + set_addrfield 'from', arg + end + + # Returns who the email is from as an Array of email address strings instead to an Array of + # TMail::Address objects which is what Mail#from_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.from #=> ["mikel@me.org", "mikel@you.org"] + def from( default = nil ) + addrs2specs(from_addrs(nil)) || default + end + + # Destructively sets the "From:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = ["mikel@abc.com", "Mikel <mikel@xyz.com>"] + # mail.from #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['from'].to_s #=> "mikel@abc.com, Mikel <mikel@xyz.com>" + def from=( *strs ) + set_string_array_attr 'From', strs + end + + # Returns the "friendly" human readable part of the address + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = "Mikel Lindsaar <mikel@abc.com>" + # mail.friendly_from #=> "Mikel Lindsaar" + def friendly_from( default = nil ) + h = @header['from'] + a, = h.addrs + return default unless a + return a.phrase if a.phrase + return h.comments.join(' ') unless h.comments.empty? + a.spec + end + + # Return a TMail::Addresses instance for each entry in the "Reply-To:" field of the mail object header. + # + # If the "Reply-To:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to_addrs #=> nil + # mail.reply_to_addrs([]) #=> [] + # mail.reply_to = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.reply_to_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def reply_to_addrs( default = nil ) + if h = @header['reply-to'] + h.addrs.blank? ? default : h.addrs + else + default + end + end + + # Destructively set the to value of the "Reply-To:" header to equal the passed in argument. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to_addrs = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.reply_to_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def reply_to_addrs=( arg ) + set_addrfield 'reply-to', arg + end + + # Returns who the email is from as an Array of email address strings instead to an Array of + # TMail::Address objects which is what Mail#reply_to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.reply_to #=> ["mikel@me.org", "mikel@you.org"] + def reply_to( default = nil ) + addrs2specs(reply_to_addrs(nil)) || default + end + + # Destructively sets the "Reply-To:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to = ["mikel@abc.com", "Mikel <mikel@xyz.com>"] + # mail.reply_to #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['reply_to'].to_s #=> "mikel@abc.com, Mikel <mikel@xyz.com>" + def reply_to=( *strs ) + set_string_array_attr 'Reply-To', strs + end + + # Return a TMail::Addresses instance of the "Sender:" field of the mail object header. + # + # If the "Sender:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender #=> nil + # mail.sender([]) #=> [] + # mail.sender = "Mikel <mikel@me.org>" + # mail.reply_to_addrs #=> [#<TMail::Address mikel@me.org>] + def sender_addr( default = nil ) + f = @header['sender'] or return default + f.addr or return default + end + + # Destructively set the to value of the "Sender:" header to equal the passed in argument. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender_addrs = "Mikel <mikel@me.org>, another Mikel <mikel@you.org>" + # mail.sender_addrs #=> [#<TMail::Address mikel@me.org>, #<TMail::Address mikel@you.org>] + def sender_addr=( addr ) + if addr + h = HeaderField.internal_new('sender', @config) + h.addr = addr + @header['sender'] = h + else + @header.delete 'sender' + end + addr + end + + # Returns who the sender of this mail is as string instead to an Array of + # TMail::Address objects which is what Mail#sender_addr returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender = "Mikel <mikel@me.org>" + # mail.sender #=> "mikel@me.org" + def sender( default = nil ) + f = @header['sender'] or return default + a = f.addr or return default + a.spec + end + + # Destructively sets the "Sender:" field to the passed string (which should be a valid + # email address) + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender = "mikel@abc.com" + # mail.sender #=> "mikel@abc.org" + # mail['sender'].to_s #=> "mikel@abc.com" + def sender=( str ) + set_string_attr 'Sender', str + end + + #== Subject methods + + # Returns the subject of the mail instance. + # + # If the subject field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.subject #=> nil + # mail.subject("") #=> "" + # mail.subject = "Hello" + # mail.subject #=> "Hello" + def subject( default = nil ) + if h = @header['subject'] + h.body + else + default + end + end + alias quoted_subject subject + + # Destructively sets the passed string as the subject of the mail message. + # + # Example + # + # mail = TMail::Mail.new + # mail.subject #=> "This subject" + # mail.subject = "Another subject" + # mail.subject #=> "Another subject" + def subject=( str ) + set_string_attr 'Subject', str + end + + #== Message Identity & Threading Methods + + # Returns the message ID for this mail object instance. + # + # If the message_id field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.message_id #=> nil + # mail.message_id(TMail.new_message_id) #=> "<47404c5326d9c_2ad4fbb80161@baci.local.tmail>" + # mail.message_id = TMail.new_message_id + # mail.message_id #=> "<47404c5326d9c_2ad4fbb80161@baci.local.tmail>" + def message_id( default = nil ) + if h = @header['message-id'] + h.id || default + else + default + end + end + + # Destructively sets the message ID of the mail object instance to the passed in string + # + # Invalid message IDs are ignored (silently, unless configured otherwise) and result in + # a nil message ID. Left and right angle brackets are required. + # + # Example: + # + # mail = TMail::Mail.new + # mail.message_id = "<348F04F142D69C21-291E56D292BC@xxxx.net>" + # mail.message_id #=> "<348F04F142D69C21-291E56D292BC@xxxx.net>" + # mail.message_id = "this_is_my_badly_formatted_message_id" + # mail.message_id #=> nil + def message_id=( str ) + set_string_attr 'Message-Id', str + end + + # Returns the "In-Reply-To:" field contents as an array of this mail instance if it exists + # + # If the in_reply_to field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.in_reply_to #=> nil + # mail.in_reply_to([]) #=> [] + # TMail::Mail.load("../test/fixtures/raw_email_reply") + # mail.in_reply_to #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def in_reply_to( default = nil ) + if h = @header['in-reply-to'] + h.ids + else + default + end + end + + # Destructively sets the value of the "In-Reply-To:" field of an email. + # + # Accepts an array of a single string of a message id + # + # Example: + # + # mail = TMail::Mail.new + # mail.in_reply_to = ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + # mail.in_reply_to #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def in_reply_to=( *idstrs ) + set_string_array_attr 'In-Reply-To', idstrs + end + + # Returns the references of this email (prior messages relating to this message) + # as an array of message ID strings. Useful when you are trying to thread an + # email. + # + # If the references field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.references #=> nil + # mail.references([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email_reply") + # mail.references #=> ["<473FF3B8.9020707@xxx.org>", "<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def references( default = nil ) + if h = @header['references'] + h.refs + else + default + end + end + + # Destructively sets the value of the "References:" field of an email. + # + # Accepts an array of strings of message IDs + # + # Example: + # + # mail = TMail::Mail.new + # mail.references = ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + # mail.references #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def references=( *strs ) + set_string_array_attr 'References', strs + end + + #== MIME header methods + + # Returns the listed MIME version of this email from the "Mime-Version:" header field + # + # If the mime_version field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.mime_version #=> nil + # mail.mime_version([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.mime_version #=> "1.0" + def mime_version( default = nil ) + if h = @header['mime-version'] + h.version || default + else + default + end + end + + def mime_version=( m, opt = nil ) + if opt + if h = @header['mime-version'] + h.major = m + h.minor = opt + else + store 'Mime-Version', "#{m}.#{opt}" + end + else + store 'Mime-Version', m + end + m + end + + # Returns the current "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.content_type #=> nil + # mail.content_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.content_type #=> "text/plain" + def content_type( default = nil ) + if h = @header['content-type'] + h.content_type || default + else + default + end + end + + # Returns the current main type of the "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.main_type #=> nil + # mail.main_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.main_type #=> "text" + def main_type( default = nil ) + if h = @header['content-type'] + h.main_type || default + else + default + end + end + + # Returns the current sub type of the "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sub_type #=> nil + # mail.sub_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.sub_type #=> "plain" + def sub_type( default = nil ) + if h = @header['content-type'] + h.sub_type || default + else + default + end + end + + # Destructively sets the "Content-Type:" header field of this mail object + # + # Allows you to set the main type, sub type as well as parameters to the field. + # The main type and sub type need to be a string. + # + # The optional params hash can be passed with keys as symbols and values as a string, + # or strings as keys and values. + # + # Example: + # + # mail = TMail::Mail.new + # mail.set_content_type("text", "plain") + # mail.to_s #=> "Content-Type: text/plain\n\n" + # + # mail.set_content_type("text", "plain", {:charset => "EUC-KR", :format => "flowed"}) + # mail.to_s #=> "Content-Type: text/plain; charset=EUC-KR; format=flowed\n\n" + # + # mail.set_content_type("text", "plain", {"charset" => "EUC-KR", "format" => "flowed"}) + # mail.to_s #=> "Content-Type: text/plain; charset=EUC-KR; format=flowed\n\n" + def set_content_type( str, sub = nil, param = nil ) + if sub + main, sub = str, sub + else + main, sub = str.split(%r</>, 2) + raise ArgumentError, "sub type missing: #{str.inspect}" unless sub + end + if h = @header['content-type'] + h.main_type = main + h.sub_type = sub + h.params.clear + else + store 'Content-Type', "#{main}/#{sub}" + end + @header['content-type'].params.replace param if param + str + end + + alias content_type= set_content_type + + # Returns the named type parameter as a string, from the "Content-Type:" header. + # + # Example: + # + # mail = TMail::Mail.new + # mail.type_param("charset") #=> nil + # mail.type_param("charset", []) #=> [] + # mail.set_content_type("text", "plain", {:charset => "EUC-KR", :format => "flowed"}) + # mail.type_param("charset") #=> "EUC-KR" + # mail.type_param("format") #=> "flowed" + def type_param( name, default = nil ) + if h = @header['content-type'] + h[name] || default + else + default + end + end + + # Returns the character set of the email. Returns nil if no encoding set or returns + # whatever default you pass as a parameter - note passing the parameter does NOT change + # the mail object in any way. + # + # Example: + # + # mail = TMail::Mail.load("path_to/utf8_email") + # mail.charset #=> "UTF-8" + # + # mail = TMail::Mail.new + # mail.charset #=> nil + # mail.charset("US-ASCII") #=> "US-ASCII" + def charset( default = nil ) + if h = @header['content-type'] + h['charset'] or default + else + default + end + end + + # Destructively sets the character set used by this mail object to the passed string, you + # should note though that this does nothing to the mail body, just changes the header + # value, you will need to transliterate the body as well to match whatever you put + # in this header value if you are changing character sets. + # + # Example: + # + # mail = TMail::Mail.new + # mail.charset #=> nil + # mail.charset = "UTF-8" + # mail.charset #=> "UTF-8" + def charset=( str ) + if str + if h = @header[ 'content-type' ] + h['charset'] = str + else + store 'Content-Type', "text/plain; charset=#{str}" + end + end + str + end + + # Returns the transfer encoding of the email. Returns nil if no encoding set or returns + # whatever default you pass as a parameter - note passing the parameter does NOT change + # the mail object in any way. + # + # Example: + # + # mail = TMail::Mail.load("path_to/base64_encoded_email") + # mail.transfer_encoding #=> "base64" + # + # mail = TMail::Mail.new + # mail.transfer_encoding #=> nil + # mail.transfer_encoding("base64") #=> "base64" + def transfer_encoding( default = nil ) + if h = @header['content-transfer-encoding'] + h.encoding || default + else + default + end + end + + # Destructively sets the transfer encoding of the mail object to the passed string, you + # should note though that this does nothing to the mail body, just changes the header + # value, you will need to encode or decode the body as well to match whatever you put + # in this header value. + # + # Example: + # + # mail = TMail::Mail.new + # mail.transfer_encoding #=> nil + # mail.transfer_encoding = "base64" + # mail.transfer_encoding #=> "base64" + def transfer_encoding=( str ) + set_string_attr 'Content-Transfer-Encoding', str + end + + alias encoding transfer_encoding + alias encoding= transfer_encoding= + alias content_transfer_encoding transfer_encoding + alias content_transfer_encoding= transfer_encoding= + + # Returns the content-disposition of the mail object, returns nil or the passed + # default value if given + # + # Example: + # + # mail = TMail::Mail.load("path_to/raw_mail_with_attachment") + # mail.disposition #=> "attachment" + # + # mail = TMail::Mail.load("path_to/plain_simple_email") + # mail.disposition #=> nil + # mail.disposition(false) #=> false + def disposition( default = nil ) + if h = @header['content-disposition'] + h.disposition || default + else + default + end + end + + alias content_disposition disposition + + # Allows you to set the content-disposition of the mail object. Accepts a type + # and a hash of parameters. + # + # Example: + # + # mail.set_disposition("attachment", {:filename => "test.rb"}) + # mail.disposition #=> "attachment" + # mail['content-disposition'].to_s #=> "attachment; filename=test.rb" + def set_disposition( str, params = nil ) + if h = @header['content-disposition'] + h.disposition = str + h.params.clear + else + store('Content-Disposition', str) + h = @header['content-disposition'] + end + h.params.replace params if params + end + + alias disposition= set_disposition + alias set_content_disposition set_disposition + alias content_disposition= set_disposition + + # Returns the value of a parameter in an existing content-disposition header + # + # Example: + # + # mail.set_disposition("attachment", {:filename => "test.rb"}) + # mail['content-disposition'].to_s #=> "attachment; filename=test.rb" + # mail.disposition_param("filename") #=> "test.rb" + # mail.disposition_param("missing_param_key") #=> nil + # mail.disposition_param("missing_param_key", false) #=> false + # mail.disposition_param("missing_param_key", "Nothing to see here") #=> "Nothing to see here" + def disposition_param( name, default = nil ) + if h = @header['content-disposition'] + h[name] || default + else + default + end + end + + # Convert the Mail object's body into a Base64 encoded email + # returning the modified Mail object + def base64_encode! + store 'Content-Transfer-Encoding', 'Base64' + self.body = base64_encode + end + + # Return the result of encoding the TMail::Mail object body + # without altering the current body + def base64_encode + Base64.folding_encode(self.body) + end + + # Convert the Mail object's body into a Base64 decoded email + # returning the modified Mail object + def base64_decode! + if /base64/i === self.transfer_encoding('') + store 'Content-Transfer-Encoding', '8bit' + self.body = base64_decode + end + end + + # Returns the result of decoding the TMail::Mail object body + # without altering the current body + def base64_decode + Base64.decode(self.body, @config.strict_base64decode?) + end + + # Returns an array of each destination in the email message including to: cc: or bcc: + # + # Example: + # + # mail.to = "Mikel <mikel@lindsaar.net>" + # mail.cc = "Trans <t@t.com>" + # mail.bcc = "bob <bob@me.com>" + # mail.destinations #=> ["mikel@lindsaar.net", "t@t.com", "bob@me.com"] + def destinations( default = nil ) + ret = [] + %w( to cc bcc ).each do |nm| + if h = @header[nm] + h.addrs.each {|i| ret.push i.address } + end + end + ret.empty? ? default : ret + end + + # Yields a block of destination, yielding each as a string. + # (from the destinations example) + # mail.each_destination { |d| puts "#{d.class}: #{d}" } + # String: mikel@lindsaar.net + # String: t@t.com + # String: bob@me.com + def each_destination( &block ) + destinations([]).each do |i| + if Address === i + yield i + else + i.each(&block) + end + end + end + + alias each_dest each_destination + + # Returns an array of reply to addresses that the Mail object has, + # or if the Mail message has no reply-to, returns an array of the + # Mail objects from addresses. Else returns the default which can + # either be passed as a parameter or defaults to nil + # + # Example: + # mail.from = "Mikel <mikel@lindsaar.net>" + # mail.reply_to = nil + # mail.reply_addresses #=> [""] + # + def reply_addresses( default = nil ) + reply_to_addrs(nil) or from_addrs(nil) or default + end + + # Returns the "sender" field as an array -> useful to find out who to + # send an error email to. + def error_reply_addresses( default = nil ) + if s = sender(nil) + [s] + else + from_addrs(default) + end + end + + # Returns true if the Mail object is a multipart message + def multipart? + main_type('').downcase == 'multipart' + end + + # Creates a new email in reply to self. Sets the In-Reply-To and + # References headers for you automagically. + # + # Example: + # mail = TMail::Mail.load("my_email") + # reply_email = mail.create_reply + # reply_email.class #=> TMail::Mail + # reply_email.references #=> ["<d3b8cf8e49f04480850c28713a1f473e@lindsaar.net>"] + # reply_email.in_reply_to #=> ["<d3b8cf8e49f04480850c28713a1f473e@lindsaar.net>"] + def create_reply + setup_reply create_empty_mail() + end + + # Creates a new email in reply to self. Sets the In-Reply-To and + # References headers for you automagically. + # + # Example: + # mail = TMail::Mail.load("my_email") + # forward_email = mail.create_forward + # forward_email.class #=> TMail::Mail + # forward_email.content_type #=> "multipart/mixed" + # forward_email.body #=> "Attachment: (unnamed)" + # forward_email.encoded #=> Returns the original email as a MIME attachment + def create_forward + setup_forward create_empty_mail() + end + + #:stopdoc: + private + + def create_empty_mail + self.class.new(StringPort.new(''), @config) + end + + def setup_reply( mail ) + if tmp = reply_addresses(nil) + mail.to_addrs = tmp + end + + mid = message_id(nil) + tmp = references(nil) || [] + tmp.push mid if mid + mail.in_reply_to = [mid] if mid + mail.references = tmp unless tmp.empty? + mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') + mail.mime_version = '1.0' + mail + end + + def setup_forward( mail ) + m = Mail.new(StringPort.new('')) + m.body = decoded + m.set_content_type 'message', 'rfc822' + m.encoding = encoding('7bit') + mail.parts.push m + # call encoded to reparse the message + mail.encoded + mail + end + + #:startdoc: + end # class Mail + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb new file mode 100644 index 0000000000..6c0e251102 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb @@ -0,0 +1,3 @@ +#:stopdoc: +require 'tmail/mailbox' +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb new file mode 100644 index 0000000000..5a319907ae --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb @@ -0,0 +1,578 @@ +=begin rdoc + += Mail class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + + + +require 'tmail/interface' +require 'tmail/encode' +require 'tmail/header' +require 'tmail/port' +require 'tmail/config' +require 'tmail/utils' +require 'tmail/attachments' +require 'tmail/quoting' +require 'socket' + +module TMail + + # == Mail Class + # + # Accessing a TMail object done via the TMail::Mail class. As email can be fairly complex + # creatures, you will find a large amount of accessor and setter methods in this class! + # + # Most of the below methods handle the header, in fact, what TMail does best is handle the + # header of the email object. There are only a few methods that deal directly with the body + # of the email, such as base64_encode and base64_decode. + # + # === Using TMail inside your code + # + # The usual way is to install the gem (see the {README}[link:/README] on how to do this) and + # then put at the top of your class: + # + # require 'tmail' + # + # You can then create a new TMail object in your code with: + # + # @email = TMail::Mail.new + # + # Or if you have an email as a string, you can initialize a new TMail::Mail object and get it + # to parse that string for you like so: + # + # @email = TMail::Mail.parse(email_text) + # + # You can also read a single email off the disk, for example: + # + # @email = TMail::Mail.load('filename.txt') + # + # Also, you can read a mailbox (usual unix mbox format) and end up with an array of TMail + # objects by doing something like this: + # + # # Note, we pass true as the last variable to open the mailbox read only + # mailbox = TMail::UNIXMbox.new("mailbox", nil, true) + # @emails = [] + # mailbox.each_port { |m| @emails << TMail::Mail.new(m) } + # + class Mail + + class << self + + # Opens an email that has been saved out as a file by itself. + # + # This function will read a file non-destructively and then parse + # the contents and return a TMail::Mail object. + # + # Does not handle multiple email mailboxes (like a unix mbox) for that + # use the TMail::UNIXMbox class. + # + # Example: + # mail = TMail::Mail.load('filename') + # + def load( fname ) + new(FilePort.new(fname)) + end + + alias load_from load + alias loadfrom load + + # Parses an email from the supplied string and returns a TMail::Mail + # object. + # + # Example: + # require 'rubygems'; require 'tmail' + # email_string =<<HEREDOC + # To: mikel@lindsaar.net + # From: mikel@me.com + # Subject: This is a short Email + # + # Hello there Mikel! + # + # HEREDOC + # mail = TMail::Mail.parse(email_string) + # #=> #<TMail::Mail port=#<TMail::StringPort:id=0xa30ac0> bodyport=nil> + # mail.body + # #=> "Hello there Mikel!\n\n" + def parse( str ) + new(StringPort.new(str)) + end + + end + + def initialize( port = nil, conf = DEFAULT_CONFIG ) #:nodoc: + @port = port || StringPort.new + @config = Config.to_config(conf) + + @header = {} + @body_port = nil + @body_parsed = false + @epilogue = '' + @parts = [] + + @port.ropen {|f| + parse_header f + parse_body f unless @port.reproducible? + } + end + + # Provides access to the port this email is using to hold it's data + # + # Example: + # mail = TMail::Mail.parse(email_string) + # mail.port + # #=> #<TMail::StringPort:id=0xa2c952> + attr_reader :port + + def inspect + "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>" + end + + # + # to_s interfaces + # + + public + + include StrategyInterface + + def write_back( eol = "\n", charset = 'e' ) + parse_body + @port.wopen {|stream| encoded eol, charset, stream } + end + + def accept( strategy ) + with_multipart_encoding(strategy) { + ordered_each do |name, field| + next if field.empty? + strategy.header_name canonical(name) + field.accept strategy + strategy.puts + end + strategy.puts + body_port().ropen {|r| + strategy.write r.read + } + } + end + + private + + def canonical( name ) + name.split(/-/).map {|s| s.capitalize }.join('-') + end + + def with_multipart_encoding( strategy ) + if parts().empty? # DO NOT USE @parts + yield + + else + bound = ::TMail.new_boundary + if @header.key? 'content-type' + @header['content-type'].params['boundary'] = bound + else + store 'Content-Type', %<multipart/mixed; boundary="#{bound}"> + end + + yield + + parts().each do |tm| + strategy.puts + strategy.puts '--' + bound + tm.accept strategy + end + strategy.puts + strategy.puts '--' + bound + '--' + strategy.write epilogue() + end + end + + ### + ### header + ### + + public + + ALLOW_MULTIPLE = { + 'received' => true, + 'resent-date' => true, + 'resent-from' => true, + 'resent-sender' => true, + 'resent-to' => true, + 'resent-cc' => true, + 'resent-bcc' => true, + 'resent-message-id' => true, + 'comments' => true, + 'keywords' => true + } + USE_ARRAY = ALLOW_MULTIPLE + + def header + @header.dup + end + + # Returns a TMail::AddressHeader object of the field you are querying. + # Examples: + # @mail['from'] #=> #<TMail::AddressHeader "mikel@test.com.au"> + # @mail['to'] #=> #<TMail::AddressHeader "mikel@test.com.au"> + # + # You can get the string value of this by passing "to_s" to the query: + # Example: + # @mail['to'].to_s #=> "mikel@test.com.au" + def []( key ) + @header[key.downcase] + end + + def sub_header(key, param) + (hdr = self[key]) ? hdr[param] : nil + end + + alias fetch [] + + # Allows you to set or delete TMail header objects at will. + # Eamples: + # @mail = TMail::Mail.new + # @mail['to'].to_s # => 'mikel@test.com.au' + # @mail['to'] = 'mikel@elsewhere.org' + # @mail['to'].to_s # => 'mikel@elsewhere.org' + # @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n" + # @mail['to'] = nil + # @mail['to'].to_s # => nil + # @mail.encoded # => "\r\n" + # + # Note: setting mail[] = nil actualy deletes the header field in question from the object, + # it does not just set the value of the hash to nil + def []=( key, val ) + dkey = key.downcase + + if val.nil? + @header.delete dkey + return nil + end + + case val + when String + header = new_hf(key, val) + when HeaderField + ; + when Array + ALLOW_MULTIPLE.include? dkey or + raise ArgumentError, "#{key}: Header must not be multiple" + @header[dkey] = val + return val + else + header = new_hf(key, val.to_s) + end + if ALLOW_MULTIPLE.include? dkey + (@header[dkey] ||= []).push header + else + @header[dkey] = header + end + + val + end + + alias store []= + + # Allows you to loop through each header in the TMail::Mail object in a block + # Example: + # @mail['to'] = 'mikel@elsewhere.org' + # @mail['from'] = 'me@me.com' + # @mail.each_header { |k,v| puts "#{k} = #{v}" } + # # => from = me@me.com + # # => to = mikel@elsewhere.org + def each_header + @header.each do |key, val| + [val].flatten.each {|v| yield key, v } + end + end + + alias each_pair each_header + + def each_header_name( &block ) + @header.each_key(&block) + end + + alias each_key each_header_name + + def each_field( &block ) + @header.values.flatten.each(&block) + end + + alias each_value each_field + + FIELD_ORDER = %w( + return-path received + resent-date resent-from resent-sender resent-to + resent-cc resent-bcc resent-message-id + date from sender reply-to to cc bcc + message-id in-reply-to references + subject comments keywords + mime-version content-type content-transfer-encoding + content-disposition content-description + ) + + def ordered_each + list = @header.keys + FIELD_ORDER.each do |name| + if list.delete(name) + [@header[name]].flatten.each {|v| yield name, v } + end + end + list.each do |name| + [@header[name]].flatten.each {|v| yield name, v } + end + end + + def clear + @header.clear + end + + def delete( key ) + @header.delete key.downcase + end + + def delete_if + @header.delete_if do |key,val| + if Array === val + val.delete_if {|v| yield key, v } + val.empty? + else + yield key, val + end + end + end + + def keys + @header.keys + end + + def key?( key ) + @header.key? key.downcase + end + + def values_at( *args ) + args.map {|k| @header[k.downcase] }.flatten + end + + alias indexes values_at + alias indices values_at + + private + + def parse_header( f ) + name = field = nil + unixfrom = nil + + while line = f.gets + case line + when /\A[ \t]/ # continue from prev line + raise SyntaxError, 'mail is began by space' unless field + field << ' ' << line.strip + + when /\A([^\: \t]+):\s*/ # new header line + add_hf name, field if field + name = $1 + field = $' #.strip + + when /\A\-*\s*\z/ # end of header + add_hf name, field if field + name = field = nil + break + + when /\AFrom (\S+)/ + unixfrom = $1 + + when /^charset=.*/ + + else + raise SyntaxError, "wrong mail header: '#{line.inspect}'" + end + end + add_hf name, field if name + + if unixfrom + add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path'] + end + end + + def add_hf( name, field ) + key = name.downcase + field = new_hf(name, field) + + if ALLOW_MULTIPLE.include? key + (@header[key] ||= []).push field + else + @header[key] = field + end + end + + def new_hf( name, field ) + HeaderField.new(name, field, @config) + end + + ### + ### body + ### + + public + + def body_port + parse_body + @body_port + end + + def each( &block ) + body_port().ropen {|f| f.each(&block) } + end + + def quoted_body + body_port.ropen {|f| return f.read } + end + + def quoted_body= str + body_port.wopen { |f| f.write str } + str + end + + def body=( str ) + # Sets the body of the email to a new (encoded) string. + # + # We also reparses the email if the body is ever reassigned, this is a performance hit, however when + # you assign the body, you usually want to be able to make sure that you can access the attachments etc. + # + # Usage: + # + # mail.body = "Hello, this is\nthe body text" + # # => "Hello, this is\nthe body" + # mail.body + # # => "Hello, this is\nthe body" + @body_parsed = false + parse_body(StringInput.new(str)) + parse_body + @body_port.wopen {|f| f.write str } + str + end + + alias preamble quoted_body + alias preamble= quoted_body= + + def epilogue + parse_body + @epilogue.dup + end + + def epilogue=( str ) + parse_body + @epilogue = str + str + end + + def parts + parse_body + @parts + end + + def each_part( &block ) + parts().each(&block) + end + + # Returns true if the content type of this part of the email is + # a disposition attachment + def disposition_is_attachment? + (self['content-disposition'] && self['content-disposition'].disposition == "attachment") + end + + # Returns true if this part's content main type is text, else returns false. + # By main type is meant "text/plain" is text. "text/html" is text + def content_type_is_text? + self.header['content-type'] && (self.header['content-type'].main_type != "text") + end + + private + + def parse_body( f = nil ) + return if @body_parsed + if f + parse_body_0 f + else + @port.ropen {|f| + skip_header f + parse_body_0 f + } + end + @body_parsed = true + end + + def skip_header( f ) + while line = f.gets + return if /\A[\r\n]*\z/ === line + end + end + + def parse_body_0( f ) + if multipart? + read_multipart f + else + @body_port = @config.new_body_port(self) + @body_port.wopen {|w| + w.write f.read + } + end + end + + def read_multipart( src ) + bound = @header['content-type'].params['boundary'] + is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/ + lastbound = "--#{bound}--" + + ports = [ @config.new_preamble_port(self) ] + begin + f = ports.last.wopen + while line = src.gets + if is_sep === line + f.close + break if line.strip == lastbound + ports.push @config.new_part_port(self) + f = ports.last.wopen + else + f << line + end + end + @epilogue = (src.read || '') + ensure + f.close if f and not f.closed? + end + + @body_port = ports.shift + @parts = ports.map {|p| self.class.new(p, @config) } + end + + end # class Mail + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb new file mode 100644 index 0000000000..b0bc6a7f74 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb @@ -0,0 +1,495 @@ +=begin rdoc + += Mailbox and Mbox interaction class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/port' +require 'socket' +require 'mutex_m' + + +unless [].respond_to?(:sort_by) +module Enumerable#:nodoc: + def sort_by + map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] } + end +end +end + + +module TMail + + class MhMailbox + + PORT_CLASS = MhPort + + def initialize( dir ) + edir = File.expand_path(dir) + raise ArgumentError, "not directory: #{dir}"\ + unless FileTest.directory? edir + @dirname = edir + @last_file = nil + @last_atime = nil + end + + def directory + @dirname + end + + alias dirname directory + + attr_accessor :last_atime + + def inspect + "#<#{self.class} #{@dirname}>" + end + + def close + end + + def new_port + PORT_CLASS.new(next_file_name()) + end + + def each_port + mail_files().each do |path| + yield PORT_CLASS.new(path) + end + @last_atime = Time.now + end + + alias each each_port + + def reverse_each_port + mail_files().reverse_each do |path| + yield PORT_CLASS.new(path) + end + @last_atime = Time.now + end + + alias reverse_each reverse_each_port + + # old #each_mail returns Port + #def each_mail + # each_port do |port| + # yield Mail.new(port) + # end + #end + + def each_new_port( mtime = nil, &block ) + mtime ||= @last_atime + return each_port(&block) unless mtime + return unless File.mtime(@dirname) >= mtime + + mail_files().each do |path| + yield PORT_CLASS.new(path) if File.mtime(path) > mtime + end + @last_atime = Time.now + end + + private + + def mail_files + Dir.entries(@dirname)\ + .select {|s| /\A\d+\z/ === s }\ + .map {|s| s.to_i }\ + .sort\ + .map {|i| "#{@dirname}/#{i}" }\ + .select {|path| FileTest.file? path } + end + + def next_file_name + unless n = @last_file + n = 0 + Dir.entries(@dirname)\ + .select {|s| /\A\d+\z/ === s }\ + .map {|s| s.to_i }.sort\ + .each do |i| + next unless FileTest.file? "#{@dirname}/#{i}" + n = i + end + end + begin + n += 1 + end while FileTest.exist? "#{@dirname}/#{n}" + @last_file = n + + "#{@dirname}/#{n}" + end + + end # MhMailbox + + MhLoader = MhMailbox + + + class UNIXMbox + + class << self + alias newobj new + end + + # Creates a new mailbox object that you can iterate through to collect the + # emails from with "each_port". + # + # You need to pass it a filename of a unix mailbox format file, the format of this + # file can be researched at this page at {wikipedia}[link:http://en.wikipedia.org/wiki/Mbox] + # + # ==== Parameters + # + # +filename+: The filename of the mailbox you want to open + # + # +tmpdir+: Can be set to override TMail using the system environment's temp dir. TMail will first + # use the temp dir specified by you (if any) or then the temp dir specified in the Environment's TEMP + # value then the value in the Environment's TMP value or failing all of the above, '/tmp' + # + # +readonly+: If set to false, each email you take from the mail box will be removed from the mailbox. + # default is *false* - ie, it *WILL* truncate your mailbox file to ZERO once it has read the emails out. + # + # ==== Options: + # + # None + # + # ==== Examples: + # + # # First show using readonly true: + # + # require 'ftools' + # File.size("../test/fixtures/mailbox") + # #=> 20426 + # + # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox", nil, true) + # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=true.....> + # + # mailbox.each_port do |port| + # mail = TMail::Mail.new(port) + # puts mail.subject + # end + # #Testing mailbox 1 + # #Testing mailbox 2 + # #Testing mailbox 3 + # #Testing mailbox 4 + # require 'ftools' + # File.size?("../test/fixtures/mailbox") + # #=> 20426 + # + # # Now show with readonly set to the default false + # + # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox") + # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=false.....> + # + # mailbox.each_port do |port| + # mail = TMail::Mail.new(port) + # puts mail.subject + # end + # #Testing mailbox 1 + # #Testing mailbox 2 + # #Testing mailbox 3 + # #Testing mailbox 4 + # + # File.size?("../test/fixtures/mailbox") + # #=> nil + def UNIXMbox.new( filename, tmpdir = nil, readonly = false ) + tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp' + newobj(filename, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false) + end + + def UNIXMbox.lock( fname ) + begin + f = File.open(fname, 'r+') + f.flock File::LOCK_EX + yield f + ensure + f.flock File::LOCK_UN + f.close if f and not f.closed? + end + end + + def UNIXMbox.static_new( fname, dir, readonly = false ) + newobj(fname, dir, readonly, true) + end + + def initialize( fname, mhdir, readonly, static ) + @filename = fname + @readonly = readonly + @closed = false + + Dir.mkdir mhdir + @real = MhMailbox.new(mhdir) + @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static) + ObjectSpace.define_finalizer self, @finalizer + end + + def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p ) + lambda { + if writeback_p + lock(mboxfile) {|f| + mh.each_port do |port| + f.puts create_from_line(port) + port.ropen {|r| + f.puts r.read + } + end + } + end + if cleanup_p + Dir.foreach(mh.dirname) do |fname| + next if /\A\.\.?\z/ === fname + File.unlink "#{mh.dirname}/#{fname}" + end + Dir.rmdir mh.dirname + end + } + end + + # make _From line + def UNIXMbox.create_from_line( port ) + sprintf 'From %s %s', + fromaddr(), TextUtils.time2str(File.mtime(port.filename)) + end + + def UNIXMbox.fromaddr(port) + h = HeaderField.new_from_port(port, 'Return-Path') || + HeaderField.new_from_port(port, 'From') || + HeaderField.new_from_port(port, 'EnvelopeSender') or return 'nobody' + a = h.addrs[0] or return 'nobody' + a.spec + end + + def close + return if @closed + + ObjectSpace.undefine_finalizer self + @finalizer.call + @finalizer = nil + @real = nil + @closed = true + @updated = nil + end + + def each_port( &block ) + close_check + update + @real.each_port(&block) + end + + alias each each_port + + def reverse_each_port( &block ) + close_check + update + @real.reverse_each_port(&block) + end + + alias reverse_each reverse_each_port + + # old #each_mail returns Port + #def each_mail( &block ) + # each_port do |port| + # yield Mail.new(port) + # end + #end + + def each_new_port( mtime = nil ) + close_check + update + @real.each_new_port(mtime) {|p| yield p } + end + + def new_port + close_check + @real.new_port + end + + private + + def close_check + @closed and raise ArgumentError, 'accessing already closed mbox' + end + + def update + return if FileTest.zero?(@filename) + return if @updated and File.mtime(@filename) < @updated + w = nil + port = nil + time = nil + UNIXMbox.lock(@filename) {|f| + begin + f.each do |line| + if /\AFrom / === line + w.close if w + File.utime time, time, port.filename if time + + port = @real.new_port + w = port.wopen + time = fromline2time(line) + else + w.print line if w + end + end + ensure + if w and not w.closed? + w.close + File.utime time, time, port.filename if time + end + end + f.truncate(0) unless @readonly + @updated = Time.now + } + end + + def fromline2time( line ) + m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \ + or return nil + Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i) + end + + end # UNIXMbox + + MboxLoader = UNIXMbox + + + class Maildir + + extend Mutex_m + + PORT_CLASS = MaildirPort + + @seq = 0 + def Maildir.unique_number + synchronize { + @seq += 1 + return @seq + } + end + + def initialize( dir = nil ) + @dirname = dir || ENV['MAILDIR'] + raise ArgumentError, "not directory: #{@dirname}"\ + unless FileTest.directory? @dirname + @new = "#{@dirname}/new" + @tmp = "#{@dirname}/tmp" + @cur = "#{@dirname}/cur" + end + + def directory + @dirname + end + + def inspect + "#<#{self.class} #{@dirname}>" + end + + def close + end + + def each_port + mail_files(@cur).each do |path| + yield PORT_CLASS.new(path) + end + end + + alias each each_port + + def reverse_each_port + mail_files(@cur).reverse_each do |path| + yield PORT_CLASS.new(path) + end + end + + alias reverse_each reverse_each_port + + def new_port + fname = nil + tmpfname = nil + newfname = nil + + begin + fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}" + + tmpfname = "#{@tmp}/#{fname}" + newfname = "#{@new}/#{fname}" + end while FileTest.exist? tmpfname + + if block_given? + File.open(tmpfname, 'w') {|f| yield f } + File.rename tmpfname, newfname + PORT_CLASS.new(newfname) + else + File.open(tmpfname, 'w') {|f| f.write "\n\n" } + PORT_CLASS.new(tmpfname) + end + end + + def each_new_port + mail_files(@new).each do |path| + dest = @cur + '/' + File.basename(path) + File.rename path, dest + yield PORT_CLASS.new(dest) + end + + check_tmp + end + + TOO_OLD = 60 * 60 * 36 # 36 hour + + def check_tmp + old = Time.now.to_i - TOO_OLD + + each_filename(@tmp) do |full, fname| + if FileTest.file? full and + File.stat(full).mtime.to_i < old + File.unlink full + end + end + end + + private + + def mail_files( dir ) + Dir.entries(dir)\ + .select {|s| s[0] != ?. }\ + .sort_by {|s| s.slice(/\A\d+/).to_i }\ + .map {|s| "#{dir}/#{s}" }\ + .select {|path| FileTest.file? path } + end + + def each_filename( dir ) + Dir.foreach(dir) do |fname| + path = "#{dir}/#{fname}" + if fname[0] != ?. and FileTest.file? path + yield path, fname + end + end + end + + end # Maildir + + MaildirLoader = Maildir + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb new file mode 100644 index 0000000000..e52772793f --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb @@ -0,0 +1,6 @@ +#:stopdoc: +require 'tmail/version' +require 'tmail/mail' +require 'tmail/mailbox' +require 'tmail/core_extensions' +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb new file mode 100644 index 0000000000..6c0e251102 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb @@ -0,0 +1,3 @@ +#:stopdoc: +require 'tmail/mailbox' +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb new file mode 100644 index 0000000000..65147228a1 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb @@ -0,0 +1,248 @@ +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +#:stopdoc: +require 'nkf' +#:startdoc: + +module TMail + + class Mail + + def send_to( smtp ) + do_send_to(smtp) do + ready_to_send + end + end + + def send_text_to( smtp ) + do_send_to(smtp) do + ready_to_send + mime_encode + end + end + + def do_send_to( smtp ) + from = from_address or raise ArgumentError, 'no from address' + (dests = destinations).empty? and raise ArgumentError, 'no receipient' + yield + send_to_0 smtp, from, dests + end + private :do_send_to + + def send_to_0( smtp, from, to ) + smtp.ready(from, to) do |f| + encoded "\r\n", 'j', f, '' + end + end + + def ready_to_send + delete_no_send_fields + add_message_id + add_date + end + + NOSEND_FIELDS = %w( + received + bcc + ) + + def delete_no_send_fields + NOSEND_FIELDS.each do |nm| + delete nm + end + delete_if {|n,v| v.empty? } + end + + def add_message_id( fqdn = nil ) + self.message_id = ::TMail::new_message_id(fqdn) + end + + def add_date + self.date = Time.now + end + + def mime_encode + if parts.empty? + mime_encode_singlepart + else + mime_encode_multipart true + end + end + + def mime_encode_singlepart + self.mime_version = '1.0' + b = body + if NKF.guess(b) != NKF::BINARY + mime_encode_text b + else + mime_encode_binary b + end + end + + def mime_encode_text( body ) + self.body = NKF.nkf('-j -m0', body) + self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'} + self.encoding = '7bit' + end + + def mime_encode_binary( body ) + self.body = [body].pack('m') + self.set_content_type 'application', 'octet-stream' + self.encoding = 'Base64' + end + + def mime_encode_multipart( top = true ) + self.mime_version = '1.0' if top + self.set_content_type 'multipart', 'mixed' + e = encoding(nil) + if e and not /\A(?:7bit|8bit|binary)\z/i === e + raise ArgumentError, + 'using C.T.Encoding with multipart mail is not permitted' + end + end + + end + + #:stopdoc: + class DeleteFields + + NOSEND_FIELDS = %w( + received + bcc + ) + + def initialize( nosend = nil, delempty = true ) + @no_send_fields = nosend || NOSEND_FIELDS.dup + @delete_empty_fields = delempty + end + + attr :no_send_fields + attr :delete_empty_fields, true + + def exec( mail ) + @no_send_fields.each do |nm| + delete nm + end + delete_if {|n,v| v.empty? } if @delete_empty_fields + end + + end + #:startdoc: + + #:stopdoc: + class AddMessageId + + def initialize( fqdn = nil ) + @fqdn = fqdn + end + + attr :fqdn, true + + def exec( mail ) + mail.message_id = ::TMail::new_msgid(@fqdn) + end + + end + #:startdoc: + + #:stopdoc: + class AddDate + + def exec( mail ) + mail.date = Time.now + end + + end + #:startdoc: + + #:stopdoc: + class MimeEncodeAuto + + def initialize( s = nil, m = nil ) + @singlepart_composer = s || MimeEncodeSingle.new + @multipart_composer = m || MimeEncodeMulti.new + end + + attr :singlepart_composer + attr :multipart_composer + + def exec( mail ) + if mail._builtin_multipart? + then @multipart_composer + else @singlepart_composer end.exec mail + end + + end + #:startdoc: + + #:stopdoc: + class MimeEncodeSingle + + def exec( mail ) + mail.mime_version = '1.0' + b = mail.body + if NKF.guess(b) != NKF::BINARY + on_text b + else + on_binary b + end + end + + def on_text( body ) + mail.body = NKF.nkf('-j -m0', body) + mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'} + mail.encoding = '7bit' + end + + def on_binary( body ) + mail.body = [body].pack('m') + mail.set_content_type 'application', 'octet-stream' + mail.encoding = 'Base64' + end + + end + #:startdoc: + + #:stopdoc: + class MimeEncodeMulti + + def exec( mail, top = true ) + mail.mime_version = '1.0' if top + mail.set_content_type 'multipart', 'mixed' + e = encoding(nil) + if e and not /\A(?:7bit|8bit|binary)\z/i === e + raise ArgumentError, + 'using C.T.Encoding with multipart mail is not permitted' + end + mail.parts.each do |m| + exec m, false if m._builtin_multipart? + end + end + + end + #:startdoc: +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb new file mode 100644 index 0000000000..22b0a126ca --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb @@ -0,0 +1,132 @@ +=begin rdoc + += Obsolete methods that are depriciated + +If you really want to see them, go to lib/tmail/obsolete.rb and view to your +heart's content. + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +module TMail #:nodoc: + + class Mail + alias include? key? + alias has_key? key? + + def values + ret = [] + each_field {|v| ret.push v } + ret + end + + def value?( val ) + HeaderField === val or return false + + [ @header[val.name.downcase] ].flatten.include? val + end + + alias has_value? value? + end + + class Mail + def from_addr( default = nil ) + addr, = from_addrs(nil) + addr || default + end + + def from_address( default = nil ) + if a = from_addr(nil) + a.spec + else + default + end + end + + alias from_address= from_addrs= + + def from_phrase( default = nil ) + if a = from_addr(nil) + a.phrase + else + default + end + end + + alias msgid message_id + alias msgid= message_id= + + alias each_dest each_destination + end + + class Address + alias route routes + alias addr spec + + def spec=( str ) + @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) } + end + + alias addr= spec= + alias address= spec= + end + + class MhMailbox + alias new_mail new_port + alias each_mail each_port + alias each_newmail each_new_port + end + class UNIXMbox + alias new_mail new_port + alias each_mail each_port + alias each_newmail each_new_port + end + class Maildir + alias new_mail new_port + alias each_mail each_port + alias each_newmail each_new_port + end + + extend TextUtils + + class << self + alias msgid? message_id? + alias boundary new_boundary + alias msgid new_message_id + alias new_msgid new_message_id + end + + def Mail.boundary + ::TMail.new_boundary + end + + def Mail.msgid + ::TMail.new_message_id + end + +end # module TMail +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb new file mode 100644 index 0000000000..ab1a828471 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb @@ -0,0 +1,1476 @@ +#:stopdoc: +# DO NOT MODIFY!!!! +# This file is automatically generated by racc 1.4.5 +# from racc grammer file "parser.y". +# +# +# parser.rb: generated by racc (runtime embedded) +# +###### racc/parser.rb begin +unless $".index 'racc/parser.rb' +$".push 'racc/parser.rb' + +self.class.module_eval <<'..end racc/parser.rb modeval..id8076474214', 'racc/parser.rb', 1 +# +# $Id: parser.rb,v 1.7 2005/11/20 17:31:32 aamine Exp $ +# +# Copyright (c) 1999-2005 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the same terms of ruby. +# +# As a special exception, when this code is copied by Racc +# into a Racc output file, you may use that output file +# without restriction. +# + +unless defined?(NotImplementedError) + NotImplementedError = NotImplementError +end + +module Racc + class ParseError < StandardError; end +end +unless defined?(::ParseError) + ParseError = Racc::ParseError +end + +module Racc + + unless defined?(Racc_No_Extentions) + Racc_No_Extentions = false + end + + class Parser + + Racc_Runtime_Version = '1.4.5' + Racc_Runtime_Revision = '$Revision: 1.7 $'.split[1] + + Racc_Runtime_Core_Version_R = '1.4.5' + Racc_Runtime_Core_Revision_R = '$Revision: 1.7 $'.split[1] + begin + require 'racc/cparse' + # Racc_Runtime_Core_Version_C = (defined in extention) + Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2] + unless new.respond_to?(:_racc_do_parse_c, true) + raise LoadError, 'old cparse.so' + end + if Racc_No_Extentions + raise LoadError, 'selecting ruby version of racc runtime core' + end + + Racc_Main_Parsing_Routine = :_racc_do_parse_c + Racc_YY_Parse_Method = :_racc_yyparse_c + Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C + Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C + Racc_Runtime_Type = 'c' + rescue LoadError + Racc_Main_Parsing_Routine = :_racc_do_parse_rb + Racc_YY_Parse_Method = :_racc_yyparse_rb + Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R + Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R + Racc_Runtime_Type = 'ruby' + end + + def Parser.racc_runtime_type + Racc_Runtime_Type + end + + private + + def _racc_setup + @yydebug = false unless self.class::Racc_debug_parser + @yydebug = false unless defined?(@yydebug) + if @yydebug + @racc_debug_out = $stderr unless defined?(@racc_debug_out) + @racc_debug_out ||= $stderr + end + arg = self.class::Racc_arg + arg[13] = true if arg.size < 14 + arg + end + + def _racc_init_sysvars + @racc_state = [0] + @racc_tstack = [] + @racc_vstack = [] + + @racc_t = nil + @racc_val = nil + + @racc_read_next = true + + @racc_user_yyerror = false + @racc_error_status = 0 + end + + ### + ### do_parse + ### + + def do_parse + __send__(Racc_Main_Parsing_Routine, _racc_setup(), false) + end + + def next_token + raise NotImplementedError, "#{self.class}\#next_token is not defined" + end + + def _racc_do_parse_rb(arg, in_debug) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + + _racc_init_sysvars + tok = act = i = nil + nerr = 0 + + catch(:racc_end_parse) { + while true + if i = action_pointer[@racc_state[-1]] + if @racc_read_next + if @racc_t != 0 # not EOF + tok, @racc_val = next_token() + unless tok # EOF + @racc_t = 0 + else + @racc_t = (token_table[tok] or 1) # error token + end + racc_read_token(@racc_t, tok, @racc_val) if @yydebug + @racc_read_next = false + end + end + i += @racc_t + unless i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] + end + else + act = action_default[@racc_state[-1]] + end + while act = _racc_evalact(act, arg) + ; + end + end + } + end + + ### + ### yyparse + ### + + def yyparse(recv, mid) + __send__(Racc_YY_Parse_Method, recv, mid, _racc_setup(), true) + end + + def _racc_yyparse_rb(recv, mid, arg, c_debug) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + + _racc_init_sysvars + tok = nil + act = nil + i = nil + nerr = 0 + + catch(:racc_end_parse) { + until i = action_pointer[@racc_state[-1]] + while act = _racc_evalact(action_default[@racc_state[-1]], arg) + ; + end + end + recv.__send__(mid) do |tok, val| + unless tok + @racc_t = 0 + else + @racc_t = (token_table[tok] or 1) # error token + end + @racc_val = val + @racc_read_next = false + + i += @racc_t + unless i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] + end + while act = _racc_evalact(act, arg) + ; + end + + while not (i = action_pointer[@racc_state[-1]]) or + not @racc_read_next or + @racc_t == 0 # $ + unless i and i += @racc_t and + i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] + end + while act = _racc_evalact(act, arg) + ; + end + end + end + } + end + + ### + ### common + ### + + def _racc_evalact(act, arg) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + nerr = 0 # tmp + + if act > 0 and act < shift_n + # + # shift + # + if @racc_error_status > 0 + @racc_error_status -= 1 unless @racc_t == 1 # error token + end + @racc_vstack.push @racc_val + @racc_state.push act + @racc_read_next = true + if @yydebug + @racc_tstack.push @racc_t + racc_shift @racc_t, @racc_tstack, @racc_vstack + end + + elsif act < 0 and act > -reduce_n + # + # reduce + # + code = catch(:racc_jump) { + @racc_state.push _racc_do_reduce(arg, act) + false + } + if code + case code + when 1 # yyerror + @racc_user_yyerror = true # user_yyerror + return -reduce_n + when 2 # yyaccept + return shift_n + else + raise '[Racc Bug] unknown jump code' + end + end + + elsif act == shift_n + # + # accept + # + racc_accept if @yydebug + throw :racc_end_parse, @racc_vstack[0] + + elsif act == -reduce_n + # + # error + # + case @racc_error_status + when 0 + unless arg[21] # user_yyerror + nerr += 1 + on_error @racc_t, @racc_val, @racc_vstack + end + when 3 + if @racc_t == 0 # is $ + throw :racc_end_parse, nil + end + @racc_read_next = true + end + @racc_user_yyerror = false + @racc_error_status = 3 + while true + if i = action_pointer[@racc_state[-1]] + i += 1 # error token + if i >= 0 and + (act = action_table[i]) and + action_check[i] == @racc_state[-1] + break + end + end + throw :racc_end_parse, nil if @racc_state.size <= 1 + @racc_state.pop + @racc_vstack.pop + if @yydebug + @racc_tstack.pop + racc_e_pop @racc_state, @racc_tstack, @racc_vstack + end + end + return act + + else + raise "[Racc Bug] unknown action #{act.inspect}" + end + + racc_next_state(@racc_state[-1], @racc_state) if @yydebug + + nil + end + + def _racc_do_reduce(arg, act) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + state = @racc_state + vstack = @racc_vstack + tstack = @racc_tstack + + i = act * -3 + len = reduce_table[i] + reduce_to = reduce_table[i+1] + method_id = reduce_table[i+2] + void_array = [] + + tmp_t = tstack[-len, len] if @yydebug + tmp_v = vstack[-len, len] + tstack[-len, len] = void_array if @yydebug + vstack[-len, len] = void_array + state[-len, len] = void_array + + # tstack must be updated AFTER method call + if use_result + vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) + else + vstack.push __send__(method_id, tmp_v, vstack) + end + tstack.push reduce_to + + racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug + + k1 = reduce_to - nt_base + if i = goto_pointer[k1] + i += state[-1] + if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 + return curstate + end + end + goto_default[k1] + end + + def on_error(t, val, vstack) + raise ParseError, sprintf("\nparse error on value %s (%s)", + val.inspect, token_to_str(t) || '?') + end + + def yyerror + throw :racc_jump, 1 + end + + def yyaccept + throw :racc_jump, 2 + end + + def yyerrok + @racc_error_status = 0 + end + + # + # for debugging output + # + + def racc_read_token(t, tok, val) + @racc_debug_out.print 'read ' + @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' + @racc_debug_out.puts val.inspect + @racc_debug_out.puts + end + + def racc_shift(tok, tstack, vstack) + @racc_debug_out.puts "shift #{racc_token2str tok}" + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_reduce(toks, sim, tstack, vstack) + out = @racc_debug_out + out.print 'reduce ' + if toks.empty? + out.print ' <none>' + else + toks.each {|t| out.print ' ', racc_token2str(t) } + end + out.puts " --> #{racc_token2str(sim)}" + + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_accept + @racc_debug_out.puts 'accept' + @racc_debug_out.puts + end + + def racc_e_pop(state, tstack, vstack) + @racc_debug_out.puts 'error recovering mode: pop token' + racc_print_states state + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_next_state(curstate, state) + @racc_debug_out.puts "goto #{curstate}" + racc_print_states state + @racc_debug_out.puts + end + + def racc_print_stacks(t, v) + out = @racc_debug_out + out.print ' [' + t.each_index do |i| + out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' + end + out.puts ' ]' + end + + def racc_print_states(s) + out = @racc_debug_out + out.print ' [' + s.each {|st| out.print ' ', st } + out.puts ' ]' + end + + def racc_token2str(tok) + self.class::Racc_token_to_s_table[tok] or + raise "[Racc Bug] can't convert token #{tok} to string" + end + + def token_to_str(t) + self.class::Racc_token_to_s_table[t] + end + + end + +end +..end racc/parser.rb modeval..id8076474214 +end +###### racc/parser.rb end + + +# +# parser.rb +# +# Copyright (c) 1998-2007 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the terms of +# the GNU Lesser General Public License version 2.1. +# + +require 'tmail/scanner' +require 'tmail/utils' + + +module TMail + + class Parser < Racc::Parser + +module_eval <<'..end parser.y modeval..id7b0b3dccb7', 'parser.y', 340 + + include TextUtils + + def self.parse( ident, str, cmt = nil ) + new.parse(ident, str, cmt) + end + + MAILP_DEBUG = false + + def initialize + self.debug = MAILP_DEBUG + end + + def debug=( flag ) + @yydebug = flag && Racc_debug_parser + @scanner_debug = flag + end + + def debug + @yydebug + end + + def parse( ident, str, comments = nil ) + @scanner = Scanner.new(str, ident, comments) + @scanner.debug = @scanner_debug + @first = [ident, ident] + result = yyparse(self, :parse_in) + comments.map! {|c| to_kcode(c) } if comments + result + end + + private + + def parse_in( &block ) + yield @first + @scanner.scan(&block) + end + + def on_error( t, val, vstack ) + raise SyntaxError, "parse error on token #{racc_token2str t}" + end + +..end parser.y modeval..id7b0b3dccb7 + +##### racc 1.4.5 generates ### + +racc_reduce_table = [ + 0, 0, :racc_error, + 2, 35, :_reduce_1, + 2, 35, :_reduce_2, + 2, 35, :_reduce_3, + 2, 35, :_reduce_4, + 2, 35, :_reduce_5, + 2, 35, :_reduce_6, + 2, 35, :_reduce_7, + 2, 35, :_reduce_8, + 2, 35, :_reduce_9, + 2, 35, :_reduce_10, + 2, 35, :_reduce_11, + 2, 35, :_reduce_12, + 6, 36, :_reduce_13, + 0, 48, :_reduce_none, + 2, 48, :_reduce_none, + 3, 49, :_reduce_16, + 5, 49, :_reduce_17, + 1, 50, :_reduce_18, + 7, 37, :_reduce_19, + 0, 51, :_reduce_none, + 2, 51, :_reduce_21, + 0, 52, :_reduce_none, + 2, 52, :_reduce_23, + 1, 58, :_reduce_24, + 3, 58, :_reduce_25, + 2, 58, :_reduce_26, + 0, 53, :_reduce_none, + 2, 53, :_reduce_28, + 0, 54, :_reduce_29, + 3, 54, :_reduce_30, + 0, 55, :_reduce_none, + 2, 55, :_reduce_32, + 2, 55, :_reduce_33, + 0, 56, :_reduce_none, + 2, 56, :_reduce_35, + 1, 61, :_reduce_36, + 1, 61, :_reduce_37, + 0, 57, :_reduce_none, + 2, 57, :_reduce_39, + 1, 38, :_reduce_none, + 1, 38, :_reduce_none, + 3, 38, :_reduce_none, + 1, 46, :_reduce_none, + 1, 46, :_reduce_none, + 1, 46, :_reduce_none, + 1, 39, :_reduce_none, + 2, 39, :_reduce_47, + 1, 64, :_reduce_48, + 3, 64, :_reduce_49, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 69, :_reduce_52, + 3, 69, :_reduce_53, + 1, 47, :_reduce_none, + 1, 47, :_reduce_none, + 2, 47, :_reduce_56, + 2, 67, :_reduce_none, + 3, 65, :_reduce_58, + 2, 65, :_reduce_59, + 1, 70, :_reduce_60, + 2, 70, :_reduce_61, + 4, 62, :_reduce_62, + 3, 62, :_reduce_63, + 2, 72, :_reduce_none, + 2, 73, :_reduce_65, + 4, 73, :_reduce_66, + 3, 63, :_reduce_67, + 1, 63, :_reduce_68, + 1, 74, :_reduce_none, + 2, 74, :_reduce_70, + 1, 71, :_reduce_71, + 3, 71, :_reduce_72, + 1, 59, :_reduce_73, + 3, 59, :_reduce_74, + 1, 76, :_reduce_75, + 2, 76, :_reduce_76, + 1, 75, :_reduce_none, + 1, 75, :_reduce_none, + 1, 75, :_reduce_none, + 1, 77, :_reduce_none, + 1, 77, :_reduce_none, + 1, 77, :_reduce_none, + 1, 66, :_reduce_none, + 2, 66, :_reduce_none, + 3, 60, :_reduce_85, + 1, 40, :_reduce_86, + 3, 40, :_reduce_87, + 1, 79, :_reduce_none, + 2, 79, :_reduce_89, + 1, 41, :_reduce_90, + 2, 41, :_reduce_91, + 3, 42, :_reduce_92, + 5, 43, :_reduce_93, + 3, 43, :_reduce_94, + 0, 80, :_reduce_95, + 5, 80, :_reduce_96, + 5, 80, :_reduce_97, + 1, 44, :_reduce_98, + 3, 45, :_reduce_99, + 0, 81, :_reduce_none, + 1, 81, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none ] + +racc_reduce_n = 109 + +racc_shift_n = 167 + +racc_action_table = [ + -70, -69, 23, 25, 145, 146, 29, 31, 105, 106, + 16, 17, 20, 22, 136, 27, -70, -69, 32, 101, + -70, -69, 153, 100, 113, 115, -70, -69, -70, 109, + 75, 23, 25, 101, 154, 29, 31, 142, 143, 16, + 17, 20, 22, 107, 27, 23, 25, 32, 98, 29, + 31, 96, 94, 16, 17, 20, 22, 78, 27, 23, + 25, 32, 112, 29, 31, 74, 91, 16, 17, 20, + 22, 88, 117, 92, 81, 32, 23, 25, 80, 123, + 29, 31, 100, 125, 16, 17, 20, 22, 126, 23, + 25, 109, 32, 29, 31, 91, 128, 16, 17, 20, + 22, 129, 27, 23, 25, 32, 101, 29, 31, 101, + 130, 16, 17, 20, 22, 79, 52, 23, 25, 32, + 78, 29, 31, 133, 78, 16, 17, 20, 22, 77, + 23, 25, 75, 32, 29, 31, 65, 62, 16, 17, + 20, 22, 139, 23, 25, 101, 32, 29, 31, 60, + 100, 16, 17, 20, 22, 44, 27, 101, 147, 32, + 23, 25, 120, 148, 29, 31, 151, 152, 16, 17, + 20, 22, 42, 27, 156, 158, 32, 23, 25, 120, + 40, 29, 31, 15, 163, 16, 17, 20, 22, 40, + 27, 23, 25, 32, 68, 29, 31, 165, 166, 16, + 17, 20, 22, nil, 27, 23, 25, 32, nil, 29, + 31, 74, nil, 16, 17, 20, 22, nil, 23, 25, + nil, 32, 29, 31, nil, nil, 16, 17, 20, 22, + nil, 23, 25, nil, 32, 29, 31, nil, nil, 16, + 17, 20, 22, nil, 23, 25, nil, 32, 29, 31, + nil, nil, 16, 17, 20, 22, nil, 23, 25, nil, + 32, 29, 31, nil, nil, 16, 17, 20, 22, nil, + 27, 23, 25, 32, nil, 29, 31, nil, nil, 16, + 17, 20, 22, nil, 23, 25, nil, 32, 29, 31, + nil, nil, 16, 17, 20, 22, nil, 23, 25, nil, + 32, 29, 31, nil, nil, 16, 17, 20, 22, nil, + 84, 25, nil, 32, 29, 31, nil, 87, 16, 17, + 20, 22, 4, 6, 7, 8, 9, 10, 11, 12, + 13, 1, 2, 3, 84, 25, nil, nil, 29, 31, + nil, 87, 16, 17, 20, 22, 84, 25, nil, nil, + 29, 31, nil, 87, 16, 17, 20, 22, 84, 25, + nil, nil, 29, 31, nil, 87, 16, 17, 20, 22, + 84, 25, nil, nil, 29, 31, nil, 87, 16, 17, + 20, 22, 84, 25, nil, nil, 29, 31, nil, 87, + 16, 17, 20, 22, 84, 25, nil, nil, 29, 31, + nil, 87, 16, 17, 20, 22 ] + +racc_action_check = [ + 75, 28, 68, 68, 136, 136, 68, 68, 72, 72, + 68, 68, 68, 68, 126, 68, 75, 28, 68, 67, + 75, 28, 143, 66, 86, 86, 75, 28, 75, 75, + 28, 3, 3, 86, 143, 3, 3, 134, 134, 3, + 3, 3, 3, 73, 3, 151, 151, 3, 62, 151, + 151, 60, 56, 151, 151, 151, 151, 51, 151, 52, + 52, 151, 80, 52, 52, 52, 50, 52, 52, 52, + 52, 45, 89, 52, 42, 52, 71, 71, 41, 96, + 71, 71, 97, 98, 71, 71, 71, 71, 100, 7, + 7, 101, 71, 7, 7, 102, 104, 7, 7, 7, + 7, 105, 7, 8, 8, 7, 108, 8, 8, 111, + 112, 8, 8, 8, 8, 40, 8, 9, 9, 8, + 36, 9, 9, 117, 121, 9, 9, 9, 9, 33, + 10, 10, 70, 9, 10, 10, 13, 12, 10, 10, + 10, 10, 130, 2, 2, 131, 10, 2, 2, 11, + 135, 2, 2, 2, 2, 6, 2, 138, 139, 2, + 90, 90, 90, 140, 90, 90, 141, 142, 90, 90, + 90, 90, 5, 90, 147, 150, 90, 127, 127, 127, + 4, 127, 127, 1, 156, 127, 127, 127, 127, 158, + 127, 26, 26, 127, 26, 26, 26, 162, 163, 26, + 26, 26, 26, nil, 26, 27, 27, 26, nil, 27, + 27, 27, nil, 27, 27, 27, 27, nil, 154, 154, + nil, 27, 154, 154, nil, nil, 154, 154, 154, 154, + nil, 122, 122, nil, 154, 122, 122, nil, nil, 122, + 122, 122, 122, nil, 76, 76, nil, 122, 76, 76, + nil, nil, 76, 76, 76, 76, nil, 38, 38, nil, + 76, 38, 38, nil, nil, 38, 38, 38, 38, nil, + 38, 55, 55, 38, nil, 55, 55, nil, nil, 55, + 55, 55, 55, nil, 94, 94, nil, 55, 94, 94, + nil, nil, 94, 94, 94, 94, nil, 59, 59, nil, + 94, 59, 59, nil, nil, 59, 59, 59, 59, nil, + 114, 114, nil, 59, 114, 114, nil, 114, 114, 114, + 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 77, 77, nil, nil, 77, 77, + nil, 77, 77, 77, 77, 77, 44, 44, nil, nil, + 44, 44, nil, 44, 44, 44, 44, 44, 113, 113, + nil, nil, 113, 113, nil, 113, 113, 113, 113, 113, + 88, 88, nil, nil, 88, 88, nil, 88, 88, 88, + 88, 88, 74, 74, nil, nil, 74, 74, nil, 74, + 74, 74, 74, 74, 129, 129, nil, nil, 129, 129, + nil, 129, 129, 129, 129, 129 ] + +racc_action_pointer = [ + 320, 152, 129, 17, 165, 172, 137, 75, 89, 103, + 116, 135, 106, 105, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 177, 191, 1, nil, + nil, nil, nil, 109, nil, nil, 94, nil, 243, nil, + 99, 64, 74, nil, 332, 52, nil, nil, nil, nil, + 50, 31, 45, nil, nil, 257, 36, nil, nil, 283, + 22, nil, 16, nil, nil, nil, -3, -10, -12, nil, + 103, 62, -8, 15, 368, 0, 230, 320, nil, nil, + 47, nil, nil, nil, nil, nil, 4, nil, 356, 50, + 146, nil, nil, nil, 270, nil, 65, 56, 52, nil, + 57, 62, 79, nil, 68, 81, nil, nil, 77, nil, + nil, 80, 96, 344, 296, nil, nil, 108, nil, nil, + nil, 98, 217, nil, nil, nil, -19, 163, nil, 380, + 128, 116, nil, nil, 14, 124, -26, nil, 128, 141, + 148, 141, 152, 7, nil, nil, nil, 160, nil, nil, + 149, 31, nil, nil, 204, nil, 167, nil, 174, nil, + nil, nil, 169, 184, nil, nil, nil ] + +racc_action_default = [ + -109, -109, -109, -109, -14, -109, -20, -109, -109, -109, + -109, -109, -109, -109, -10, -95, -105, -106, -77, -44, + -107, -11, -108, -79, -43, -102, -109, -109, -60, -103, + -55, -104, -78, -68, -54, -71, -45, -12, -109, -1, + -109, -109, -109, -2, -109, -22, -51, -48, -50, -3, + -40, -41, -109, -46, -4, -86, -5, -88, -6, -90, + -109, -7, -95, -8, -9, -98, -100, -61, -59, -56, + -69, -109, -109, -109, -109, -75, -109, -109, -57, -15, + -109, 167, -73, -80, -82, -21, -24, -81, -109, -27, + -109, -83, -47, -89, -109, -91, -109, -100, -109, -99, + -101, -75, -58, -52, -109, -109, -64, -63, -65, -76, + -72, -67, -109, -109, -109, -26, -23, -109, -29, -49, + -84, -42, -87, -92, -94, -95, -109, -109, -62, -109, + -109, -25, -74, -28, -31, -100, -109, -53, -66, -109, + -109, -34, -109, -109, -93, -96, -97, -109, -18, -13, + -38, -109, -30, -33, -109, -32, -16, -19, -14, -35, + -36, -37, -109, -109, -39, -85, -17 ] + +racc_goto_table = [ + 39, 67, 70, 73, 38, 66, 69, 24, 37, 57, + 59, 36, 55, 67, 99, 90, 85, 157, 69, 108, + 83, 134, 111, 76, 49, 53, 141, 70, 73, 150, + 118, 89, 45, 155, 159, 149, 140, 21, 14, 19, + 119, 102, 64, 63, 61, 124, 70, 104, 58, 132, + 83, 56, 97, 83, 54, 93, 43, 5, 131, 95, + 116, nil, 76, nil, 83, 76, nil, 127, nil, 38, + nil, nil, nil, 103, 138, nil, 110, nil, nil, nil, + nil, nil, nil, 144, nil, nil, nil, nil, nil, 83, + 83, nil, nil, nil, 57, nil, nil, 122, nil, 121, + nil, nil, nil, nil, nil, 83, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 135, nil, nil, nil, nil, + nil, nil, 93, nil, nil, nil, 70, 161, 38, 70, + 162, 160, 137, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 164 ] + +racc_goto_check = [ + 2, 37, 37, 29, 36, 46, 28, 13, 13, 41, + 41, 31, 45, 37, 47, 32, 24, 23, 28, 25, + 44, 20, 25, 42, 4, 4, 21, 37, 29, 22, + 19, 18, 17, 26, 27, 16, 15, 12, 11, 33, + 34, 35, 10, 9, 8, 47, 37, 29, 7, 43, + 44, 6, 46, 44, 5, 41, 3, 1, 25, 41, + 24, nil, 42, nil, 44, 42, nil, 32, nil, 36, + nil, nil, nil, 13, 25, nil, 41, nil, nil, nil, + nil, nil, nil, 47, nil, nil, nil, nil, nil, 44, + 44, nil, nil, nil, 41, nil, nil, 45, nil, 31, + nil, nil, nil, nil, nil, 44, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 46, nil, nil, nil, nil, + nil, nil, 41, nil, nil, nil, 37, 29, 36, 37, + 29, 28, 13, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 2 ] + +racc_goto_pointer = [ + nil, 57, -4, 50, 17, 46, 42, 38, 33, 31, + 29, 37, 35, 5, nil, -94, -105, 26, -14, -59, + -97, -108, -112, -133, -28, -55, -110, -117, -20, -24, + nil, 9, -35, 37, -50, -27, 1, -25, nil, nil, + nil, 0, -5, -65, -24, 3, -10, -52 ] + +racc_goto_default = [ + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 48, 41, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 86, nil, nil, 30, 34, + 50, 51, nil, 46, 47, nil, 26, 28, 71, 72, + 33, 35, 114, 82, 18, nil, nil, nil ] + +racc_token_table = { + false => 0, + Object.new => 1, + :DATETIME => 2, + :RECEIVED => 3, + :MADDRESS => 4, + :RETPATH => 5, + :KEYWORDS => 6, + :ENCRYPTED => 7, + :MIMEVERSION => 8, + :CTYPE => 9, + :CENCODING => 10, + :CDISPOSITION => 11, + :ADDRESS => 12, + :MAILBOX => 13, + :DIGIT => 14, + :ATOM => 15, + "," => 16, + ":" => 17, + :FROM => 18, + :BY => 19, + "@" => 20, + :DOMLIT => 21, + :VIA => 22, + :WITH => 23, + :ID => 24, + :FOR => 25, + ";" => 26, + "<" => 27, + ">" => 28, + "." => 29, + :QUOTED => 30, + :TOKEN => 31, + "/" => 32, + "=" => 33 } + +racc_use_result_var = false + +racc_nt_base = 34 + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ +'$end', +'error', +'DATETIME', +'RECEIVED', +'MADDRESS', +'RETPATH', +'KEYWORDS', +'ENCRYPTED', +'MIMEVERSION', +'CTYPE', +'CENCODING', +'CDISPOSITION', +'ADDRESS', +'MAILBOX', +'DIGIT', +'ATOM', +'","', +'":"', +'FROM', +'BY', +'"@"', +'DOMLIT', +'VIA', +'WITH', +'ID', +'FOR', +'";"', +'"<"', +'">"', +'"."', +'QUOTED', +'TOKEN', +'"/"', +'"="', +'$start', +'content', +'datetime', +'received', +'addrs_TOP', +'retpath', +'keys', +'enc', +'version', +'ctype', +'cencode', +'cdisp', +'addr_TOP', +'mbox', +'day', +'hour', +'zone', +'from', +'by', +'via', +'with', +'id', +'for', +'received_datetime', +'received_domain', +'domain', +'msgid', +'received_addrspec', +'routeaddr', +'spec', +'addrs', +'group_bare', +'commas', +'group', +'addr', +'mboxes', +'addr_phrase', +'local_head', +'routes', +'at_domains', +'local', +'word', +'dots', +'domword', +'atom', +'phrase', +'params', +'opt_semicolon'] + +Racc_debug_parser = false + +##### racc system variables end ##### + + # reduce 0 omitted + +module_eval <<'.,.,', 'parser.y', 16 + def _reduce_1( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 17 + def _reduce_2( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 18 + def _reduce_3( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 19 + def _reduce_4( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 20 + def _reduce_5( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 21 + def _reduce_6( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 22 + def _reduce_7( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 23 + def _reduce_8( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 24 + def _reduce_9( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 25 + def _reduce_10( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 26 + def _reduce_11( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 27 + def _reduce_12( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 36 + def _reduce_13( val, _values) + t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0) + (t + val[4] - val[5]).localtime + end +.,., + + # reduce 14 omitted + + # reduce 15 omitted + +module_eval <<'.,.,', 'parser.y', 45 + def _reduce_16( val, _values) + (val[0].to_i * 60 * 60) + + (val[2].to_i * 60) + end +.,., + +module_eval <<'.,.,', 'parser.y', 51 + def _reduce_17( val, _values) + (val[0].to_i * 60 * 60) + + (val[2].to_i * 60) + + (val[4].to_i) + end +.,., + +module_eval <<'.,.,', 'parser.y', 56 + def _reduce_18( val, _values) + timezone_string_to_unixtime(val[0]) + end +.,., + +module_eval <<'.,.,', 'parser.y', 61 + def _reduce_19( val, _values) + val + end +.,., + + # reduce 20 omitted + +module_eval <<'.,.,', 'parser.y', 67 + def _reduce_21( val, _values) + val[1] + end +.,., + + # reduce 22 omitted + +module_eval <<'.,.,', 'parser.y', 73 + def _reduce_23( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 79 + def _reduce_24( val, _values) + join_domain(val[0]) + end +.,., + +module_eval <<'.,.,', 'parser.y', 83 + def _reduce_25( val, _values) + join_domain(val[2]) + end +.,., + +module_eval <<'.,.,', 'parser.y', 87 + def _reduce_26( val, _values) + join_domain(val[0]) + end +.,., + + # reduce 27 omitted + +module_eval <<'.,.,', 'parser.y', 93 + def _reduce_28( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 98 + def _reduce_29( val, _values) + [] + end +.,., + +module_eval <<'.,.,', 'parser.y', 103 + def _reduce_30( val, _values) + val[0].push val[2] + val[0] + end +.,., + + # reduce 31 omitted + +module_eval <<'.,.,', 'parser.y', 109 + def _reduce_32( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 113 + def _reduce_33( val, _values) + val[1] + end +.,., + + # reduce 34 omitted + +module_eval <<'.,.,', 'parser.y', 119 + def _reduce_35( val, _values) + val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 125 + def _reduce_36( val, _values) + val[0].spec + end +.,., + +module_eval <<'.,.,', 'parser.y', 129 + def _reduce_37( val, _values) + val[0].spec + end +.,., + + # reduce 38 omitted + +module_eval <<'.,.,', 'parser.y', 136 + def _reduce_39( val, _values) + val[1] + end +.,., + + # reduce 40 omitted + + # reduce 41 omitted + + # reduce 42 omitted + + # reduce 43 omitted + + # reduce 44 omitted + + # reduce 45 omitted + + # reduce 46 omitted + +module_eval <<'.,.,', 'parser.y', 146 + def _reduce_47( val, _values) + [ Address.new(nil, nil) ] + end +.,., + +module_eval <<'.,.,', 'parser.y', 152 + def _reduce_48( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 157 + def _reduce_49( val, _values) + val[0].push val[2] + val[0] + end +.,., + + # reduce 50 omitted + + # reduce 51 omitted + +module_eval <<'.,.,', 'parser.y', 165 + def _reduce_52( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 170 + def _reduce_53( val, _values) + val[0].push val[2] + val[0] + end +.,., + + # reduce 54 omitted + + # reduce 55 omitted + +module_eval <<'.,.,', 'parser.y', 178 + def _reduce_56( val, _values) + val[1].phrase = Decoder.decode(val[0]) + val[1] + end +.,., + + # reduce 57 omitted + +module_eval <<'.,.,', 'parser.y', 185 + def _reduce_58( val, _values) + AddressGroup.new(val[0], val[2]) + end +.,., + +module_eval <<'.,.,', 'parser.y', 185 + def _reduce_59( val, _values) + AddressGroup.new(val[0], []) + end +.,., + +module_eval <<'.,.,', 'parser.y', 188 + def _reduce_60( val, _values) + val[0].join('.') + end +.,., + +module_eval <<'.,.,', 'parser.y', 189 + def _reduce_61( val, _values) + val[0] << ' ' << val[1].join('.') + end +.,., + +module_eval <<'.,.,', 'parser.y', 196 + def _reduce_62( val, _values) + val[2].routes.replace val[1] + val[2] + end +.,., + +module_eval <<'.,.,', 'parser.y', 200 + def _reduce_63( val, _values) + val[1] + end +.,., + + # reduce 64 omitted + +module_eval <<'.,.,', 'parser.y', 203 + def _reduce_65( val, _values) + [ val[1].join('.') ] + end +.,., + +module_eval <<'.,.,', 'parser.y', 204 + def _reduce_66( val, _values) + val[0].push val[3].join('.'); val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 206 + def _reduce_67( val, _values) + Address.new( val[0], val[2] ) + end +.,., + +module_eval <<'.,.,', 'parser.y', 207 + def _reduce_68( val, _values) + Address.new( val[0], nil ) + end +.,., + + # reduce 69 omitted + +module_eval <<'.,.,', 'parser.y', 210 + def _reduce_70( val, _values) + val[0].push ''; val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 213 + def _reduce_71( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 222 + def _reduce_72( val, _values) + val[1].times do + val[0].push '' + end + val[0].push val[2] + val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 224 + def _reduce_73( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 233 + def _reduce_74( val, _values) + val[1].times do + val[0].push '' + end + val[0].push val[2] + val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 234 + def _reduce_75( val, _values) + 0 + end +.,., + +module_eval <<'.,.,', 'parser.y', 235 + def _reduce_76( val, _values) + 1 + end +.,., + + # reduce 77 omitted + + # reduce 78 omitted + + # reduce 79 omitted + + # reduce 80 omitted + + # reduce 81 omitted + + # reduce 82 omitted + + # reduce 83 omitted + + # reduce 84 omitted + +module_eval <<'.,.,', 'parser.y', 253 + def _reduce_85( val, _values) + val[1] = val[1].spec + val.join('') + end +.,., + +module_eval <<'.,.,', 'parser.y', 254 + def _reduce_86( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 255 + def _reduce_87( val, _values) + val[0].push val[2]; val[0] + end +.,., + + # reduce 88 omitted + +module_eval <<'.,.,', 'parser.y', 258 + def _reduce_89( val, _values) + val[0] << ' ' << val[1] + end +.,., + +module_eval <<'.,.,', 'parser.y', 265 + def _reduce_90( val, _values) + val.push nil + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 269 + def _reduce_91( val, _values) + val + end +.,., + +module_eval <<'.,.,', 'parser.y', 274 + def _reduce_92( val, _values) + [ val[0].to_i, val[2].to_i ] + end +.,., + +module_eval <<'.,.,', 'parser.y', 279 + def _reduce_93( val, _values) + [ val[0].downcase, val[2].downcase, decode_params(val[3]) ] + end +.,., + +module_eval <<'.,.,', 'parser.y', 283 + def _reduce_94( val, _values) + [ val[0].downcase, nil, decode_params(val[1]) ] + end +.,., + +module_eval <<'.,.,', 'parser.y', 288 + def _reduce_95( val, _values) + {} + end +.,., + +module_eval <<'.,.,', 'parser.y', 293 + def _reduce_96( val, _values) + val[0][ val[2].downcase ] = ('"' + val[4].to_s + '"') + val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 298 + def _reduce_97( val, _values) + val[0][ val[2].downcase ] = val[4] + val[0] + end +.,., + +module_eval <<'.,.,', 'parser.y', 303 + def _reduce_98( val, _values) + val[0].downcase + end +.,., + +module_eval <<'.,.,', 'parser.y', 308 + def _reduce_99( val, _values) + [ val[0].downcase, decode_params(val[1]) ] + end +.,., + + # reduce 100 omitted + + # reduce 101 omitted + + # reduce 102 omitted + + # reduce 103 omitted + + # reduce 104 omitted + + # reduce 105 omitted + + # reduce 106 omitted + + # reduce 107 omitted + + # reduce 108 omitted + + def _reduce_none( val, _values) + val[0] + end + + end # class Parser + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/port.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/port.rb new file mode 100644 index 0000000000..445f0e632b --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/port.rb @@ -0,0 +1,379 @@ +=begin rdoc + += Port class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/stringio' + + +module TMail + + class Port + def reproducible? + false + end + end + + + ### + ### FilePort + ### + + class FilePort < Port + + def initialize( fname ) + @filename = File.expand_path(fname) + super() + end + + attr_reader :filename + + alias ident filename + + def ==( other ) + other.respond_to?(:filename) and @filename == other.filename + end + + alias eql? == + + def hash + @filename.hash + end + + def inspect + "#<#{self.class}:#{@filename}>" + end + + def reproducible? + true + end + + def size + File.size @filename + end + + + def ropen( &block ) + File.open(@filename, &block) + end + + def wopen( &block ) + File.open(@filename, 'w', &block) + end + + def aopen( &block ) + File.open(@filename, 'a', &block) + end + + + def read_all + ropen {|f| + return f.read + } + end + + + def remove + File.unlink @filename + end + + def move_to( port ) + begin + File.link @filename, port.filename + rescue Errno::EXDEV + copy_to port + end + File.unlink @filename + end + + alias mv move_to + + def copy_to( port ) + if FilePort === port + copy_file @filename, port.filename + else + File.open(@filename) {|r| + port.wopen {|w| + while s = r.sysread(4096) + w.write << s + end + } } + end + end + + alias cp copy_to + + private + + # from fileutils.rb + def copy_file( src, dest ) + st = r = w = nil + + File.open(src, 'rb') {|r| + File.open(dest, 'wb') {|w| + st = r.stat + begin + while true + w.write r.sysread(st.blksize) + end + rescue EOFError + end + } } + end + + end + + + module MailFlags + + def seen=( b ) + set_status 'S', b + end + + def seen? + get_status 'S' + end + + def replied=( b ) + set_status 'R', b + end + + def replied? + get_status 'R' + end + + def flagged=( b ) + set_status 'F', b + end + + def flagged? + get_status 'F' + end + + private + + def procinfostr( str, tag, true_p ) + a = str.upcase.split(//) + a.push true_p ? tag : nil + a.delete tag unless true_p + a.compact.sort.join('').squeeze + end + + end + + + class MhPort < FilePort + + include MailFlags + + private + + def set_status( tag, flag ) + begin + tmpfile = @filename + '.tmailtmp.' + $$.to_s + File.open(tmpfile, 'w') {|f| + write_status f, tag, flag + } + File.unlink @filename + File.link tmpfile, @filename + ensure + File.unlink tmpfile + end + end + + def write_status( f, tag, flag ) + stat = '' + File.open(@filename) {|r| + while line = r.gets + if line.strip.empty? + break + elsif m = /\AX-TMail-Status:/i.match(line) + stat = m.post_match.strip + else + f.print line + end + end + + s = procinfostr(stat, tag, flag) + f.puts 'X-TMail-Status: ' + s unless s.empty? + f.puts + + while s = r.read(2048) + f.write s + end + } + end + + def get_status( tag ) + File.foreach(@filename) {|line| + return false if line.strip.empty? + if m = /\AX-TMail-Status:/i.match(line) + return m.post_match.strip.include?(tag[0]) + end + } + false + end + + end + + + class MaildirPort < FilePort + + def move_to_new + new = replace_dir(@filename, 'new') + File.rename @filename, new + @filename = new + end + + def move_to_cur + new = replace_dir(@filename, 'cur') + File.rename @filename, new + @filename = new + end + + def replace_dir( path, dir ) + "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}" + end + private :replace_dir + + + include MailFlags + + private + + MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/ + + def set_status( tag, flag ) + if m = MAIL_FILE.match(File.basename(@filename)) + s, uniq, type, info, = m.to_a + return if type and type != '2' # do not change anything + newname = File.dirname(@filename) + '/' + + uniq + ':2,' + procinfostr(info.to_s, tag, flag) + else + newname = @filename + ':2,' + tag + end + + File.link @filename, newname + File.unlink @filename + @filename = newname + end + + def get_status( tag ) + m = MAIL_FILE.match(File.basename(@filename)) or return false + m[2] == '2' and m[3].to_s.include?(tag[0]) + end + + end + + + ### + ### StringPort + ### + + class StringPort < Port + + def initialize( str = '' ) + @buffer = str + super() + end + + def string + @buffer + end + + def to_s + @buffer.dup + end + + alias read_all to_s + + def size + @buffer.size + end + + def ==( other ) + StringPort === other and @buffer.equal? other.string + end + + alias eql? == + + def hash + @buffer.object_id.hash + end + + def inspect + "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>" + end + + def reproducible? + true + end + + def ropen( &block ) + @buffer or raise Errno::ENOENT, "#{inspect} is already removed" + StringInput.open(@buffer, &block) + end + + def wopen( &block ) + @buffer = '' + StringOutput.new(@buffer, &block) + end + + def aopen( &block ) + @buffer ||= '' + StringOutput.new(@buffer, &block) + end + + def remove + @buffer = nil + end + + alias rm remove + + def copy_to( port ) + port.wopen {|f| + f.write @buffer + } + end + + alias cp copy_to + + def move_to( port ) + if StringPort === port + str = @buffer + port.instance_eval { @buffer = str } + else + copy_to port + end + remove + end + + end + +end # module TMail diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb new file mode 100644 index 0000000000..cb9f4288f1 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb @@ -0,0 +1,118 @@ +=begin rdoc + += Quoting methods + +=end +module TMail + class Mail + def subject(to_charset = 'utf-8') + Unquoter.unquote_and_convert_to(quoted_subject, to_charset) + end + + def unquoted_body(to_charset = 'utf-8') + from_charset = sub_header("content-type", "charset") + case (content_transfer_encoding || "7bit").downcase + when "quoted-printable" + # the default charset is set to iso-8859-1 instead of 'us-ascii'. + # This is needed as many mailer do not set the charset but send in ISO. This is only used if no charset is set. + if !from_charset.blank? && from_charset.downcase == 'us-ascii' + from_charset = 'iso-8859-1' + end + + Unquoter.unquote_quoted_printable_and_convert_to(quoted_body, + to_charset, from_charset, true) + when "base64" + Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset, + from_charset) + when "7bit", "8bit" + Unquoter.convert_to(quoted_body, to_charset, from_charset) + when "binary" + quoted_body + else + quoted_body + end + end + + def body(to_charset = 'utf-8', &block) + attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" } + + if multipart? + parts.collect { |part| + header = part["content-type"] + + if part.multipart? + part.body(to_charset, &attachment_presenter) + elsif header.nil? + "" + elsif !attachment?(part) + part.unquoted_body(to_charset) + else + attachment_presenter.call(header["name"] || "(unnamed)") + end + }.join + else + unquoted_body(to_charset) + end + end + end + + class Unquoter + class << self + def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false) + return "" if text.nil? + text.gsub(/(.*?)(?:(?:=\?(.*?)\?(.)\?(.*?)\?=)|$)/) do + before = $1 + from_charset = $2 + quoting_method = $3 + text = $4 + + before = convert_to(before, to_charset, from_charset) if before.length > 0 + before + case quoting_method + when "q", "Q" then + unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores) + when "b", "B" then + unquote_base64_and_convert_to(text, to_charset, from_charset) + when nil then + # will be nil at the end of the string, due to the nature of + # the regex used. + "" + else + raise "unknown quoting method #{quoting_method.inspect}" + end + end + end + + def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false) + text = text.gsub(/_/, " ") unless preserve_underscores + text = text.gsub(/\r\n|\r/, "\n") # normalize newlines + convert_to(text.unpack("M*").first, to, from) + end + + def unquote_base64_and_convert_to(text, to, from) + convert_to(Base64.decode(text), to, from) + end + + begin + require 'iconv' + def convert_to(text, to, from) + return text unless to && from + text ? Iconv.iconv(to, from, text).first : "" + rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL + # the 'from' parameter specifies a charset other than what the text + # actually is...not much we can do in this case but just return the + # unconverted text. + # + # Ditto if either parameter represents an unknown charset, like + # X-UNKNOWN. + text + end + rescue LoadError + # Not providing quoting support + def convert_to(text, to, from) + warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})" + text + end + end + end + end +end diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb new file mode 100644 index 0000000000..b4fffb8abb --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb @@ -0,0 +1,58 @@ +#:stopdoc: +require 'rbconfig' + +# Attempts to require anative extension. +# Falls back to pure-ruby version, if it fails. +# +# This uses Config::CONFIG['arch'] from rbconfig. + +def require_arch(fname) + arch = Config::CONFIG['arch'] + begin + path = File.join("tmail", arch, fname) + require path + rescue LoadError => e + # try pre-built Windows binaries + if arch =~ /mswin/ + require File.join("tmail", 'mswin32', fname) + else + raise e + end + end +end + + +# def require_arch(fname) +# dext = Config::CONFIG['DLEXT'] +# begin +# if File.extname(fname) == dext +# path = fname +# else +# path = File.join("tmail","#{fname}.#{dext}") +# end +# require path +# rescue LoadError => e +# begin +# arch = Config::CONFIG['arch'] +# path = File.join("tmail", arch, "#{fname}.#{dext}") +# require path +# rescue LoadError +# case path +# when /i686/ +# path.sub!('i686', 'i586') +# when /i586/ +# path.sub!('i586', 'i486') +# when /i486/ +# path.sub!('i486', 'i386') +# else +# begin +# require fname + '.rb' +# rescue LoadError +# raise e +# end +# end +# retry +# end +# end +# end +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb new file mode 100644 index 0000000000..a5d01396b8 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb @@ -0,0 +1,49 @@ +=begin rdoc + += Scanner for TMail + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +#require 'tmail/require_arch' +require 'tmail/utils' +require 'tmail/config' + +module TMail + # NOTE: It woiuld be nice if these two libs could boith be called "tmailscanner", and + # the native extension would have precedence. However RubyGems boffs that up b/c + # it does not gaurantee load_path order. + begin + raise LoadError, 'Turned off native extentions by user choice' if ENV['NORUBYEXT'] + require('tmail/tmailscanner') # c extension + Scanner = TMailScanner + rescue LoadError + require 'tmail/scanner_r' + Scanner = TMailScanner + end +end +#:stopdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb new file mode 100644 index 0000000000..f2075502d8 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb @@ -0,0 +1,261 @@ +# scanner_r.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ +#:stopdoc: +require 'tmail/config' + +module TMail + + class TMailScanner + + Version = '1.2.3' + Version.freeze + + MIME_HEADERS = { + :CTYPE => true, + :CENCODING => true, + :CDISPOSITION => true + } + + alnum = 'a-zA-Z0-9' + atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip + tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip + atomchars = alnum + Regexp.quote(atomsyms) + tokenchars = alnum + Regexp.quote(tokensyms) + iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B' + + eucstr = "(?:[\xa1-\xfe][\xa1-\xfe])+" + sjisstr = "(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+" + utf8str = "(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+" + + quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n + domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n + comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n + + quoted_without_iso2022 = /\A[^\\"]+/n + domlit_without_iso2022 = /\A[^\\\]]+/n + comment_without_iso2022 = /\A[^\\()]+/n + + PATTERN_TABLE = {} + PATTERN_TABLE['EUC'] = + [ + /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n, + /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n, + quoted_with_iso2022, + domlit_with_iso2022, + comment_with_iso2022 + ] + PATTERN_TABLE['SJIS'] = + [ + /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n, + /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n, + quoted_with_iso2022, + domlit_with_iso2022, + comment_with_iso2022 + ] + PATTERN_TABLE['UTF8'] = + [ + /\A(?:[#{atomchars}]+|#{utf8str})+/n, + /\A(?:[#{tokenchars}]+|#{utf8str})+/n, + quoted_without_iso2022, + domlit_without_iso2022, + comment_without_iso2022 + ] + PATTERN_TABLE['NONE'] = + [ + /\A[#{atomchars}]+/n, + /\A[#{tokenchars}]+/n, + quoted_without_iso2022, + domlit_without_iso2022, + comment_without_iso2022 + ] + + + def initialize( str, scantype, comments ) + init_scanner str + @comments = comments || [] + @debug = false + + # fix scanner mode + @received = (scantype == :RECEIVED) + @is_mime_header = MIME_HEADERS[scantype] + + atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[TMail.KCODE] + @word_re = (MIME_HEADERS[scantype] ? token : atom) + end + + attr_accessor :debug + + def scan( &block ) + if @debug + scan_main do |arr| + s, v = arr + printf "%7d %-10s %s\n", + rest_size(), + s.respond_to?(:id2name) ? s.id2name : s.inspect, + v.inspect + yield arr + end + else + scan_main(&block) + end + end + + private + + RECV_TOKEN = { + 'from' => :FROM, + 'by' => :BY, + 'via' => :VIA, + 'with' => :WITH, + 'id' => :ID, + 'for' => :FOR + } + + def scan_main + until eof? + if skip(/\A[\n\r\t ]+/n) # LWSP + break if eof? + end + + if s = readstr(@word_re) + if @is_mime_header + yield [:TOKEN, s] + else + # atom + if /\A\d+\z/ === s + yield [:DIGIT, s] + elsif @received + yield [RECV_TOKEN[s.downcase] || :ATOM, s] + else + yield [:ATOM, s] + end + end + + elsif skip(/\A"/) + yield [:QUOTED, scan_quoted_word()] + + elsif skip(/\A\[/) + yield [:DOMLIT, scan_domain_literal()] + + elsif skip(/\A\(/) + @comments.push scan_comment() + + else + c = readchar() + yield [c, c] + end + end + + yield [false, '$'] + end + + def scan_quoted_word + scan_qstr(@quoted_re, /\A"/, 'quoted-word') + end + + def scan_domain_literal + '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']' + end + + def scan_qstr( pattern, terminal, type ) + result = '' + until eof? + if s = readstr(pattern) then result << s + elsif skip(terminal) then return result + elsif skip(/\A\\/) then result << readchar() + else + raise "TMail FATAL: not match in #{type}" + end + end + scan_error! "found unterminated #{type}" + end + + def scan_comment + result = '' + nest = 1 + content = @comment_re + + until eof? + if s = readstr(content) then result << s + elsif skip(/\A\)/) then nest -= 1 + return result if nest == 0 + result << ')' + elsif skip(/\A\(/) then nest += 1 + result << '(' + elsif skip(/\A\\/) then result << readchar() + else + raise 'TMail FATAL: not match in comment' + end + end + scan_error! 'found unterminated comment' + end + + # string scanner + + def init_scanner( str ) + @src = str + end + + def eof? + @src.empty? + end + + def rest_size + @src.size + end + + def readstr( re ) + if m = re.match(@src) + @src = m.post_match + m[0] + else + nil + end + end + + def readchar + readstr(/\A./) + end + + def skip( re ) + if m = re.match(@src) + @src = m.post_match + true + else + false + end + end + + def scan_error!( msg ) + raise SyntaxError, msg + end + + end + +end # module TMail +#:startdoc:
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb new file mode 100644 index 0000000000..8357398788 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb @@ -0,0 +1,280 @@ +# encoding: utf-8 +=begin rdoc + += String handling class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +class StringInput#:nodoc: + + include Enumerable + + class << self + + def new( str ) + if block_given? + begin + f = super + yield f + ensure + f.close if f + end + else + super + end + end + + alias open new + + end + + def initialize( str ) + @src = str + @pos = 0 + @closed = false + @lineno = 0 + end + + attr_reader :lineno + + def string + @src + end + + def inspect + "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>" + end + + def close + stream_check! + @pos = nil + @closed = true + end + + def closed? + @closed + end + + def pos + stream_check! + [@pos, @src.size].min + end + + alias tell pos + + def seek( offset, whence = IO::SEEK_SET ) + stream_check! + case whence + when IO::SEEK_SET + @pos = offset + when IO::SEEK_CUR + @pos += offset + when IO::SEEK_END + @pos = @src.size - offset + else + raise ArgumentError, "unknown seek flag: #{whence}" + end + @pos = 0 if @pos < 0 + @pos = [@pos, @src.size + 1].min + offset + end + + def rewind + stream_check! + @pos = 0 + end + + def eof? + stream_check! + @pos > @src.size + end + + def each( &block ) + stream_check! + begin + @src.each(&block) + ensure + @pos = 0 + end + end + + def gets + stream_check! + if idx = @src.index(?\n, @pos) + idx += 1 # "\n".size + line = @src[ @pos ... idx ] + @pos = idx + @pos += 1 if @pos == @src.size + else + line = @src[ @pos .. -1 ] + @pos = @src.size + 1 + end + @lineno += 1 + + line + end + + def getc + stream_check! + ch = @src[@pos] + @pos += 1 + @pos += 1 if @pos == @src.size + ch + end + + def read( len = nil ) + stream_check! + return read_all unless len + str = @src[@pos, len] + @pos += len + @pos += 1 if @pos == @src.size + str + end + + alias sysread read + + def read_all + stream_check! + return nil if eof? + rest = @src[@pos ... @src.size] + @pos = @src.size + 1 + rest + end + + def stream_check! + @closed and raise IOError, 'closed stream' + end + +end + + +class StringOutput#:nodoc: + + class << self + + def new( str = '' ) + if block_given? + begin + f = super + yield f + ensure + f.close if f + end + else + super + end + end + + alias open new + + end + + def initialize( str = '' ) + @dest = str + @closed = false + end + + def close + @closed = true + end + + def closed? + @closed + end + + def string + @dest + end + + alias value string + alias to_str string + + def size + @dest.size + end + + alias pos size + + def inspect + "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{object_id}>" + end + + def print( *args ) + stream_check! + raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty? + args.each do |s| + raise ArgumentError, 'nil not allowed' if s.nil? + @dest << s.to_s + end + nil + end + + def puts( *args ) + stream_check! + args.each do |str| + @dest << (s = str.to_s) + @dest << "\n" unless s[-1] == ?\n + end + @dest << "\n" if args.empty? + nil + end + + def putc( ch ) + stream_check! + @dest << ch.chr + nil + end + + def printf( *args ) + stream_check! + @dest << sprintf(*args) + nil + end + + def write( str ) + stream_check! + s = str.to_s + @dest << s + s.size + end + + alias syswrite write + + def <<( str ) + stream_check! + @dest << str.to_s + self + end + + private + + def stream_check! + @closed and raise IOError, 'closed stream' + end + +end diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb new file mode 100644 index 0000000000..dc594a4229 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb @@ -0,0 +1,337 @@ +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +# = TMail - The EMail Swiss Army Knife for Ruby +# +# The TMail library provides you with a very complete way to handle and manipulate EMails +# from within your Ruby programs. +# +# Used as the backbone for email handling by the Ruby on Rails and Nitro web frameworks as +# well as a bunch of other Ruby apps including the Ruby-Talk mailing list to newsgroup email +# gateway, it is a proven and reliable email handler that won't let you down. +# +# Originally created by Minero Aoki, TMail has been recently picked up by Mikel Lindsaar and +# is being actively maintained. Numerous backlogged bug fixes have been applied as well as +# Ruby 1.9 compatibility and a swath of documentation to boot. +# +# TMail allows you to treat an email totally as an object and allow you to get on with your +# own programming without having to worry about crafting the perfect email address validation +# parser, or assembling an email from all it's component parts. +# +# TMail handles the most complex part of the email - the header. It generates and parses +# headers and provides you with instant access to their innards through simple and logically +# named accessor and setter methods. +# +# TMail also provides a wrapper to Net/SMTP as well as Unix Mailbox handling methods to +# directly read emails from your unix mailbox, parse them and use them. +# +# Following is the comprehensive list of methods to access TMail::Mail objects. You can also +# check out TMail::Mail, TMail::Address and TMail::Headers for other lists. +module TMail + + # Provides an exception to throw on errors in Syntax within TMail's parsers + class SyntaxError < StandardError; end + + # Provides a new email boundary to separate parts of the email. This is a random + # string based off the current time, so should be fairly unique. + # + # For Example: + # + # TMail.new_boundary + # #=> "mimepart_47bf656968207_25a8fbb80114" + # TMail.new_boundary + # #=> "mimepart_47bf66051de4_25a8fbb80240" + def TMail.new_boundary + 'mimepart_' + random_tag + end + + # Provides a new email message ID. You can use this to generate unique email message + # id's for your email so you can track them. + # + # Optionally takes a fully qualified domain name (default to the current hostname + # returned by Socket.gethostname) that will be appended to the message ID. + # + # For Example: + # + # email.message_id = TMail.new_message_id + # #=> "<47bf66845380e_25a8fbb80332@baci.local.tmail>" + # email.to_s + # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@baci.local.tmail>\n\n" + # email.message_id = TMail.new_message_id("lindsaar.net") + # #=> "<47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>" + # email.to_s + # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>\n\n" + def TMail.new_message_id( fqdn = nil ) + fqdn ||= ::Socket.gethostname + "<#{random_tag()}@#{fqdn}.tmail>" + end + + #:stopdoc: + def TMail.random_tag #:nodoc: + @uniq += 1 + t = Time.now + sprintf('%x%x_%x%x%d%x', + t.to_i, t.tv_usec, + $$, Thread.current.object_id, @uniq, rand(255)) + end + private_class_method :random_tag + + @uniq = 0 + + #:startdoc: + + # Text Utils provides a namespace to define TOKENs, ATOMs, PHRASEs and CONTROL characters that + # are OK per RFC 2822. + # + # It also provides methods you can call to determine if a string is safe + module TextUtils + + aspecial = %Q|()<>[]:;.\\,"| + tspecial = %Q|()<>[];:\\,"/?=| + lwsp = %Q| \t\r\n| + control = %Q|\x00-\x1f\x7f-\xff| + + CONTROL_CHAR = /[#{control}]/n + ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n + PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n + TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n + + # Returns true if the string supplied is free from characters not allowed as an ATOM + def atom_safe?( str ) + not ATOM_UNSAFE === str + end + + # If the string supplied has ATOM unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified + def quote_atom( str ) + (ATOM_UNSAFE === str) ? dquote(str) : str + end + + # If the string supplied has PHRASE unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified + def quote_phrase( str ) + (PHRASE_UNSAFE === str) ? dquote(str) : str + end + + # Returns true if the string supplied is free from characters not allowed as a TOKEN + def token_safe?( str ) + not TOKEN_UNSAFE === str + end + + # If the string supplied has TOKEN unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified + def quote_token( str ) + (TOKEN_UNSAFE === str) ? dquote(str) : str + end + + # Wraps supplied string in double quotes unless it is already wrapped + # Returns double quoted string + def dquote( str ) #:nodoc: + unless str =~ /^".*?"$/ + '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"' + else + str + end + end + private :dquote + + # Unwraps supplied string from inside double quotes + # Returns unquoted string + def unquote( str ) + str =~ /^"(.*?)"$/ ? $1 : str + end + + # Provides a method to join a domain name by it's parts and also makes it + # ATOM safe by quoting it as needed + def join_domain( arr ) + arr.map {|i| + if /\A\[.*\]\z/ === i + i + else + quote_atom(i) + end + }.join('.') + end + + #:stopdoc: + ZONESTR_TABLE = { + 'jst' => 9 * 60, + 'eet' => 2 * 60, + 'bst' => 1 * 60, + 'met' => 1 * 60, + 'gmt' => 0, + 'utc' => 0, + 'ut' => 0, + 'nst' => -(3 * 60 + 30), + 'ast' => -4 * 60, + 'edt' => -4 * 60, + 'est' => -5 * 60, + 'cdt' => -5 * 60, + 'cst' => -6 * 60, + 'mdt' => -6 * 60, + 'mst' => -7 * 60, + 'pdt' => -7 * 60, + 'pst' => -8 * 60, + 'a' => -1 * 60, + 'b' => -2 * 60, + 'c' => -3 * 60, + 'd' => -4 * 60, + 'e' => -5 * 60, + 'f' => -6 * 60, + 'g' => -7 * 60, + 'h' => -8 * 60, + 'i' => -9 * 60, + # j not use + 'k' => -10 * 60, + 'l' => -11 * 60, + 'm' => -12 * 60, + 'n' => 1 * 60, + 'o' => 2 * 60, + 'p' => 3 * 60, + 'q' => 4 * 60, + 'r' => 5 * 60, + 's' => 6 * 60, + 't' => 7 * 60, + 'u' => 8 * 60, + 'v' => 9 * 60, + 'w' => 10 * 60, + 'x' => 11 * 60, + 'y' => 12 * 60, + 'z' => 0 * 60 + } + #:startdoc: + + # Takes a time zone string from an EMail and converts it to Unix Time (seconds) + def timezone_string_to_unixtime( str ) + if m = /([\+\-])(\d\d?)(\d\d)/.match(str) + sec = (m[2].to_i * 60 + m[3].to_i) * 60 + m[1] == '-' ? -sec : sec + else + min = ZONESTR_TABLE[str.downcase] or + raise SyntaxError, "wrong timezone format '#{str}'" + min * 60 + end + end + + #:stopdoc: + WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG ) + MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun + Jul Aug Sep Oct Nov Dec TMailBUG ) + + def time2str( tm ) + # [ruby-list:7928] + gmt = Time.at(tm.to_i) + gmt.gmtime + offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i + + # DO NOT USE strftime: setlocale() breaks it + sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d', + WDAY[tm.wday], tm.mday, MONTH[tm.month], + tm.year, tm.hour, tm.min, tm.sec, + *(offset / 60).divmod(60) + end + + + MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/ + + def message_id?( str ) + MESSAGE_ID === str + end + + + MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i + + def mime_encoded?( str ) + MIME_ENCODED === str + end + + + def decode_params( hash ) + new = Hash.new + encoded = nil + hash.each do |key, value| + if m = /\*(?:(\d+)\*)?\z/.match(key) + ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value + else + new[key] = to_kcode(value) + end + end + if encoded + encoded.each do |key, strings| + new[key] = decode_RFC2231(strings.join('')) + end + end + + new + end + + NKF_FLAGS = { + 'EUC' => '-e -m', + 'SJIS' => '-s -m' + } + + def to_kcode( str ) + flag = NKF_FLAGS[TMail.KCODE] or return str + NKF.nkf(flag, str) + end + + RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in + + def decode_RFC2231( str ) + m = RFC2231_ENCODED.match(str) or return str + begin + to_kcode(m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr }) + rescue + m.post_match.gsub(/%[\da-f]{2}/in, "") + end + end + + def quote_boundary + # Make sure the Content-Type boundary= parameter is quoted if it contains illegal characters + # (to ensure any special characters in the boundary text are escaped from the parser + # (such as = in MS Outlook's boundary text)) + if @body =~ /^(.*)boundary=(.*)$/m + preamble = $1 + remainder = $2 + if remainder =~ /;/ + remainder =~ /^(.*?)(;.*)$/m + boundary_text = $1 + post = $2.chomp + else + boundary_text = remainder.chomp + end + if boundary_text =~ /[\/\?\=]/ + boundary_text = "\"#{boundary_text}\"" unless boundary_text =~ /^".*?"$/ + @body = "#{preamble}boundary=#{boundary_text}#{post}" + end + end + end + #:startdoc: + + + end + +end diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb new file mode 100644 index 0000000000..95228497c0 --- /dev/null +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb @@ -0,0 +1,39 @@ +# +# version.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net> +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +#:stopdoc: +module TMail + module VERSION + MAJOR = 1 + MINOR = 2 + TINY = 3 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end |