Skip to content

Commit 89ec7ef

Browse files
authored
fix csp headers for generated html docs (#3357)
1 parent b747916 commit 89ec7ef

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

docs/doxygen/csp_fixer.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import os
2+
import re
3+
import uuid
4+
from pathlib import Path
5+
import logging
6+
7+
class CSPFixer:
8+
# static compiled regex patterns
9+
SCRIPT_TAG_PATTERN = re.compile(r'<script([^>]*)>')
10+
CSP_META_PATTERN = re.compile(r'<meta\s+http-equiv="Content-Security-Policy"[^>]*>\n?', re.IGNORECASE)
11+
HEAD_TAG_PATTERN = re.compile(r'<head>', re.IGNORECASE)
12+
def __init__(self, log_level=logging.INFO):
13+
self.nonce = str(uuid.uuid4())
14+
# CSP meta tags
15+
self.csp = (
16+
"default-src 'self'; "
17+
f"script-src 'self' 'nonce-{self.nonce}'; "
18+
)
19+
self.csp_meta = f'<meta http-equiv="Content-Security-Policy" content="{self.csp}">\n'
20+
logging.basicConfig(
21+
level=log_level,
22+
format='%(asctime)s - %(levelname)s - %(message)s',
23+
datefmt='%Y-%m-%d %H:%M:%S'
24+
)
25+
26+
def __add_nonce(self, html_content:str) -> str:
27+
if not html_content:
28+
return html_content
29+
30+
# replace existing script tags with nonce
31+
modified_content = self.SCRIPT_TAG_PATTERN.sub(
32+
f'<script nonce="{self.nonce}"\\1>',
33+
html_content
34+
)
35+
36+
# remove any existing CSP meta tags so there is no conflict
37+
modified_content = self.CSP_META_PATTERN.sub(
38+
'',
39+
modified_content
40+
)
41+
42+
# add CSP meta tag in head
43+
with_csp = self.HEAD_TAG_PATTERN.sub(
44+
f'<head>\n{self.csp_meta}',
45+
modified_content
46+
)
47+
48+
return with_csp
49+
50+
def update_files(self, path: Path) -> bool:
51+
# check valid path
52+
if not os.path.exists(path):
53+
logging.error(f'Path does not exist: {path}')
54+
return False
55+
56+
if not os.path.isdir(path):
57+
logging.error(f'Path is not a directory: {path}')
58+
return False
59+
success = True
60+
processed_count = 0
61+
failed_count = 0
62+
for dirpath, dirnames, filenames in os.walk(path):
63+
for filename in filenames:
64+
if filename.endswith('.html'):
65+
file_path = Path(dirpath) / filename
66+
logging.debug(f'Processing: {file_path}')
67+
try:
68+
with open(file_path, 'r', encoding='utf-8') as file:
69+
content = file.read()
70+
71+
processed = self.__add_nonce(content)
72+
73+
with open(file_path, 'w', encoding='utf-8') as file:
74+
file.write(processed)
75+
logging.debug(f'Finished: {file_path}')
76+
processed_count += 1
77+
except (OSError, IOError) as e:
78+
logging.error(f'Error processing {file_path}: {e}')
79+
failed_count += 1
80+
success = False
81+
logging.info(f"CSP update process completed. Processed {processed_count} files. Failed {failed_count} files")
82+
return success

docs/doxygen/doxygen_wrapper.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import shutil
1111
import subprocess
1212
from pathlib import Path
13+
from csp_fixer import CSPFixer
1314

1415
DOXYGEN_EXE = shutil.which("doxygen")
1516
CMAKE_EXE = shutil.which("cmake3")
@@ -234,6 +235,9 @@ def generate_all(self):
234235
future.result()
235236

236237
self._cleanup_temp_files()
238+
print("Update CSP")
239+
cspfixer = CSPFixer()
240+
cspfixer.update_files(Path(self.output_dir))
237241

238242
return dependency_map
239243

0 commit comments

Comments
 (0)