"""Compatibility code for using Cheroot with various versions of Python. Cheroot is compatible with Python versions 2.6+. This module provides a useful abstraction over the differences between Python versions, sometimes by preferring a newer idiom, sometimes an older one, and sometimes a custom one. In particular, Python 2 uses str and '' for byte strings, while Python 3 uses str and '' for unicode strings. Refer to each of these the 'native string' type for each version. Because of this major difference, this module provides two functions: 'ntob', which translates native strings (of type 'str') into byte strings regardless of Python version, and 'ntou', which translates native strings to unicode strings. This also provides a 'BytesIO' name for dealing specifically with bytes, and a 'StringIO' name for dealing with native strings. It also provides a 'base64_decode' function with native strings as input and output. """ import binascii import os import re import sys import threading import six if six.PY3: def ntob(n, encoding='ISO-8859-1'): """Return the given native string as a byte string in the given encoding.""" assert_native(n) # In Python 3, the native string type is unicode return n.encode(encoding) def ntou(n, encoding='ISO-8859-1'): """Return the given native string as a unicode string with the given encoding.""" assert_native(n) # In Python 3, the native string type is unicode return n def tonative(n, encoding='ISO-8859-1'): """Return the given string as a native string in the given encoding.""" # In Python 3, the native string type is unicode if isinstance(n, bytes): return n.decode(encoding) return n def bton(b, encoding='ISO-8859-1'): """Return the given byte string as a native string in the given encoding.""" return b.decode(encoding) else: # Python 2 def ntob(n, encoding='ISO-8859-1'): """Return the given native string as a byte string in the given encoding.""" assert_native(n) # In Python 2, the native string type is bytes. Assume it's already # in the given encoding, which for ISO-8859-1 is almost always what # was intended. return n def ntou(n, encoding='ISO-8859-1'): """Return the given native string as a unicode string with the given encoding.""" assert_native(n) # In Python 2, the native string type is bytes. # First, check for the special encoding 'escape'. The test suite uses # this to signal that it wants to pass a string with embedded \uXXXX # escapes, but without having to prefix it with u'' for Python 2, # but no prefix for Python 3. if encoding == 'escape': return six.u( re.sub(r'\\u([0-9a-zA-Z]{4})', lambda m: six.unichr(int(m.group(1), 16)), n.decode('ISO-8859-1'))) # Assume it's already in the given encoding, which for ISO-8859-1 # is almost always what was intended. return n.decode(encoding) def tonative(n, encoding='ISO-8859-1'): """Return the given string as a native string in the given encoding.""" # In Python 2, the native string type is bytes. if isinstance(n, six.text_type): return n.encode(encoding) return n def bton(b, encoding='ISO-8859-1'): """Return the given byte string as a native string in the given encoding.""" return b def assert_native(n): """Check whether the input is of nativ ``str`` type. Raises: TypeError: in case of failed check """ if not isinstance(n, str): raise TypeError('n must be a native str (got %s)' % type(n).__name__) try: # Python 3.1+ from base64 import decodebytes as _base64_decodebytes except ImportError: # Python 3.0- # since Cheroot claims compability with Python 2.6+, we must use # the legacy API of base64 from base64 import decodestring as _base64_decodebytes def base64_decode(n, encoding='ISO-8859-1'): """Return the native string base64-decoded (as a native string).""" if isinstance(n, six.text_type): b = n.encode(encoding) else: b = n b = _base64_decodebytes(b) if str is six.text_type: return b.decode(encoding) else: return b try: sorted = sorted except NameError: def sorted(i): """Return sorted sequence.""" i = i[:] i.sort() return i try: reversed = reversed except NameError: def reversed(x): """Return sequence in reversed order.""" i = len(x) while i > 0: i -= 1 yield x[i] try: # Python 3 from urllib.parse import urljoin, urlencode from urllib.parse import quote, quote_plus from urllib.request import unquote, urlopen from urllib.request import parse_http_list, parse_keqv_list except ImportError: # Python 2 from urlparse import urljoin # noqa from urllib import urlencode, urlopen # noqa from urllib import quote, quote_plus # noqa from urllib import unquote # noqa from urllib2 import parse_http_list, parse_keqv_list # noqa try: dict.iteritems # Python 2 iteritems = lambda d: d.iteritems() copyitems = lambda d: d.items() except AttributeError: # Python 3 iteritems = lambda d: d.items() copyitems = lambda d: list(d.items()) try: dict.iterkeys # Python 2 iterkeys = lambda d: d.iterkeys() copykeys = lambda d: d.keys() except AttributeError: # Python 3 iterkeys = lambda d: d.keys() copykeys = lambda d: list(d.keys()) try: dict.itervalues # Python 2 itervalues = lambda d: d.itervalues() copyvalues = lambda d: d.values() except AttributeError: # Python 3 itervalues = lambda d: d.values() copyvalues = lambda d: list(d.values()) try: # Python 3 import builtins except ImportError: # Python 2 import __builtin__ as builtins # noqa try: # Python 2. We try Python 2 first clients on Python 2 # don't try to import the 'http' module from cheroot submodules directly from Cookie import SimpleCookie, CookieError from httplib import BadStatusLine, HTTPConnection, IncompleteRead from httplib import NotConnected from BaseHTTPServer import BaseHTTPRequestHandler except ImportError: # Python 3 from http.cookies import SimpleCookie, CookieError # noqa from http.client import BadStatusLine, HTTPConnection, IncompleteRead # noqa from http.client import NotConnected # noqa from http.server import BaseHTTPRequestHandler # noqa # Some platforms don't expose HTTPSConnection, so handle it separately if six.PY3: try: from http.client import HTTPSConnection except ImportError: # Some platforms which don't have SSL don't expose HTTPSConnection HTTPSConnection = None else: try: from httplib import HTTPSConnection except ImportError: HTTPSConnection = None try: # Python 2 xrange = xrange except NameError: # Python 3 xrange = range try: # Python 3 from urllib.parse import unquote as parse_unquote def unquote_qs(atom, encoding, errors='strict'): """Return urldecoded query string.""" return parse_unquote( atom.replace('+', ' '), encoding=encoding, errors=errors) except ImportError: # Python 2 from urllib import unquote as parse_unquote def unquote_qs(atom, encoding, errors='strict'): """Return urldecoded query string.""" return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors) try: # Prefer simplejson, which is usually more advanced than the builtin # module. import simplejson as json json_decode = json.JSONDecoder().decode _json_encode = json.JSONEncoder().iterencode except ImportError: if sys.version_info >= (2, 6): # Python >=2.6 : json is part of the standard library import json json_decode = json.JSONDecoder().decode _json_encode = json.JSONEncoder().iterencode else: json = None def json_decode(s): """Alert that decoding JSON is not supported because of missing package.""" raise ValueError('No JSON library is available') def _json_encode(s): """Alert that encoding JSON is not supported because of missing package.""" raise ValueError('No JSON library is available') finally: if json and six.PY3: # The two Python 3 implementations (simplejson/json) # outputs str. We need bytes. def json_encode(value): """Return JSON string as bytes.""" for chunk in _json_encode(value): yield chunk.encode('utf8') else: json_encode = _json_encode text_or_bytes = six.text_type, six.binary_type try: import cPickle as pickle except ImportError: # In Python 2, pickle is a Python version. # In Python 3, pickle is the sped-up C version. import pickle # noqa def random20(): """Return a random string of 20 bytes.""" return binascii.hexlify(os.urandom(20)).decode('ascii') try: from _thread import get_ident as get_thread_ident except ImportError: from thread import get_ident as get_thread_ident # noqa try: # Python 3 next = next except NameError: # Python 2 def next(i): """Return the next value of an iterator.""" return i.next() if sys.version_info >= (3, 3): Timer = threading.Timer Event = threading.Event else: # Python 3.2 and earlier Timer = threading._Timer Event = threading._Event try: # Python 2.7+ from subprocess import _args_from_interpreter_flags except ImportError: def _args_from_interpreter_flags(): """Try to reconstruct original interpreter args from sys.flags for Python 2.6. Backported from Python 3.5. Aims to return a list of command-line arguments reproducing the current settings in sys.flags and sys.warnoptions. """ flag_opt_map = { 'debug': 'd', # 'inspect': 'i', # 'interactive': 'i', 'optimize': 'O', 'dont_write_bytecode': 'B', 'no_user_site': 's', 'no_site': 'S', 'ignore_environment': 'E', 'verbose': 'v', 'bytes_warning': 'b', 'quiet': 'q', 'hash_randomization': 'R', 'py3k_warning': '3', } args = [] for flag, opt in flag_opt_map.items(): v = getattr(sys.flags, flag) if v > 0: if flag == 'hash_randomization': v = 1 # Handle specification of an exact seed args.append('-' + opt * v) for opt in sys.warnoptions: args.append('-W' + opt) return args # html module come in 3.2 version try: from html import escape except ImportError: from cgi import escape # html module needed the argument quote=False because in cgi the default # is False. With quote=True the results differ. def escape_html(s, escape_quote=False): """Replace special characters "&", "<" and ">" to HTML-safe sequences. When escape_quote=True, escape (') and (") chars. """ return escape(s, quote=escape_quote)