diff options
Diffstat (limited to 'passlib/utils/__init__.py')
-rw-r--r-- | passlib/utils/__init__.py | 185 |
1 files changed, 69 insertions, 116 deletions
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 6147886..94833ad 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -61,11 +61,10 @@ from passlib.utils.decor import ( hybrid_method, ) from passlib.exc import ExpectedStringError, ExpectedTypeError -from passlib.utils.compat import (add_doc, join_bytes, join_byte_values, - join_byte_elems, irange, imap, PY3, u, - join_unicode, unicode, byte_elem_value, nextgetter, - unicode_or_str, unicode_or_bytes_types, - get_method_function, suppress_cause, PYPY) +from passlib.utils.compat import (add_doc, join_bytes, + join_unicode, + unicode_or_bytes, + get_method_function, PYPY) # local __all__ = [ # constants @@ -113,7 +112,7 @@ __all__ = [ #============================================================================= # bitsize of system architecture (32 or 64) -sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5) +sys_bits = int(math.log(sys.maxsize, 2) + 1.5) # list of hashes algs supported by crypt() on at least one OS. # XXX: move to .registry for passlib 2.0? @@ -128,13 +127,10 @@ unix_crypt_schemes = [ # list of rounds_cost constants rounds_cost_values = [ "linear", "log2" ] -# legacy import, will be removed in 1.8 -from passlib.exc import MissingBackendError - # internal helpers _BEMPTY = b'' -_UEMPTY = u("") -_USPACE = u(" ") +_UEMPTY = u"" +_USPACE = u" " # maximum password size which passlib will allow; see exc.PasswordSizeError MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096) @@ -169,30 +165,23 @@ class SequenceMixin(object): def __ne__(self, other): return not self.__eq__(other) -if PY3: - # getargspec() is deprecated, use this under py3. - # even though it's a lot more awkward to get basic info :| - - _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD - _VAR_ANY_SET = set([_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL]) +# getargspec() is deprecated, use this under py3. +# even though it's a lot more awkward to get basic info :| - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - params = inspect.signature(get_method_function(func)).parameters - if not params: - return False - arg = params.get(key) - if arg and arg.kind not in _VAR_ANY_SET: - return True - # XXX: annoying what we have to do to determine if VAR_KWDS in use. - return params[list(params)[-1]].kind == _VAR_KEYWORD +_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD +_VAR_ANY_SET = {_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL} -else: +def accepts_keyword(func, key): + """test if function accepts specified keyword""" + params = inspect.signature(get_method_function(func)).parameters + if not params: + return False + arg = params.get(key) + if arg and arg.kind not in _VAR_ANY_SET: + return True + # XXX: annoying what we have to do to determine if VAR_KWDS in use. + return params[list(params)[-1]].kind == _VAR_KEYWORD - def accepts_keyword(func, key): - """test if function accepts specified keyword""" - spec = inspect.getargspec(get_method_function(func)) - return key in spec.args or spec.keywords is not None def update_mixin_classes(target, add=None, remove=None, append=False, before=None, after=None, dryrun=False): @@ -333,16 +322,16 @@ def consteq(left, right): # http://bugs.python.org/issue14955 # validate types - if isinstance(left, unicode): - if not isinstance(right, unicode): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = False + if isinstance(left, str): + if not isinstance(right, str): + raise TypeError("inputs must be both str or both bytes") + is_bytes = False elif isinstance(left, bytes): if not isinstance(right, bytes): - raise TypeError("inputs must be both unicode or both bytes") - is_py3_bytes = PY3 + raise TypeError("inputs must be both str or both bytes") + is_bytes = True else: - raise TypeError("inputs must be both unicode or both bytes") + raise TypeError("inputs must be both str or both bytes") # do size comparison. # NOTE: the double-if construction below is done deliberately, to ensure @@ -361,8 +350,7 @@ def consteq(left, right): result = 1 # run constant-time string comparision - # TODO: use izip instead (but first verify it's faster than zip for this case) - if is_py3_bytes: + if is_bytes: for l,r in zip(tmp, right): result |= l ^ r else: @@ -442,8 +430,8 @@ def saslprep(source, param="value"): # validate type # XXX: support bytes (e.g. run through want_unicode)? # might be easier to just integrate this into cryptcontext. - if not isinstance(source, unicode): - raise TypeError("input must be unicode string, not %s" % + if not isinstance(source, str): + raise TypeError("input must be string, not %s" % (type(source),)) # mapping stage @@ -557,19 +545,11 @@ def render_bytes(source, *args): else arg for arg in args) return result.encode("latin-1") -if PY3: - # new in py32 - def bytes_to_int(value): - return int.from_bytes(value, 'big') - def int_to_bytes(value, count): - return value.to_bytes(count, 'big') -else: - # XXX: can any of these be sped up? - from binascii import hexlify, unhexlify - def bytes_to_int(value): - return int(hexlify(value),16) - def int_to_bytes(value, count): - return unhexlify(('%%0%dx' % (count<<1)) % value) +def bytes_to_int(value): + return int.from_bytes(value, 'big') + +def int_to_bytes(value, count): + return value.to_bytes(count, 'big') add_doc(bytes_to_int, "decode byte string as single big-endian integer") add_doc(int_to_bytes, "encode integer as single big-endian byte string") @@ -595,14 +575,14 @@ def utf8_repeat_string(source, size): _BNULL = b"\x00" -_UNULL = u("\x00") +_UNULL = u"\x00" def right_pad_string(source, size, pad=None): """right-pad or truncate <source> string, so it has length <size>""" cur = len(source) if size > cur: if pad is None: - pad = _UNULL if isinstance(source, unicode) else _BNULL + pad = _UNULL if isinstance(source, str) else _BNULL return source+pad*(size-cur) else: return source[:size] @@ -648,7 +628,7 @@ def utf8_truncate(source, index): # loop until we find non-continuation byte while index < end: - if byte_elem_value(source[index]) & 0xC0 != 0x80: + if source[index] & 0xC0 != 0x80: # found single-char byte, or start-char byte. break # else: found continuation byte. @@ -694,7 +674,7 @@ def is_same_codec(left, right): return _lookup_codec(left).name == _lookup_codec(right).name _B80 = b'\x80'[0] -_U80 = u('\x80') +_U80 = u'\x80' def is_ascii_safe(source): """Check if string (bytes or unicode) contains only 7-bit ascii""" r = _B80 if isinstance(source, bytes) else _U80 @@ -717,7 +697,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): the source will be transcoded from *source_encoding* to *encoding* (via unicode). - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * unicode strings will be encoded using *encoding*, and returned. @@ -732,7 +712,7 @@ def to_bytes(source, encoding="utf-8", param="value", source_encoding=None): return source.decode(source_encoding).encode(encoding) else: return source - elif isinstance(source, unicode): + elif isinstance(source, str): return source.encode(encoding) else: raise ExpectedStringError(source, param) @@ -749,42 +729,34 @@ def to_unicode(source, encoding="utf-8", param="value"): :param param: optional name of variable/noun to reference when raising errors. - :raises TypeError: if source is not unicode or bytes. + :raises TypeError: if source is not str or bytes. :returns: * returns unicode strings unchanged. * returns bytes strings decoded using *encoding* """ assert encoding - if isinstance(source, unicode): + if isinstance(source, str): return source elif isinstance(source, bytes): return source.decode(encoding) else: raise ExpectedStringError(source, param) -if PY3: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source.decode(encoding) - elif isinstance(source, unicode): - return source - else: - raise ExpectedStringError(source, param) -else: - def to_native_str(source, encoding="utf-8", param="value"): - if isinstance(source, bytes): - return source - elif isinstance(source, unicode): - return source.encode(encoding) - else: - raise ExpectedStringError(source, param) + +def to_native_str(source, encoding="utf-8", param="value"): + if isinstance(source, bytes): + return source.decode(encoding) + elif isinstance(source, str): + return source + else: + raise ExpectedStringError(source, param) + add_doc(to_native_str, - """Take in unicode or bytes, return native string. + """Take in str or bytes, returns str. - Python 2: encodes unicode using specified encoding, leaves bytes alone. - Python 3: leaves unicode alone, decodes bytes using specified encoding. + leaves str alone, decodes bytes using specified encoding. :raises TypeError: if source is not unicode or bytes. @@ -816,7 +788,7 @@ def as_bool(value, none=None, param="boolean"): recognizes strings such as "true", "false" """ assert none in [True, False, None] - if isinstance(value, unicode_or_bytes_types): + if isinstance(value, unicode_or_bytes): clean = value.lower().strip() if clean in _true_set: return True @@ -840,7 +812,7 @@ def is_safe_crypt_input(value): """ UT helper -- test if value is safe to pass to crypt.crypt(); - under PY3, can't pass non-UTF8 bytes to crypt.crypt. + since PY3 won't let us pass non-UTF8 bytes to crypt.crypt. """ if crypt_accepts_bytes or not isinstance(value, bytes): return True @@ -883,9 +855,9 @@ else: # returning NULL / None. examples include ":", ":0", "*0", etc. # safe_crypt() returns None for any string starting with one of the # chars in this string... - _invalid_prefixes = u("*:!") + _invalid_prefixes = u"*:!" - if PY3: + if True: # legacy block from PY3 compat # * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode. # * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes, @@ -905,11 +877,11 @@ else: if crypt_accepts_bytes: # PyPy3 -- all bytes accepted, but unicode encoded to ASCII, # so handling that ourselves. - if isinstance(secret, unicode): + if isinstance(secret, str): secret = secret.encode("utf-8") if _BNULL in secret: raise ValueError("null character in secret") - if isinstance(hash, unicode): + if isinstance(hash, str): hash = hash.encode("ascii") else: # CPython3's crypt() doesn't take bytes, only unicode; unicode which is then @@ -945,27 +917,7 @@ else: if not result or result[0] in _invalid_prefixes: return None return result - else: - #: see feature-detection in PY3 fork above - crypt_accepts_bytes = True - - # Python 2 crypt handler - def safe_crypt(secret, hash): - if isinstance(secret, unicode): - secret = secret.encode("utf-8") - if _NULL in secret: - raise ValueError("null character in secret") - if isinstance(hash, unicode): - hash = hash.encode("ascii") - with _safe_crypt_lock: - result = _crypt(secret, hash) - if not result: - return None - result = result.decode("ascii") - if result[0] in _invalid_prefixes: - return None - return result add_doc(safe_crypt, """Wrapper around stdlib's crypt. @@ -1006,9 +958,9 @@ def test_crypt(secret, hash): # safe_crypt() always returns unicode, which means that for py3, # 'hash' can't be bytes, or "== hash" will never be True. # under py2 unicode & str(bytes) will compare fine; - # so just enforcing "unicode_or_str" limitation - assert isinstance(hash, unicode_or_str), \ - "hash must be unicode_or_str, got %s" % type(hash) + # so just enforcing "str" limitation + assert isinstance(hash, str), \ + "hash must be str, got %s" % type(hash) assert hash, "hash must be non-empty" return safe_crypt(secret, hash) == hash @@ -1055,7 +1007,7 @@ def genseed(value=None): # this method throws error for e.g. SystemRandom instances, # so fall back to extracting 4k of state value = value.getrandbits(1 << 15) - text = u("%s %s %s %.15f %.15f %s") % ( + text = u"%s %s %s %.15f %.15f %s" % ( # if caller specified a seed value, mix it in value, @@ -1106,7 +1058,8 @@ def getrandbytes(rng, count): yield value & 0xff value >>= 3 i += 1 - return join_byte_values(helper()) + return bytes(helper()) + def getrandstr(rng, charset, count): """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng""" @@ -1132,10 +1085,10 @@ def getrandstr(rng, charset, count): value //= letters i += 1 - if isinstance(charset, unicode): + if isinstance(charset, str): return join_unicode(helper()) else: - return join_byte_elems(helper()) + return bytes(helper()) _52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz' |