You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					885 lines
				
				31 KiB
			
		
		
			
		
	
	
					885 lines
				
				31 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""
							 | 
						||
| 
								 | 
							
								passlib.utils.binary - binary data encoding/decoding/manipulation
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								from __future__ import absolute_import, division, print_function
							 | 
						||
| 
								 | 
							
								from base64 import (
							 | 
						||
| 
								 | 
							
								    b64encode,
							 | 
						||
| 
								 | 
							
								    b64decode,
							 | 
						||
| 
								 | 
							
								    b32decode as _b32decode,
							 | 
						||
| 
								 | 
							
								    b32encode as _b32encode,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError
							 | 
						||
| 
								 | 
							
								import logging
							 | 
						||
| 
								 | 
							
								log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib import exc
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import (
							 | 
						||
| 
								 | 
							
								    PY3, bascii_to_str,
							 | 
						||
| 
								 | 
							
								    irange, imap, iter_byte_chars, join_byte_values, join_byte_elems,
							 | 
						||
| 
								 | 
							
								    nextgetter, suppress_cause,
							 | 
						||
| 
								 | 
							
								    u, unicode, unicode_or_bytes_types,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from passlib.utils.decor import memoized_property
							 | 
						||
| 
								 | 
							
								# from passlib.utils import BASE64_CHARS, HASH64_CHARS
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    # constants
							 | 
						||
| 
								 | 
							
								    "BASE64_CHARS", "PADDED_BASE64_CHARS",
							 | 
						||
| 
								 | 
							
								    "AB64_CHARS",
							 | 
						||
| 
								 | 
							
								    "HASH64_CHARS",
							 | 
						||
| 
								 | 
							
								    "BCRYPT_CHARS",
							 | 
						||
| 
								 | 
							
								    "HEX_CHARS", "LOWER_HEX_CHARS", "UPPER_HEX_CHARS",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    "ALL_BYTE_VALUES",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # misc
							 | 
						||
| 
								 | 
							
								    "compile_byte_translation",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # base64
							 | 
						||
| 
								 | 
							
								    'ab64_encode', 'ab64_decode',
							 | 
						||
| 
								 | 
							
								    'b64s_encode', 'b64s_decode',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # base32
							 | 
						||
| 
								 | 
							
								    "b32encode", "b32decode",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # custom encodings
							 | 
						||
| 
								 | 
							
								    'Base64Engine',
							 | 
						||
| 
								 | 
							
								    'LazyBase64Engine',
							 | 
						||
| 
								 | 
							
								    'h64',
							 | 
						||
| 
								 | 
							
								    'h64big',
							 | 
						||
| 
								 | 
							
								    'bcrypt64',
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# constant strings
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# common salt_chars & checksum_chars values
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: standard base64 charmap
							 | 
						||
| 
								 | 
							
								BASE64_CHARS = u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: alt base64 charmap -- "." instead of "+"
							 | 
						||
| 
								 | 
							
								AB64_CHARS =   u("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: charmap used by HASH64 encoding.
							 | 
						||
| 
								 | 
							
								HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: charmap used by BCrypt
							 | 
						||
| 
								 | 
							
								BCRYPT_CHARS = u("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: std base64 chars + padding char
							 | 
						||
| 
								 | 
							
								PADDED_BASE64_CHARS = BASE64_CHARS + u("=")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: all hex chars
							 | 
						||
| 
								 | 
							
								HEX_CHARS = u("0123456789abcdefABCDEF")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: upper case hex chars
							 | 
						||
| 
								 | 
							
								UPPER_HEX_CHARS = u("0123456789ABCDEF")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: lower case hex chars
							 | 
						||
| 
								 | 
							
								LOWER_HEX_CHARS = u("0123456789abcdef")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# byte strings
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: special byte string containing all possible byte values
							 | 
						||
| 
								 | 
							
								#: NOTE: for efficiency, this is treated as singleton by some of the code
							 | 
						||
| 
								 | 
							
								ALL_BYTE_VALUES = join_byte_values(irange(256))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: some string constants we reuse
							 | 
						||
| 
								 | 
							
								B_EMPTY = b''
							 | 
						||
| 
								 | 
							
								B_NULL = b'\x00'
							 | 
						||
| 
								 | 
							
								B_EQUAL = b'='
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# byte translation
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: base list used to compile byte translations
							 | 
						||
| 
								 | 
							
								_TRANSLATE_SOURCE = list(iter_byte_chars(ALL_BYTE_VALUES))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def compile_byte_translation(mapping, source=None):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return a 256-byte string for translating bytes using specified mapping.
							 | 
						||
| 
								 | 
							
								    bytes not specified by mapping will be left alone.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param mapping:
							 | 
						||
| 
								 | 
							
								        dict mapping input byte (str or int) -> output byte (str or int).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param source:
							 | 
						||
| 
								 | 
							
								        optional existing byte translation string to use as base.
							 | 
						||
| 
								 | 
							
								        (must be 255-length byte string).  defaults to identity mapping.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        255-length byte string for passing to bytes().translate.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if source is None:
							 | 
						||
| 
								 | 
							
								        target = _TRANSLATE_SOURCE[:]
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        assert isinstance(source, bytes) and len(source) == 255
							 | 
						||
| 
								 | 
							
								        target = list(iter_byte_chars(source))
							 | 
						||
| 
								 | 
							
								    for k, v in mapping.items():
							 | 
						||
| 
								 | 
							
								        if isinstance(k, unicode_or_bytes_types):
							 | 
						||
| 
								 | 
							
								            k = ord(k)
							 | 
						||
| 
								 | 
							
								        assert isinstance(k, int) and 0 <= k < 256
							 | 
						||
| 
								 | 
							
								        if isinstance(v, unicode):
							 | 
						||
| 
								 | 
							
								            v = v.encode("ascii")
							 | 
						||
| 
								 | 
							
								        assert isinstance(v, bytes) and len(v) == 1
							 | 
						||
| 
								 | 
							
								        target[k] = v
							 | 
						||
| 
								 | 
							
								    return B_EMPTY.join(target)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# unpadding / stripped base64 encoding
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								def b64s_encode(data):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    encode using shortened base64 format which omits padding & whitespace.
							 | 
						||
| 
								 | 
							
								    uses default ``+/`` altchars.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return b2a_base64(data).rstrip(_BASE64_STRIP)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def b64s_decode(data):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    decode from shortened base64 format which omits padding & whitespace.
							 | 
						||
| 
								 | 
							
								    uses default ``+/`` altchars.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if isinstance(data, unicode):
							 | 
						||
| 
								 | 
							
								        # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            data = data.encode("ascii")
							 | 
						||
| 
								 | 
							
								        except UnicodeEncodeError:
							 | 
						||
| 
								 | 
							
								            raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
							 | 
						||
| 
								 | 
							
								    off = len(data) & 3
							 | 
						||
| 
								 | 
							
								    if off == 0:
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								    elif off == 2:
							 | 
						||
| 
								 | 
							
								        data += _BASE64_PAD2
							 | 
						||
| 
								 | 
							
								    elif off == 3:
							 | 
						||
| 
								 | 
							
								        data += _BASE64_PAD1
							 | 
						||
| 
								 | 
							
								    else:  # off == 1
							 | 
						||
| 
								 | 
							
								        raise ValueError("invalid base64 input")
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        return a2b_base64(data)
							 | 
						||
| 
								 | 
							
								    except _BinAsciiError as err:
							 | 
						||
| 
								 | 
							
								        raise suppress_cause(TypeError(err))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# adapted-base64 encoding
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_BASE64_STRIP = b"=\n"
							 | 
						||
| 
								 | 
							
								_BASE64_PAD1 = b"="
							 | 
						||
| 
								 | 
							
								_BASE64_PAD2 = b"=="
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# XXX: Passlib 1.8/1.9 -- deprecate everything that's using ab64_encode(),
							 | 
						||
| 
								 | 
							
								#      have it start outputing b64s_encode() instead? can use a64_decode() to retain backwards compat.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def ab64_encode(data):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    encode using shortened base64 format which omits padding & whitespace.
							 | 
						||
| 
								 | 
							
								    uses custom ``./`` altchars.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it is primarily used by Passlib's custom pbkdf2 hashes.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return b64s_encode(data).replace(b"+", b".")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def ab64_decode(data):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    decode from shortened base64 format which omits padding & whitespace.
							 | 
						||
| 
								 | 
							
								    uses custom ``./`` altchars, but supports decoding normal ``+/`` altchars as well.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it is primarily used by Passlib's custom pbkdf2 hashes.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if isinstance(data, unicode):
							 | 
						||
| 
								 | 
							
								        # needs bytes for replace() call, but want to accept ascii-unicode ala a2b_base64()
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            data = data.encode("ascii")
							 | 
						||
| 
								 | 
							
								        except UnicodeEncodeError:
							 | 
						||
| 
								 | 
							
								            raise suppress_cause(ValueError("string argument should contain only ASCII characters"))
							 | 
						||
| 
								 | 
							
								    return b64s_decode(data.replace(b".", b"+"))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# base32 codec
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def b32encode(source):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    wrapper around :func:`base64.b32encode` which strips padding,
							 | 
						||
| 
								 | 
							
								    and returns a native string.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # NOTE: using upper case by default here, since 'I & L' are less
							 | 
						||
| 
								 | 
							
								    #       visually ambiguous than 'i & l'
							 | 
						||
| 
								 | 
							
								    return bascii_to_str(_b32encode(source).rstrip(B_EQUAL))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: byte translation map to replace common mistyped base32 chars.
							 | 
						||
| 
								 | 
							
								#: XXX: could correct '1' -> 'I', but could be a mistyped lower-case 'l', so leaving it alone.
							 | 
						||
| 
								 | 
							
								_b32_translate = compile_byte_translation({"8": "B", "0": "O"})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: helper to add padding
							 | 
						||
| 
								 | 
							
								_b32_decode_pad = B_EQUAL * 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def b32decode(source):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    wrapper around :func:`base64.b32decode`
							 | 
						||
| 
								 | 
							
								    which handles common mistyped chars.
							 | 
						||
| 
								 | 
							
								    padding optional, ignored if present.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # encode & correct for typos
							 | 
						||
| 
								 | 
							
								    if isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								        source = source.encode("ascii")
							 | 
						||
| 
								 | 
							
								    source = source.translate(_b32_translate)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # pad things so final string is multiple of 8
							 | 
						||
| 
								 | 
							
								    remainder = len(source) & 0x7
							 | 
						||
| 
								 | 
							
								    if remainder:
							 | 
						||
| 
								 | 
							
								        source += _b32_decode_pad[:-remainder]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # XXX: py27 stdlib's version of this has some inefficiencies,
							 | 
						||
| 
								 | 
							
								    #      could look into using optimized version.
							 | 
						||
| 
								 | 
							
								    return _b32decode(source, True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# base64-variant encoding
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Base64Engine(object):
							 | 
						||
| 
								 | 
							
								    """Provides routines for encoding/decoding base64 data using
							 | 
						||
| 
								 | 
							
								    arbitrary character mappings, selectable endianness, etc.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg charmap:
							 | 
						||
| 
								 | 
							
								        A string of 64 unique characters,
							 | 
						||
| 
								 | 
							
								        which will be used to encode successive 6-bit chunks of data.
							 | 
						||
| 
								 | 
							
								        A character's position within the string should correspond
							 | 
						||
| 
								 | 
							
								        to its 6-bit value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param big:
							 | 
						||
| 
								 | 
							
								        Whether the encoding should be big-endian (default False).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. note::
							 | 
						||
| 
								 | 
							
								        This class does not currently handle base64's padding characters
							 | 
						||
| 
								 | 
							
								        in any way what so ever.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raw Bytes <-> Encoded Bytes
							 | 
						||
| 
								 | 
							
								    ===========================
							 | 
						||
| 
								 | 
							
								    The following methods convert between raw bytes,
							 | 
						||
| 
								 | 
							
								    and strings encoded using the engine's specific base64 variant:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_bytes
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_bytes
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_transposed_bytes
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_transposed_bytes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ..
							 | 
						||
| 
								 | 
							
								        .. automethod:: check_repair_unused
							 | 
						||
| 
								 | 
							
								        .. automethod:: repair_unused
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Integers <-> Encoded Bytes
							 | 
						||
| 
								 | 
							
								    ==========================
							 | 
						||
| 
								 | 
							
								    The following methods allow encoding and decoding
							 | 
						||
| 
								 | 
							
								    unsigned integers to and from the engine's specific base64 variant.
							 | 
						||
| 
								 | 
							
								    Endianess is determined by the engine's ``big`` constructor keyword.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_int6
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_int6
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_int12
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_int12
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_int24
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_int24
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: encode_int64
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode_int64
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Informational Attributes
							 | 
						||
| 
								 | 
							
								    ========================
							 | 
						||
| 
								 | 
							
								    .. attribute:: charmap
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        unicode string containing list of characters used in encoding;
							 | 
						||
| 
								 | 
							
								        position in string matches 6bit value of character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. attribute:: bytemap
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        bytes version of :attr:`charmap`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. attribute:: big
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        boolean flag indicating this using big-endian encoding.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # instance attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # public config
							 | 
						||
| 
								 | 
							
								    bytemap = None # charmap as bytes
							 | 
						||
| 
								 | 
							
								    big = None # little or big endian
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # filled in by init based on charmap.
							 | 
						||
| 
								 | 
							
								    # (byte elem: single byte under py2, 8bit int under py3)
							 | 
						||
| 
								 | 
							
								    _encode64 = None # maps 6bit value -> byte elem
							 | 
						||
| 
								 | 
							
								    _decode64 = None # maps byte elem -> 6bit value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # helpers filled in by init based on endianness
							 | 
						||
| 
								 | 
							
								    _encode_bytes = None # throws IndexError if bad value (shouldn't happen)
							 | 
						||
| 
								 | 
							
								    _decode_bytes = None # throws KeyError if bad char.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # init
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def __init__(self, charmap, big=False):
							 | 
						||
| 
								 | 
							
								        # validate charmap, generate encode64/decode64 helper functions.
							 | 
						||
| 
								 | 
							
								        if isinstance(charmap, unicode):
							 | 
						||
| 
								 | 
							
								            charmap = charmap.encode("latin-1")
							 | 
						||
| 
								 | 
							
								        elif not isinstance(charmap, bytes):
							 | 
						||
| 
								 | 
							
								            raise exc.ExpectedStringError(charmap, "charmap")
							 | 
						||
| 
								 | 
							
								        if len(charmap) != 64:
							 | 
						||
| 
								 | 
							
								            raise ValueError("charmap must be 64 characters in length")
							 | 
						||
| 
								 | 
							
								        if len(set(charmap)) != 64:
							 | 
						||
| 
								 | 
							
								            raise ValueError("charmap must not contain duplicate characters")
							 | 
						||
| 
								 | 
							
								        self.bytemap = charmap
							 | 
						||
| 
								 | 
							
								        self._encode64 = charmap.__getitem__
							 | 
						||
| 
								 | 
							
								        lookup = dict((value, idx) for idx, value in enumerate(charmap))
							 | 
						||
| 
								 | 
							
								        self._decode64 = lookup.__getitem__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # validate big, set appropriate helper functions.
							 | 
						||
| 
								 | 
							
								        self.big = big
							 | 
						||
| 
								 | 
							
								        if big:
							 | 
						||
| 
								 | 
							
								            self._encode_bytes = self._encode_bytes_big
							 | 
						||
| 
								 | 
							
								            self._decode_bytes = self._decode_bytes_big
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            self._encode_bytes = self._encode_bytes_little
							 | 
						||
| 
								 | 
							
								            self._decode_bytes = self._decode_bytes_little
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # TODO: support padding character
							 | 
						||
| 
								 | 
							
								        ##if padding is not None:
							 | 
						||
| 
								 | 
							
								        ##    if isinstance(padding, unicode):
							 | 
						||
| 
								 | 
							
								        ##        padding = padding.encode("latin-1")
							 | 
						||
| 
								 | 
							
								        ##    elif not isinstance(padding, bytes):
							 | 
						||
| 
								 | 
							
								        ##        raise TypeError("padding char must be unicode or bytes")
							 | 
						||
| 
								 | 
							
								        ##    if len(padding) != 1:
							 | 
						||
| 
								 | 
							
								        ##        raise ValueError("padding must be single character")
							 | 
						||
| 
								 | 
							
								        ##self.padding = padding
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def charmap(self):
							 | 
						||
| 
								 | 
							
								        """charmap as unicode"""
							 | 
						||
| 
								 | 
							
								        return self.bytemap.decode("latin-1")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # encoding byte strings
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def encode_bytes(self, source):
							 | 
						||
| 
								 | 
							
								        """encode bytes to base64 string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg source: byte string to encode.
							 | 
						||
| 
								 | 
							
								        :returns: byte string containing encoded data.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        chunks, tail = divmod(len(source), 3)
							 | 
						||
| 
								 | 
							
								        if PY3:
							 | 
						||
| 
								 | 
							
								            next_value = nextgetter(iter(source))
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            next_value = nextgetter(ord(elem) for elem in source)
							 | 
						||
| 
								 | 
							
								        gen = self._encode_bytes(next_value, chunks, tail)
							 | 
						||
| 
								 | 
							
								        out = join_byte_elems(imap(self._encode64, gen))
							 | 
						||
| 
								 | 
							
								        ##if tail:
							 | 
						||
| 
								 | 
							
								        ##    padding = self.padding
							 | 
						||
| 
								 | 
							
								        ##    if padding:
							 | 
						||
| 
								 | 
							
								        ##        out += padding * (3-tail)
							 | 
						||
| 
								 | 
							
								        return out
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _encode_bytes_little(self, next_value, chunks, tail):
							 | 
						||
| 
								 | 
							
								        """helper used by encode_bytes() to handle little-endian encoding"""
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # output bit layout:
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # first byte:   v1 543210
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # second byte:  v1 ....76
							 | 
						||
| 
								 | 
							
								        #              +v2 3210..
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # third byte:   v2 ..7654
							 | 
						||
| 
								 | 
							
								        #              +v3 10....
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # fourth byte:  v3 765432
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        idx = 0
							 | 
						||
| 
								 | 
							
								        while idx < chunks:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            v3 = next_value()
							 | 
						||
| 
								 | 
							
								            yield v1 & 0x3f
							 | 
						||
| 
								 | 
							
								            yield ((v2 & 0x0f)<<2)|(v1>>6)
							 | 
						||
| 
								 | 
							
								            yield ((v3 & 0x03)<<4)|(v2>>4)
							 | 
						||
| 
								 | 
							
								            yield v3>>2
							 | 
						||
| 
								 | 
							
								            idx += 1
							 | 
						||
| 
								 | 
							
								        if tail:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            if tail == 1:
							 | 
						||
| 
								 | 
							
								                # note: 4 msb of last byte are padding
							 | 
						||
| 
								 | 
							
								                yield v1 & 0x3f
							 | 
						||
| 
								 | 
							
								                yield v1>>6
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                assert tail == 2
							 | 
						||
| 
								 | 
							
								                # note: 2 msb of last byte are padding
							 | 
						||
| 
								 | 
							
								                v2 = next_value()
							 | 
						||
| 
								 | 
							
								                yield v1 & 0x3f
							 | 
						||
| 
								 | 
							
								                yield ((v2 & 0x0f)<<2)|(v1>>6)
							 | 
						||
| 
								 | 
							
								                yield v2>>4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _encode_bytes_big(self, next_value, chunks, tail):
							 | 
						||
| 
								 | 
							
								        """helper used by encode_bytes() to handle big-endian encoding"""
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # output bit layout:
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # first byte:   v1 765432
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # second byte:  v1 10....
							 | 
						||
| 
								 | 
							
								        #              +v2 ..7654
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # third byte:   v2 3210..
							 | 
						||
| 
								 | 
							
								        #              +v3 ....76
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # fourth byte:  v3 543210
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        idx = 0
							 | 
						||
| 
								 | 
							
								        while idx < chunks:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            v3 = next_value()
							 | 
						||
| 
								 | 
							
								            yield v1>>2
							 | 
						||
| 
								 | 
							
								            yield ((v1&0x03)<<4)|(v2>>4)
							 | 
						||
| 
								 | 
							
								            yield ((v2&0x0f)<<2)|(v3>>6)
							 | 
						||
| 
								 | 
							
								            yield v3 & 0x3f
							 | 
						||
| 
								 | 
							
								            idx += 1
							 | 
						||
| 
								 | 
							
								        if tail:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            if tail == 1:
							 | 
						||
| 
								 | 
							
								                # note: 4 lsb of last byte are padding
							 | 
						||
| 
								 | 
							
								                yield v1>>2
							 | 
						||
| 
								 | 
							
								                yield (v1&0x03)<<4
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                assert tail == 2
							 | 
						||
| 
								 | 
							
								                # note: 2 lsb of last byte are padding
							 | 
						||
| 
								 | 
							
								                v2 = next_value()
							 | 
						||
| 
								 | 
							
								                yield v1>>2
							 | 
						||
| 
								 | 
							
								                yield ((v1&0x03)<<4)|(v2>>4)
							 | 
						||
| 
								 | 
							
								                yield ((v2&0x0f)<<2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # decoding byte strings
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_bytes(self, source):
							 | 
						||
| 
								 | 
							
								        """decode bytes from base64 string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg source: byte string to decode.
							 | 
						||
| 
								 | 
							
								        :returns: byte string containing decoded data.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        ##padding = self.padding
							 | 
						||
| 
								 | 
							
								        ##if padding:
							 | 
						||
| 
								 | 
							
								        ##    # TODO: add padding size check?
							 | 
						||
| 
								 | 
							
								        ##    source = source.rstrip(padding)
							 | 
						||
| 
								 | 
							
								        chunks, tail = divmod(len(source), 4)
							 | 
						||
| 
								 | 
							
								        if tail == 1:
							 | 
						||
| 
								 | 
							
								            # only 6 bits left, can't encode a whole byte!
							 | 
						||
| 
								 | 
							
								            raise ValueError("input string length cannot be == 1 mod 4")
							 | 
						||
| 
								 | 
							
								        next_value = nextgetter(imap(self._decode64, source))
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return join_byte_values(self._decode_bytes(next_value, chunks, tail))
							 | 
						||
| 
								 | 
							
								        except KeyError as err:
							 | 
						||
| 
								 | 
							
								            raise ValueError("invalid character: %r" % (err.args[0],))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _decode_bytes_little(self, next_value, chunks, tail):
							 | 
						||
| 
								 | 
							
								        """helper used by decode_bytes() to handle little-endian encoding"""
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # input bit layout:
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # first byte:   v1 ..543210
							 | 
						||
| 
								 | 
							
								        #              +v2 10......
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # second byte:  v2 ....5432
							 | 
						||
| 
								 | 
							
								        #              +v3 3210....
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # third byte:   v3 ......54
							 | 
						||
| 
								 | 
							
								        #              +v4 543210..
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        idx = 0
							 | 
						||
| 
								 | 
							
								        while idx < chunks:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            v3 = next_value()
							 | 
						||
| 
								 | 
							
								            v4 = next_value()
							 | 
						||
| 
								 | 
							
								            yield v1 | ((v2 & 0x3) << 6)
							 | 
						||
| 
								 | 
							
								            yield (v2>>2) | ((v3 & 0xF) << 4)
							 | 
						||
| 
								 | 
							
								            yield (v3>>4) | (v4<<2)
							 | 
						||
| 
								 | 
							
								            idx += 1
							 | 
						||
| 
								 | 
							
								        if tail:
							 | 
						||
| 
								 | 
							
								            # tail is 2 or 3
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            yield v1 | ((v2 & 0x3) << 6)
							 | 
						||
| 
								 | 
							
								            # NOTE: if tail == 2, 4 msb of v2 are ignored (should be 0)
							 | 
						||
| 
								 | 
							
								            if tail == 3:
							 | 
						||
| 
								 | 
							
								                # NOTE: 2 msb of v3 are ignored (should be 0)
							 | 
						||
| 
								 | 
							
								                v3 = next_value()
							 | 
						||
| 
								 | 
							
								                yield (v2>>2) | ((v3 & 0xF) << 4)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _decode_bytes_big(self, next_value, chunks, tail):
							 | 
						||
| 
								 | 
							
								        """helper used by decode_bytes() to handle big-endian encoding"""
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # input bit layout:
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # first byte:   v1 543210..
							 | 
						||
| 
								 | 
							
								        #              +v2 ......54
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # second byte:  v2 3210....
							 | 
						||
| 
								 | 
							
								        #              +v3 ....5432
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # third byte:   v3 10......
							 | 
						||
| 
								 | 
							
								        #              +v4 ..543210
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        idx = 0
							 | 
						||
| 
								 | 
							
								        while idx < chunks:
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            v3 = next_value()
							 | 
						||
| 
								 | 
							
								            v4 = next_value()
							 | 
						||
| 
								 | 
							
								            yield (v1<<2) | (v2>>4)
							 | 
						||
| 
								 | 
							
								            yield ((v2&0xF)<<4) | (v3>>2)
							 | 
						||
| 
								 | 
							
								            yield ((v3&0x3)<<6) | v4
							 | 
						||
| 
								 | 
							
								            idx += 1
							 | 
						||
| 
								 | 
							
								        if tail:
							 | 
						||
| 
								 | 
							
								            # tail is 2 or 3
							 | 
						||
| 
								 | 
							
								            v1 = next_value()
							 | 
						||
| 
								 | 
							
								            v2 = next_value()
							 | 
						||
| 
								 | 
							
								            yield (v1<<2) | (v2>>4)
							 | 
						||
| 
								 | 
							
								            # NOTE: if tail == 2, 4 lsb of v2 are ignored (should be 0)
							 | 
						||
| 
								 | 
							
								            if tail == 3:
							 | 
						||
| 
								 | 
							
								                # NOTE: 2 lsb of v3 are ignored (should be 0)
							 | 
						||
| 
								 | 
							
								                v3 = next_value()
							 | 
						||
| 
								 | 
							
								                yield ((v2&0xF)<<4) | (v3>>2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # encode/decode helpers
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # padmap2/3 - dict mapping last char of string ->
							 | 
						||
| 
								 | 
							
								    # equivalent char with no padding bits set.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __make_padset(self, bits):
							 | 
						||
| 
								 | 
							
								        """helper to generate set of valid last chars & bytes"""
							 | 
						||
| 
								 | 
							
								        pset = set(c for i,c in enumerate(self.bytemap) if not i & bits)
							 | 
						||
| 
								 | 
							
								        pset.update(c for i,c in enumerate(self.charmap) if not i & bits)
							 | 
						||
| 
								 | 
							
								        return frozenset(pset)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def _padinfo2(self):
							 | 
						||
| 
								 | 
							
								        """mask to clear padding bits, and valid last bytes (for strings 2 % 4)"""
							 | 
						||
| 
								 | 
							
								        # 4 bits of last char unused (lsb for big, msb for little)
							 | 
						||
| 
								 | 
							
								        bits = 15 if self.big else (15<<2)
							 | 
						||
| 
								 | 
							
								        return ~bits, self.__make_padset(bits)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @memoized_property
							 | 
						||
| 
								 | 
							
								    def _padinfo3(self):
							 | 
						||
| 
								 | 
							
								        """mask to clear padding bits, and valid last bytes (for strings 3 % 4)"""
							 | 
						||
| 
								 | 
							
								        # 2 bits of last char unused (lsb for big, msb for little)
							 | 
						||
| 
								 | 
							
								        bits = 3 if self.big else (3<<4)
							 | 
						||
| 
								 | 
							
								        return ~bits, self.__make_padset(bits)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def check_repair_unused(self, source):
							 | 
						||
| 
								 | 
							
								        """helper to detect & clear invalid unused bits in last character.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg source:
							 | 
						||
| 
								 | 
							
								            encoded data (as ascii bytes or unicode).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :returns:
							 | 
						||
| 
								 | 
							
								            `(True, result)` if the string was repaired,
							 | 
						||
| 
								 | 
							
								            `(False, source)` if the string was ok as-is.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # figure out how many padding bits there are in last char.
							 | 
						||
| 
								 | 
							
								        tail = len(source) & 3
							 | 
						||
| 
								 | 
							
								        if tail == 2:
							 | 
						||
| 
								 | 
							
								            mask, padset = self._padinfo2
							 | 
						||
| 
								 | 
							
								        elif tail == 3:
							 | 
						||
| 
								 | 
							
								            mask, padset = self._padinfo3
							 | 
						||
| 
								 | 
							
								        elif not tail:
							 | 
						||
| 
								 | 
							
								            return False, source
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise ValueError("source length must != 1 mod 4")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # check if last char is ok (padset contains bytes & unicode versions)
							 | 
						||
| 
								 | 
							
								        last = source[-1]
							 | 
						||
| 
								 | 
							
								        if last in padset:
							 | 
						||
| 
								 | 
							
								            return False, source
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # we have dirty bits - repair the string by decoding last char,
							 | 
						||
| 
								 | 
							
								        # clearing the padding bits via <mask>, and encoding new char.
							 | 
						||
| 
								 | 
							
								        if isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								            cm = self.charmap
							 | 
						||
| 
								 | 
							
								            last = cm[cm.index(last) & mask]
							 | 
						||
| 
								 | 
							
								            assert last in padset, "failed to generate valid padding char"
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            # NOTE: this assumes ascii-compat encoding, and that
							 | 
						||
| 
								 | 
							
								            # all chars used by encoding are 7-bit ascii.
							 | 
						||
| 
								 | 
							
								            last = self._encode64(self._decode64(last) & mask)
							 | 
						||
| 
								 | 
							
								            assert last in padset, "failed to generate valid padding char"
							 | 
						||
| 
								 | 
							
								            if PY3:
							 | 
						||
| 
								 | 
							
								                last = bytes([last])
							 | 
						||
| 
								 | 
							
								        return True, source[:-1] + last
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def repair_unused(self, source):
							 | 
						||
| 
								 | 
							
								        return self.check_repair_unused(source)[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ##def transcode(self, source, other):
							 | 
						||
| 
								 | 
							
								    ##    return ''.join(
							 | 
						||
| 
								 | 
							
								    ##        other.charmap[self.charmap.index(char)]
							 | 
						||
| 
								 | 
							
								    ##        for char in source
							 | 
						||
| 
								 | 
							
								    ##    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ##def random_encoded_bytes(self, size, random=None, unicode=False):
							 | 
						||
| 
								 | 
							
								    ##    "return random encoded string of given size"
							 | 
						||
| 
								 | 
							
								    ##    data = getrandstr(random or rng,
							 | 
						||
| 
								 | 
							
								    ##                      self.charmap if unicode else self.bytemap, size)
							 | 
						||
| 
								 | 
							
								    ##    return self.repair_unused(data)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # transposed encoding/decoding
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def encode_transposed_bytes(self, source, offsets):
							 | 
						||
| 
								 | 
							
								        """encode byte string, first transposing source using offset list"""
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        tmp = join_byte_elems(source[off] for off in offsets)
							 | 
						||
| 
								 | 
							
								        return self.encode_bytes(tmp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_transposed_bytes(self, source, offsets):
							 | 
						||
| 
								 | 
							
								        """decode byte string, then reverse transposition described by offset list"""
							 | 
						||
| 
								 | 
							
								        # NOTE: if transposition does not use all bytes of source,
							 | 
						||
| 
								 | 
							
								        # the original can't be recovered... and join_byte_elems() will throw
							 | 
						||
| 
								 | 
							
								        # an error because 1+ values in <buf> will be None.
							 | 
						||
| 
								 | 
							
								        tmp = self.decode_bytes(source)
							 | 
						||
| 
								 | 
							
								        buf = [None] * len(offsets)
							 | 
						||
| 
								 | 
							
								        for off, char in zip(offsets, tmp):
							 | 
						||
| 
								 | 
							
								            buf[off] = char
							 | 
						||
| 
								 | 
							
								        return join_byte_elems(buf)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # integer decoding helpers - mainly used by des_crypt family
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def _decode_int(self, source, bits):
							 | 
						||
| 
								 | 
							
								        """decode base64 string -> integer
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg source: base64 string to decode.
							 | 
						||
| 
								 | 
							
								        :arg bits: number of bits in resulting integer.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :raises ValueError:
							 | 
						||
| 
								 | 
							
								            * if the string contains invalid base64 characters.
							 | 
						||
| 
								 | 
							
								            * if the string is not long enough - it must be at least
							 | 
						||
| 
								 | 
							
								              ``int(ceil(bits/6))`` in length.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :returns:
							 | 
						||
| 
								 | 
							
								            a integer in the range ``0 <= n < 2**bits``
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        big = self.big
							 | 
						||
| 
								 | 
							
								        pad = -bits % 6
							 | 
						||
| 
								 | 
							
								        chars = (bits+pad)/6
							 | 
						||
| 
								 | 
							
								        if len(source) != chars:
							 | 
						||
| 
								 | 
							
								            raise ValueError("source must be %d chars" % (chars,))
							 | 
						||
| 
								 | 
							
								        decode = self._decode64
							 | 
						||
| 
								 | 
							
								        out = 0
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            for c in source if big else reversed(source):
							 | 
						||
| 
								 | 
							
								                out = (out<<6) + decode(c)
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            raise ValueError("invalid character in string: %r" % (c,))
							 | 
						||
| 
								 | 
							
								        if pad:
							 | 
						||
| 
								 | 
							
								            # strip padding bits
							 | 
						||
| 
								 | 
							
								            if big:
							 | 
						||
| 
								 | 
							
								                out >>= pad
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                out &= (1<<bits)-1
							 | 
						||
| 
								 | 
							
								        return out
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # optimized versions for common integer sizes
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_int6(self, source):
							 | 
						||
| 
								 | 
							
								        """decode single character -> 6 bit integer"""
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        if len(source) != 1:
							 | 
						||
| 
								 | 
							
								            raise ValueError("source must be exactly 1 byte")
							 | 
						||
| 
								 | 
							
								        if PY3:
							 | 
						||
| 
								 | 
							
								            # convert to 8bit int before doing lookup
							 | 
						||
| 
								 | 
							
								            source = source[0]
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return self._decode64(source)
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            raise ValueError("invalid character")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_int12(self, source):
							 | 
						||
| 
								 | 
							
								        """decodes 2 char string -> 12-bit integer"""
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        if len(source) != 2:
							 | 
						||
| 
								 | 
							
								            raise ValueError("source must be exactly 2 bytes")
							 | 
						||
| 
								 | 
							
								        decode = self._decode64
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            if self.big:
							 | 
						||
| 
								 | 
							
								                return decode(source[1]) + (decode(source[0])<<6)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                return decode(source[0]) + (decode(source[1])<<6)
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            raise ValueError("invalid character")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_int24(self, source):
							 | 
						||
| 
								 | 
							
								        """decodes 4 char string -> 24-bit integer"""
							 | 
						||
| 
								 | 
							
								        if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("source must be bytes, not %s" % (type(source),))
							 | 
						||
| 
								 | 
							
								        if len(source) != 4:
							 | 
						||
| 
								 | 
							
								            raise ValueError("source must be exactly 4 bytes")
							 | 
						||
| 
								 | 
							
								        decode = self._decode64
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            if self.big:
							 | 
						||
| 
								 | 
							
								                return decode(source[3]) + (decode(source[2])<<6)+ \
							 | 
						||
| 
								 | 
							
								                       (decode(source[1])<<12) + (decode(source[0])<<18)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                return decode(source[0]) + (decode(source[1])<<6)+ \
							 | 
						||
| 
								 | 
							
								                       (decode(source[2])<<12) + (decode(source[3])<<18)
							 | 
						||
| 
								 | 
							
								        except KeyError:
							 | 
						||
| 
								 | 
							
								            raise ValueError("invalid character")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_int30(self, source):
							 | 
						||
| 
								 | 
							
								        """decode 5 char string -> 30 bit integer"""
							 | 
						||
| 
								 | 
							
								        return self._decode_int(source, 30)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def decode_int64(self, source):
							 | 
						||
| 
								 | 
							
								        """decode 11 char base64 string -> 64-bit integer
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        this format is used primarily by des-crypt & variants to encode
							 | 
						||
| 
								 | 
							
								        the DES output value used as a checksum.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self._decode_int(source, 64)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # integer encoding helpers - mainly used by des_crypt family
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def _encode_int(self, value, bits):
							 | 
						||
| 
								 | 
							
								        """encode integer into base64 format
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg value: non-negative integer to encode
							 | 
						||
| 
								 | 
							
								        :arg bits: number of bits to encode
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :returns:
							 | 
						||
| 
								 | 
							
								            a string of length ``int(ceil(bits/6.0))``.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert value >= 0, "caller did not sanitize input"
							 | 
						||
| 
								 | 
							
								        pad = -bits % 6
							 | 
						||
| 
								 | 
							
								        bits += pad
							 | 
						||
| 
								 | 
							
								        if self.big:
							 | 
						||
| 
								 | 
							
								            itr = irange(bits-6, -6, -6)
							 | 
						||
| 
								 | 
							
								            # shift to add lsb padding.
							 | 
						||
| 
								 | 
							
								            value <<= pad
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            itr = irange(0, bits, 6)
							 | 
						||
| 
								 | 
							
								            # padding is msb, so no change needed.
							 | 
						||
| 
								 | 
							
								        return join_byte_elems(imap(self._encode64,
							 | 
						||
| 
								 | 
							
								                                ((value>>off) & 0x3f for off in itr)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								    # optimized versions for common integer sizes
							 | 
						||
| 
								 | 
							
								    #---------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def encode_int6(self, value):
							 | 
						||
| 
								 | 
							
								        """encodes 6-bit integer -> single hash64 character"""
							 | 
						||
| 
								 | 
							
								        if value < 0 or value > 63:
							 | 
						||
| 
								 | 
							
								            raise ValueError("value out of range")
							 | 
						||
| 
								 | 
							
								        if PY3:
							 | 
						||
| 
								 | 
							
								            return self.bytemap[value:value+1]
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return self._encode64(value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def encode_int12(self, value):
							 | 
						||
| 
								 | 
							
								        """encodes 12-bit integer -> 2 char string"""
							 | 
						||
| 
								 | 
							
								        if value < 0 or value > 0xFFF:
							 | 
						||
| 
								 | 
							
								            raise ValueError("value out of range")
							 | 
						||
| 
								 | 
							
								        raw = [value & 0x3f, (value>>6) & 0x3f]
							 | 
						||
| 
								 | 
							
								        if self.big:
							 | 
						||
| 
								 | 
							
								            raw = reversed(raw)
							 | 
						||
| 
								 | 
							
								        return join_byte_elems(imap(self._encode64, raw))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def encode_int24(self, value):
							 | 
						||
| 
								 | 
							
								        """encodes 24-bit integer -> 4 char string"""
							 | 
						||
| 
								 | 
							
								        if value < 0 or value > 0xFFFFFF:
							 | 
						||
| 
								 | 
							
								            raise ValueError("value out of range")
							 | 
						||
| 
								 | 
							
								        raw = [value & 0x3f, (value>>6) & 0x3f,
							 | 
						||
| 
								 | 
							
								               (value>>12) & 0x3f, (value>>18) & 0x3f]
							 | 
						||
| 
								 | 
							
								        if self.big:
							 | 
						||
| 
								 | 
							
								            raw = reversed(raw)
							 | 
						||
| 
								 | 
							
								        return join_byte_elems(imap(self._encode64, raw))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def encode_int30(self, value):
							 | 
						||
| 
								 | 
							
								        """decode 5 char string -> 30 bit integer"""
							 | 
						||
| 
								 | 
							
								        if value < 0 or value > 0x3fffffff:
							 | 
						||
| 
								 | 
							
								            raise ValueError("value out of range")
							 | 
						||
| 
								 | 
							
								        return self._encode_int(value, 30)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def encode_int64(self, value):
							 | 
						||
| 
								 | 
							
								        """encode 64-bit integer -> 11 char hash64 string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        this format is used primarily by des-crypt & variants to encode
							 | 
						||
| 
								 | 
							
								        the DES output value used as a checksum.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if value < 0 or value > 0xffffffffffffffff:
							 | 
						||
| 
								 | 
							
								            raise ValueError("value out of range")
							 | 
						||
| 
								 | 
							
								        return self._encode_int(value, 64)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eof
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class LazyBase64Engine(Base64Engine):
							 | 
						||
| 
								 | 
							
								    """Base64Engine which delays initialization until it's accessed"""
							 | 
						||
| 
								 | 
							
								    _lazy_opts = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *args, **kwds):
							 | 
						||
| 
								 | 
							
								        self._lazy_opts = (args, kwds)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _lazy_init(self):
							 | 
						||
| 
								 | 
							
								        args, kwds = self._lazy_opts
							 | 
						||
| 
								 | 
							
								        super(LazyBase64Engine, self).__init__(*args, **kwds)
							 | 
						||
| 
								 | 
							
								        del self._lazy_opts
							 | 
						||
| 
								 | 
							
								        self.__class__ = Base64Engine
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getattribute__(self, attr):
							 | 
						||
| 
								 | 
							
								        if not attr.startswith("_"):
							 | 
						||
| 
								 | 
							
								            self._lazy_init()
							 | 
						||
| 
								 | 
							
								        return object.__getattribute__(self, attr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# common variants
							 | 
						||
| 
								 | 
							
								#-------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								h64 = LazyBase64Engine(HASH64_CHARS)
							 | 
						||
| 
								 | 
							
								h64big = LazyBase64Engine(HASH64_CHARS, big=True)
							 | 
						||
| 
								 | 
							
								bcrypt64 = LazyBase64Engine(BCRYPT_CHARS, big=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |