VirtualMailManager/emailaddress.py
changeset 571 a4aead244f75
parent 568 14abdd04ddf5
child 643 df1e3b67882a
child 675 d24f094d1cb5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/emailaddress.py	Thu Jun 28 19:26:50 2012 +0000
@@ -0,0 +1,155 @@
+# -*- coding: UTF-8 -*-
+# Copyright (c) 2008 - 2012, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.emailaddress
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Virtual Mail Manager's EmailAddress class to handle e-mail addresses.
+"""
+import re
+
+from VirtualMailManager.domain import check_domainname, get_gid
+from VirtualMailManager.constants import \
+     DOMAIN_NO_NAME, INVALID_ADDRESS, LOCALPART_INVALID, LOCALPART_TOO_LONG, \
+     DOMAIN_INVALID
+from VirtualMailManager.errors import DomainError, EmailAddressError as EAErr
+
+
+RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")
+_ = lambda msg: msg
+
+
+class EmailAddress(object):
+    """Simple class for validated e-mail addresses."""
+    __slots__ = ('_localpart', '_domainname')
+
+    def __init__(self, address, _validate=True):
+        """Creates a new instance from the string/unicode ``address``."""
+        assert isinstance(address, basestring)
+        self._localpart = None
+        self._domainname = None
+        if _validate:
+            self._chk_address(address)
+
+    @property
+    def localpart(self):
+        """The local-part of the address *local-part@domain*"""
+        return self._localpart
+
+    @property
+    def domainname(self):
+        """The domain part of the address *local-part@domain*"""
+        return self._domainname
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart == other._localpart and \
+                    self._domainname == other._domainname
+        return NotImplemented
+
+    def __ne__(self, other):
+        if isinstance(other, self.__class__):
+            return self._localpart != other._localpart or \
+                    self._domainname != other._domainname
+        return NotImplemented
+
+    def __hash__(self):
+        return hash((self._localpart.lower(), self._domainname.lower()))
+
+    def __repr__(self):
+        return "EmailAddress('%s@%s')" % (self._localpart, self._domainname)
+
+    def __str__(self):
+        return '%s@%s' % (self._localpart, self._domainname)
+
+    def _chk_address(self, address):
+        """Checks if the string ``address`` could be used for an e-mail
+        address.  If so, it will assign the corresponding values to the
+        attributes `_localpart` and `_domainname`."""
+        parts = address.split('@')
+        p_len = len(parts)
+        if p_len < 2:
+            raise EAErr(_(u"Missing the '@' sign in address: '%s'") % address,
+                        INVALID_ADDRESS)
+        elif p_len > 2:
+            raise EAErr(_(u"Too many '@' signs in address: '%s'") % address,
+                        INVALID_ADDRESS)
+        if not parts[0]:
+            raise EAErr(_(u"Missing local-part in address: '%s'") % address,
+                        LOCALPART_INVALID)
+        if not parts[1]:
+            raise EAErr(_(u"Missing domain name in address: '%s'") % address,
+                        DOMAIN_NO_NAME)
+        self._localpart = check_localpart(parts[0])
+        self._domainname = check_domainname(parts[1])
+
+
+class DestinationEmailAddress(EmailAddress):
+    """Provides additionally the domains group ID - when the domain is known
+    in the database."""
+    __slots__ = ('_gid', '_localhost')
+
+    def __init__(self, address, dbh, _validate=False):
+        """Creates a new DestinationEmailAddress instance
+
+        Arguments:
+
+        `address`: string/unicode
+          a e-mail address like user@example.com
+        `dbh`: pyPgSQL.PgSQL.Connection/pyPgSQL.PgSQL.connection
+          a database connection for the database access
+        """
+        super(DestinationEmailAddress, self).__init__(address, _validate)
+        self._localhost = False
+        if not _validate:
+            try:
+                self._chk_address(address)
+            except DomainError, err:
+                if err.code is DOMAIN_INVALID and \
+                   address.split('@')[1] == 'localhost':
+                    self._localhost = True
+                    self._domainname = 'localhost'
+                else:
+                    raise
+        self._gid = 0
+        if not self._localhost:
+            self._find_domain(dbh)
+        else:
+            self._localpart = self._localpart.lower()
+
+    def _find_domain(self, dbh):
+        """Checks if the domain is known"""
+        self._gid = get_gid(dbh, self._domainname)
+        if self._gid:
+            self._localpart = self._localpart.lower()
+
+    @property
+    def at_localhost(self):
+        """True when the address is something@localhost."""
+        return self._localhost
+
+    @property
+    def gid(self):
+        """The domains group ID. 0 if the domain is not known."""
+        return self._gid
+
+
+def check_localpart(localpart):
+    """Returns the validated local-part `localpart`.
+
+    Throws a `EmailAddressError` if the local-part is too long or contains
+    invalid characters.
+    """
+    if len(localpart) > 64:
+        raise EAErr(_(u"The local-part '%s' is too long.") % localpart,
+                    LOCALPART_TOO_LONG)
+    invalid_chars = set(RE_LOCALPART.findall(localpart))
+    if invalid_chars:
+        i_chars = u''.join((u'"%s" ' % c for c in invalid_chars))
+        raise EAErr(_(u"The local-part '%(l_part)s' contains invalid "
+                      u"characters: %(i_chars)s") % {'l_part': localpart,
+                    'i_chars': i_chars}, LOCALPART_INVALID)
+    return localpart
+
+del _