10 import locale |
10 import locale |
11 |
11 |
12 from encodings.idna import ToASCII, ToUnicode |
12 from encodings.idna import ToASCII, ToUnicode |
13 |
13 |
14 from VirtualMailManager.constants.ERROR import \ |
14 from VirtualMailManager.constants.ERROR import \ |
15 DOMAIN_INVALID, DOMAIN_TOO_LONG, NOT_EXECUTABLE, NO_SUCH_BINARY, \ |
15 DOMAIN_INVALID, DOMAIN_TOO_LONG, LOCALPART_INVALID, LOCALPART_TOO_LONG, \ |
16 NO_SUCH_DIRECTORY |
16 NOT_EXECUTABLE, NO_SUCH_BINARY, NO_SUCH_DIRECTORY |
17 from VirtualMailManager.constants.VERSION import * |
17 from VirtualMailManager.constants.VERSION import * |
18 from VirtualMailManager.Exceptions import VMMException |
18 from VirtualMailManager.Exceptions import VMMException |
19 |
19 |
20 |
20 |
21 __all__ = [ |
21 __all__ = [ |
22 # imported modules |
22 # imported modules |
23 'os', 're', 'locale', |
23 'os', 're', 'locale', |
24 # version information from VERSION |
24 # version information from VERSION |
25 '__author__', '__date__', '__version__', |
25 '__author__', '__date__', '__version__', |
26 # error codes |
26 # error codes |
27 'ENCODING', 'ace2idna', 'chk_domainname', 'exec_ok', 'expand_path', |
27 'ENCODING', 'ace2idna', 'check_domainname', 'check_localpart', 'exec_ok', |
28 'get_unicode', 'idn2ascii', 'is_dir', |
28 'expand_path', 'get_unicode', 'idn2ascii', 'is_dir', |
29 ] |
29 ] |
30 |
30 |
31 |
31 |
32 # Try to set all of the locales according to the current |
32 # Try to set all of the locales according to the current |
33 # environment variables and get the character encoding. |
33 # environment variables and get the character encoding. |
35 locale.setlocale(locale.LC_ALL, '') |
35 locale.setlocale(locale.LC_ALL, '') |
36 except locale.Error: |
36 except locale.Error: |
37 locale.setlocale(locale.LC_ALL, 'C') |
37 locale.setlocale(locale.LC_ALL, 'C') |
38 ENCODING = locale.nl_langinfo(locale.CODESET) |
38 ENCODING = locale.nl_langinfo(locale.CODESET) |
39 |
39 |
40 RE_ASCII_CHARS = """^[\x20-\x7E]*$""" |
40 RE_ASCII_CHARS = r"^[\x20-\x7E]*$" |
41 RE_DOMAIN = """^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$""" |
41 RE_DOMAIN = r"^(?:[a-z0-9-]{1,63}\.){1,}[a-z]{2,6}$" |
|
42 RE_LOCALPART = r"[^\w!#$%&'\*\+-\.\/=?^_`{\|}~]" |
|
43 |
|
44 |
|
45 # there may be many domain and e-mail address checks |
|
46 re_obj_domain = re.compile(RE_DOMAIN) |
|
47 re_obj_localpart = re.compile(RE_LOCALPART) |
42 |
48 |
43 |
49 |
44 gettext.install('vmm', '/usr/local/share/locale', unicode=1) |
50 gettext.install('vmm', '/usr/local/share/locale', unicode=1) |
|
51 |
45 |
52 |
46 def get_unicode(string): |
53 def get_unicode(string): |
47 """Converts `string` to `unicode`, if necessary.""" |
54 """Converts `string` to `unicode`, if necessary.""" |
48 if isinstance(string, unicode): |
55 if isinstance(string, unicode): |
49 return string |
56 return string |
50 return unicode(string, ENCODING, 'replace') |
57 return unicode(string, ENCODING, 'replace') |
|
58 |
51 |
59 |
52 def expand_path(path): |
60 def expand_path(path): |
53 """Expands paths, starting with ``.`` or ``~``, to an absolute path.""" |
61 """Expands paths, starting with ``.`` or ``~``, to an absolute path.""" |
54 if path.startswith('.'): |
62 if path.startswith('.'): |
55 return os.path.abspath(path) |
63 return os.path.abspath(path) |
56 if path.startswith('~'): |
64 if path.startswith('~'): |
57 return os.path.expanduser(path) |
65 return os.path.expanduser(path) |
58 return path |
66 return path |
59 |
67 |
|
68 |
60 def is_dir(path): |
69 def is_dir(path): |
61 """Checks if ``path`` is a directory. |
70 """Checks if ``path`` is a directory. |
62 |
71 |
63 Throws a `VMMException` if ``path`` is not a directory. |
72 Throws a `VMMException` if ``path`` is not a directory. |
64 """ |
73 """ |
65 path = expand_path(path) |
74 path = expand_path(path) |
66 if not os.path.isdir(path): |
75 if not os.path.isdir(path): |
67 raise VMMException(_(u'“%s” is not a directory') % get_unicode(path), |
76 raise VMMException(_(u'“%s” is not a directory') % |
68 NO_SUCH_DIRECTORY) |
77 get_unicode(path), NO_SUCH_DIRECTORY) |
69 return path |
78 return path |
|
79 |
70 |
80 |
71 def exec_ok(binary): |
81 def exec_ok(binary): |
72 """Checks if the ``binary`` exists and if it is executable. |
82 """Checks if the ``binary`` exists and if it is executable. |
73 |
83 |
74 Throws a `VMMException` if the ``binary`` isn't a file or is not |
84 Throws a `VMMException` if the ``binary`` isn't a file or is not |
81 if not os.access(binary, os.X_OK): |
91 if not os.access(binary, os.X_OK): |
82 raise VMMException(_(u'File is not executable: “%s”') % |
92 raise VMMException(_(u'File is not executable: “%s”') % |
83 get_unicode(binary), NOT_EXECUTABLE) |
93 get_unicode(binary), NOT_EXECUTABLE) |
84 return binary |
94 return binary |
85 |
95 |
|
96 |
86 def idn2ascii(domainname): |
97 def idn2ascii(domainname): |
87 """Converts the idn domain name `domainname` into punycode.""" |
98 """Converts the idn domain name `domainname` into punycode.""" |
88 return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl]) |
99 return '.'.join([ToASCII(lbl) for lbl in domainname.split('.') if lbl]) |
|
100 |
89 |
101 |
90 def ace2idna(domainname): |
102 def ace2idna(domainname): |
91 """Converts the domain name `domainname` from ACE according to IDNA.""" |
103 """Converts the domain name `domainname` from ACE according to IDNA.""" |
92 return u'.'.join([ToUnicode(lbl) for lbl in domainname.split('.') if lbl]) |
104 return u'.'.join([ToUnicode(lbl) for lbl in domainname.split('.') if lbl]) |
93 |
105 |
94 def chk_domainname(domainname): |
106 |
|
107 def check_domainname(domainname): |
95 """Returns the validated domain name `domainname`. |
108 """Returns the validated domain name `domainname`. |
96 |
109 |
97 It also converts the name of the domain from IDN to ASCII, if necessary. |
110 It also converts the name of the domain from IDN to ASCII, if necessary. |
98 |
111 |
99 Throws an VMMException, if the domain name is too long or doesn't look |
112 Throws an `VMMException`, if the domain name is too long or doesn't look |
100 like a valid domain name (label.label.label). |
113 like a valid domain name (label.label.label). |
101 """ |
114 """ |
102 if not re.match(RE_ASCII_CHARS, domainname): |
115 if not re_obj_domain.match(domainname): |
103 domainname = idn2ascii(domainname) |
116 domainname = idn2ascii(domainname) |
104 if len(domainname) > 255: |
117 if len(domainname) > 255: |
105 raise VMMException(_(u'The domain name is too long.'), DOMAIN_TOO_LONG) |
118 raise VMMException(_(u'The domain name is too long'), DOMAIN_TOO_LONG) |
106 if not re.match(RE_DOMAIN, domainname): |
119 if not re_obj_domain.match(domainname): |
107 raise VMMException(_(u'The domain name “%s” is invalid.') % domainname, |
120 raise VMMException(_(u'The domain name %r is invalid') % domainname, |
108 DOMAIN_INVALID) |
121 DOMAIN_INVALID) |
109 return domainname |
122 return domainname |
|
123 |
|
124 |
|
125 def check_localpart(localpart): |
|
126 """Returns the validated local-part *localpart*. |
|
127 |
|
128 Throws a `VMMException` if the local-part is too long or contains |
|
129 invalid characters. |
|
130 """ |
|
131 if len(localpart) > 64: |
|
132 raise VMMException(_(u'The local-part %r is too long') % localpart, |
|
133 LOCALPART_TOO_LONG) |
|
134 invalid_chars = set(re_obj_localpart.findall(localpart)) |
|
135 if invalid_chars: |
|
136 i_chars = u''.join((u'"%s" ' % c for c in invalid_chars)) |
|
137 raise VMMException(_(u"The local-part %(l_part)r contains invalid \ |
|
138 characters: %(i_chars)s") % |
|
139 {'l_part': localpart, 'i_chars': i_chars}, |
|
140 LOCALPART_INVALID) |
|
141 return localpart |