diff options
22 files changed, 309 insertions, 35 deletions
diff --git a/mitmproxy/contentviews/css.py b/mitmproxy/contentviews/css.py index 353a3257..8fa09ed3 100644 --- a/mitmproxy/contentviews/css.py +++ b/mitmproxy/contentviews/css.py @@ -1,8 +1,51 @@ -import logging +import re +import time -import cssutils +from mitmproxy.contentviews import base +from mitmproxy.utils import strutils -from . import base +""" +A custom CSS prettifier. Compared to other prettifiers, its main features are: + +- Implemented in pure Python. +- Modifies whitespace only. +- Works with any input. +- Considerably faster than e.g. cssutils. +""" + +CSS_SPECIAL_AREAS = ( + ("'", strutils.NO_ESCAPE + "'"), + ('"', strutils.NO_ESCAPE + '"'), + (r"/\*", r"\*/"), + ("//", "$") +) +CSS_SPECIAL_CHARS = "{};:" + + +def beautify(data: str, indent: str = " "): + """Beautify a string containing CSS code""" + data = strutils.escape_special_areas( + data.strip(), + CSS_SPECIAL_AREAS, + CSS_SPECIAL_CHARS, + ) + + # Add newlines + data = re.sub(r"\s*;\s*", ";\n", data) + data = re.sub(r"\s*{\s*", " {\n", data) + data = re.sub(r"\s*}\s*", "\n}\n\n", data) + + # Fix incorrect ":" placement + data = re.sub(r"\s*:\s*(?=[^{]+})", ": ", data) + # Fix no space after "," + data = re.sub(r"\s*,\s*", ", ", data) + + # indent + data = re.sub("\n[ \t]+", "\n", data) + data = re.sub("\n(?![}\n])(?=[^{]*})", "\n" + indent, data) + + data = strutils.unescape_special_areas(data) + return data.rstrip("\n") + "\n" class ViewCSS(base.View): @@ -13,13 +56,15 @@ class ViewCSS(base.View): ] def __call__(self, data, **metadata): - cssutils.log.setLevel(logging.CRITICAL) - cssutils.ser.prefs.keepComments = True - cssutils.ser.prefs.omitLastSemicolon = False - cssutils.ser.prefs.indentClosingBrace = False - cssutils.ser.prefs.validOnly = False + data = data.decode("utf8", "surrogateescape") + beautified = beautify(data) + return "CSS", base.format_text(beautified) - sheet = cssutils.parseString(data) - beautified = sheet.cssText - return "CSS", base.format_text(beautified) +if __name__ == "__main__": # pragma: no cover + with open("../tools/web/static/vendor.css") as f: + data = f.read() + + t = time.time() + x = beautify(data) + print("Beautifying vendor.css took {:.2}s".format(time.time() - t)) diff --git a/test/mitmproxy/contentviews/test_css.py b/test/mitmproxy/contentviews/test_css.py index ecb9259b..814f6e83 100644 --- a/test/mitmproxy/contentviews/test_css.py +++ b/test/mitmproxy/contentviews/test_css.py @@ -1,29 +1,42 @@ +import pytest + from mitmproxy.contentviews import css from mitmproxy.test import tutils from . import full_eval -try: - import cssutils -except: - cssutils = None - - -def test_view_css(): +data = tutils.test_data.push("mitmproxy/contentviews/test_css_data/") + + +@pytest.mark.parametrize("filename", [ + "animation-keyframe.css", + "blank-lines-and-spaces.css", + "block-comment.css", + "empty-rule.css", + "import-directive.css", + "indentation.css", + "media-directive.css", + "quoted-string.css", + "selectors.css", + "simple.css", +]) +def test_beautify(filename): + path = data.path(filename) + with open(path) as f: + input = f.read() + with open("-formatted.".join(path.rsplit(".", 1))) as f: + expected = f.read() + formatted = css.beautify(input) + assert formatted == expected + + +def test_simple(): v = full_eval(css.ViewCSS()) - - with open(tutils.test_data.path('mitmproxy/data/1.css'), 'r') as fp: - fixture_1 = fp.read() - - result = v('a') - - if cssutils: - assert len(list(result[1])) == 0 - else: - assert len(list(result[1])) == 1 - - result = v(fixture_1) - - if cssutils: - assert len(list(result[1])) > 1 - else: - assert len(list(result[1])) == 1 + assert v(b"#foo{color:red}") == ('CSS', [ + [('text', '#foo {')], + [('text', ' color: red')], + [('text', '}')] + ]) + assert v(b"") == ('CSS', [[('text', '')]]) + assert v(b"console.log('not really css')") == ( + 'CSS', [[('text', "console.log('not really css')")]] + ) diff --git a/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css b/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css new file mode 100644 index 00000000..3f91d508 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css @@ -0,0 +1,11 @@ +@-webkit-keyframes anim { +0% { + -webkit-transform: translate3d(0px, 0px, 0px); +} + +100% { + -webkit-transform: translate3d(150px, 0px, 0px) +} + + +} diff --git a/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css b/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css new file mode 100644 index 00000000..ce63da5c --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css @@ -0,0 +1,3 @@ +@-webkit-keyframes anim { +0% { -webkit-transform: translate3d(0px, 0px, 0px); } +100% { -webkit-transform: translate3d(150px, 0px, 0px) }} diff --git a/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css new file mode 100644 index 00000000..de6bd045 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css @@ -0,0 +1,35 @@ +/* only one blank line between */ +menu { + color: red +} + +navi { + color: black +} + +/* automatically insert a blank line */ +button { + border: 1px +} + +sidebar { + color: #ffe +} + +/* always whitespace before { */ +hidden { + opacity: 0% +} + +/* no blank lines inside ruleset */ +imprint { + color: blue; + opacity: 0.5; + font-size: small +} + +/* before colon: no space, after colon: one space only */ +footer { + font-family: Arial; + float: right; +} diff --git a/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css new file mode 100644 index 00000000..c6892105 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css @@ -0,0 +1,30 @@ +/* only one blank line between */ +menu { color: red } + + + + +navi { color: black } + +/* automatically insert a blank line */ +button { border: 1px } sidebar { color: #ffe } + +/* always whitespace before { */ +hidden{opacity:0%} + +/* no blank lines inside ruleset */ +imprint { + color: blue; + + + opacity: 0.5; + + font-size: small +} + +/* before colon: no space, after colon: one space only */ +footer { + font-family: Arial; + + float :right; + } diff --git a/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css b/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css new file mode 100644 index 00000000..83e0f4e6 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css @@ -0,0 +1,22 @@ +/* line comment */ +navigation { + color: blue +} + +menu { + /* line comment inside */ + border: 2px +} + +/* block +comment */ +sidebar { + color: red +} + +invisible { + /* block + * comment + * inside */ + color: #eee +} diff --git a/test/mitmproxy/contentviews/test_css_data/block-comment.css b/test/mitmproxy/contentviews/test_css_data/block-comment.css new file mode 100644 index 00000000..3ba26540 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/block-comment.css @@ -0,0 +1,18 @@ +/* line comment */ +navigation { color: blue } + +menu { + /* line comment inside */ + border: 2px +} + +/* block + comment */ +sidebar { color: red } + +invisible { + /* block + * comment + * inside */ + color: #eee +} diff --git a/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css b/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css new file mode 100644 index 00000000..7c0a78f4 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css @@ -0,0 +1,2 @@ +menu { +} diff --git a/test/mitmproxy/contentviews/test_css_data/empty-rule.css b/test/mitmproxy/contentviews/test_css_data/empty-rule.css new file mode 100644 index 00000000..7d6ecfcd --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/empty-rule.css @@ -0,0 +1 @@ +menu{} diff --git a/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css b/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css new file mode 100644 index 00000000..08a0ad57 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css @@ -0,0 +1,8 @@ +menu { + background-color: red +} + +@import url('foobar.css') screen; +nav { + margin: 0 +} diff --git a/test/mitmproxy/contentviews/test_css_data/import-directive.css b/test/mitmproxy/contentviews/test_css_data/import-directive.css new file mode 100644 index 00000000..61979f0a --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/import-directive.css @@ -0,0 +1,2 @@ +menu{background-color:red} @import url('foobar.css') screen; +nav{margin:0} diff --git a/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css b/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css new file mode 100644 index 00000000..18ea527d --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css @@ -0,0 +1,3 @@ +navigation { + color: blue +} diff --git a/test/mitmproxy/contentviews/test_css_data/indentation.css b/test/mitmproxy/contentviews/test_css_data/indentation.css new file mode 100644 index 00000000..77e00f83 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/indentation.css @@ -0,0 +1,3 @@ + navigation { + color: blue + } diff --git a/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css b/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css new file mode 100644 index 00000000..84d95421 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css @@ -0,0 +1,17 @@ +@import "subs.css"; +@import "print-main.css" print; +@media print { +body { + font-size: 10pt +} + +nav { + color: blue; +} + + +} + +h1 { + color: red; +} diff --git a/test/mitmproxy/contentviews/test_css_data/media-directive.css b/test/mitmproxy/contentviews/test_css_data/media-directive.css new file mode 100644 index 00000000..ddf67c58 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/media-directive.css @@ -0,0 +1,7 @@ +@import "subs.css"; +@import "print-main.css" print; +@media print { + body { font-size: 10pt } + nav { color: blue; } +} +h1 {color: red; } diff --git a/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css b/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css new file mode 100644 index 00000000..ab4c3412 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css @@ -0,0 +1,7 @@ +nav:after { + content: '}' +} + +nav:before { + content: "}" +} diff --git a/test/mitmproxy/contentviews/test_css_data/quoted-string.css b/test/mitmproxy/contentviews/test_css_data/quoted-string.css new file mode 100644 index 00000000..f5f3279e --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/quoted-string.css @@ -0,0 +1,2 @@ +nav:after{content:'}'} +nav:before{content:"}"} diff --git a/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css b/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css new file mode 100644 index 00000000..166251cb --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css @@ -0,0 +1,19 @@ +* { + border: 0px solid blue; +} + +div[class="{}"] { + color: red; +} + +a[id=\"foo"] { + padding: 0; +} + +[id=\"foo"] { + margin: 0; +} + +#menu, #nav, #footer { + color: royalblue; +} diff --git a/test/mitmproxy/contentviews/test_css_data/selectors.css b/test/mitmproxy/contentviews/test_css_data/selectors.css new file mode 100644 index 00000000..dc36f9e5 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/selectors.css @@ -0,0 +1,5 @@ +* { border: 0px solid blue; } +div[class="{}"] { color: red; } +a[id=\"foo"] { padding: 0; } +[id=\"foo"] { margin: 0; } +#menu, #nav, #footer { color: royalblue; } diff --git a/test/mitmproxy/contentviews/test_css_data/simple-formatted.css b/test/mitmproxy/contentviews/test_css_data/simple-formatted.css new file mode 100644 index 00000000..9435236b --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/simple-formatted.css @@ -0,0 +1,16 @@ +menu { + color: blue; +} + +box { + border-radius: 4px; + background-color: red +} + +a { + color: green +} + +b { + color: red +} diff --git a/test/mitmproxy/contentviews/test_css_data/simple.css b/test/mitmproxy/contentviews/test_css_data/simple.css new file mode 100644 index 00000000..33b29a03 --- /dev/null +++ b/test/mitmproxy/contentviews/test_css_data/simple.css @@ -0,0 +1,5 @@ +menu { color: blue; } + +box { border-radius: 4px; background-color: red } +a { color: green } +b { color: red } |