From 5499e3fa51f1d3c562f11dfc9e6c94084e259566 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 17:08:24 +0300 Subject: [PATCH 01/12] Remove content from f-strings --- src/turbo_response/renderers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/turbo_response/renderers.py b/src/turbo_response/renderers.py index c479796..6d34aff 100644 --- a/src/turbo_response/renderers.py +++ b/src/turbo_response/renderers.py @@ -11,7 +11,11 @@ def render_turbo_stream(action: Action, target: str, content: str = "") -> str: :return: ** string """ - return f'' + return ( + f'" + ) def render_turbo_frame(dom_id: str, content: str = "") -> str: @@ -24,4 +28,4 @@ def render_turbo_frame(dom_id: str, content: str = "") -> str: :return: ** string """ - return f'{content.strip()}' + return f'' + content.strip() + "" From 0987035ebea3823d44d495c7d76987fecab10c40 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 18:01:08 +0300 Subject: [PATCH 02/12] Render inside templates --- src/turbo_response/renderers.py | 18 +++++++---- src/turbo_response/template.py | 35 ++++++++++++++-------- src/turbo_response/tests/test_renderers.py | 11 ++++--- src/turbo_response/tests/test_views.py | 6 ++-- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/turbo_response/renderers.py b/src/turbo_response/renderers.py index 6d34aff..8488a52 100644 --- a/src/turbo_response/renderers.py +++ b/src/turbo_response/renderers.py @@ -1,4 +1,7 @@ # Local +# Django +from django.template import Context, Template + from .constants import Action @@ -11,10 +14,12 @@ def render_turbo_stream(action: Action, target: str, content: str = "") -> str: :return: ** string """ - return ( - f'" + turbo_stream_tmpl = Template( + '' + ) + + return turbo_stream_tmpl.render( + Context({"action": action.value, "target": target, "content": content}) ) @@ -28,4 +33,7 @@ def render_turbo_frame(dom_id: str, content: str = "") -> str: :return: ** string """ - return f'' + content.strip() + "" + turbo_frame_tmpl = Template( + '{{ content }}' + ) + return turbo_frame_tmpl.render(Context({"dom_id": dom_id, "content": content})) diff --git a/src/turbo_response/template.py b/src/turbo_response/template.py index ecd5362..ed70cd7 100644 --- a/src/turbo_response/template.py +++ b/src/turbo_response/template.py @@ -3,6 +3,7 @@ # Django from django.template.loader import render_to_string +from django.utils.safestring import mark_safe # Local from .constants import Action @@ -27,15 +28,17 @@ def render_turbo_stream_template( return render_turbo_stream( action, target, - render_to_string( - template, - { - **(context or {}), - "turbo_stream_target": target, - "turbo_stream_action": action.value, - "is_turbo_stream": True, - }, - **template_kwargs, + mark_safe( + render_to_string( + template, + { + **(context or {}), + "turbo_stream_target": target, + "turbo_stream_action": action.value, + "is_turbo_stream": True, + }, + **template_kwargs, + ).strip() ), ) @@ -56,9 +59,15 @@ def render_turbo_frame_template( return render_turbo_frame( dom_id, - render_to_string( - template, - {**(context or {}), "turbo_frame_dom_id": dom_id, "is_turbo_frame": True}, - **kwargs, + mark_safe( + render_to_string( + template, + { + **(context or {}), + "turbo_frame_dom_id": dom_id, + "is_turbo_frame": True, + }, + **kwargs, + ).strip() ), ) diff --git a/src/turbo_response/tests/test_renderers.py b/src/turbo_response/tests/test_renderers.py index a3ece53..2226ec4 100644 --- a/src/turbo_response/tests/test_renderers.py +++ b/src/turbo_response/tests/test_renderers.py @@ -10,7 +10,7 @@ def test_render_empty_stream(self): == '' ) - def test_render_content(self): + def test_render_content_xss(self): s = render_turbo_stream( action=Action.REPLACE, target="test", @@ -18,7 +18,7 @@ def test_render_content(self): ) assert ( s - == '' + == '' ) @@ -27,9 +27,12 @@ def test_render_empty_frame(self): s = render_turbo_frame("test") assert s == '' - def test_render_content(self): + def test_render_content_xss(self): s = render_turbo_frame( dom_id="test", content="
my content
", ) - assert s == '
my content
' + assert ( + s + == '<div>my content</div>' + ) diff --git a/src/turbo_response/tests/test_views.py b/src/turbo_response/tests/test_views.py index 1ebfa85..20ccc50 100644 --- a/src/turbo_response/tests/test_views.py +++ b/src/turbo_response/tests/test_views.py @@ -319,6 +319,7 @@ def test_get(self, rf): resp = self.MyView.as_view()(req) assert resp.status_code == http.HTTPStatus.OK assert resp["Content-Type"] == "text/html; charset=utf-8" + resp.content.startswith(b'
my content
') assert resp.content == b'done' @@ -332,7 +333,6 @@ def test_get(self, rf): resp = self.MyView.as_view()(req) assert resp.status_code == http.HTTPStatus.OK assert resp["Content-Type"] == "text/html; charset=utf-8" - assert ( - resp.render().content - == b'
my content
' + assert resp.render().content.startswith( + b'
my content
' ) From af1045b9f20c9762a66455696e61d1376060da9a Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 18:27:51 +0300 Subject: [PATCH 03/12] Render inside templates --- .../tests/templates/simple.html | 2 +- src/turbo_response/tests/test_frame.py | 30 +++++++++++++++---- src/turbo_response/tests/test_renderers.py | 18 +++++++++++ src/turbo_response/tests/test_stream.py | 18 +++++++---- src/turbo_response/tests/test_template.py | 6 ++-- src/turbo_response/tests/test_views.py | 6 ++++ 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/turbo_response/tests/templates/simple.html b/src/turbo_response/tests/templates/simple.html index 20a24dd..bc1886b 100644 --- a/src/turbo_response/tests/templates/simple.html +++ b/src/turbo_response/tests/templates/simple.html @@ -1 +1 @@ -
my content
+
{{ msg }}
diff --git a/src/turbo_response/tests/test_frame.py b/src/turbo_response/tests/test_frame.py index 90d0607..8488534 100644 --- a/src/turbo_response/tests/test_frame.py +++ b/src/turbo_response/tests/test_frame.py @@ -8,7 +8,11 @@ def test_render(self): assert s == 'OK' def test_template(self): - s = TurboFrame("my-form").template("simple.html", {}).render() + s = ( + TurboFrame("my-form") + .template("simple.html", {"msg": "my content"}) + .render() + ) assert "my content" in s assert '' in s @@ -20,19 +24,31 @@ def test_response(self): def test_template_render(self, rf): req = rf.get("/") - s = TurboFrame("my-form").template("simple.html", {}, request=req).render() + s = ( + TurboFrame("my-form") + .template("simple.html", {"msg": "my content"}, request=req) + .render() + ) assert "my content" in s assert '
' ) + def test_render_content(self): + s = render_turbo_stream( + action=Action.REPLACE, + target="test", + content="my content", + ) + assert ( + s + == '' + ) + def test_render_content_xss(self): s = render_turbo_stream( action=Action.REPLACE, @@ -27,6 +38,13 @@ def test_render_empty_frame(self): s = render_turbo_frame("test") assert s == '' + def test_render_content(self): + s = render_turbo_frame( + dom_id="test", + content="my content", + ) + assert s == 'my content' + def test_render_content_xss(self): s = render_turbo_frame( dom_id="test", diff --git a/src/turbo_response/tests/test_stream.py b/src/turbo_response/tests/test_stream.py index dd78518..0defb31 100644 --- a/src/turbo_response/tests/test_stream.py +++ b/src/turbo_response/tests/test_stream.py @@ -11,14 +11,18 @@ def test_render(self): ) def test_template(self): - s = TurboStream("my-form").append.template("simple.html", {}).render() + s = ( + TurboStream("my-form") + .append.template("simple.html", {"msg": "my content"}) + .render() + ) assert "my content" in s assert '' in s def test_template_with_req_init(self, rf): s = ( TurboStream("my-form") - .append.template("simple.html", {}, request=rf.get("/")) + .append.template("simple.html", {"msg": "my content"}, request=rf.get("/")) .render() ) assert "my content" in s @@ -27,7 +31,7 @@ def test_template_with_req_init(self, rf): def test_template_with_req_arg(self, rf): s = ( TurboStream("my-form") - .append.template("simple.html", {}) + .append.template("simple.html", {"msg": "my content"}) .render(request=rf.get("/")) ) assert "my content" in s @@ -43,7 +47,7 @@ def test_template_response_req_init(self, rf): req = rf.get("/") resp = ( TurboStream("my-form") - .append.template("simple.html", {}, request=req) + .append.template("simple.html", {"msg": "my content"}, request=req) .response() ) assert resp.status_code == 200 @@ -55,7 +59,11 @@ def test_template_response_req_init(self, rf): def test_template_response(self, rf): req = rf.get("/") - resp = TurboStream("my-form").append.template("simple.html", {}).response(req) + resp = ( + TurboStream("my-form") + .append.template("simple.html", {"msg": "my content"}) + .response(req) + ) assert resp.status_code == 200 assert "is_turbo_stream" in resp.context_data assert resp._request == req diff --git a/src/turbo_response/tests/test_template.py b/src/turbo_response/tests/test_template.py index 97065fd..f6f9e34 100644 --- a/src/turbo_response/tests/test_template.py +++ b/src/turbo_response/tests/test_template.py @@ -9,7 +9,7 @@ class TestRenderTurboStreamTemplate: def test_render(self): s = render_turbo_stream_template( - "simple.html", {}, action=Action.UPDATE, target="test" + "simple.html", {"msg": "my content"}, action=Action.UPDATE, target="test" ) assert ( s @@ -19,5 +19,7 @@ def test_render(self): class TestRenderTurboTemplate: def test_render(self): - s = render_turbo_frame_template("simple.html", {}, dom_id="test") + s = render_turbo_frame_template( + "simple.html", {"msg": "my content"}, dom_id="test" + ) assert s == '
my content
' diff --git a/src/turbo_response/tests/test_views.py b/src/turbo_response/tests/test_views.py index 20ccc50..6e556ab 100644 --- a/src/turbo_response/tests/test_views.py +++ b/src/turbo_response/tests/test_views.py @@ -94,6 +94,9 @@ def test_get(self, rf): class MyView(TurboStreamTemplateView): template_name = "simple.html" + def get_context_data(self, **kwargs): + return {**kwargs, "msg": "my content"} + req = rf.get("/") resp = MyView.as_view( turbo_stream_target="test", turbo_stream_action=Action.REPLACE @@ -328,6 +331,9 @@ class MyView(TurboFrameTemplateView): turbo_frame_dom_id = "test" template_name = "simple.html" + def get_context_data(self, **kwargs): + return {**kwargs, "msg": "my content"} + def test_get(self, rf): req = rf.get("/") resp = self.MyView.as_view()(req) From 097e86f6344169882b44d6b236636eb7193e0d95 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 18:34:55 +0300 Subject: [PATCH 04/12] Additional xss tests --- src/turbo_response/tests/test_renderers.py | 11 ++++------- src/turbo_response/tests/test_template.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/turbo_response/tests/test_renderers.py b/src/turbo_response/tests/test_renderers.py index 4a8236e..cbbb3ed 100644 --- a/src/turbo_response/tests/test_renderers.py +++ b/src/turbo_response/tests/test_renderers.py @@ -25,11 +25,11 @@ def test_render_content_xss(self): s = render_turbo_stream( action=Action.REPLACE, target="test", - content="
my content
", + content="", ) assert ( s - == '' + == '' ) @@ -48,9 +48,6 @@ def test_render_content(self): def test_render_content_xss(self): s = render_turbo_frame( dom_id="test", - content="
my content
", - ) - assert ( - s - == '<div>my content</div>' + content="", ) + assert s == '<script></script>' diff --git a/src/turbo_response/tests/test_template.py b/src/turbo_response/tests/test_template.py index f6f9e34..fbfc610 100644 --- a/src/turbo_response/tests/test_template.py +++ b/src/turbo_response/tests/test_template.py @@ -16,6 +16,18 @@ def test_render(self): == '' ) + def test_render_with_xss(self): + s = render_turbo_stream_template( + "simple.html", + {"msg": ""}, + action=Action.UPDATE, + target="test", + ) + assert ( + s + == '' + ) + class TestRenderTurboTemplate: def test_render(self): @@ -23,3 +35,12 @@ def test_render(self): "simple.html", {"msg": "my content"}, dom_id="test" ) assert s == '
my content
' + + def test_render_with_xss(self): + s = render_turbo_frame_template( + "simple.html", {"msg": ""}, dom_id="test" + ) + assert ( + s + == '
<script></script>
' + ) From 44823f9087fc0022de376fac933c12f7fe6e3c4b Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 18:43:25 +0300 Subject: [PATCH 05/12] Ensure no regressions with template responses --- src/turbo_response/tests/test_frame.py | 2 +- src/turbo_response/tests/test_stream.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/turbo_response/tests/test_frame.py b/src/turbo_response/tests/test_frame.py index 8488534..3a5a919 100644 --- a/src/turbo_response/tests/test_frame.py +++ b/src/turbo_response/tests/test_frame.py @@ -67,5 +67,5 @@ def test_template_response_req_in_arg(self, rf): assert "is_turbo_frame" in resp.context_data assert resp._request == req content = resp.render().content - assert b"my content" in content + assert b"
my content
" in content assert b'my content" in content assert b' Date: Thu, 1 Apr 2021 18:52:58 +0300 Subject: [PATCH 06/12] Tidyup --- src/turbo_response/renderers.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/turbo_response/renderers.py b/src/turbo_response/renderers.py index 8488a52..af68477 100644 --- a/src/turbo_response/renderers.py +++ b/src/turbo_response/renderers.py @@ -14,13 +14,9 @@ def render_turbo_stream(action: Action, target: str, content: str = "") -> str: :return: ** string """ - turbo_stream_tmpl = Template( + return Template( '' - ) - - return turbo_stream_tmpl.render( - Context({"action": action.value, "target": target, "content": content}) - ) + ).render(Context({"action": action.value, "target": target, "content": content})) def render_turbo_frame(dom_id: str, content: str = "") -> str: @@ -33,7 +29,6 @@ def render_turbo_frame(dom_id: str, content: str = "") -> str: :return: ** string """ - turbo_frame_tmpl = Template( + return Template( '{{ content }}' - ) - return turbo_frame_tmpl.render(Context({"dom_id": dom_id, "content": content})) + ).render(Context({"dom_id": dom_id, "content": content})) From 90cd0390b54ac39f941566b0b379bf1ca0978085 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 20:27:44 +0300 Subject: [PATCH 07/12] Added is_safe arg and cache template instances --- src/turbo_response/renderers.py | 47 +++++++++++++++++----- src/turbo_response/template.py | 45 ++++++++++----------- src/turbo_response/tests/test_renderers.py | 17 +++++++- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/turbo_response/renderers.py b/src/turbo_response/renderers.py index af68477..4eea8d5 100644 --- a/src/turbo_response/renderers.py +++ b/src/turbo_response/renderers.py @@ -1,34 +1,63 @@ -# Local +# Standard Library +from functools import lru_cache + # Django from django.template import Context, Template +from django.template.engine import Engine +from django.utils.safestring import mark_safe +# Local from .constants import Action -def render_turbo_stream(action: Action, target: str, content: str = "") -> str: +@lru_cache() +def get_turbo_stream_template() -> Template: + return Engine.get_default().from_string( + '', + ) + + +@lru_cache() +def get_turbo_frame_template() -> Template: + return Engine.get_default().from_string( + '{{ content }}' + ) + + +def render_turbo_stream( + action: Action, target: str, content: str = "", is_safe: bool = False +) -> str: """Wraps content in correct tags. :param action: action type :param target: the DOM ID target of the stream :param content: content to be wrapped. Can be empty. + :param is_safe: mark content safe for HTML escaping. :return: ** string """ - return Template( - '' - ).render(Context({"action": action.value, "target": target, "content": content})) + if is_safe: + content = mark_safe(content) + + return get_turbo_stream_template().render( + Context({"action": action.value, "target": target, "content": content}) + ) -def render_turbo_frame(dom_id: str, content: str = "") -> str: +def render_turbo_frame(dom_id: str, content: str = "", is_safe: bool = False) -> str: """ Wraps a response in correct ** tags. :param dom_id: a DOM ID present in the content :param content: content of the turbo-frame + :param is_safe: mark content safe for HTML escaping. :return: ** string """ - return Template( - '{{ content }}' - ).render(Context({"dom_id": dom_id, "content": content})) + if is_safe: + content = mark_safe(content) + + return get_turbo_frame_template().render( + Context({"dom_id": dom_id, "content": content}) + ) diff --git a/src/turbo_response/template.py b/src/turbo_response/template.py index ed70cd7..5037566 100644 --- a/src/turbo_response/template.py +++ b/src/turbo_response/template.py @@ -3,7 +3,6 @@ # Django from django.template.loader import render_to_string -from django.utils.safestring import mark_safe # Local from .constants import Action @@ -28,18 +27,17 @@ def render_turbo_stream_template( return render_turbo_stream( action, target, - mark_safe( - render_to_string( - template, - { - **(context or {}), - "turbo_stream_target": target, - "turbo_stream_action": action.value, - "is_turbo_stream": True, - }, - **template_kwargs, - ).strip() - ), + render_to_string( + template, + { + **(context or {}), + "turbo_stream_target": target, + "turbo_stream_action": action.value, + "is_turbo_stream": True, + }, + **template_kwargs, + ).strip(), + is_safe=True, ) @@ -59,15 +57,14 @@ def render_turbo_frame_template( return render_turbo_frame( dom_id, - mark_safe( - render_to_string( - template, - { - **(context or {}), - "turbo_frame_dom_id": dom_id, - "is_turbo_frame": True, - }, - **kwargs, - ).strip() - ), + render_to_string( + template, + { + **(context or {}), + "turbo_frame_dom_id": dom_id, + "is_turbo_frame": True, + }, + **kwargs, + ).strip(), + is_safe=True, ) diff --git a/src/turbo_response/tests/test_renderers.py b/src/turbo_response/tests/test_renderers.py index cbbb3ed..e86e83d 100644 --- a/src/turbo_response/tests/test_renderers.py +++ b/src/turbo_response/tests/test_renderers.py @@ -32,6 +32,18 @@ def test_render_content_xss(self): == '' ) + def test_render_content_is_safe(self): + s = render_turbo_stream( + action=Action.REPLACE, + target="test", + content="", + is_safe=True, + ) + assert ( + s + == '' + ) + class TestRenderTurboFrame: def test_render_empty_frame(self): @@ -45,9 +57,10 @@ def test_render_content(self): ) assert s == 'my content' - def test_render_content_xss(self): + def test_render_content_is_safe(self): s = render_turbo_frame( dom_id="test", content="", + is_safe=True, ) - assert s == '<script></script>' + assert s == '' From 891f3b37bc100347d4d82664284a73d772c9fbf7 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 20:30:53 +0300 Subject: [PATCH 08/12] Tidyup: move tmpl functions to end --- src/turbo_response/renderers.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/turbo_response/renderers.py b/src/turbo_response/renderers.py index 4eea8d5..69eb02f 100644 --- a/src/turbo_response/renderers.py +++ b/src/turbo_response/renderers.py @@ -10,20 +10,6 @@ from .constants import Action -@lru_cache() -def get_turbo_stream_template() -> Template: - return Engine.get_default().from_string( - '', - ) - - -@lru_cache() -def get_turbo_frame_template() -> Template: - return Engine.get_default().from_string( - '{{ content }}' - ) - - def render_turbo_stream( action: Action, target: str, content: str = "", is_safe: bool = False ) -> str: @@ -61,3 +47,17 @@ def render_turbo_frame(dom_id: str, content: str = "", is_safe: bool = False) -> return get_turbo_frame_template().render( Context({"dom_id": dom_id, "content": content}) ) + + +@lru_cache() +def get_turbo_stream_template() -> Template: + return Engine.get_default().from_string( + '', + ) + + +@lru_cache() +def get_turbo_frame_template() -> Template: + return Engine.get_default().from_string( + '{{ content }}' + ) From 4b555db5bbd0089e9150415ecdf5f9288bff2b18 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 20:50:58 +0300 Subject: [PATCH 09/12] Add is_safe arg to response classes --- src/turbo_response/response.py | 27 +++++++++++++++++++-------- src/turbo_response/stream.py | 16 ++++++++++++---- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/turbo_response/response.py b/src/turbo_response/response.py index 1c8a38f..d3d4122 100644 --- a/src/turbo_response/response.py +++ b/src/turbo_response/response.py @@ -68,11 +68,12 @@ def __init__( *, action: Optional[Action] = None, target: Optional[str] = None, - **kwargs, + is_safe: bool = False, + **response_kwargs, ): if action and target and isinstance(content, str): - content = render_turbo_stream(action, target, content) - super().__init__(content, **kwargs) + content = render_turbo_stream(action, target, content, is_safe=is_safe) + super().__init__(content, **response_kwargs) class TurboStreamTemplateResponse(TurboStreamResponseMixin, TemplateResponse): @@ -117,17 +118,27 @@ def __init__( @property def rendered_content(self) -> str: return render_turbo_stream( - action=self._action, target=self._target, content=super().rendered_content + action=self._action, + target=self._target, + content=super().rendered_content, + is_safe=True, ) class TurboFrameResponse(HttpResponse): """Handles turbo-frame template response.""" - def __init__(self, content: str = "", *, dom_id: str, **kwargs): + def __init__( + self, + content: str = "", + *, + dom_id: str, + is_safe: bool = False, + **response_kwargs, + ): super().__init__( - render_turbo_frame(dom_id, content), - **kwargs, + render_turbo_frame(dom_id, content, is_safe=is_safe), + **response_kwargs, ) @@ -164,4 +175,4 @@ def __init__( @property def rendered_content(self) -> str: - return render_turbo_frame(self._dom_id, super().rendered_content) + return render_turbo_frame(self._dom_id, super().rendered_content, is_safe=True) diff --git a/src/turbo_response/stream.py b/src/turbo_response/stream.py index 971cbad..de0a2ca 100644 --- a/src/turbo_response/stream.py +++ b/src/turbo_response/stream.py @@ -63,22 +63,30 @@ def __init__(self, target: str, action: Action): self.action = action self.target = target - def render(self, content: str = "") -> str: + def render(self, content: str = "", is_safe: bool = False) -> str: """ :param content: enclosed content + :param is_safe: mark content safe for HTML escaping. + :return: a ** string """ return render_turbo_stream( - action=self.action, target=self.target, content=content + action=self.action, target=self.target, content=content, is_safe=is_safe ) - def response(self, content: str = "", **response_kwargs) -> TurboStreamResponse: + def response( + self, content: str = "", is_safe: bool = False, **response_kwargs + ) -> TurboStreamResponse: """ :param content: enclosed content :return: a ** HTTP response wrapper """ return TurboStreamResponse( - action=self.action, target=self.target, content=content, **response_kwargs + action=self.action, + target=self.target, + content=content, + is_safe=is_safe, + **response_kwargs, ) def template( From 1e8315631682ed3d6a305f6c9b781767ed1484d7 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Thu, 1 Apr 2021 20:58:43 +0300 Subject: [PATCH 10/12] More tests for stream methods --- src/turbo_response/tests/test_stream.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/turbo_response/tests/test_stream.py b/src/turbo_response/tests/test_stream.py index 45e1ccb..3401b7c 100644 --- a/src/turbo_response/tests/test_stream.py +++ b/src/turbo_response/tests/test_stream.py @@ -10,6 +10,20 @@ def test_render(self): == '' ) + def test_render_xss(self): + s = TurboStream("my-form").append.render("") + assert ( + s + == '' + ) + + def test_render_is_safe(self): + s = TurboStream("my-form").append.render("", is_safe=True) + assert ( + s + == '' + ) + def test_template(self): s = ( TurboStream("my-form") @@ -19,6 +33,15 @@ def test_template(self): assert "my content" in s assert '' in s + def test_template_xss(self): + s = ( + TurboStream("my-form") + .append.template("simple.html", {"msg": ""}) + .render() + ) + assert "<script></script>" in s + assert '' in s + def test_template_with_req_init(self, rf): s = ( TurboStream("my-form") @@ -43,6 +66,18 @@ def test_response(self): assert b"OK" in resp.content assert b'") + assert resp.status_code == 200 + assert b"<script></script>" in resp.content + assert b'", is_safe=True) + assert resp.status_code == 200 + assert b"" in resp.content + assert b' Date: Thu, 1 Apr 2021 21:06:59 +0300 Subject: [PATCH 11/12] Add is_safe arg to frame classes --- src/turbo_response/frame.py | 14 +++++++---- src/turbo_response/tests/test_frame.py | 32 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/turbo_response/frame.py b/src/turbo_response/frame.py index c92883a..c7ad5ec 100644 --- a/src/turbo_response/frame.py +++ b/src/turbo_response/frame.py @@ -59,20 +59,26 @@ def __init__(self, dom_id: str): """ self.dom_id = dom_id - def render(self, content: str = "") -> str: + def render(self, content: str = "", is_safe: bool = False) -> str: """ :param content: enclosed content + :param is_safe: mark content safe for HTML escaping. + :return: a ** string """ - return render_turbo_frame(dom_id=self.dom_id, content=content) + return render_turbo_frame(dom_id=self.dom_id, content=content, is_safe=is_safe) - def response(self, content: str = "", **response_kwargs) -> TurboFrameResponse: + def response( + self, content: str = "", is_safe: bool = False, **response_kwargs + ) -> TurboFrameResponse: """ :param content: enclosed content + :param is_safe: mark content safe for HTML escaping. + :return: a ** HTTP response """ return TurboFrameResponse( - dom_id=self.dom_id, content=content, **response_kwargs + dom_id=self.dom_id, content=content, is_safe=is_safe, **response_kwargs ) def template( diff --git a/src/turbo_response/tests/test_frame.py b/src/turbo_response/tests/test_frame.py index 3a5a919..b3678a5 100644 --- a/src/turbo_response/tests/test_frame.py +++ b/src/turbo_response/tests/test_frame.py @@ -7,6 +7,16 @@ def test_render(self): s = TurboFrame("my-form").render("OK") assert s == 'OK' + def test_render_xss(self): + s = TurboFrame("my-form").render("") + assert ( + s == '<script></script>' + ) + + def test_render_is_safe(self): + s = TurboFrame("my-form").render("", is_safe=True) + assert s == '' + def test_template(self): s = ( TurboFrame("my-form") @@ -22,6 +32,18 @@ def test_response(self): assert b"OK" in resp.content assert b'") + assert resp.status_code == 200 + assert b"<script></script>" in resp.content + assert b'", is_safe=True) + assert resp.status_code == 200 + assert b"" in resp.content + assert b'"}, request=req) + .render() + ) + assert "<script></script>" in s + assert ' Date: Thu, 1 Apr 2021 22:43:02 +0300 Subject: [PATCH 12/12] Add tests and is_safe option for views/mixins --- src/turbo_response/mixins.py | 12 +++-- src/turbo_response/tests/test_views.py | 75 +++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/turbo_response/mixins.py b/src/turbo_response/mixins.py index 757b349..9d9e760 100644 --- a/src/turbo_response/mixins.py +++ b/src/turbo_response/mixins.py @@ -79,10 +79,12 @@ def get_response_content(self) -> str: return "" - def render_turbo_stream(self, **response_kwargs) -> HttpResponse: + def render_turbo_stream( + self, is_safe: bool = False, **response_kwargs + ) -> HttpResponse: """Returns a turbo-stream response.""" return self.get_turbo_stream().response( - self.get_response_content(), **response_kwargs + self.get_response_content(), is_safe=is_safe, **response_kwargs ) @@ -254,11 +256,13 @@ class TurboFrameResponseMixin(TurboFrameMixin): def get_response_content(self) -> str: return "" - def render_turbo_frame(self, **response_kwargs) -> HttpResponse: + def render_turbo_frame( + self, is_safe: bool = False, **response_kwargs + ) -> HttpResponse: """Renders a turbo frame to response.""" return self.get_turbo_frame().response( - self.get_response_content(), **response_kwargs + self.get_response_content(), is_safe=is_safe, **response_kwargs ) diff --git a/src/turbo_response/tests/test_views.py b/src/turbo_response/tests/test_views.py index 6e556ab..8e5c33c 100644 --- a/src/turbo_response/tests/test_views.py +++ b/src/turbo_response/tests/test_views.py @@ -88,6 +88,39 @@ def get_response_content(self): b'