Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/renderers/html.py: 80%

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

124 statements  

1from typing import Any, ClassVar, Dict, Iterable, Optional, Tuple, Union, Literal 

2from urllib.parse import unquote 

3from ..core import BaseRenderer, BlockState 

4from ..util import escape as escape_text 

5from ..util import safe_entity, striptags 

6 

7 

8class HTMLRenderer(BaseRenderer): 

9 """A renderer for converting Markdown to HTML.""" 

10 

11 _escape: bool 

12 _allow_harmful_protocols: Optional[Union[bool, Iterable[str]]] 

13 NAME: ClassVar[Literal["html"]] = "html" 

14 SAFE_PROTOCOLS: ClassVar[Tuple[str, ...]] = ( 

15 "http:", 

16 "https:", 

17 "mailto:", 

18 "tel:", 

19 "ftp:", 

20 "ftps:", 

21 "irc:", 

22 "ircs:", 

23 ) 

24 GOOD_DATA_PROTOCOLS: ClassVar[Tuple[str, ...]] = ( 

25 "data:image/gif;", 

26 "data:image/png;", 

27 "data:image/jpeg;", 

28 "data:image/webp;", 

29 ) 

30 

31 def __init__( 

32 self, 

33 escape: bool = True, 

34 allow_harmful_protocols: Optional[Union[bool, Iterable[str]]] = None, 

35 ) -> None: 

36 super(HTMLRenderer, self).__init__() 

37 self._allow_harmful_protocols = allow_harmful_protocols 

38 self._escape = escape 

39 

40 def render_token(self, token: Dict[str, Any], state: BlockState) -> str: 

41 # backward compitable with v2 

42 func = self._get_method(token["type"]) 

43 attrs = token.get("attrs") 

44 

45 if "raw" in token: 

46 text = token["raw"] 

47 elif "children" in token: 

48 text = self.render_tokens(token["children"], state) 

49 else: 

50 if attrs: 

51 return func(**attrs) 

52 else: 

53 return func() 

54 if attrs: 

55 return func(text, **attrs) 

56 else: 

57 return func(text) 

58 

59 def safe_url(self, url: str) -> str: 

60 """Ensure the given URL is safe. This method is used for rendering 

61 links, images, and etc. 

62 """ 

63 allow_harmful_protocols = self._allow_harmful_protocols 

64 if allow_harmful_protocols is True: 

65 return escape_text(url) 

66 

67 _url = _unquote_url(url).lower().lstrip() 

68 if allow_harmful_protocols and _url.startswith(tuple(allow_harmful_protocols)): 

69 return escape_text(url) 

70 

71 if _is_safe_url(_url, self.SAFE_PROTOCOLS, self.GOOD_DATA_PROTOCOLS): 

72 return escape_text(url) 

73 return "#harmful-link" 

74 

75 def text(self, text: str) -> str: 

76 if self._escape: 

77 return escape_text(text) 

78 return safe_entity(text) 

79 

80 def emphasis(self, text: str) -> str: 

81 return "<em>" + text + "</em>" 

82 

83 def strong(self, text: str) -> str: 

84 return "<strong>" + text + "</strong>" 

85 

86 def link(self, text: str, url: str, title: Optional[str] = None) -> str: 

87 s = '<a href="' + self.safe_url(url) + '"' 

88 if title: 

89 s += ' title="' + safe_entity(title) + '"' 

90 return s + ">" + text + "</a>" 

91 

92 def image(self, text: str, url: str, title: Optional[str] = None) -> str: 

93 src = self.safe_url(url) 

94 alt = striptags(text) 

95 s = '<img src="' + src + '" alt="' + alt + '"' 

96 if title: 

97 s += ' title="' + safe_entity(title) + '"' 

98 return s + " />" 

99 

100 def codespan(self, text: str) -> str: 

101 return "<code>" + escape_text(text) + "</code>" 

102 

103 def linebreak(self) -> str: 

104 return "<br />\n" 

105 

106 def softbreak(self) -> str: 

107 return "\n" 

108 

109 def inline_html(self, html: str) -> str: 

110 if self._escape: 

111 return escape_text(html) 

112 return html 

113 

114 def paragraph(self, text: str) -> str: 

115 return "<p>" + text + "</p>\n" 

116 

117 def heading(self, text: str, level: int, **attrs: Any) -> str: 

118 tag = "h" + str(level) 

119 html = "<" + tag 

120 _id = attrs.get("id") 

121 if _id: 

122 html += ' id="' + escape_text(_id) + '"' 

123 return html + ">" + text + "</" + tag + ">\n" 

124 

125 def blank_line(self) -> str: 

126 return "" 

127 

128 def thematic_break(self) -> str: 

129 return "<hr />\n" 

130 

131 def block_text(self, text: str) -> str: 

132 return text 

133 

134 def block_code(self, code: str, info: Optional[str] = None) -> str: 

135 html = "<pre><code" 

136 if info is not None: 

137 info = safe_entity(info.strip()) 

138 if info: 

139 lang = info.split(None, 1)[0] 

140 html += ' class="language-' + lang + '"' 

141 return html + ">" + escape_text(code) + "</code></pre>\n" 

142 

143 def block_quote(self, text: str) -> str: 

144 return "<blockquote>\n" + text + "</blockquote>\n" 

145 

146 def block_html(self, html: str) -> str: 

147 if self._escape: 

148 return "<p>" + escape_text(html.strip()) + "</p>\n" 

149 return html + "\n" 

150 

151 def block_error(self, text: str) -> str: 

152 return '<div class="error"><pre>' + escape_text(text) + "</pre></div>\n" 

153 

154 def list(self, text: str, ordered: bool, **attrs: Any) -> str: 

155 if ordered: 

156 html = "<ol" 

157 start = attrs.get("start") 

158 if start is not None: 

159 html += ' start="' + str(start) + '"' 

160 return html + ">\n" + text + "</ol>\n" 

161 return "<ul>\n" + text + "</ul>\n" 

162 

163 def list_item(self, text: str) -> str: 

164 return "<li>" + text + "</li>\n" 

165 

166 

167def _unquote_url(url: str) -> str: 

168 for _ in range(3): 

169 decoded = unquote(url) 

170 if decoded == url: 

171 break 

172 url = decoded 

173 return url 

174 

175 

176def _is_safe_url(url: str, safe_protocols: Tuple[str, ...], good_data_protocols: Tuple[str, ...]) -> bool: 

177 if url.startswith(safe_protocols): 

178 return True 

179 if url.startswith(good_data_protocols): 

180 return True 

181 if url.startswith(("/", "#", "?")): 

182 return True 

183 return ":" not in url.split("/", 1)[0]