Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ jobs:
- name: Test
run: |
make testone
- name: Test ReDoS
run: |
make testredos
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [pull #626] Fix XSS when encoding incomplete tags (#625)
- [pull #628] Fix TypeError in MiddleWordEm extra when options was None (#627)
- [pull #630] Fix nbsp breaking tables (#629)
- [pull #634] Fix ReDoS in HTML tokenizer regex (#633)


## python-markdown2 2.5.3
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ test:
testone:
cd test && python test.py -- -knownfailure

.PHONY: testredos
testredos:
python test/test_redos.py

.PHONY: pygments
pygments:
[[ -d deps/pygments ]] || ( \
Expand Down
2 changes: 1 addition & 1 deletion lib/markdown2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,7 @@ def _run_span_gamut(self, text: str) -> str:
\s+ # whitespace after tag
(?:[^\t<>"'=/]+:)?
[^<>"'=/]+= # attr name
(?:".*?"|'.*?'|[^<>"'=/\s]+) # value, quoted or unquoted. If unquoted, no spaces allowed
(?:"[^"]*?"|'[^']*?'|[^<>"'=/\s]+) # value, quoted or unquoted. If unquoted, no spaces allowed
)*
\s*/?>
|
Expand Down
90 changes: 90 additions & 0 deletions test/test_redos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import logging
import subprocess
import sys
import time
from pathlib import Path

log = logging.getLogger("test")
LIB_DIR = Path(__file__).parent.parent / "lib"


def pull_387_example_1():
# https://github.com/trentm/python-markdown2/pull/387
return "[#a" + " " * 3456


def pull_387_example_2():
# https://github.com/trentm/python-markdown2/pull/387
return "```" + "\n" * 3456


def pull_387_example_3():
# https://github.com/trentm/python-markdown2/pull/387
return "-*-" + " " * 3456


def pull_402():
# https://github.com/trentm/python-markdown2/pull/402
return " " * 100_000 + "$"


def issue493():
# https://github.com/trentm/python-markdown2/issues/493
return "**_" + "*_" * 38730 * 10 + "\x00"


def issue_633():
# https://github.com/trentm/python-markdown2/issues/633
return '<p m="1"' * 2500 + " " * 5000 + "</div"


# whack everything in a dict for easy lookup later on
CASES = {
fn.__name__: fn
for fn in [
pull_387_example_1,
pull_387_example_2,
pull_387_example_3,
pull_402,
issue493,
issue_633,
]
}


if __name__ == "__main__":
logging.basicConfig()

if "--execute" in sys.argv:
testcase = CASES[sys.argv[sys.argv.index("--execute") + 1]]
sys.path.insert(0, str(LIB_DIR))
from markdown2 import markdown

markdown(testcase())
sys.exit(0)

print("-- ReDoS tests")

fails = []
start_time = time.time()
for testcase in CASES:
print(f"markdown2/redos/{testcase} ... ", end="")

testcase_start_time = time.time()
try:
subprocess.run([sys.executable, __file__, "--execute", testcase], timeout=3)
except subprocess.TimeoutExpired:
fails.append(testcase)
print(f"FAIL ({time.time() - testcase_start_time:.3f}s)")
else:
print(f"ok ({time.time() - testcase_start_time:.3f}s)")

print("----------------------------------------------------------------------")
print(f"Ran {len(CASES)} tests in {time.time() - start_time:.3f}s")

if fails:
print("FAIL:", fails)
else:
print("OK")

sys.exit(len(fails))
Loading