fix: harden document normalization
Reject overlapping source and destination roots before any write so normalization cannot replace the raw Ministry archive. Recover an interrupted directory publication before resume checks and require the normalized target to exist before skipping a manifest success. Treat malformed link and image URLs as unsafe attributes, add regressions for all review findings, and bump the backend version to 0.2.2.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -55,7 +56,7 @@ class MinjustNormalizationTest(unittest.TestCase):
|
||||
document,
|
||||
10,
|
||||
{
|
||||
"ru": '<meta charset=unicode><style>x</style><p style="color:red">Статья 1 Закон<script>bad()</script></p><a href="javascript:bad">ссылка</a>',
|
||||
"ru": '<meta charset=unicode><style>x</style><p style="color:red">Статья 1 Закон<script>bad()</script></p><a href="javascript:bad">ссылка</a><a href="http://[">сломанная ссылка</a><img src="http://[">',
|
||||
},
|
||||
)
|
||||
self.edition(document, 20, {"ky": "<p>1. Кыргызча жобо</p>"})
|
||||
@@ -73,6 +74,7 @@ class MinjustNormalizationTest(unittest.TestCase):
|
||||
self.assertNotIn("script", safe_html)
|
||||
self.assertNotIn("style=", safe_html)
|
||||
self.assertNotIn("javascript:", safe_html)
|
||||
self.assertNotIn("http://[", safe_html)
|
||||
self.assertIn("Статья 1 Закон", safe_html)
|
||||
parsed = json.loads(fragments)
|
||||
self.assertEqual(parsed[0]["type"], "article")
|
||||
@@ -116,6 +118,35 @@ class MinjustNormalizationTest(unittest.TestCase):
|
||||
("success", None, None),
|
||||
)
|
||||
|
||||
def test_rejects_overlapping_input_and_output(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
source = Path(temporary) / "source"
|
||||
metadata = self.document(source) / "metadata.json"
|
||||
original = metadata.read_bytes()
|
||||
|
||||
for output in (source, source / "normalized", source.parent):
|
||||
with self.subTest(output=output):
|
||||
with self.assertRaisesRegex(ValueError, "must not overlap"):
|
||||
normalize_archive(source, output)
|
||||
self.assertEqual(metadata.read_bytes(), original)
|
||||
|
||||
def test_recovers_interrupted_directory_publication_before_skip(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
base = Path(temporary)
|
||||
source = base / "source"
|
||||
output = base / "normalized"
|
||||
self.document(source)
|
||||
first = normalize_archive(source, output)
|
||||
target = output / "documents/1"
|
||||
backup = output / "documents/.1.previous"
|
||||
os.replace(target, backup)
|
||||
|
||||
second = normalize_archive(source, output)
|
||||
|
||||
self.assertEqual((first.normalized, second.skipped), (1, 1))
|
||||
self.assertTrue((target / "document.json").is_file())
|
||||
self.assertFalse(backup.exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user