Skip to content

Commit a7d6ee4

Browse files
committed
Mark diff colors safe and escape raw diff input
For HTML escaping of the diff view we have to consider two things. 1. Diff input comes from two git checkouts of the project at specific revisions. The revisions sdocs are considered untrusted user input, could contain special characters and must be escaped. 2. After analyzing with difflib we add a bit HTML to colorize the output. This specific HTML fragments are trusted and safe. Relates to #1920.
1 parent 9531d7e commit a7d6ee4

File tree

9 files changed

+141
-53
lines changed

9 files changed

+141
-53
lines changed

strictdoc/export/html/generators/view_objects/diff_screen_results_view_object.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from datetime import datetime
44
from typing import Optional
55

6+
from markupsafe import Markup
7+
68
from strictdoc import __version__
79
from strictdoc.core.project_config import ProjectConfig
810
from strictdoc.export.html.html_templates import JinjaEnvironment
@@ -58,31 +60,30 @@ def __init__(
5860
self.strictdoc_version = __version__
5961
self.error_message: Optional[str] = None
6062

61-
def render_screen(self, jinja_environment: JinjaEnvironment):
62-
template = jinja_environment.environment.overlay(
63-
autoescape=False
64-
).get_template("screens/git/index.jinja")
65-
return template.render(view_object=self)
63+
def render_screen(self, jinja_environment: JinjaEnvironment) -> Markup:
64+
return jinja_environment.render_template_as_markup(
65+
"screens/git/index.jinja", view_object=self
66+
)
6667

67-
def render_url(self, url: str):
68-
return self.link_renderer.render_url(url)
68+
def render_url(self, url: str) -> Markup:
69+
return Markup(self.link_renderer.render_url(url))
6970

70-
def render_node_link(self, incoming_link, document, document_type):
71+
def render_node_link(self, incoming_link, document, document_type) -> str:
7172
return self.link_renderer.render_node_link(
7273
incoming_link, document, document_type
7374
)
7475

75-
def render_static_url(self, url: str):
76-
return self.link_renderer.render_static_url(url)
76+
def render_static_url(self, url: str) -> Markup:
77+
return Markup(self.link_renderer.render_static_url(url))
7778

7879
def render_static_url_with_prefix(self, url: str) -> str:
7980
return self.link_renderer.render_static_url_with_prefix(url)
8081

81-
def render_local_anchor(self, node):
82+
def render_local_anchor(self, node) -> str:
8283
return self.link_renderer.render_local_anchor(node)
8384

8485
def is_empty_tree(self) -> bool:
8586
return self.document_tree_iterator.is_empty_tree()
8687

87-
def date_today(self):
88+
def date_today(self) -> str:
8889
return datetime.today().strftime("%Y-%m-%d")

strictdoc/export/html/generators/view_objects/diff_screen_view_object.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from dataclasses import dataclass
33
from datetime import datetime
44

5+
from markupsafe import Markup
6+
57
from strictdoc import __version__
68
from strictdoc.core.project_config import ProjectConfig
79
from strictdoc.export.html.html_templates import JinjaEnvironment
@@ -39,27 +41,27 @@ def __init__(
3941
self.is_running_on_server: bool = project_config.is_running_on_server
4042
self.strictdoc_version = __version__
4143

42-
def render_screen(self, jinja_environment: JinjaEnvironment):
44+
def render_screen(self, jinja_environment: JinjaEnvironment) -> Markup:
4345
return jinja_environment.render_template_as_markup(
4446
"screens/git/index.jinja", view_object=self
4547
)
4648

47-
def render_url(self, url: str):
48-
return self.link_renderer.render_url(url)
49+
def render_url(self, url: str) -> Markup:
50+
return Markup(self.link_renderer.render_url(url))
4951

50-
def render_node_link(self, incoming_link, document, document_type):
52+
def render_node_link(self, incoming_link, document, document_type) -> str:
5153
return self.link_renderer.render_node_link(
5254
incoming_link, document, document_type
5355
)
5456

55-
def render_static_url(self, url: str):
56-
return self.link_renderer.render_static_url(url)
57+
def render_static_url(self, url: str) -> Markup:
58+
return Markup(self.link_renderer.render_static_url(url))
5759

5860
def render_static_url_with_prefix(self, url: str) -> str:
5961
return self.link_renderer.render_static_url_with_prefix(url)
6062

61-
def render_local_anchor(self, node):
63+
def render_local_anchor(self, node) -> str:
6264
return self.link_renderer.render_local_anchor(node)
6365

64-
def date_today(self):
66+
def date_today(self) -> str:
6567
return datetime.today().strftime("%Y-%m-%d")

strictdoc/git/change.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from enum import Enum
22
from typing import Dict, List, Optional, Union
33

4+
from markupsafe import Markup
5+
46
from strictdoc.backend.sdoc.models.document import SDocDocument
57
from strictdoc.backend.sdoc.models.node import (
68
SDocNode,
@@ -36,24 +38,24 @@ def __init__(
3638
rhs_document: Optional[SDocDocument],
3739
uid_modified: bool,
3840
title_modified: bool,
39-
lhs_colored_title_diff: Optional[str],
40-
rhs_colored_title_diff: Optional[str],
41+
lhs_colored_title_diff: Optional[Markup],
42+
rhs_colored_title_diff: Optional[Markup],
4143
):
4244
assert lhs_document is not None or rhs_document is not None
4345
if matched_uid is not None:
4446
assert len(matched_uid) > 0
4547
self.matched_uid: Optional[str] = matched_uid
4648
self.uid_modified: bool = uid_modified
4749
self.title_modified: bool = title_modified
48-
self.lhs_colored_title_diff: Optional[str] = lhs_colored_title_diff
49-
self.rhs_colored_title_diff: Optional[str] = rhs_colored_title_diff
50+
self.lhs_colored_title_diff: Optional[Markup] = lhs_colored_title_diff
51+
self.rhs_colored_title_diff: Optional[Markup] = rhs_colored_title_diff
5052

5153
self.lhs_document: Optional[SDocDocument] = lhs_document
5254
self.rhs_document: Optional[SDocDocument] = rhs_document
5355

5456
self.change_type: ChangeType = ChangeType.DOCUMENT_MODIFIED
5557

56-
def get_colored_title_diff(self, side: str) -> Optional[str]:
58+
def get_colored_title_diff(self, side: str) -> Optional[Markup]:
5759
assert self.title_modified
5860
if side == "left":
5961
return self.lhs_colored_title_diff
@@ -74,8 +76,8 @@ def __init__(
7476
rhs_section: Optional[SDocSection],
7577
uid_modified: bool,
7678
title_modified: bool,
77-
lhs_colored_title_diff: Optional[str],
78-
rhs_colored_title_diff: Optional[str],
79+
lhs_colored_title_diff: Optional[Markup],
80+
rhs_colored_title_diff: Optional[Markup],
7981
):
8082
assert lhs_section is not None or rhs_section is not None
8183
if matched_uid is not None:
@@ -85,8 +87,8 @@ def __init__(
8587
self.section_token: Optional[str] = section_token
8688
self.uid_modified: bool = uid_modified
8789
self.title_modified: bool = title_modified
88-
self.lhs_colored_title_diff: Optional[str] = lhs_colored_title_diff
89-
self.rhs_colored_title_diff: Optional[str] = rhs_colored_title_diff
90+
self.lhs_colored_title_diff: Optional[Markup] = lhs_colored_title_diff
91+
self.rhs_colored_title_diff: Optional[Markup] = rhs_colored_title_diff
9092

9193
self.lhs_section: Optional[SDocSection] = lhs_section
9294
self.rhs_section: Optional[SDocSection] = rhs_section
@@ -108,7 +110,7 @@ def __init__(
108110
def is_paired_change(self) -> bool:
109111
return self.lhs_section is not None and self.rhs_section is not None
110112

111-
def get_colored_title_diff(self, side: str) -> Optional[str]:
113+
def get_colored_title_diff(self, side: str) -> Optional[Markup]:
112114
assert self.title_modified
113115
if side == "left":
114116
return self.lhs_colored_title_diff
@@ -125,8 +127,8 @@ def __init__(
125127
field_name: str,
126128
lhs_field: Optional[SDocNodeField],
127129
rhs_field: Optional[SDocNodeField],
128-
left_diff: Optional[str],
129-
right_diff: Optional[str],
130+
left_diff: Optional[Markup],
131+
right_diff: Optional[Markup],
130132
):
131133
assert isinstance(field_name, str) and len(field_name) > 0
132134
assert lhs_field is not None or rhs_field is not None
@@ -137,10 +139,10 @@ def __init__(
137139
self.field_name: str = field_name
138140
self.lhs_field: Optional[SDocNodeField] = lhs_field
139141
self.rhs_field: Optional[SDocNodeField] = rhs_field
140-
self.left_diff: Optional[str] = left_diff
141-
self.right_diff: Optional[str] = right_diff
142+
self.left_diff: Optional[Markup] = left_diff
143+
self.right_diff: Optional[Markup] = right_diff
142144

143-
def get_colored_free_text_diff(self, side: str) -> Optional[str]:
145+
def get_colored_free_text_diff(self, side: str) -> Optional[Markup]:
144146
if side == "left":
145147
return self.left_diff
146148
if side == "right":

strictdoc/git/project_diff_analyzer.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from dataclasses import dataclass, field
55
from typing import Any, Dict, List, Optional, Set, Tuple, Union
66

7+
from markupsafe import Markup
8+
79
from strictdoc.backend.sdoc.models.document import SDocDocument
810
from strictdoc.backend.sdoc.models.node import (
911
SDocNode,
@@ -24,7 +26,7 @@
2426
SectionChange,
2527
)
2628
from strictdoc.helpers.cast import assert_cast, assert_optional_cast
27-
from strictdoc.helpers.diff import get_colored_diff_string, similar
29+
from strictdoc.helpers.diff import get_colored_html_diff_string, similar
2830
from strictdoc.helpers.mid import MID
2931

3032

@@ -347,8 +349,8 @@ def _iterate_one_index(
347349

348350
uid_modified: bool = False
349351
title_modified: bool = False
350-
lhs_colored_title_diff: Optional[str] = None
351-
rhs_colored_title_diff: Optional[str] = None
352+
lhs_colored_title_diff: Optional[Markup] = None
353+
rhs_colored_title_diff: Optional[Markup] = None
352354

353355
# If there is another section and the UIDs are not the
354356
# same, consider the UID modified.
@@ -366,12 +368,12 @@ def _iterate_one_index(
366368
if other_document_or_none is not None:
367369
if document.title != other_document_or_none.title:
368370
title_modified = True
369-
lhs_colored_title_diff = get_colored_diff_string(
371+
lhs_colored_title_diff = get_colored_html_diff_string(
370372
document.title,
371373
other_document_or_none.title,
372374
"left",
373375
)
374-
rhs_colored_title_diff = get_colored_diff_string(
376+
rhs_colored_title_diff = get_colored_html_diff_string(
375377
document.title,
376378
other_document_or_none.title,
377379
"right",
@@ -461,8 +463,8 @@ def _iterate_one_index(
461463

462464
uid_modified: bool = False
463465
title_modified: bool = False
464-
lhs_colored_title_diff: Optional[str] = None
465-
rhs_colored_title_diff: Optional[str] = None
466+
lhs_colored_title_diff: Optional[Markup] = None
467+
rhs_colored_title_diff: Optional[Markup] = None
466468

467469
# If there is another section and the UIDs are not the
468470
# same, consider the UID modified.
@@ -481,14 +483,14 @@ def _iterate_one_index(
481483
if node.title != other_section_or_none.title:
482484
title_modified = True
483485
lhs_colored_title_diff = (
484-
get_colored_diff_string(
486+
get_colored_html_diff_string(
485487
node.title,
486488
other_section_or_none.title,
487489
"left",
488490
)
489491
)
490492
rhs_colored_title_diff = (
491-
get_colored_diff_string(
493+
get_colored_html_diff_string(
492494
node.title,
493495
other_section_or_none.title,
494496
"right",
@@ -769,10 +771,10 @@ def create_field_change(
769771
other_requirement_field_value = (
770772
other_requirement_field.get_text_value()
771773
)
772-
left_diff = get_colored_diff_string(
774+
left_diff = get_colored_html_diff_string(
773775
requirement_field_value, other_requirement_field_value, "left"
774776
)
775-
right_diff = get_colored_diff_string(
777+
right_diff = get_colored_html_diff_string(
776778
requirement_field_value, other_requirement_field_value, "right"
777779
)
778780

@@ -857,12 +859,12 @@ def create_comment_field_changes(
857859
comment_other_value = changed_other_field_.get_text_value()
858860
assert comment_other_value is not None
859861

860-
left_diff = get_colored_diff_string(
862+
left_diff = get_colored_html_diff_string(
861863
comment_value,
862864
comment_other_value,
863865
"left",
864866
)
865-
right_diff = get_colored_diff_string(
867+
right_diff = get_colored_html_diff_string(
866868
comment_value,
867869
comment_other_value,
868870
"right",

strictdoc/helpers/diff.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
import difflib
33
from difflib import SequenceMatcher
44

5+
from markupsafe import Markup, escape
6+
57

68
def similar(a, b):
79
return SequenceMatcher(None, a, b).ratio()
810

911

10-
red = lambda text: f'<span class="lambda_red">{text}</span>'
11-
green = lambda text: f'<span class="lambda_green">{text}</span>'
12-
white = lambda text: f"<span>{text}</span>"
12+
red = lambda text: f'<span class="lambda_red">{escape(text)}</span>'
13+
green = lambda text: f'<span class="lambda_green">{escape(text)}</span>'
14+
white = lambda text: f"<span>{escape(text)}</span>"
1315

1416

15-
def get_colored_diff_string(old: str, new: str, flag: str):
17+
def get_colored_html_diff_string(old: str, new: str, flag: str) -> Markup:
1618
assert old is not None
1719
assert new is not None
1820
assert flag in ("left", "right")
@@ -33,4 +35,4 @@ def get_colored_diff_string(old: str, new: str, flag: str):
3335
result += red(old[code[1] : code[2]])
3436
else:
3537
result += green(new[code[3] : code[4]])
36-
return result
38+
return Markup(result)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[DOCUMENT]
2+
TITLE: Doc Title with special characters <>
3+
4+
[SECTION]
5+
TITLE: To be removed section with special characters <>
6+
7+
[REQUIREMENT]
8+
TITLE: To be removed title with special characters <>
9+
STATEMENT: To be removed statement with special characters <>
10+
11+
[/SECTION]
12+
13+
[SECTION]
14+
UID: SECT-1
15+
TITLE: To be changed section with special characters <>
16+
17+
[REQUIREMENT]
18+
UID: REQ-1
19+
TITLE: To be changed title with special characters <>
20+
STATEMENT: To be changed statement with special characters <>
21+
22+
[/SECTION]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[DOCUMENT]
2+
TITLE: Doc Title with more special characters <>&"'
3+
4+
[SECTION]
5+
TITLE: Added section with more special characters <>&"'
6+
7+
[REQUIREMENT]
8+
TITLE: Added title with more special characters <>&"'
9+
STATEMENT: Added statement with more special characters <>&"'
10+
11+
[/SECTION]
12+
13+
[SECTION]
14+
UID: SECT-1
15+
TITLE: Changed section with more special characters <>&"'
16+
17+
[REQUIREMENT]
18+
UID: REQ-1
19+
TITLE: Changed title with more special characters <>&"'
20+
STATEMENT: Changed statement with more special characters <>&"'
21+
22+
[/SECTION]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[project]
2+
title = "Test Project"
3+
4+
features = [
5+
"DIFF",
6+
]

0 commit comments

Comments
 (0)