"""Markup written by a model and rendered in somebody's browser.
The uploads route already serves SVG under a sandbox CSP. This is the belt to
that brace: what a model hands back is checked before it is stored, because
"the model would not do that" is not a security control.
Run: DATABASE_URL=sqlite:///:memory: PYTHONPATH=backend python -m unittest discover -s backend/tests
"""
import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
import unittest
from app.services import illustrate
def svg(inner: str = '', attrs: str = '') -> str:
return (f'')
class WhatIsAccepted(unittest.TestCase):
def test_a_plain_diagram_passes(self):
self.assertTrue(illustrate.check(svg()))
def test_shapes_text_and_markers_pass(self):
self.assertTrue(illustrate.check(svg(
''
'Day 3')))
def test_a_path_may_contain_the_letters_of_a_command(self):
# `d` is full of letters; the remote-reference check must not read a
# curve as a URL.
self.assertTrue(illustrate.check(svg('')))
class WhatIsRefused(unittest.TestCase):
def test_script(self):
self.assertIsNone(illustrate.check(svg('')))
def test_an_event_handler(self):
self.assertIsNone(illustrate.check(svg(attrs='onload="steal()"')))
self.assertIsNone(illustrate.check(svg('')))
def test_anything_that_reaches_outside_the_document(self):
self.assertIsNone(illustrate.check(svg('')))
self.assertIsNone(illustrate.check(svg('')))
self.assertIsNone(illustrate.check(
svg('')))
def test_foreign_objects_and_frames(self):
self.assertIsNone(illustrate.check(svg('hi')))
self.assertIsNone(illustrate.check(svg('')))
def test_not_an_svg_at_all(self):
self.assertIsNone(illustrate.check('
no'))
self.assertIsNone(illustrate.check('I decided a diagram would not help.'))
self.assertIsNone(illustrate.check(''))
def test_broken_markup(self):
self.assertIsNone(illustrate.check(': refused rather than guessed
def test_prose_instead_of_an_answer_is_unusable(self):
self.assertIsNone(illustrate.read_reply("I think a diagram would not help."))
self.assertIsNone(illustrate.read_reply(""))
class ReferencesInsideTheFile(unittest.TestCase):
"""`url(#id)` is not a remote reference.
Every marker, gradient and clip path in SVG points at a entry a few
lines above with url(#name). The first version of the guard refused any
url( at all and threw away good drawings for pointing at their own
arrowheads — three of them on the first real article.
"""
def test_a_marker_pointing_at_its_own_defs_is_fine(self):
self.assertTrue(illustrate.check(svg(
''
'')))
def test_a_gradient_and_a_clip_path_are_fine(self):
self.assertTrue(illustrate.check(svg(
''
'')))
def test_a_url_that_leaves_the_document_is_still_refused(self):
for reach in ('url(http://evil/x)', 'url(//evil/x)', 'url( https://evil/x )'):
self.assertIsNone(illustrate.check(svg(f'')), reach)
class TheNamespaceIsAddedRatherThanDemanded(unittest.TestCase):
"""An SVG in an is a standalone document and needs xmlns.
Models supply it about half the time. This was the whole of "some figures
render and some show their alt text" — not the guard, not the thumbnailer,
not the file size: three of the first nine generated figures simply had no
namespace, so the browser refused to draw them.
"""
def test_a_missing_namespace_is_written_in(self):
drawn = ''
fixed = illustrate.check(drawn)
self.assertIsNotNone(fixed)
self.assertIn('xmlns="http://www.w3.org/2000/svg"', fixed)
# And nothing else about the drawing changes.
self.assertIn('', fixed)
def test_one_that_has_it_is_left_alone(self):
drawn = svg()
self.assertEqual(illustrate.check(drawn), drawn)
self.assertEqual(illustrate.check(drawn).count("xmlns="), 1)