1 # -*- coding: UTF-8 -*- |
|
2 # Copyright (c) 2008 - 2010, Pascal Volk |
|
3 # See COPYING for distribution information. |
|
4 |
|
5 """ |
|
6 VirtualMailManager.EmailAddress |
|
7 |
|
8 Virtual Mail Manager's EmailAddress class to handle e-mail addresses. |
|
9 """ |
|
10 import re |
|
11 |
|
12 from VirtualMailManager.Domain import check_domainname |
|
13 from VirtualMailManager.constants import \ |
|
14 DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG |
|
15 from VirtualMailManager.errors import EmailAddressError as EAErr |
|
16 |
|
17 |
|
18 RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]") |
|
19 _ = lambda msg: msg |
|
20 |
|
21 |
|
22 class EmailAddress(object): |
|
23 """Simple class for validated e-mail addresses.""" |
|
24 __slots__ = ('_localpart', '_domainname') |
|
25 |
|
26 def __init__(self, address): |
|
27 """Creates a new instance from the string/unicode ``address``.""" |
|
28 assert isinstance(address, basestring) |
|
29 self._localpart = None |
|
30 self._domainname = None |
|
31 self._chk_address(address) |
|
32 |
|
33 @property |
|
34 def localpart(self): |
|
35 """The local-part of the address *local-part@domain*""" |
|
36 return self._localpart |
|
37 |
|
38 @property |
|
39 def domainname(self): |
|
40 """The domain part of the address *local-part@domain*""" |
|
41 return self._domainname |
|
42 |
|
43 def __eq__(self, other): |
|
44 if isinstance(other, self.__class__): |
|
45 return self._localpart == other.localpart and \ |
|
46 self._domainname == other.domainname |
|
47 return NotImplemented |
|
48 |
|
49 def __ne__(self, other): |
|
50 if isinstance(other, self.__class__): |
|
51 return self._localpart != other.localpart or \ |
|
52 self._domainname != other.domainname |
|
53 return NotImplemented |
|
54 |
|
55 def __hash__(self): |
|
56 return hash((self._localpart.lower(), self._domainname.lower())) |
|
57 |
|
58 def __repr__(self): |
|
59 return "EmailAddress('%s@%s')" % (self._localpart, self._domainname) |
|
60 |
|
61 def __str__(self): |
|
62 return '%s@%s' % (self._localpart, self._domainname) |
|
63 |
|
64 def _chk_address(self, address): |
|
65 """Checks if the string ``address`` could be used for an e-mail |
|
66 address. If so, it will assign the corresponding values to the |
|
67 attributes `_localpart` and `_domainname`.""" |
|
68 parts = address.split('@') |
|
69 p_len = len(parts) |
|
70 if p_len < 2: |
|
71 raise EAErr(_(u"Missing the '@' sign in address %r") % address, |
|
72 INVALID_ADDRESS) |
|
73 elif p_len > 2: |
|
74 raise EAErr(_(u"Too many '@' signs in address %r") % address, |
|
75 INVALID_ADDRESS) |
|
76 if not parts[0]: |
|
77 raise EAErr(_(u'Missing local-part in address %r') % address, |
|
78 LOCALPART_INVALID) |
|
79 if not parts[1]: |
|
80 raise EAErr(_(u'Missing domain name in address %r') % address, |
|
81 DOMAIN_NO_NAME) |
|
82 self._localpart = check_localpart(parts[0]) |
|
83 self._domainname = check_domainname(parts[1]) |
|
84 |
|
85 |
|
86 def check_localpart(localpart): |
|
87 """Returns the validated local-part `localpart`. |
|
88 |
|
89 Throws a `EmailAddressError` if the local-part is too long or contains |
|
90 invalid characters. |
|
91 """ |
|
92 if len(localpart) > 64: |
|
93 raise EAErr(_(u"The local-part '%s' is too long") % localpart, |
|
94 LOCALPART_TOO_LONG) |
|
95 invalid_chars = set(RE_LOCALPART.findall(localpart)) |
|
96 if invalid_chars: |
|
97 i_chars = u''.join((u'"%s" ' % c for c in invalid_chars)) |
|
98 raise EAErr(_(u"The local-part '%(l_part)s' contains invalid " |
|
99 u"characters: %(i_chars)s") % {'l_part': localpart, |
|
100 'i_chars': i_chars}, LOCALPART_INVALID) |
|
101 return localpart |
|
102 |
|
103 |
|
104 del _ |
|