From f45f4e677e8cddba8160d1e4e02ca8a4515e3456 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 20 Oct 2016 10:11:58 +1300 Subject: netlib.strutils -> mitmproxy.utils.strutils --- examples/custom_contentviews.py | 2 +- examples/har_dump.py | 2 +- examples/tcp_message.py | 2 +- mitmproxy/addons/dumper.py | 2 +- mitmproxy/addons/serverplayback.py | 2 +- mitmproxy/contentviews.py | 2 +- mitmproxy/flowfilter.py | 2 +- mitmproxy/io_compat.py | 2 +- mitmproxy/proxy/config.py | 2 +- mitmproxy/proxy/protocol/websockets.py | 2 +- mitmproxy/tools/console/grideditor/col_bytes.py | 2 +- mitmproxy/tools/console/master.py | 4 +- mitmproxy/utils/strutils.py | 142 ++++++++++++++++++++++++ netlib/http/headers.py | 2 +- netlib/http/message.py | 4 +- netlib/http/request.py | 2 +- netlib/strutils.py | 142 ------------------------ netlib/tcp.py | 2 +- netlib/websockets/frame.py | 2 +- netlib/websockets/utils.py | 3 +- netlib/wsgi.py | 4 +- pathod/language/base.py | 2 +- pathod/language/message.py | 2 +- pathod/language/websockets.py | 2 +- pathod/log.py | 2 +- pathod/pathoc.py | 2 +- test/mitmproxy/test_utils_strutils.py | 96 ++++++++++++++++ test/netlib/test_strutils.py | 95 ---------------- 28 files changed, 269 insertions(+), 261 deletions(-) create mode 100644 mitmproxy/utils/strutils.py delete mode 100644 netlib/strutils.py create mode 100644 test/mitmproxy/test_utils_strutils.py delete mode 100644 test/netlib/test_strutils.py diff --git a/examples/custom_contentviews.py b/examples/custom_contentviews.py index 5a63e2a0..3558eaca 100644 --- a/examples/custom_contentviews.py +++ b/examples/custom_contentviews.py @@ -2,7 +2,7 @@ import string import lxml.html import lxml.etree from mitmproxy import contentviews -from netlib import strutils +from mitmproxy.utils import strutils class ViewPigLatin(contentviews.View): diff --git a/examples/har_dump.py b/examples/har_dump.py index deed2e70..d01e6cdd 100644 --- a/examples/har_dump.py +++ b/examples/har_dump.py @@ -15,7 +15,7 @@ import pytz import mitmproxy from mitmproxy import version -from netlib import strutils +from mitmproxy.utils import strutils from netlib.http import cookies HAR = {} diff --git a/examples/tcp_message.py b/examples/tcp_message.py index b431c23f..d7c9c42e 100644 --- a/examples/tcp_message.py +++ b/examples/tcp_message.py @@ -8,7 +8,7 @@ tcp_message Inline Script Hook API Demonstration example cmdline invocation: mitmdump -T --host --tcp ".*" -q -s examples/tcp_message.py """ -from netlib import strutils +from mitmproxy.utils import strutils def tcp_message(tcp_msg): diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py index 013fa337..d690c000 100644 --- a/mitmproxy/addons/dumper.py +++ b/mitmproxy/addons/dumper.py @@ -9,7 +9,7 @@ from mitmproxy import ctx from mitmproxy import exceptions from mitmproxy import flowfilter from mitmproxy.utils import human -from netlib import strutils +from mitmproxy.utils import strutils def indent(n, text): diff --git a/mitmproxy/addons/serverplayback.py b/mitmproxy/addons/serverplayback.py index 1161ce23..0b52918c 100644 --- a/mitmproxy/addons/serverplayback.py +++ b/mitmproxy/addons/serverplayback.py @@ -1,7 +1,7 @@ import urllib import hashlib -from netlib import strutils +from mitmproxy.utils import strutils from mitmproxy import exceptions from mitmproxy import ctx from mitmproxy import io diff --git a/mitmproxy/contentviews.py b/mitmproxy/contentviews.py index 9a70b104..07bf09f5 100644 --- a/mitmproxy/contentviews.py +++ b/mitmproxy/contentviews.py @@ -35,7 +35,7 @@ from mitmproxy import exceptions from mitmproxy.contrib.wbxml import ASCommandResponse from netlib import http from netlib import multidict -from netlib import strutils +from mitmproxy.utils import strutils from netlib.http import url try: diff --git a/mitmproxy/flowfilter.py b/mitmproxy/flowfilter.py index f1454fd1..ee5224c6 100644 --- a/mitmproxy/flowfilter.py +++ b/mitmproxy/flowfilter.py @@ -40,7 +40,7 @@ from mitmproxy import http from mitmproxy import tcp from mitmproxy import flow -from netlib import strutils +from mitmproxy.utils import strutils import pyparsing as pp from typing import Callable diff --git a/mitmproxy/io_compat.py b/mitmproxy/io_compat.py index 7f8f41b3..68c747ea 100644 --- a/mitmproxy/io_compat.py +++ b/mitmproxy/io_compat.py @@ -5,7 +5,7 @@ This module handles the import of mitmproxy flows generated by old versions. from typing import Any from mitmproxy import version -from netlib import strutils +from mitmproxy.utils import strutils def convert_011_012(data): diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index 201dfdf7..a6fc739b 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -4,7 +4,7 @@ import os import re from typing import Any -from netlib import strutils +from mitmproxy.utils import strutils from OpenSSL import SSL, crypto diff --git a/mitmproxy/proxy/protocol/websockets.py b/mitmproxy/proxy/protocol/websockets.py index 816ec92d..636748a1 100644 --- a/mitmproxy/proxy/protocol/websockets.py +++ b/mitmproxy/proxy/protocol/websockets.py @@ -4,7 +4,7 @@ import struct from OpenSSL import SSL from mitmproxy import exceptions from mitmproxy.proxy.protocol import base -from netlib import strutils +from mitmproxy.utils import strutils from netlib import tcp from netlib import websockets diff --git a/mitmproxy/tools/console/grideditor/col_bytes.py b/mitmproxy/tools/console/grideditor/col_bytes.py index c951ce44..f580e947 100644 --- a/mitmproxy/tools/console/grideditor/col_bytes.py +++ b/mitmproxy/tools/console/grideditor/col_bytes.py @@ -4,7 +4,7 @@ from typing import Callable, Optional import urwid from mitmproxy.tools.console import signals from mitmproxy.tools.console.grideditor import base -from netlib import strutils +from mitmproxy.utils import strutils def read_file(filename: str, callback: Callable[..., None], escaped: bool) -> Optional[str]: diff --git a/mitmproxy/tools/console/master.py b/mitmproxy/tools/console/master.py index 3cc721b2..7ff0026e 100644 --- a/mitmproxy/tools/console/master.py +++ b/mitmproxy/tools/console/master.py @@ -35,7 +35,9 @@ from mitmproxy.tools.console import signals from mitmproxy.tools.console import statusbar from mitmproxy.tools.console import window from mitmproxy.flowfilter import FMarked -from netlib import tcp, strutils +from mitmproxy.utils import strutils + +from netlib import tcp EVENTLOG_SIZE = 500 diff --git a/mitmproxy/utils/strutils.py b/mitmproxy/utils/strutils.py new file mode 100644 index 00000000..57cfbc79 --- /dev/null +++ b/mitmproxy/utils/strutils.py @@ -0,0 +1,142 @@ +import re +import codecs + + +def always_bytes(unicode_or_bytes, *encode_args): + if isinstance(unicode_or_bytes, str): + return unicode_or_bytes.encode(*encode_args) + elif isinstance(unicode_or_bytes, bytes) or unicode_or_bytes is None: + return unicode_or_bytes + else: + raise TypeError("Expected str or bytes, but got {}.".format(type(unicode_or_bytes).__name__)) + + +def native(s, *encoding_opts): + """ + Convert :py:class:`bytes` or :py:class:`unicode` to the native + :py:class:`str` type, using latin1 encoding if conversion is necessary. + + https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types + """ + if not isinstance(s, (bytes, str)): + raise TypeError("%r is neither bytes nor unicode" % s) + if isinstance(s, bytes): + return s.decode(*encoding_opts) + return s + + +# Translate control characters to "safe" characters. This implementation initially +# replaced them with the matching control pictures (http://unicode.org/charts/PDF/U2400.pdf), +# but that turned out to render badly with monospace fonts. We are back to "." therefore. +_control_char_trans = { + x: ord(".") # x + 0x2400 for unicode control group pictures + for x in range(32) +} +_control_char_trans[127] = ord(".") # 0x2421 +_control_char_trans_newline = _control_char_trans.copy() +for x in ("\r", "\n", "\t"): + del _control_char_trans_newline[ord(x)] + + +_control_char_trans = str.maketrans(_control_char_trans) +_control_char_trans_newline = str.maketrans(_control_char_trans_newline) + + +def escape_control_characters(text: str, keep_spacing=True) -> str: + """ + Replace all unicode C1 control characters from the given text with a single "." + + Args: + keep_spacing: If True, tabs and newlines will not be replaced. + """ + if not isinstance(text, str): + raise ValueError("text type must be unicode but is {}".format(type(text).__name__)) + + trans = _control_char_trans_newline if keep_spacing else _control_char_trans + return text.translate(trans) + + +def bytes_to_escaped_str(data, keep_spacing=False, escape_single_quotes=False): + """ + Take bytes and return a safe string that can be displayed to the user. + + Single quotes are always escaped, double quotes are never escaped: + "'" + bytes_to_escaped_str(...) + "'" + gives a valid Python string. + + Args: + keep_spacing: If True, tabs and newlines will not be escaped. + """ + + if not isinstance(data, bytes): + raise ValueError("data must be bytes, but is {}".format(data.__class__.__name__)) + # We always insert a double-quote here so that we get a single-quoted string back + # https://stackoverflow.com/questions/29019340/why-does-python-use-different-quotes-for-representing-strings-depending-on-their + ret = repr(b'"' + data).lstrip("b")[2:-1] + if not escape_single_quotes: + ret = re.sub(r"(? bool: + if not s or len(s) == 0: + return False + + return sum( + i < 9 or 13 < i < 32 or 126 < i + for i in s[:100] + ) / len(s[:100]) > 0.3 + + +def is_xml(s: bytes) -> bool: + return s.strip().startswith(b"<") + + +def clean_hanging_newline(t): + """ + Many editors will silently add a newline to the final line of a + document (I'm looking at you, Vim). This function fixes this common + problem at the risk of removing a hanging newline in the rare cases + where the user actually intends it. + """ + if t and t[-1] == "\n": + return t[:-1] + return t + + +def hexdump(s): + """ + Returns: + A generator of (offset, hex, str) tuples + """ + for i in range(0, len(s), 16): + offset = "{:0=10x}".format(i) + part = s[i:i + 16] + x = " ".join("{:0=2x}".format(i) for i in part) + x = x.ljust(47) # 16*2 + 15 + part_repr = native(escape_control_characters( + part.decode("ascii", "replace").replace(u"\ufffd", u"."), + False + )) + yield (offset, x, part_repr) diff --git a/netlib/http/headers.py b/netlib/http/headers.py index 39673f1a..6c30d278 100644 --- a/netlib/http/headers.py +++ b/netlib/http/headers.py @@ -2,7 +2,7 @@ import re import collections from netlib import multidict -from netlib import strutils +from mitmproxy.utils import strutils # See also: http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ diff --git a/netlib/http/message.py b/netlib/http/message.py index 1980b0ab..133a53ce 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -2,7 +2,9 @@ import re import warnings from typing import Optional -from netlib import encoding, strutils, basetypes +from mitmproxy.utils import strutils +from netlib import encoding +from netlib import basetypes from netlib.http import headers diff --git a/netlib/http/request.py b/netlib/http/request.py index dd6f4164..3479fa4c 100644 --- a/netlib/http/request.py +++ b/netlib/http/request.py @@ -2,7 +2,7 @@ import re import urllib from netlib import multidict -from netlib import strutils +from mitmproxy.utils import strutils from netlib.http import multipart from netlib.http import cookies from netlib.http import headers as nheaders diff --git a/netlib/strutils.py b/netlib/strutils.py deleted file mode 100644 index 57cfbc79..00000000 --- a/netlib/strutils.py +++ /dev/null @@ -1,142 +0,0 @@ -import re -import codecs - - -def always_bytes(unicode_or_bytes, *encode_args): - if isinstance(unicode_or_bytes, str): - return unicode_or_bytes.encode(*encode_args) - elif isinstance(unicode_or_bytes, bytes) or unicode_or_bytes is None: - return unicode_or_bytes - else: - raise TypeError("Expected str or bytes, but got {}.".format(type(unicode_or_bytes).__name__)) - - -def native(s, *encoding_opts): - """ - Convert :py:class:`bytes` or :py:class:`unicode` to the native - :py:class:`str` type, using latin1 encoding if conversion is necessary. - - https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types - """ - if not isinstance(s, (bytes, str)): - raise TypeError("%r is neither bytes nor unicode" % s) - if isinstance(s, bytes): - return s.decode(*encoding_opts) - return s - - -# Translate control characters to "safe" characters. This implementation initially -# replaced them with the matching control pictures (http://unicode.org/charts/PDF/U2400.pdf), -# but that turned out to render badly with monospace fonts. We are back to "." therefore. -_control_char_trans = { - x: ord(".") # x + 0x2400 for unicode control group pictures - for x in range(32) -} -_control_char_trans[127] = ord(".") # 0x2421 -_control_char_trans_newline = _control_char_trans.copy() -for x in ("\r", "\n", "\t"): - del _control_char_trans_newline[ord(x)] - - -_control_char_trans = str.maketrans(_control_char_trans) -_control_char_trans_newline = str.maketrans(_control_char_trans_newline) - - -def escape_control_characters(text: str, keep_spacing=True) -> str: - """ - Replace all unicode C1 control characters from the given text with a single "." - - Args: - keep_spacing: If True, tabs and newlines will not be replaced. - """ - if not isinstance(text, str): - raise ValueError("text type must be unicode but is {}".format(type(text).__name__)) - - trans = _control_char_trans_newline if keep_spacing else _control_char_trans - return text.translate(trans) - - -def bytes_to_escaped_str(data, keep_spacing=False, escape_single_quotes=False): - """ - Take bytes and return a safe string that can be displayed to the user. - - Single quotes are always escaped, double quotes are never escaped: - "'" + bytes_to_escaped_str(...) + "'" - gives a valid Python string. - - Args: - keep_spacing: If True, tabs and newlines will not be escaped. - """ - - if not isinstance(data, bytes): - raise ValueError("data must be bytes, but is {}".format(data.__class__.__name__)) - # We always insert a double-quote here so that we get a single-quoted string back - # https://stackoverflow.com/questions/29019340/why-does-python-use-different-quotes-for-representing-strings-depending-on-their - ret = repr(b'"' + data).lstrip("b")[2:-1] - if not escape_single_quotes: - ret = re.sub(r"(? bool: - if not s or len(s) == 0: - return False - - return sum( - i < 9 or 13 < i < 32 or 126 < i - for i in s[:100] - ) / len(s[:100]) > 0.3 - - -def is_xml(s: bytes) -> bool: - return s.strip().startswith(b"<") - - -def clean_hanging_newline(t): - """ - Many editors will silently add a newline to the final line of a - document (I'm looking at you, Vim). This function fixes this common - problem at the risk of removing a hanging newline in the rare cases - where the user actually intends it. - """ - if t and t[-1] == "\n": - return t[:-1] - return t - - -def hexdump(s): - """ - Returns: - A generator of (offset, hex, str) tuples - """ - for i in range(0, len(s), 16): - offset = "{:0=10x}".format(i) - part = s[i:i + 16] - x = " ".join("{:0=2x}".format(i) for i in part) - x = x.ljust(47) # 16*2 + 15 - part_repr = native(escape_control_characters( - part.decode("ascii", "replace").replace(u"\ufffd", u"."), - False - )) - yield (offset, x, part_repr) diff --git a/netlib/tcp.py b/netlib/tcp.py index aeb1d447..aed79388 100644 --- a/netlib/tcp.py +++ b/netlib/tcp.py @@ -10,7 +10,7 @@ import binascii from typing import Optional # noqa -from netlib import strutils +from mitmproxy.utils import strutils import certifi from backports import ssl_match_hostname diff --git a/netlib/websockets/frame.py b/netlib/websockets/frame.py index 02d74112..e022a95c 100644 --- a/netlib/websockets/frame.py +++ b/netlib/websockets/frame.py @@ -3,7 +3,7 @@ import struct import io from netlib import tcp -from netlib import strutils +from mitmproxy.utils import strutils from netlib import utils from mitmproxy.utils import human from .masker import Masker diff --git a/netlib/websockets/utils.py b/netlib/websockets/utils.py index fdec074e..98043662 100644 --- a/netlib/websockets/utils.py +++ b/netlib/websockets/utils.py @@ -8,7 +8,8 @@ import base64 import hashlib import os -from netlib import http, strutils +from netlib import http +from mitmproxy.utils import strutils MAGIC = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' VERSION = "13" diff --git a/netlib/wsgi.py b/netlib/wsgi.py index 11e4aba9..5a54cd70 100644 --- a/netlib/wsgi.py +++ b/netlib/wsgi.py @@ -3,7 +3,9 @@ import traceback import urllib import io -from netlib import http, tcp, strutils +from netlib import http +from netlib import tcp +from mitmproxy.utils import strutils class ClientConn: diff --git a/pathod/language/base.py b/pathod/language/base.py index 7410bbd4..44a888c0 100644 --- a/pathod/language/base.py +++ b/pathod/language/base.py @@ -4,7 +4,7 @@ import abc import functools import pyparsing as pp -from netlib import strutils +from mitmproxy.utils import strutils from mitmproxy.utils import human from . import generators, exceptions diff --git a/pathod/language/message.py b/pathod/language/message.py index 03b4a2cf..6cdaaa0b 100644 --- a/pathod/language/message.py +++ b/pathod/language/message.py @@ -1,6 +1,6 @@ import abc from . import actions, exceptions -from netlib import strutils +from mitmproxy.utils import strutils LOG_TRUNCATE = 1024 diff --git a/pathod/language/websockets.py b/pathod/language/websockets.py index 417944af..d2291f82 100644 --- a/pathod/language/websockets.py +++ b/pathod/language/websockets.py @@ -1,7 +1,7 @@ import random import string import netlib.websockets -from netlib import strutils +from mitmproxy.utils import strutils import pyparsing as pp from . import base, generators, actions, message diff --git a/pathod/log.py b/pathod/log.py index 0d1bca41..4e5f355f 100644 --- a/pathod/log.py +++ b/pathod/log.py @@ -1,6 +1,6 @@ import time -from netlib import strutils +from mitmproxy.utils import strutils from mitmproxy.utils import human diff --git a/pathod/pathoc.py b/pathod/pathoc.py index e9fa5c43..0cf08a60 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -12,7 +12,7 @@ import OpenSSL.crypto import logging from netlib.tutils import treq -from netlib import strutils +from mitmproxy.utils import strutils from netlib import tcp, certutils, websockets, socks from netlib import exceptions from netlib.http import http1 diff --git a/test/mitmproxy/test_utils_strutils.py b/test/mitmproxy/test_utils_strutils.py new file mode 100644 index 00000000..2843688f --- /dev/null +++ b/test/mitmproxy/test_utils_strutils.py @@ -0,0 +1,96 @@ +from mitmproxy.utils import strutils +from netlib import tutils + + +def test_always_bytes(): + assert strutils.always_bytes(bytes(bytearray(range(256)))) == bytes(bytearray(range(256))) + assert strutils.always_bytes("foo") == b"foo" + with tutils.raises(ValueError): + strutils.always_bytes(u"\u2605", "ascii") + with tutils.raises(TypeError): + strutils.always_bytes(42, "ascii") + + +def test_native(): + with tutils.raises(TypeError): + strutils.native(42) + assert strutils.native(u"foo") == u"foo" + assert strutils.native(b"foo") == u"foo" + + +def test_escape_control_characters(): + assert strutils.escape_control_characters(u"one") == u"one" + assert strutils.escape_control_characters(u"\00ne") == u".ne" + assert strutils.escape_control_characters(u"\nne") == u"\nne" + assert strutils.escape_control_characters(u"\nne", False) == u".ne" + assert strutils.escape_control_characters(u"\u2605") == u"\u2605" + assert ( + strutils.escape_control_characters(bytes(bytearray(range(128))).decode()) == + u'.........\t\n..\r.................. !"#$%&\'()*+,-./0123456789:;<' + u'=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.' + ) + assert ( + strutils.escape_control_characters(bytes(bytearray(range(128))).decode(), False) == + u'................................ !"#$%&\'()*+,-./0123456789:;<' + u'=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.' + ) + + with tutils.raises(ValueError): + strutils.escape_control_characters(b"foo") + + +def test_bytes_to_escaped_str(): + assert strutils.bytes_to_escaped_str(b"foo") == "foo" + assert strutils.bytes_to_escaped_str(b"\b") == r"\x08" + assert strutils.bytes_to_escaped_str(br"&!?=\)") == r"&!?=\\)" + assert strutils.bytes_to_escaped_str(b'\xc3\xbc') == r"\xc3\xbc" + assert strutils.bytes_to_escaped_str(b"'") == r"'" + assert strutils.bytes_to_escaped_str(b'"') == r'"' + + assert strutils.bytes_to_escaped_str(b"'", escape_single_quotes=True) == r"\'" + assert strutils.bytes_to_escaped_str(b'"', escape_single_quotes=True) == r'"' + + assert strutils.bytes_to_escaped_str(b"\r\n\t") == "\\r\\n\\t" + assert strutils.bytes_to_escaped_str(b"\r\n\t", True) == "\r\n\t" + + assert strutils.bytes_to_escaped_str(b"\n", True) == "\n" + assert strutils.bytes_to_escaped_str(b"\\n", True) == "\\ \\ n".replace(" ", "") + assert strutils.bytes_to_escaped_str(b"\\\n", True) == "\\ \\ \n".replace(" ", "") + assert strutils.bytes_to_escaped_str(b"\\\\n", True) == "\\ \\ \\ \\ n".replace(" ", "") + + with tutils.raises(ValueError): + strutils.bytes_to_escaped_str(u"such unicode") + + +def test_escaped_str_to_bytes(): + assert strutils.escaped_str_to_bytes("foo") == b"foo" + assert strutils.escaped_str_to_bytes("\x08") == b"\b" + assert strutils.escaped_str_to_bytes("&!?=\\\\)") == br"&!?=\)" + assert strutils.escaped_str_to_bytes(u"\\x08") == b"\b" + assert strutils.escaped_str_to_bytes(u"&!?=\\\\)") == br"&!?=\)" + assert strutils.escaped_str_to_bytes(u"\u00fc") == b'\xc3\xbc' + + with tutils.raises(ValueError): + strutils.escaped_str_to_bytes(b"very byte") + + +def test_is_mostly_bin(): + assert not strutils.is_mostly_bin(b"foo\xFF") + assert strutils.is_mostly_bin(b"foo" + b"\xFF" * 10) + assert not strutils.is_mostly_bin("") + + +def test_is_xml(): + assert not strutils.is_xml(b"foo") + assert strutils.is_xml(b"?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.' - ) - assert ( - strutils.escape_control_characters(bytes(bytearray(range(128))).decode(), False) == - u'................................ !"#$%&\'()*+,-./0123456789:;<' - u'=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.' - ) - - with tutils.raises(ValueError): - strutils.escape_control_characters(b"foo") - - -def test_bytes_to_escaped_str(): - assert strutils.bytes_to_escaped_str(b"foo") == "foo" - assert strutils.bytes_to_escaped_str(b"\b") == r"\x08" - assert strutils.bytes_to_escaped_str(br"&!?=\)") == r"&!?=\\)" - assert strutils.bytes_to_escaped_str(b'\xc3\xbc') == r"\xc3\xbc" - assert strutils.bytes_to_escaped_str(b"'") == r"'" - assert strutils.bytes_to_escaped_str(b'"') == r'"' - - assert strutils.bytes_to_escaped_str(b"'", escape_single_quotes=True) == r"\'" - assert strutils.bytes_to_escaped_str(b'"', escape_single_quotes=True) == r'"' - - assert strutils.bytes_to_escaped_str(b"\r\n\t") == "\\r\\n\\t" - assert strutils.bytes_to_escaped_str(b"\r\n\t", True) == "\r\n\t" - - assert strutils.bytes_to_escaped_str(b"\n", True) == "\n" - assert strutils.bytes_to_escaped_str(b"\\n", True) == "\\ \\ n".replace(" ", "") - assert strutils.bytes_to_escaped_str(b"\\\n", True) == "\\ \\ \n".replace(" ", "") - assert strutils.bytes_to_escaped_str(b"\\\\n", True) == "\\ \\ \\ \\ n".replace(" ", "") - - with tutils.raises(ValueError): - strutils.bytes_to_escaped_str(u"such unicode") - - -def test_escaped_str_to_bytes(): - assert strutils.escaped_str_to_bytes("foo") == b"foo" - assert strutils.escaped_str_to_bytes("\x08") == b"\b" - assert strutils.escaped_str_to_bytes("&!?=\\\\)") == br"&!?=\)" - assert strutils.escaped_str_to_bytes(u"\\x08") == b"\b" - assert strutils.escaped_str_to_bytes(u"&!?=\\\\)") == br"&!?=\)" - assert strutils.escaped_str_to_bytes(u"\u00fc") == b'\xc3\xbc' - - with tutils.raises(ValueError): - strutils.escaped_str_to_bytes(b"very byte") - - -def test_is_mostly_bin(): - assert not strutils.is_mostly_bin(b"foo\xFF") - assert strutils.is_mostly_bin(b"foo" + b"\xFF" * 10) - assert not strutils.is_mostly_bin("") - - -def test_is_xml(): - assert not strutils.is_xml(b"foo") - assert strutils.is_xml(b"