3 = Address handling class
7 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
9 # Permission is hereby granted, free of charge, to any person obtaining
10 # a copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to
14 # permit persons to whom the Software is furnished to do so, subject to
15 # the following conditions:
17 # The above copyright notice and this permission notice shall be
18 # included in all copies or substantial portions of the Software.
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29 # with permission of Minero Aoki.
32 require 'tmail/encode'
33 require 'tmail/parser'
40 # Provides a complete handling library for email addresses. Can parse a string of an
41 # address directly or take in preformatted addresses themselves. Allows you to add
42 # and remove phrases from the front of the address and provides a compare function for
45 # == Parsing and Handling a Valid Address:
47 # Just pass the email address in as a string to Address.parse:
49 # email = TMail::Address.parse('Mikel Lindsaar <mikel@lindsaar.net>)
50 # #=> #<TMail::Address mikel@lindsaar.net>
52 # #=> "mikel@lindsaar.net"
57 # email.name # Aliased as phrase as well
58 # #=> "Mikel Lindsaar"
60 # == Detecting an Invalid Address
62 # If you want to check the syntactical validity of an email address, just pass it to
63 # Address.parse and catch any SyntaxError:
66 # TMail::Mail.parse("mikel 2@@@@@ me .com")
67 # rescue TMail::SyntaxError
68 # puts("Invalid Email Address Detected")
70 # puts("Address is valid")
72 # #=> "Invalid Email Address Detected"
75 include TextUtils
#:nodoc:
77 # Sometimes you need to parse an address, TMail can do it for you and provide you with
78 # a fairly robust method of detecting a valid address.
80 # Takes in a string, returns a TMail::Address object.
82 # Raises a TMail::SyntaxError on invalid email format
83 def Address
.parse( str
)
84 Parser
.parse
:ADDRESS, special_quote_address(str
)
87 def Address
.special_quote_address(str
) #:nodoc:
88 # Takes a string which is an address and adds quotation marks to special
89 # edge case methods that the RACC parser can not handle.
91 # Right now just handles two edge cases:
93 # Full stop as the last character of the display name:
94 # Mikel L. <mikel@me.com>
96 # "Mikel L." <mikel@me.com>
98 # Unquoted @ symbol in the display name:
99 # mikel@me.com <mikel@me.com>
101 # "mikel@me.com" <mikel@me.com>
103 # Any other address not matching these patterns just gets returned as is.
105 # This handles the missing "" in an older version of Apple Mail.app
106 # around the display name when the display name contains a '@'
107 # like 'mikel@me.com <mikel@me.com>'
108 # Just quotes it to: '"mikel@me.com" <mikel@me.com>'
109 when str
=~
/\A([^"].+@.+[^"])\s(<.*?>)\Z/
110 return "\"#{$1}\" #{$2}"
111 # This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing
112 # full stop before the address section. Just quotes it to
113 # '"Mikel A. <mikel@me.com>"
114 when str
=~
/\A(.*?\.)\s(<.*?>)\Z/
115 return "\"#{$1}\" #{$2}"
121 def address_group
? #:nodoc:
125 # Address.new(local, domain)
129 # * local - Left of the at symbol
131 # * domain - Array of the domain split at the periods.
135 # Address.new("mikel", ["lindsaar", "net"])
136 # #=> "#<TMail::Address mikel@lindsaar.net>"
137 def initialize( local
, domain
)
140 raise SyntaxError
, 'empty word in domain' if s
.empty
?
144 # This is to catch an unquoted "@" symbol in the local part of the
145 # address. Handles addresses like <"@"@me.com> and makes sure they
146 # stay like <"@"@me.com> (previously were becoming <@@me.com>)
147 if local
&& (local
.join
== '@' || local
.join
=~
/\A[^"].*?@.*?[^"]\Z/)
148 @local = "\"#{local.join}\""
158 # Provides the name or 'phrase' of the email address.
162 # email = TMail::Address.parse("Mikel Lindsaar <mikel@lindsaar.net>")
164 # #=> "Mikel Lindsaar"
169 # Setter method for the name or phrase of the email
173 # email = TMail::Address.parse("mikel@lindsaar.net")
176 # email.name = "Mikel Lindsaar"
178 # #=> "Mikel Lindsaar <mikel@me.com>"
181 @name = nil if str
and str
.empty
?
189 # This is still here from RFC 822, and is now obsolete per RFC2822 Section 4.
191 # "When interpreting addresses, the route portion SHOULD be ignored."
193 # It is still here, so you can access it.
195 # Routes return the route portion at the front of the email address, if any.
198 # email = TMail::Address.parse( "<@sa,@another:Mikel@me.com>")
199 # => #<TMail::Address Mikel@me.com>
201 # => "<@sa,@another:Mikel@me.com>"
203 # => ["sa", "another"]
209 "#<#{self.class} #{address()}>"
212 # Returns the local part of the email address
216 # email = TMail::Address.parse("mikel@lindsaar.net")
220 return nil unless @local
221 return '""' if @local.size
== 1 and @local[0].empty
?
222 # Check to see if it is an array before trying to map it
223 if @local.respond_to
?(:map)
224 @local.map
{|i
| quote_atom(i
) }.join('.')
230 # Returns the domain part of the email address
234 # email = TMail::Address.parse("mikel@lindsaar.net")
238 return nil unless @domain
242 # Returns the full specific address itself
246 # email = TMail::Address.parse("mikel@lindsaar.net")
248 # #=> "mikel@lindsaar.net"
261 # Provides == function to the email. Only checks the actual address
262 # and ignores the name/phrase component
266 # addr1 = TMail::Address.parse("My Address <mikel@lindsaar.net>")
267 # #=> "#<TMail::Address mikel@lindsaar.net>"
268 # addr2 = TMail::Address.parse("Another <mikel@lindsaar.net>")
269 # #=> "#<TMail::Address mikel@lindsaar.net>"
273 other
.respond_to
? :spec and self.spec
== other
.spec
278 # Provides a unique hash value for this record against the local and domain
279 # parts, ignores the name/phrase value
281 # email = TMail::Address.parse("mikel@lindsaar.net")
285 @local.hash ^
@domain.hash
288 # Duplicates a TMail::Address object returning the duplicate
290 # addr1 = TMail::Address.parse("mikel@lindsaar.net")
292 # addr1.id == addr2.id
295 obj
= self.class.new(@local.dup
, @domain.dup
)
296 obj
.name
= @name.dup
if @name
297 obj
.routes
.replace
@routes
301 include StrategyInterface
#:nodoc:
303 def accept( strategy
, dummy1
= nil, dummy2
= nil ) #:nodoc:
305 strategy
.meta
'<>' # empty return-path
309 spec_p
= (not @name and @routes.empty
?)
311 strategy
.phrase
@name
314 tmp
= spec_p
? '' : '<'
315 unless @routes.empty
?
316 tmp
<< @routes.map
{|i
| '@' + i
}.join(',') << ':'
319 tmp
<< '>' unless spec_p
335 def initialize( name
, addrs
)
343 other
.respond_to
? :to_a and @addresses == other
.to_a
349 map
{|i
| i
.hash
}.hash
365 @addresses.each(&block
)
375 @addresses.include? a
380 @addresses.each
do |a
|
381 if a
.respond_to
? :flatten
390 def each_address( &block
)
404 include StrategyInterface
406 def accept( strategy
, dummy1
= nil, dummy2
= nil )
407 strategy
.phrase
@name