diff --git a/cms/grading/steps/whitediff.py b/cms/grading/steps/whitediff.py index f5b7d39e13..f887543578 100644 --- a/cms/grading/steps/whitediff.py +++ b/cms/grading/steps/whitediff.py @@ -120,8 +120,12 @@ def _white_diff(output: typing.BinaryIO, res: typing.BinaryIO) -> tuple[bool, st lout = lout[:LENGTH_LIMIT] + b"..." if len(lres) > LENGTH_LIMIT: lres = lres[:LENGTH_LIMIT] + b"..." - lout = lout.decode("utf-8", errors='backslashreplace') - lres = lres.decode("utf-8", errors='backslashreplace') + # NUL is valid UTF-8, so backslashreplace lets it through, + # but PostgreSQL cannot store it in a text column. + lout = lout.decode("utf-8", errors='backslashreplace') \ + .replace("\x00", "\\x00") + lres = lres.decode("utf-8", errors='backslashreplace') \ + .replace("\x00", "\\x00") return False, f"Expected `{lres}`, found `{lout}` on line {line}" diff --git a/cmstestsuite/unit_tests/grading/steps/whitediff_test.py b/cmstestsuite/unit_tests/grading/steps/whitediff_test.py index 22c54e6e97..66c566e425 100755 --- a/cmstestsuite/unit_tests/grading/steps/whitediff_test.py +++ b/cmstestsuite/unit_tests/grading/steps/whitediff_test.py @@ -72,6 +72,13 @@ def test_diff_wrong_long_line(self): line2 = line1 + "0" self.assertFalse(self._diff(line1, line2)) + def test_diff_nul_byte_escaped_in_admin_text(self): + correct, admin_text = _white_diff( + BytesIO(b"hello\x00world"), BytesIO(b"helloworld")) + self.assertFalse(correct) + self.assertNotIn("\x00", admin_text) + self.assertIn("hello\\x00world", admin_text) + if __name__ == "__main__": unittest.main()