=begin rdoc = Address handling class =end #-- # Copyright (c) 1998-2003 Minero Aoki # # 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 ) # #=> # # 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. # Returns: # "Mikel L." # # Unquoted @ symbol in the display name: # mikel@me.com # Returns: # "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 ' # Just quotes it to: '"mikel@me.com" ' when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/ return "\"#{$1}\" #{$2}" # This handles cases where 'Mikel A. ' which is a trailing # full stop before the address section. Just quotes it to # '"Mikel A. " 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"]) # #=> "#" 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 ") # 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 " 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>") # => # # 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 ") # #=> "#" # addr2 = TMail::Address.parse("Another ") # #=> "#" # 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