diff --git a/etpgrf/config.py b/etpgrf/config.py index 93e9aa1..656f0d4 100644 --- a/etpgrf/config.py +++ b/etpgrf/config.py @@ -102,8 +102,7 @@ CHAR_PLACEHOLDER = '\uFFFC' # Уникальная строка-заполн CHAR_AMP_PLACEHOLDER = '\uFFFD' # Маркер-плейсхолдер для амперсанда (&), чтобы избежать его двойного кодирования в & при замене на мнемонику. CHAR_NODE_SEPARATOR = '\uFFFF' # Маркер границы текстовых узлов (Non-character). -# === КОНСТАНТЫ ДЛЯ САНИТИЗАЦИИ === -# TODO: Их обработку (очистку) нужно добавить в модуль sanitization.py на входе. +# === ПЛЕЙСХОЛДЕРЫ (ДЛЯ САНИТАЙЗИНГА НА ХОДЕ) === CHARS_SYMBOLS_TO_BAN = frozenset([ CHAR_UNIT_SEPARATOR, CHAR_PLACEHOLDER, CHAR_AMP_PLACEHOLDER, CHAR_NODE_SEPARATOR ]) diff --git a/etpgrf/sanitizer.py b/etpgrf/sanitizer.py index d77672b..1046d9d 100644 --- a/etpgrf/sanitizer.py +++ b/etpgrf/sanitizer.py @@ -4,9 +4,10 @@ import logging from bs4 import BeautifulSoup from .config import (SANITIZE_ALL_HTML, SANITIZE_ETPGRF, SANITIZE_NONE, - HANGING_PUNCTUATION_CLASSES, PROTECTED_HTML_TAGS, + PROTECTED_HTML_TAGS, HANGING_PUNCTUATION_SYMBOLS_CLASSES, - HANGING_PUNCTUATION_SPACE_CLASSES_FLAT) + HANGING_PUNCTUATION_SPACE_CLASSES_FLAT, + CHARS_SYMBOLS_TO_BAN) logger = logging.getLogger(__name__) @@ -50,6 +51,7 @@ class SanitizerProcessor: """ if self.mode == SANITIZE_ETPGRF: if not self._etp_selector: + self._strip_banned_chars_from_soup(soup) return soup # Используем CSS-селектор для быстрого поиска всех нужных элементов @@ -60,6 +62,7 @@ class SanitizerProcessor: for span in spans_to_clean: span.unwrap() + self._strip_banned_chars_from_soup(soup) return soup elif self.mode == SANITIZE_ALL_HTML: @@ -74,7 +77,31 @@ class SanitizerProcessor: # 2. Извлекаем чистый текст из оставшегося дерева. # get_text() работает на уровне C (в lxml) и намного быстрее ручного обхода. - return soup.get_text() + text = soup.get_text() + return self._strip_banned_chars_from_string(text) # Если режим не задан, ничего не делаем return soup + + def _strip_banned_chars_from_soup(self, soup: BeautifulSoup) -> None: + """ + Удаляет запрещенные символы из всего содержимого soup-объекта. + + :param soup: Объект BeautifulSoup для обработки. + """ + for element in soup.find_all(string=True): + if isinstance(element, str): + new_string = self._strip_banned_chars_from_string(element) + element.replace_with(new_string) + + def _strip_banned_chars_from_string(self, text: str) -> str: + """ + Удаляет запрещенные символы из строки. + + :param text: Исходная строка. + :return: Строка без запрещенных символов. + """ + # Удаляем все символы, которые есть в CHARS_SYMBOLS_TO_BAN + for char in CHARS_SYMBOLS_TO_BAN: + text = text.replace(char, "") + return text diff --git a/tests/test_sanitizer.py b/tests/test_sanitizer.py index 9d219e0..c79b3de 100644 --- a/tests/test_sanitizer.py +++ b/tests/test_sanitizer.py @@ -4,7 +4,7 @@ import pytest from bs4 import BeautifulSoup from etpgrf.sanitizer import SanitizerProcessor -from etpgrf.config import SANITIZE_NONE, SANITIZE_ETPGRF, SANITIZE_ALL_HTML +from etpgrf.config import SANITIZE_NONE, SANITIZE_ETPGRF, SANITIZE_ALL_HTML, CHARS_SYMBOLS_TO_BAN def test_sanitizer_mode_none(): @@ -83,4 +83,20 @@ def test_sanitizer_mode_etpgrf(case_id, description, html_input, expected_html): result_soup = processor.process(soup) - assert str(result_soup) == expected_html \ No newline at end of file + assert str(result_soup) == expected_html + + +@pytest.mark.parametrize("mode", [SANITIZE_ETPGRF, SANITIZE_ALL_HTML]) +def test_sanitizer_strips_service_placeholders(mode): + """ + Проверяет, что в обоих режимах удаляются запрещенные символы (плейсхолдеры, используемые внутри типографа). + Это важно для защиты от потенциальных XSS-атак или других проблем с безопасностью, связанных с этими символами. + """ + placeholder = next(iter(CHARS_SYMBOLS_TO_BAN)) + html_input = f'
Start{placeholder}End
' + soup = BeautifulSoup(html_input, 'html.parser') + processor = SanitizerProcessor(mode=mode) + result = processor.process(soup) + output = str(result) if isinstance(result, BeautifulSoup) else result + assert placeholder not in output + assert 'StartEnd' in output