1 # -*- coding: UTF-8 -*- |
|
2 # Copyright (c) 2008 - 2014, Pascal Volk |
|
3 # See COPYING for distribution information. |
|
4 """ |
|
5 VirtualMailManager.emailaddress |
|
6 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
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, get_gid |
|
13 from VirtualMailManager.constants import \ |
|
14 DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG, \ |
|
15 DOMAIN_INVALID |
|
16 from VirtualMailManager.errors import DomainError, EmailAddressError as EAErr |
|
17 |
|
18 |
|
19 RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]") |
|
20 _ = lambda msg: msg |
|
21 |
|
22 |
|
23 class EmailAddress(object): |
|
24 """Simple class for validated e-mail addresses.""" |
|
25 __slots__ = ('_localpart', '_domainname') |
|
26 |
|
27 def __init__(self, address, _validate=True): |
|
28 """Creates a new instance from the string/unicode ``address``.""" |
|
29 assert isinstance(address, basestring) |
|
30 self._localpart = None |
|
31 self._domainname = None |
|
32 if _validate: |
|
33 self._chk_address(address) |
|
34 |
|
35 @property |
|
36 def localpart(self): |
|
37 """The local-part of the address *local-part@domain*""" |
|
38 return self._localpart |
|
39 |
|
40 @property |
|
41 def domainname(self): |
|
42 """The domain part of the address *local-part@domain*""" |
|
43 return self._domainname |
|
44 |
|
45 def __eq__(self, other): |
|
46 if isinstance(other, self.__class__): |
|
47 return self._localpart == other._localpart and \ |
|
48 self._domainname == other._domainname |
|
49 return NotImplemented |
|
50 |
|
51 def __ne__(self, other): |
|
52 if isinstance(other, self.__class__): |
|
53 return self._localpart != other._localpart or \ |
|
54 self._domainname != other._domainname |
|
55 return NotImplemented |
|
56 |
|
57 def __hash__(self): |
|
58 return hash((self._localpart.lower(), self._domainname.lower())) |
|
59 |
|
60 def __repr__(self): |
|
61 return "EmailAddress('%s@%s')" % (self._localpart, self._domainname) |
|
62 |
|
63 def __str__(self): |
|
64 return '%s@%s' % (self._localpart, self._domainname) |
|
65 |
|
66 def _chk_address(self, address): |
|
67 """Checks if the string ``address`` could be used for an e-mail |
|
68 address. If so, it will assign the corresponding values to the |
|
69 attributes `_localpart` and `_domainname`.""" |
|
70 parts = address.split('@') |
|
71 p_len = len(parts) |
|
72 if p_len < 2: |
|
73 raise EAErr(_(u"Missing the '@' sign in address: '%s'") % address, |
|
74 INVALID_ADDRESS) |
|
75 elif p_len > 2: |
|
76 raise EAErr(_(u"Too many '@' signs in address: '%s'") % address, |
|
77 INVALID_ADDRESS) |
|
78 if not parts[0]: |
|
79 raise EAErr(_(u"Missing local-part in address: '%s'") % address, |
|
80 LOCALPART_INVALID) |
|
81 if not parts[1]: |
|
82 raise EAErr(_(u"Missing domain name in address: '%s'") % address, |
|
83 DOMAIN_NO_NAME) |
|
84 self._localpart = check_localpart(parts[0]) |
|
85 self._domainname = check_domainname(parts[1]) |
|
86 |
|
87 |
|
88 class DestinationEmailAddress(EmailAddress): |
|
89 """Provides additionally the domains group ID - when the domain is known |
|
90 in the database.""" |
|
91 __slots__ = ('_gid', '_localhost') |
|
92 |
|
93 def __init__(self, address, dbh, _validate=False): |
|
94 """Creates a new DestinationEmailAddress instance |
|
95 |
|
96 Arguments: |
|
97 |
|
98 `address`: string/unicode |
|
99 a e-mail address like user@example.com |
|
100 `dbh`: pyPgSQL.PgSQL.Connection/pyPgSQL.PgSQL.connection |
|
101 a database connection for the database access |
|
102 """ |
|
103 super(DestinationEmailAddress, self).__init__(address, _validate) |
|
104 self._localhost = False |
|
105 if not _validate: |
|
106 try: |
|
107 self._chk_address(address) |
|
108 except DomainError, err: |
|
109 if err.code is DOMAIN_INVALID and \ |
|
110 address.split('@')[1] == 'localhost': |
|
111 self._localhost = True |
|
112 self._domainname = 'localhost' |
|
113 else: |
|
114 raise |
|
115 self._gid = 0 |
|
116 if not self._localhost: |
|
117 self._find_domain(dbh) |
|
118 else: |
|
119 self._localpart = self._localpart.lower() |
|
120 |
|
121 def _find_domain(self, dbh): |
|
122 """Checks if the domain is known""" |
|
123 self._gid = get_gid(dbh, self._domainname) |
|
124 if self._gid: |
|
125 self._localpart = self._localpart.lower() |
|
126 |
|
127 @property |
|
128 def at_localhost(self): |
|
129 """True when the address is something@localhost.""" |
|
130 return self._localhost |
|
131 |
|
132 @property |
|
133 def gid(self): |
|
134 """The domains group ID. 0 if the domain is not known.""" |
|
135 return self._gid |
|
136 |
|
137 |
|
138 def check_localpart(localpart): |
|
139 """Returns the validated local-part `localpart`. |
|
140 |
|
141 Throws a `EmailAddressError` if the local-part is too long or contains |
|
142 invalid characters. |
|
143 """ |
|
144 if len(localpart) > 64: |
|
145 raise EAErr(_(u"The local-part '%s' is too long.") % localpart, |
|
146 LOCALPART_TOO_LONG) |
|
147 invalid_chars = set(RE_LOCALPART.findall(localpart)) |
|
148 if invalid_chars: |
|
149 i_chars = u''.join((u'"%s" ' % c for c in invalid_chars)) |
|
150 raise EAErr(_(u"The local-part '%(l_part)s' contains invalid " |
|
151 u"characters: %(i_chars)s") % {'l_part': localpart, |
|
152 'i_chars': i_chars}, LOCALPART_INVALID) |
|
153 return localpart |
|
154 |
|
155 del _ |
|