diff --git a/course/page/code.py b/course/page/code.py index 2978954c56cbb54aecd6e7a07f9411efa10a8fb7..ecb14118c42e2584663f033d2e33edd9c0c71842 100644 --- a/course/page/code.py +++ b/course/page/code.py @@ -894,12 +894,9 @@ class PythonCodeQuestion(PageBaseWithTitle, PageBaseWithValue): if name in ["type"]: return True elif name == "src": - if is_allowed_data_uri([ - "audio/wav", - ], value): - return bleach.sanitizer.VALUE_SAFE - else: - return False + return is_allowed_data_uri([ + "audio/wav", + ], value) else: return False @@ -907,11 +904,10 @@ class PythonCodeQuestion(PageBaseWithTitle, PageBaseWithValue): if name in ["alt", "title"]: return True elif name == "src": - if is_allowed_data_uri([ - "image/png", - "image/jpeg", - ], value): - return bleach.sanitizer.VALUE_SAFE + return is_allowed_data_uri([ + "image/png", + "image/jpeg", + ], value) else: return False @@ -925,7 +921,11 @@ class PythonCodeQuestion(PageBaseWithTitle, PageBaseWithValue): "audio": filter_audio_attributes, "source": filter_source_attributes, "img": filter_img_attributes, - }) + }, + + # Fixed https://github.com/inducer/relate/issues/435 + # Ref: https://github.com/mozilla/bleach/issues/348 + protocols=["data"]) bulk_feedback_bits.extend( sanitize(snippet) for snippet in response.html) diff --git a/requirements.txt b/requirements.txt index 3dc2f8e9ad884e797326ec402c9b7d914898130c..063206e95bdf693d877c538977adfd9d1ef22d24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -96,9 +96,7 @@ git+https://github.com/celery/django-celery.git@6232c79557517afa74967fcb980c0db2 django_select2>=5.5.0 # To sanitize HTML generated by user code -# for https://github.com/mozilla/bleach/pull/346 -git+https://github.com/inducer/bleach@prevent-attribute-value-filtering -#bleach>=2.0,<3 +bleach>=2.0,<3 # Custom user migration created using # https://bitbucket.org/spookylukey/django_custom_user_migration diff --git a/tests/test_pages/test_code.py b/tests/test_pages/test_code.py index 7fc9b02ce4590d98c5bedcfbdc6194dda4620c30..b108e98eab666a87fb39259f8aca669d45b2b531 100644 --- a/tests/test_pages/test_code.py +++ b/tests/test_pages/test_code.py @@ -783,36 +783,38 @@ class CodeQuestionTest(SingleCoursePageSandboxTestBaseMixin, def test_html_audio(self): b64_data = "T2dnUwACAAAAAAAAAAA+HAAAAAAAAGyawCEBQGZpc2h" - audio1 = ( + audio_valid = ( '<audio controls><source src="data:audio/wav;base64,' '%s" type="audio/wav">' '</audio>' % b64_data) - audio1_1 = ( + audio_invalid1 = ( '<audio control><source src="data:audio/wav;base64,' '%s" type="audio/wav">' '</audio>' % b64_data) - audio1_2 = ( + audio_invalid2 = ( '<audio controls><source href="data:audio/wav;base64,' '%s" type="audio/wav">' '</audio>' % b64_data) - audio2 = ( + audio_invalid3 = ( '<audio><source src="data:audio/wav;base64,' '%s" type="audio/wav">' '</audio>' % b64_data) - audio3 = ( + audio_invalid4 = ( '<audio controls><source src="data:audio/ogg;base64,' '%s" type="audio/ogg">' '</audio>' % b64_data) - audio4 = ( + audio_invalid5 = ( '<audio controls><source src="hosse.wav" type="audio/wav">' '</audio>') - html = [audio1, audio1_1, audio1_2, audio2, audio3, audio4] + html = [audio_valid, audio_invalid1, audio_invalid2, audio_invalid3, + audio_invalid4, audio_invalid5] self.assert_runpy_result_and_response( "user_error", - expected_msgs=[audio1], - not_execpted_msgs=[audio1_1, audio1_2, audio2, audio3, audio4], + expected_msgs=[audio_valid], + not_execpted_msgs=[audio_invalid1, audio_invalid2, audio_invalid3, + audio_invalid4, audio_invalid5], html=html, in_html=True ) @@ -823,34 +825,63 @@ class CodeQuestionTest(SingleCoursePageSandboxTestBaseMixin, "6XZn90AAAAJcEhZcwAADsIAAA7CARUoSoAAAAAMSURBVBjTYzjAcAAAAwQBgXn" "6PNcAAAAASUVORK5CYII=") - img1 = ( + img_valid = ( '<img src="data:image/png;base64,%s" alt="test img" ' 'title="test image">' % b64_data) - img1_1 = ( + img_invalid1 = ( '<img src="data:image/png;base64,%s" ' 'alt="test img" ' 'width="126" ' 'height="44">' % b64_data) - img1_2 = ( + img_invalid2 = ( '<img href="data:image/png;base64,%s" ' 'alt="test img" title="test image">' % b64_data) - img2 = ( + img_invalid3 = ( '<img src="data:image/bmp;base64,%s" ' 'alt="test img" title="test image">' % b64_data) - html = [img1, img1_1, img1_2, img2] + html = [img_valid, img_invalid1, img_invalid2, img_invalid3] self.assert_runpy_result_and_response( "user_error", - expected_msgs=[img1], - # not_execpted_msgs=[img1_1, img1_2, img2], + expected_msgs=[img_valid], + not_execpted_msgs=[img_invalid1, img_invalid2, img_invalid3], html=html, in_html=True, ) + def test_html_with_data_protocol_for_other_tags_sanitized(self): + # Fixed https://github.com/inducer/relate/issues/435 + # Ref: https://github.com/mozilla/bleach/issues/348 + b64_data = ("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAQMAAAB=") + + html_strings = [ + '<a src="data:,Hello%2C%20Evil%20World!"></a>', + '<a href="data:,Hello%2C%20Evil%20World!"></a>', + '<a src="data:text/html;base64,%s"</a>' % b64_data, + '<a src="data:text/html;base64,%s"</a>' % b64_data, + + '<script src="data:text/html,<script>alert("Evil");</script>"', + '<script href="data:text/html,<script>alert("Evil");</script>"', + '<script src="data:text/html;base64,%s"</script>' % b64_data, + '<script href="data:text/html;base64,%s"</script>' % b64_data, + + '<style src="data:,Evilcss">', + '<style href="data:,Evilcss">', + '<style src="data:image/png;base64,%s">' % b64_data, + '<style href="data:image/png;base64,%s">' % b64_data, + ] + + self.assert_runpy_result_and_response( + "user_error", + not_execpted_msgs=html_strings + ["Evil", b64_data], + html=html_strings, + in_html=True, + ) + def test_html_non_text_bleached_in_feedback(self): self.assert_runpy_result_and_response( "user_error",