VirtualMailManager/__init__.py
author Pascal Volk <neverseen@users.sourceforge.net>
Fri, 26 Feb 2010 02:35:25 +0000
branchv0.6.x
changeset 216 0c8c053b451c
parent 215 33f727efa7c4
child 234 e88ba0fb1281
permissions -rw-r--r--
Moved VirtualMailManager/Exceptions to VirtualMailManager/errors. Renamed VMM*Exception classes to *Error. No longer add the attribute 'message' to VMMError if it doesn't exist, like in Python 2.4. It has been deprecated as of Python 2.6. Also removed the methods code() and msg(), the values are now accessible via the attributes 'code' and 'msg'.

# -*- coding: UTF-8 -*-
# Copyright (c) 2007 - 2010, Pascal Volk
# See COPYING for distribution information.

"""
    VirtualMailManager

    VirtualMailManager package initialization code
"""

import gettext
import os
import re
import locale

from encodings.idna import ToASCII, ToUnicode

from VirtualMailManager.constants.ERROR import \
     DOMAIN_INVALID, DOMAIN_TOO_LONG, LOCALPART_INVALID, LOCALPART_TOO_LONG, \
     NOT_EXECUTABLE, NO_SUCH_BINARY, NO_SUCH_DIRECTORY
from VirtualMailManager.constants.version import __author__, __date__, \
     __version__
from VirtualMailManager.errors import VMMError


__all__ = [
    # version information from VERSION
    '__author__', '__date__', '__version__',
    # defined stuff
    'ENCODING', 'ace2idna', 'check_domainname', 'check_localpart', 'exec_ok',
    'expand_path', 'get_unicode', 'idn2ascii', 'is_dir',
]


# Try to set all of the locales according to the current
# environment variables and get the character encoding.
try:
    locale.setlocale(locale.LC_ALL, '')
except locale.Error:
    locale.setlocale(locale.LC_ALL, 'C')
ENCODING = locale.nl_langinfo(locale.CODESET)

# there may be many domain and e-mail address checks
RE_DOMAIN = re.compile(r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$")
RE_LOCALPART = re.compile(r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]")

gettext.install('vmm', '/usr/local/share/locale', unicode=1)


_ = lambda msg: msg


def get_unicode(string):
    """Converts `string` to `unicode`, if necessary."""
    if isinstance(string, unicode):
        return string
    return unicode(string, ENCODING, 'replace')


def expand_path(path):
    """Expands paths, starting with ``.`` or ``~``, to an absolute path."""
    if path.startswith('.'):
        return os.path.abspath(path)
    if path.startswith('~'):
        return os.path.expanduser(path)
    return path


def is_dir(path):
    """Checks if `path` is a directory.

    Throws a `VMMError` if `path` is not a directory.

    """
    path = expand_path(path)
    if not os.path.isdir(path):
        raise VMMError(_(u"'%s' is not a directory") % get_unicode(path),
                       NO_SUCH_DIRECTORY)
    return path


def exec_ok(binary):
    """Checks if the `binary` exists and if it is executable.

    Throws a `VMMError` if the `binary` isn't a file or is not
    executable.

    """
    binary = expand_path(binary)
    if not os.path.isfile(binary):
        raise VMMError(_(u"'%s' is not a file") % get_unicode(binary),
                       NO_SUCH_BINARY)
    if not os.access(binary, os.X_OK):
        raise VMMError(_(u"File is not executable: '%s'") % 
                       get_unicode(binary), NOT_EXECUTABLE)
    return binary


def idn2ascii(domainname):
    """Converts the idn domain name `domainname` into punycode."""
    return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl])


def ace2idna(domainname):
    """Converts the domain name `domainname` from ACE according to IDNA."""
    return u'.'.join([ToUnicode(lbl) for lbl in domainname.split('.') if lbl])


def check_domainname(domainname):
    """Returns the validated domain name `domainname`.

    It also converts the name of the domain from IDN to ASCII, if
    necessary.

    Throws an `VMMError`, if the domain name is too long or doesn't
    look like a valid domain name (label.label.label).

    """
    if not RE_DOMAIN.match(domainname):
        domainname = idn2ascii(domainname)
    if len(domainname) > 255:
        raise VMMError(_(u'The domain name is too long'), DOMAIN_TOO_LONG)
    if not RE_DOMAIN.match(domainname):
        raise VMMError(_(u'The domain name %r is invalid') % domainname,
                       DOMAIN_INVALID)
    return domainname


def check_localpart(localpart):
    """Returns the validated local-part `localpart`.

    Throws a `VMMError` if the local-part is too long or contains
    invalid characters.

    """
    if len(localpart) > 64:
        raise VMMError(_(u'The local-part %r 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 VMMError(_(u"The local-part %(l_part)r contains invalid \
characters: %(i_chars)s") %
                       {'l_part': localpart, 'i_chars': i_chars},
                       LOCALPART_INVALID)
    return localpart


del _