diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-06-16 23:35:17 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-06-16 23:35:17 -0400 |
| commit | bd6eddcf055233ca1775975445d9576ffe0a3fe4 (patch) | |
| tree | f06087fa3de2ac9aaab2e29ec0dc5875eab363a5 /passlib/context.py | |
| parent | 435a8464f8363f7d8d20d0e1f5abe852edc0a40e (diff) | |
| download | passlib-bd6eddcf055233ca1775975445d9576ffe0a3fe4.tar.gz | |
basic work to make CryptContext unicode aware.
* updated CryptContext UTs as well.
* also added some general unicode<->bytes helpers to utils (needs UTs).
* also a few 2to3 conditional hints added to CryptContext so passlib can load
Diffstat (limited to 'passlib/context.py')
| -rw-r--r-- | passlib/context.py | 125 |
1 files changed, 91 insertions, 34 deletions
diff --git a/passlib/context.py b/passlib/context.py index 04373eb..77ea10c 100644 --- a/passlib/context.py +++ b/passlib/context.py @@ -5,7 +5,11 @@ from __future__ import with_statement #core from cStringIO import StringIO -from ConfigParser import ConfigParser, SafeConfigParser, InterpolationSyntaxError +# Py2k # + #note: importing this to handle passlib 1.4 / earlier files +from ConfigParser import ConfigParser, InterpolationSyntaxError +# end Py2k # +from ConfigParser import SafeConfigParser import inspect import re import hashlib @@ -18,7 +22,8 @@ from warnings import warn from pkg_resources import resource_string #libs from passlib.registry import get_crypt_handler, _unload_handler_name -from passlib.utils import Undef, is_crypt_handler, splitcomma, rng +from passlib.utils import to_bytes, to_unicode, bytes, Undef, \ + is_crypt_handler, splitcomma, rng #pkg #local __all__ = [ @@ -71,8 +76,13 @@ def _parse_policy_value(cat, name, opt, value): def parse_policy_items(source): "helper to parse CryptPolicy options" + # py2k # if hasattr(source, "iteritems"): - source = source.iteritems() + source = source.iteritems() + # py3k # + #if hasattr(source, "items"): + # source = source.items() + # end py3k # for key, value in source: cat, name, opt = _parse_policy_key(key) if name == "context": @@ -88,6 +98,7 @@ def parse_policy_items(source): value = _parse_policy_value(cat, name, opt, value) yield cat, name, opt, value +# Py2k # def _is_legacy_parse_error(err): "helper for parsing config files" #NOTE: passlib 1.4 and earlier used ConfigParser, @@ -110,6 +121,7 @@ def _is_legacy_parse_error(err): if value == "'%' must be followed by '%' or '(', found: '%'": return True return False +# end Py2k # class CryptPolicy(object): """stores configuration options for a CryptContext object. @@ -148,63 +160,100 @@ class CryptPolicy(object): .. note:: Instances of CryptPolicy should be treated as immutable. + Use the :meth:`replace` method to mutate existing instances. """ #========================================================= #class methods #========================================================= @classmethod - def from_path(cls, path, section="passlib"): + def from_path(cls, path, section="passlib", encoding="utf-8"): """create new policy from specified section of an ini file. :arg path: path to ini file :param section: option name of section to read from. + :arg encoding: optional encoding (defaults to utf-8) :raises EnvironmentError: if the file cannot be read :returns: new CryptPolicy instance. """ - p = SafeConfigParser() - if not p.read([path]): - raise EnvironmentError("failed to read config file") - try: - items = p.items(section) - except InterpolationSyntaxError, err: - if not _is_legacy_parse_error(err): - raise - #support for deprecated 1.4 behavior, will be removed in 1.6 - warn("from_path(): the file %r contains an unescaped '%%', this will be fatal in passlib 1.6" % (path,), stacklevel=2) - p = ConfigParser() - if not p.read([path]): - raise EnvironmentError("failed to read config file") - items = p.items(section) - return cls(**dict(items)) - + #NOTE: we want config parser object to have native strings as keys. + # so we parse as bytes under py2, and unicode under py3. + # + # encoding issues are handled under py2 via to_bytes(), + # which ensures everything is utf-8 internally. + + # Py2k # + if encoding == "utf-8": + #we want utf-8 anyways, so just load file in raw mode. + with open(path, "rb") as stream: + return cls._from_stream(stream, section, path) + else: + #kinda hacked - load whole file, transcode, and parse. + with open(path, "rb") as stream: + source = stream.read() + source = source.decode(encoding).encode("utf-8") + return cls._from_stream(StringIO(source), section, path) + # Py3k # + #with open(path, "r", encoding=encoding) as stream: + # return cls._from_stream(stream, section, path) + # end Py3k # + @classmethod - def from_string(cls, source, section="passlib"): + def from_string(cls, source, section="passlib", encoding="utf-8"): """create new policy from specified section of an ini-formatted string. - :arg source: string containing ini-formatted content. + :arg source: bytes/unicode string containing ini-formatted content. :param section: option name of section to read from. + :arg encoding: optional encoding if source is bytes (defaults to utf-8) :returns: new CryptPolicy instance. """ - b = StringIO(source) + #NOTE: we want config parser object to have native strings as keys. + # so we parse as bytes under py2, and unicode under py3. + # to handle encoding issues under py2, we use + # "to_bytes()" to transcode to utf-8 as needed. + + # Py2k # + source = to_bytes(source, "utf-8", source_encoding=encoding, errname="source") + # Py3k # + #source = to_unicode(source, encoding, errname="source") + # end Py3k # + return cls._from_stream(StringIO(source), section, "<???>") + + @classmethod + def _from_stream(cls, stream, section, filename=None): + "helper for from_string / from_path" + # Py2k # + pos = stream.tell() + # end Py2k # + p = SafeConfigParser() - p.readfp(b) + p.readfp(stream, filename or "<???>") + + # Py2k # try: items = p.items(section) except InterpolationSyntaxError, err: if not _is_legacy_parse_error(err): raise #support for deprecated 1.4 behavior, will be removed in 1.6 - warn("from_string(): the provided string contains an unescaped '%', this will be fatal in passlib 1.6", stacklevel=2) + if filename: + warn("from_path(): the file %r contains an unescaped '%%', this will be fatal in passlib 1.6" % (path,), stacklevel=3) + else: + warn("from_string(): the provided string contains an unescaped '%', this will be fatal in passlib 1.6", stacklevel=3) p = ConfigParser() - b.seek(0) - p.readfp(b) + stream.seek(pos) + p.readfp(stream) items = p.items(section) + + # py3k # + #items = p.items(section) + # end py3k # + return cls(**dict(items)) - + @classmethod def from_source(cls, source): """create new policy from input. @@ -226,7 +275,7 @@ class CryptPolicy(object): elif isinstance(source, dict): return cls(**source) - elif isinstance(source, (str,unicode)): + elif isinstance(source, (bytes,unicode)): #FIXME: this autodetection makes me uncomfortable... if any(c in source for c in "\n\r\t") or not source.strip(" \t./\;:"): #none of these chars should be in filepaths, but should be in config string return cls.from_string(source) @@ -608,6 +657,8 @@ class CryptPolicy(object): def _escape_ini_pair(self, k, v): if isinstance(v, str): v = v.replace("%", "%%") #escape any percent signs. + elif isinstance(v, (int, long)): + v = str(v) return k,v def _write_to_parser(self, parser, section): @@ -619,15 +670,21 @@ class CryptPolicy(object): def to_file(self, stream, section="passlib"): "serialize to INI format and write to specified stream" - p = ConfigParser() + p = SafeConfigParser() self._write_to_parser(p, section) p.write(stream) - def to_string(self, section="passlib"): + def to_string(self, section="passlib", encoding=None): "render to INI string; inverse of from_string() constructor" - b = StringIO() - self.to_file(b, section) - return b.getvalue() + buf = StringIO() + self.to_file(buf, section) + out = buf.getvalue() + # Py2k # + out = out.decode("utf-8") + # end Py2k # + if encoding: + out = out.encode(encoding) + return out ##def to_path(self, path, section="passlib", update=False): ## "write to INI file" |
