+4
-1
@@ -486,7 +486,10 @@ class PostParser:
|
||||
html_content = []
|
||||
|
||||
if debug:
|
||||
html_content.append(f'<div class="title">Title: {data["html"]["title"]}</div><br>')
|
||||
# title comes from _generate_title (user-controlled post text) and never goes
|
||||
# through bleach — escape it before embedding, same as raw_message below.
|
||||
title_escaped = html.escape(str(data["html"]["title"]))
|
||||
html_content.append(f'<div class="title">Title: {title_escaped}</div><br>')
|
||||
html_content.append(f'<div class="message-body">{data["html"]["body"]}</div>')
|
||||
html_content.append(f'<div class="message-footer">{data["html"]["footer"]}</div>')
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[pytest]
|
||||
testpaths = tests
|
||||
addopts = -ra
|
||||
# The stage-1 anti-hang tests are async; run them without per-test event loops
|
||||
# being set up by hand. (The tests also carry @pytest.mark.asyncio explicitly, so
|
||||
# they work in strict mode too — but auto keeps the plugin's requirement obvious.)
|
||||
asyncio_mode = auto
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::DeprecationWarning
|
||||
@@ -0,0 +1,17 @@
|
||||
"""Shared test bootstrap (issue #17).
|
||||
|
||||
Ensure the repo root is importable and install the config mock BEFORE any test
|
||||
module imports application code. Pytest imports conftest.py before collecting
|
||||
test modules, so doing this here (instead of a per-module preamble) makes the
|
||||
suite order-independent regardless of collection order or invocation directory.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
import tests.mock_config as _mock_config
|
||||
|
||||
sys.modules['config'] = _mock_config
|
||||
@@ -7,13 +7,6 @@
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
import sys
|
||||
import os
|
||||
# Add project root to sys.path to find post_parser
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# Mock the config module
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from pyrogram.types import Message
|
||||
from post_parser import PostParser
|
||||
|
||||
@@ -7,14 +7,6 @@
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add project root to sys.path to find post_parser
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# Mock the config module before importing PostParser
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from pyrogram.types import Message, Chat, Reaction, MessageReactions
|
||||
from pyrogram.enums import MessageMediaType
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
# pylance: disable=reportMissingImports, reportMissingModuleSource
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
import sys
|
||||
import os
|
||||
# Add project root to sys.path to find post_parser
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# Mock the config module
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from pyrogram.types import Message, Document
|
||||
from pyrogram.enums import MessageMediaType
|
||||
|
||||
@@ -10,18 +10,12 @@ Stage 1 (anti-hang) regression tests:
|
||||
worker, and task_done stays balanced so queue.join() completes.
|
||||
- Gate cancellation during the spacing wait does not lose the permit.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
# Add project root to sys.path and mock the config module (same pattern as the other tests).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
import tg_throttle
|
||||
import tg_cache
|
||||
from pyrogram import errors
|
||||
|
||||
@@ -18,17 +18,12 @@ Covers the four scenarios from the plan plus the subtle in-flight-dedup lifecycl
|
||||
fresh partials and non-temp files.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
# Add project root to sys.path and mock the config module (same pattern as the other tests).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
import api_server
|
||||
from pyrogram import errors
|
||||
from pyrogram.enums import MessageMediaType
|
||||
|
||||
@@ -36,16 +36,11 @@ All 206 responses that carry data return byte-for-byte identical slices old vs n
|
||||
real regression is hidden behind an "accepted difference".
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
# Add project root to sys.path and mock the config module (same pattern as the other tests).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@ Covers:
|
||||
- 4.4 sanitize coverage (XSS): a <script> / onerror= / javascript: payload is stripped in
|
||||
ALL outputs — rss, html-feed, single-post html, and json — each with exactly one pass.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import copy
|
||||
import pickle
|
||||
import asyncio
|
||||
@@ -28,9 +26,6 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from pyrogram.enums import MessageMediaType
|
||||
|
||||
import post_parser as pp_module
|
||||
@@ -289,6 +284,27 @@ async def test_xss_stripped_in_single_post_html_debug():
|
||||
assert "<script>" in html_out, "debug raw dump was not html-escaped"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_xss_in_title_escaped_in_debug_html():
|
||||
# Issue #13: the debug branch of _format_html embeds data["html"]["title"], which is
|
||||
# generated from user-controlled content and never passes through bleach. A poll whose
|
||||
# question carries a <script> tag yields the title "📊 Poll: <script>alert(1)</script>"
|
||||
# verbatim — it must be html-escaped before being embedded in the debug output.
|
||||
msg = make_message(25, media=MessageMediaType.POLL)
|
||||
msg.poll = SimpleNamespace(question="<script>alert(1)</script>")
|
||||
parser = PostParser(_make_client_returning(msg))
|
||||
html_out = await parser.get_post("testchan", 25, "html", debug=True)
|
||||
|
||||
# No live <script> tag anywhere in the output (title div, body, footer, raw <pre>).
|
||||
assert "<script>" not in html_out, "debug html left a live <script> tag"
|
||||
# The title div itself carries the payload only in escaped form (proving html.escape
|
||||
# ran on the title, not just on the raw_message <pre> dump).
|
||||
title_lines = [line for line in html_out.split("\n") if 'class="title"' in line]
|
||||
assert title_lines, "debug output must contain the title div"
|
||||
assert "<script>alert(1)</script>" in title_lines[0], \
|
||||
"title was not html-escaped in debug output"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_xss_stripped_in_rss_feed(monkeypatch):
|
||||
async def fake_get_chat(client, channel):
|
||||
|
||||
@@ -17,17 +17,12 @@ Covers:
|
||||
- gotcha: str(channel) key discipline — an int-ish channel on the hot path keys the
|
||||
accumulator (and thus the UPDATE) by the string form.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
import api_server
|
||||
from file_io import init_db_sync, update_media_file_access_bulk_sync
|
||||
|
||||
|
||||
@@ -15,16 +15,10 @@ Covers:
|
||||
fake client's get_me / safe_get_messages proves neither is ever called.
|
||||
- TelegramClient.watchdog_last_ok_age(): None when never probed; a positive float afterwards.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
# Add project root to sys.path and mock the config module (same pattern as the other tests).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import api_server
|
||||
|
||||
@@ -27,8 +27,6 @@ Automated here:
|
||||
an int-ish channel records the str-keyed timestamp, and the flush UPDATE matches the
|
||||
str-keyed DB row (hit -> flush -> DB), the exact affinity gotcha the plan warns about.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import sqlite3
|
||||
import asyncio
|
||||
@@ -36,10 +34,6 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
# Add project root to sys.path and mock the config module (same pattern as the other tests).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
Reference in New Issue
Block a user