Coverage for /pythoncovmergedfiles/medio/medio/src/turbohtml/tools/fuzz/stylesheet_targets.py: 49%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

49 statements  

1"""Whole stylesheet inputs must reach the public minifier without an HTML text boundary.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Final 

6 

7from turbohtml.clean import minify_css 

8 

9from .atheris_registry import Target 

10from .round_trip_oracles import OutOfScopeError, css_semantics_check, fixpoint_check 

11 

12if TYPE_CHECKING: 

13 from collections.abc import Callable 

14 

15__all__ = ["stylesheet_observation", "stylesheet_targets"] 

16 

17 

18def stylesheet_targets() -> tuple[Target, ...]: 

19 """Own the public minifier through an exact stylesheet input.""" 

20 return ( 

21 Target("css-stylesheet", _stylesheet, ("turbohtml.clean.minify_css",), (UnicodeDecodeError, OutOfScopeError)), 

22 ) 

23 

24 

25def _stylesheet(data: bytes) -> None: 

26 stylesheet_observation(data) 

27 

28 

29def stylesheet_observation(data: bytes, printer: Callable[[str], str] = minify_css) -> str: 

30 """Check supported values and selectors against a fixed DOM context.""" 

31 source: Final = data.decode("utf-8") 

32 if _unclosed_css_blocks(source): 

33 msg = "CSS input has an unclosed block" 

34 raise OutOfScopeError(msg) 

35 if failure := fixpoint_check(source, printer, numeric=True): 

36 if _unclosed_css_blocks(printer(source)): 

37 msg = "CSS output has an unclosed block" 

38 raise OutOfScopeError(msg) 

39 raise AssertionError(failure) 

40 if failure := css_semantics_check( 

41 '<style></style><p class="x">text<span id="target">child</span></p>', 

42 printer, 

43 stylesheet=source, 

44 ): 

45 raise AssertionError(failure) 

46 return printer(source) 

47 

48 

49def _unclosed_css_blocks(source: str) -> bool: 

50 depth = 0 

51 quote = "" 

52 index = 0 

53 while index < len(source): 

54 char = source[index] 

55 if quote: 

56 if char == "\\": 

57 index += 1 

58 elif char == quote: 

59 quote = "" 

60 elif source.startswith("/*", index): 

61 end = source.find("*/", index + 2) 

62 index = len(source) if end < 0 else end + 2 

63 continue 

64 elif char in "\"'": 

65 quote = char 

66 elif char == "{": 

67 depth += 1 

68 elif char == "}": 

69 depth -= 1 

70 index += 1 

71 return depth > 0