VirtualMailManager/emailaddress.py
changeset 760 b678a1c43027
parent 748 659c4476c57c
child 761 e4e656f19771
equal deleted inserted replaced
748:659c4476c57c 760:b678a1c43027
     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 _