##// END OF EJS Templates
markdown: use bleach to cleanup html from markdown. This also enabled strict...
marcink -
r2440:86196e6b default
parent child Browse files
Show More
@@ -0,0 +1,403 b''
1 all_tags = [
2 "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio",
3 "b", "base", "basefont", "bdi", "bdo", "bgsound", "big", "blink", "blockquote", "body", "br", "button",
4 "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "content",
5 "data", "datalist", "dd", "del", "detals", "dfn", "dialog", "dir", "div", "dl", "dt",
6 "element", "em", "embed",
7 "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset",
8 "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
9 "i", "iframe", "image", "img", "input", "ins", "isindex",
10 "kbd", "keygen",
11 "label", "legend", "li", "link", "listing",
12 "main", "map", "mark", "marquee", "menu", "menuitem", "meta", "meter", "multicol",
13 "nav", "nobr", "noembed", "noframes", "noscript",
14 "object", "ol", "optgroup", "option", "output",
15 "p", "param", "picture", "plaintext", "pre", "progress",
16 "q",
17 "rp", "rt", "ruby",
18 "s", "samp", "script", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup",
19 "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt",
20 "u", "ul",
21 "var", "video",
22 "wbr",
23 "xmp",
24 ]
25
26 # List tags that, if included in a page, could break markup or open XSS.
27 generally_xss_unsafe = [
28 "applet", "audio",
29 "bgsound", "body",
30 "canvas",
31 "embed",
32 "frame", "frameset",
33 "head", "html",
34 "iframe",
35 "link",
36 "meta",
37 "object",
38 "param",
39 "source", "script",
40 "ruby", "rt",
41 "title", "track",
42 "video",
43 "xmp"
44 ]
45
46 # Tags that, if included on the page, will probably not break markup or open
47 # XSS. Note that these must be combined with attribute whitelisting, or things
48 # like <img> and <style> could still be unsafe.
49 generally_xss_safe = list(set(all_tags) - set(generally_xss_unsafe))
50 generally_xss_safe.sort()
51
52 # Tags suitable for rendering markdown
53 markdown_tags = [
54 "h1", "h2", "h3", "h4", "h5", "h6",
55 "b", "i", "strong", "em", "tt",
56 "p", "br",
57 "span", "div", "blockquote", "code", "hr", "pre", "del",
58 "ul", "ol", "li",
59 "dl", "dd", "dt",
60 "table", "thead", "tbody", "tfoot", "tr", "th", "td",
61 "img",
62 "a",
63 ]
64
65 markdown_attrs = {
66 "*": ["class", "style", "align"],
67 "img": ["src", "alt", "title"],
68 "a": ["href", "alt", "title", "name"],
69 "abbr": ["title"],
70 "acronym": ["title"],
71 "pre": ["lang"]
72 }
73
74 standard_styles = [
75 # Taken from https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
76 # This includes pseudo-classes, pseudo-elements, @-rules, units, and
77 # selectors in addition to properties, but it doesn't matter for our
78 # purposes -- we don't need to filter styles..
79 ":active", "::after (:after)", "align-content", "align-items", "align-self",
80 "all", "<angle>", "animation", "animation-delay", "animation-direction",
81 "animation-duration", "animation-fill-mode", "animation-iteration-count",
82 "animation-name", "animation-play-state", "animation-timing-function",
83 "@annotation", "annotation()", "attr()", "::backdrop", "backface-visibility",
84 "background", "background-attachment", "background-blend-mode",
85 "background-clip", "background-color", "background-image", "background-origin",
86 "background-position", "background-repeat", "background-size", "<basic-shape>",
87 "::before (:before)", "<blend-mode>", "blur()", "border", "border-bottom",
88 "border-bottom-color", "border-bottom-left-radius",
89 "border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
90 "border-collapse", "border-color", "border-image", "border-image-outset",
91 "border-image-repeat", "border-image-slice", "border-image-source",
92 "border-image-width", "border-left", "border-left-color", "border-left-style",
93 "border-left-width", "border-radius", "border-right", "border-right-color",
94 "border-right-style", "border-right-width", "border-spacing", "border-style",
95 "border-top", "border-top-color", "border-top-left-radius",
96 "border-top-right-radius", "border-top-style", "border-top-width",
97 "border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
98 "break-after", "break-before", "break-inside", "brightness()", "calc()",
99 "caption-side", "ch", "@character-variant", "character-variant()", "@charset",
100 ":checked", "circle()", "clear", "clip", "clip-path", "cm", "color", "<color>",
101 "columns", "column-count", "column-fill", "column-gap", "column-rule",
102 "column-rule-color", "column-rule-style", "column-rule-width", "column-span",
103 "column-width", "content", "contrast()", "<counter>", "counter-increment",
104 "counter-reset", "@counter-style", "cubic-bezier()", "cursor",
105 "<custom-ident>", ":default", "deg", ":dir()", "direction", ":disabled",
106 "display", "@document", "dpcm", "dpi", "dppx", "drop-shadow()", "element()",
107 "ellipse()", "em", ":empty", "empty-cells", ":enabled", "ex", "filter",
108 ":first", ":first-child", "::first-letter", "::first-line",
109 ":first-of-type", "flex", "flex-basis", "flex-direction",
110 "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "float", ":focus",
111 "font", "@font-face", "font-family", "font-feature-settings",
112 "@font-feature-values", "font-kerning", "font-language-override", "font-size",
113 "font-size-adjust", "font-stretch", "font-style", "font-synthesis",
114 "font-variant", "font-variant-alternates", "font-variant-caps",
115 "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
116 "font-variant-position", "font-weight", "<frequency>", ":fullscreen", "grad",
117 "<gradient>", "grayscale()", "grid", "grid-area", "grid-auto-columns",
118 "grid-auto-flow", "grid-auto-position", "grid-auto-rows", "grid-column",
119 "grid-column-start", "grid-column-end", "grid-row", "grid-row-start",
120 "grid-row-end", "grid-template", "grid-template-areas", "grid-template-rows",
121 "grid-template-columns", "height", ":hover", "hsl()", "hsla()", "hue-rotate()",
122 "hyphens", "hz", "<image>", "image()", "image-rendering", "image-resolution",
123 "image-orientation", "ime-mode", "@import", "in", ":indeterminate", "inherit",
124 "initial", ":in-range", "inset()", "<integer>", ":invalid", "invert()",
125 "isolation", "justify-content", "@keyframes", "khz", ":lang()", ":last-child",
126 ":last-of-type", "left", ":left", "<length>", "letter-spacing",
127 "linear-gradient()", "line-break", "line-height", ":link", "list-style",
128 "list-style-image", "list-style-position", "list-style-type", "margin",
129 "margin-bottom", "margin-left", "margin-right", "margin-top", "marks", "mask",
130 "mask-type", "matrix()", "matrix3d()", "max-height", "max-width", "@media",
131 "min-height", "minmax()", "min-width", "mix-blend-mode", "mm", "ms",
132 "@namespace", ":not()", ":nth-child()", ":nth-last-child()",
133 ":nth-last-of-type()", ":nth-of-type()", "<number>", "object-fit",
134 "object-position", ":only-child", ":only-of-type", "opacity", "opacity()",
135 ":optional", "order", "@ornaments", "ornaments()", "orphans", "outline",
136 "outline-color", "outline-offset", "outline-style", "outline-width",
137 ":out-of-range", "overflow", "overflow-wrap", "overflow-x", "overflow-y",
138 "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
139 "@page", "page-break-after", "page-break-before", "page-break-inside", "pc",
140 "<percentage>", "perspective", "perspective()", "perspective-origin",
141 "pointer-events", "polygon()", "position", "<position>", "pt", "px", "quotes",
142 "rad", "radial-gradient()", "<ratio>", ":read-only", ":read-write", "rect()",
143 "rem", "repeat()", "::repeat-index", "::repeat-item",
144 "repeating-linear-gradient()", "repeating-radial-gradient()", ":required",
145 "resize", "<resolution>", "rgb()", "rgba()", "right", ":right", ":root",
146 "rotate()", "rotatex()", "rotatey()", "rotatez()", "rotate3d()", "ruby-align",
147 "ruby-merge", "ruby-position", "s", "saturate()", "scale()", "scalex()",
148 "scaley()", "scalez()", "scale3d()", ":scope", "scroll-behavior",
149 "::selection", "sepia()", "<shape>", "shape-image-threshold", "shape-margin",
150 "shape-outside", "skew()", "skewx()", "skewy()", "steps()", "<string>",
151 "@styleset", "styleset()", "@stylistic", "stylistic()", "@supports", "@swash",
152 "swash()", "symbol()", "table-layout", "tab-size", ":target", "text-align",
153 "text-align-last", "text-combine-upright", "text-decoration",
154 "text-decoration-color", "text-decoration-line", "text-decoration-style",
155 "text-indent", "text-orientation", "text-overflow", "text-rendering",
156 "text-shadow", "text-transform", "text-underline-position", "<time>",
157 "<timing-function>", "top", "touch-action", "transform", "transform-origin",
158 "transform-style", "transition", "transition-delay", "transition-duration",
159 "transition-property", "transition-timing-function", "translate()",
160 "translatex()", "translatey()", "translatez()", "translate3d()", "turn",
161 "unicode-bidi", "unicode-range", "unset", "<uri>", "url()", "<user-ident>",
162 ":valid", "::value", "var()", "vertical-align", "vh", "@viewport",
163 "visibility", ":visited", "vmax", "vmin", "vw", "white-space", "widows",
164 "width", "will-change", "word-break", "word-spacing", "word-wrap",
165 "writing-mode", "z-index",
166
167 ]
168
169 webkit_prefixed_styles = [
170 # Webkit-prefixed styles
171 # https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Webkit_Extensions
172 "-webkit-animation", "-webkit-animation-delay", "-webkit-animation-direction",
173 "-webkit-animation-duration", "-webkit-animation-fill-mode",
174 "-webkit-animation-iteration-count", "-webkit-animation-name",
175 "-webkit-animation-play-state", "-webkit-animation-timing-function",
176 "-webkit-backface-visibility", "-webkit-border-image", "-webkit-column-count",
177 "-webkit-column-gap", "-webkit-column-width", "-webkit-column-rule",
178 "-webkit-column-rule-width", "-webkit-column-rule-style",
179 "-webkit-column-rule-color", "-webkit-columns", "-webkit-column-span",
180 "-webkit-font-feature-settings", "-webkit-font-kerning",
181 "-webkit-font-size-delta", "-webkit-font-variant-ligatures",
182 "-webkit-grid-column", "-webkit-grid-row", "-webkit-hyphens", "-webkit-mask",
183 "-webkit-mask-clip", "-webkit-mask-composite", "-webkit-mask-image",
184 "-webkit-mask-origin", "-webkit-mask-position", "-webkit-mask-repeat",
185 "-webkit-mask-size", "-webkit-perspective", "-webkit-perspective-origin",
186 "-webkit-region-fragment", "-webkit-shape-outside", "-webkit-text-emphasis",
187 "-webkit-text-emphasis-color", "-webkit-text-emphasis-position",
188 "-webkit-text-emphasis-style", "-webkit-transform", "-webkit-transform-origin",
189 "-webkit-transform-style", "-webkit-transition", "-webkit-transition-delay",
190 "-webkit-transition-duration", "-webkit-transition-property",
191 "-webkit-transition-timing-function", "-epub-word-break", "-epub-writing-mode",
192 # WebKit-prefixed properties with an unprefixed counterpart
193 "-webkit-background-clip", "-webkit-background-origin",
194 "-webkit-background-size", "-webkit-border-bottom-left-radius",
195 "-webkit-border-bottom-right-radius", "-webkit-border-radius",
196 "-webkit-border-top-left-radius", "-webkit-border-top-right-radius",
197 "-webkit-box-sizing", "-epub-caption-side", "-webkit-opacity",
198 "-epub-text-transform",
199 ]
200
201 mozilla_prefixed_styles = [
202 "-moz-column-count", "-moz-column-fill", "-moz-column-gap",
203 "-moz-column-width", "-moz-column-rule", "-moz-column-rule-width",
204 "-moz-column-rule-style", "-moz-column-rule-color",
205 "-moz-font-feature-settings", "-moz-font-language-override", "-moz-hyphens",
206 "-moz-text-align-last", "-moz-text-decoration-color",
207 "-moz-text-decoration-line", "-moz-text-decoration-style",
208 ]
209
210 all_prefixed_styles = [
211 # From http://peter.sh/experiments/vendor-prefixed-css-property-overview/
212 "-ms-accelerator", "-webkit-app-region", "-webkit-appearance",
213 "-webkit-appearance", "-moz-appearance", "-webkit-aspect-ratio",
214 "-webkit-backdrop-filter", "backface-visibility",
215 "-webkit-backface-visibility", "backface-visibility", "backface-visibility",
216 "-webkit-background-composite", "-webkit-background-composite", "-moz-binding",
217 "-ms-block-progression", "-webkit-border-after", "-webkit-border-after",
218 "-webkit-border-after-color", "-webkit-border-after-color",
219 "-webkit-border-after-style", "-webkit-border-after-style",
220 "-webkit-border-after-width", "-webkit-border-after-width",
221 "-webkit-border-before", "-webkit-border-before",
222 "-webkit-border-before-color", "-webkit-border-before-color",
223 "-webkit-border-before-style", "-webkit-border-before-style",
224 "-webkit-border-before-width", "-webkit-border-before-width",
225 "-moz-border-bottom-colors", "-webkit-border-end", "-webkit-border-end",
226 "-moz-border-end", "-webkit-border-end-color", "-webkit-border-end-color",
227 "-moz-border-end-color", "-webkit-border-end-style",
228 "-webkit-border-end-style", "-moz-border-end-style",
229 "-webkit-border-end-width", "-webkit-border-end-width",
230 "-moz-border-end-width", "-webkit-border-fit",
231 "-webkit-border-horizontal-spacing", "-webkit-border-horizontal-spacing",
232 "-moz-border-left-colors", "-moz-border-right-colors", "-webkit-border-start",
233 "-webkit-border-start", "-moz-border-start", "-webkit-border-start-color",
234 "-webkit-border-start-color", "-moz-border-start-color",
235 "-webkit-border-start-style", "-webkit-border-start-style",
236 "-moz-border-start-style", "-webkit-border-start-width",
237 "-webkit-border-start-width", "-moz-border-start-width",
238 "-moz-border-top-colors", "-webkit-border-vertical-spacing",
239 "-webkit-border-vertical-spacing", "-webkit-box-align", "-webkit-box-align",
240 "-moz-box-align", "-webkit-box-decoration-break",
241 "-webkit-box-decoration-break", "box-decoration-break",
242 "-webkit-box-direction", "-webkit-box-direction", "-moz-box-direction",
243 "-webkit-box-flex", "-webkit-box-flex", "-moz-box-flex",
244 "-webkit-box-flex-group", "-webkit-box-flex-group", "-webkit-box-lines",
245 "-webkit-box-lines", "-webkit-box-ordinal-group", "-webkit-box-ordinal-group",
246 "-moz-box-ordinal-group", "-webkit-box-orient", "-webkit-box-orient",
247 "-moz-box-orient", "-webkit-box-pack", "-webkit-box-pack", "-moz-box-pack",
248 "-webkit-box-reflect", "-webkit-box-reflect", "clip-path", "-webkit-clip-path",
249 "clip-path", "clip-path", "-webkit-color-correction", "-webkit-column-axis",
250 "-webkit-column-break-after", "-webkit-column-break-after",
251 "-webkit-column-break-before", "-webkit-column-break-before",
252 "-webkit-column-break-inside", "-webkit-column-break-inside",
253 "-webkit-column-count", "column-count", "-moz-column-count", "column-count",
254 "column-fill", "column-fill", "-moz-column-fill", "column-fill",
255 "-webkit-column-gap", "column-gap", "-moz-column-gap", "column-gap",
256 "-webkit-column-rule", "column-rule", "-moz-column-rule", "column-rule",
257 "-webkit-column-rule-color", "column-rule-color", "-moz-column-rule-color",
258 "column-rule-color", "-webkit-column-rule-style", "column-rule-style",
259 "-moz-column-rule-style", "column-rule-style", "-webkit-column-rule-width",
260 "column-rule-width", "-moz-column-rule-width", "column-rule-width",
261 "-webkit-column-span", "column-span", "column-span", "-webkit-column-width",
262 "column-width", "-moz-column-width", "column-width", "-webkit-columns",
263 "columns", "-moz-columns", "columns", "-ms-content-zoom-chaining",
264 "-ms-content-zoom-limit", "-ms-content-zoom-limit-max",
265 "-ms-content-zoom-limit-min", "-ms-content-zoom-snap",
266 "-ms-content-zoom-snap-points", "-ms-content-zoom-snap-type",
267 "-ms-content-zooming", "-moz-control-character-visibility",
268 "-webkit-cursor-visibility", "-webkit-dashboard-region", "filter",
269 "-webkit-filter", "filter", "filter", "-ms-flex-align", "-ms-flex-item-align",
270 "-ms-flex-line-pack", "-ms-flex-negative", "-ms-flex-order", "-ms-flex-pack",
271 "-ms-flex-positive", "-ms-flex-preferred-size", "-moz-float-edge",
272 "-webkit-flow-from", "-ms-flow-from", "-webkit-flow-into", "-ms-flow-into",
273 "-webkit-font-feature-settings", "-webkit-font-feature-settings",
274 "font-feature-settings", "font-feature-settings", "font-kerning",
275 "-webkit-font-kerning", "font-kerning", "-webkit-font-size-delta",
276 "-webkit-font-size-delta", "-webkit-font-smoothing", "-webkit-font-smoothing",
277 "font-variant-ligatures", "-webkit-font-variant-ligatures",
278 "font-variant-ligatures", "-moz-force-broken-image-icon", "grid",
279 "-webkit-grid", "grid", "grid-area", "-webkit-grid-area", "grid-area",
280 "grid-auto-columns", "-webkit-grid-auto-columns", "grid-auto-columns",
281 "grid-auto-flow", "-webkit-grid-auto-flow", "grid-auto-flow", "grid-auto-rows",
282 "-webkit-grid-auto-rows", "grid-auto-rows", "grid-column",
283 "-webkit-grid-column", "grid-column", "-ms-grid-column",
284 "-ms-grid-column-align", "grid-column-end", "-webkit-grid-column-end",
285 "grid-column-end", "-ms-grid-column-span", "grid-column-start",
286 "-webkit-grid-column-start", "grid-column-start", "-ms-grid-columns",
287 "grid-row", "-webkit-grid-row", "grid-row", "-ms-grid-row",
288 "-ms-grid-row-align", "grid-row-end", "-webkit-grid-row-end", "grid-row-end",
289 "-ms-grid-row-span", "grid-row-start", "-webkit-grid-row-start",
290 "grid-row-start", "-ms-grid-rows", "grid-template", "-webkit-grid-template",
291 "grid-template", "grid-template-areas", "-webkit-grid-template-areas",
292 "grid-template-areas", "grid-template-columns",
293 "-webkit-grid-template-columns", "grid-template-columns", "grid-template-rows",
294 "-webkit-grid-template-rows", "grid-template-rows", "-ms-high-contrast-adjust",
295 "-webkit-highlight", "-webkit-hyphenate-character",
296 "-webkit-hyphenate-character", "-webkit-hyphenate-limit-after",
297 "-webkit-hyphenate-limit-before", "-ms-hyphenate-limit-chars",
298 "-webkit-hyphenate-limit-lines", "-ms-hyphenate-limit-lines",
299 "-ms-hyphenate-limit-zone", "-webkit-hyphens", "-moz-hyphens", "-ms-hyphens",
300 "-moz-image-region", "-ms-ime-align", "-webkit-initial-letter",
301 "-ms-interpolation-mode", "justify-self", "-webkit-justify-self",
302 "-webkit-line-align", "-webkit-line-box-contain", "-webkit-line-box-contain",
303 "-webkit-line-break", "-webkit-line-break", "line-break", "-webkit-line-clamp",
304 "-webkit-line-clamp", "-webkit-line-grid", "-webkit-line-snap",
305 "-webkit-locale", "-webkit-locale", "-webkit-logical-height",
306 "-webkit-logical-height", "-webkit-logical-width", "-webkit-logical-width",
307 "-webkit-margin-after", "-webkit-margin-after",
308 "-webkit-margin-after-collapse", "-webkit-margin-after-collapse",
309 "-webkit-margin-before", "-webkit-margin-before",
310 "-webkit-margin-before-collapse", "-webkit-margin-before-collapse",
311 "-webkit-margin-bottom-collapse", "-webkit-margin-bottom-collapse",
312 "-webkit-margin-collapse", "-webkit-margin-collapse", "-webkit-margin-end",
313 "-webkit-margin-end", "-moz-margin-end", "-webkit-margin-start",
314 "-webkit-margin-start", "-moz-margin-start", "-webkit-margin-top-collapse",
315 "-webkit-margin-top-collapse", "-webkit-marquee", "-webkit-marquee-direction",
316 "-webkit-marquee-increment", "-webkit-marquee-repetition",
317 "-webkit-marquee-speed", "-webkit-marquee-style", "mask", "-webkit-mask",
318 "mask", "-webkit-mask-box-image", "-webkit-mask-box-image",
319 "-webkit-mask-box-image-outset", "-webkit-mask-box-image-outset",
320 "-webkit-mask-box-image-repeat", "-webkit-mask-box-image-repeat",
321 "-webkit-mask-box-image-slice", "-webkit-mask-box-image-slice",
322 "-webkit-mask-box-image-source", "-webkit-mask-box-image-source",
323 "-webkit-mask-box-image-width", "-webkit-mask-box-image-width",
324 "-webkit-mask-clip", "-webkit-mask-clip", "-webkit-mask-composite",
325 "-webkit-mask-composite", "-webkit-mask-image", "-webkit-mask-image",
326 "-webkit-mask-origin", "-webkit-mask-origin", "-webkit-mask-position",
327 "-webkit-mask-position", "-webkit-mask-position-x", "-webkit-mask-position-x",
328 "-webkit-mask-position-y", "-webkit-mask-position-y", "-webkit-mask-repeat",
329 "-webkit-mask-repeat", "-webkit-mask-repeat-x", "-webkit-mask-repeat-x",
330 "-webkit-mask-repeat-y", "-webkit-mask-repeat-y", "-webkit-mask-size",
331 "-webkit-mask-size", "mask-source-type", "-webkit-mask-source-type",
332 "-moz-math-display", "-moz-math-variant", "-webkit-max-logical-height",
333 "-webkit-max-logical-height", "-webkit-max-logical-width",
334 "-webkit-max-logical-width", "-webkit-min-logical-height",
335 "-webkit-min-logical-height", "-webkit-min-logical-width",
336 "-webkit-min-logical-width", "-webkit-nbsp-mode", "-moz-orient",
337 "-moz-osx-font-smoothing", "-moz-outline-radius",
338 "-moz-outline-radius-bottomleft", "-moz-outline-radius-bottomright",
339 "-moz-outline-radius-topleft", "-moz-outline-radius-topright",
340 "-webkit-overflow-scrolling", "-ms-overflow-style", "-webkit-padding-after",
341 "-webkit-padding-after", "-webkit-padding-before", "-webkit-padding-before",
342 "-webkit-padding-end", "-webkit-padding-end", "-moz-padding-end",
343 "-webkit-padding-start", "-webkit-padding-start", "-moz-padding-start",
344 "perspective", "-webkit-perspective", "perspective", "perspective",
345 "perspective-origin", "-webkit-perspective-origin", "perspective-origin",
346 "perspective-origin", "-webkit-perspective-origin-x",
347 "-webkit-perspective-origin-x", "perspective-origin-x",
348 "-webkit-perspective-origin-y", "-webkit-perspective-origin-y",
349 "perspective-origin-y", "-webkit-print-color-adjust",
350 "-webkit-print-color-adjust", "-webkit-region-break-after",
351 "-webkit-region-break-before", "-webkit-region-break-inside",
352 "-webkit-region-fragment", "-webkit-rtl-ordering", "-webkit-rtl-ordering",
353 "-webkit-ruby-position", "-webkit-ruby-position", "ruby-position",
354 "-moz-script-level", "-moz-script-min-size", "-moz-script-size-multiplier",
355 "-ms-scroll-chaining", "-ms-scroll-limit", "-ms-scroll-limit-x-max",
356 "-ms-scroll-limit-x-min", "-ms-scroll-limit-y-max", "-ms-scroll-limit-y-min",
357 "-ms-scroll-rails", "-webkit-scroll-snap-coordinate",
358 "-webkit-scroll-snap-destination", "-webkit-scroll-snap-points-x",
359 "-ms-scroll-snap-points-x", "-webkit-scroll-snap-points-y",
360 "-ms-scroll-snap-points-y", "-webkit-scroll-snap-type", "-ms-scroll-snap-type",
361 "-ms-scroll-snap-x", "-ms-scroll-snap-y", "-ms-scroll-translation",
362 "-ms-scrollbar-3dlight-color", "shape-image-threshold",
363 "-webkit-shape-image-threshold", "shape-margin", "-webkit-shape-margin",
364 "shape-outside", "-webkit-shape-outside", "-moz-stack-sizing", "tab-size",
365 "tab-size", "-moz-tab-size", "-webkit-tap-highlight-color",
366 "-webkit-tap-highlight-color", "text-align-last", "-webkit-text-align-last",
367 "-moz-text-align-last", "text-align-last", "-webkit-text-combine",
368 "-webkit-text-combine", "-ms-text-combine-horizontal", "text-decoration-color",
369 "-webkit-text-decoration-color", "text-decoration-color",
370 "text-decoration-color", "text-decoration-line",
371 "-webkit-text-decoration-line", "text-decoration-line",
372 "-webkit-text-decoration-skip", "text-decoration-style",
373 "-webkit-text-decoration-style", "text-decoration-style",
374 "-webkit-text-decorations-in-effect", "-webkit-text-decorations-in-effect",
375 "-webkit-text-emphasis", "text-emphasis", "-webkit-text-emphasis-color",
376 "text-emphasis-color", "-webkit-text-emphasis-position",
377 "text-emphasis-position", "-webkit-text-emphasis-style", "text-emphasis-style",
378 "-webkit-text-fill-color", "-webkit-text-fill-color", "text-justify",
379 "-webkit-text-justify", "text-justify", "-webkit-text-orientation",
380 "-webkit-text-orientation", "text-orientation", "-webkit-text-security",
381 "-webkit-text-security", "-webkit-text-size-adjust", "-moz-text-size-adjust",
382 "-ms-text-size-adjust", "-webkit-text-stroke", "-webkit-text-stroke",
383 "-webkit-text-stroke-color", "-webkit-text-stroke-color",
384 "-webkit-text-stroke-width", "-webkit-text-stroke-width",
385 "text-underline-position", "-webkit-text-underline-position",
386 "text-underline-position", "-webkit-touch-callout", "-ms-touch-select",
387 "transform", "-webkit-transform", "transform", "transform", "transform-origin",
388 "-webkit-transform-origin", "transform-origin", "transform-origin",
389 "-webkit-transform-origin-x", "-webkit-transform-origin-x",
390 "transform-origin-x", "-webkit-transform-origin-y",
391 "-webkit-transform-origin-y", "transform-origin-y",
392 "-webkit-transform-origin-z", "-webkit-transform-origin-z",
393 "transform-origin-z", "transform-style", "-webkit-transform-style",
394 "transform-style", "transform-style", "-webkit-user-drag", "-webkit-user-drag",
395 "-moz-user-focus", "-moz-user-input", "-webkit-user-modify",
396 "-webkit-user-modify", "-moz-user-modify", "-webkit-user-select",
397 "-webkit-user-select", "-moz-user-select", "-ms-user-select",
398 "-moz-window-dragging", "-moz-window-shadow", "-ms-wrap-flow",
399 "-ms-wrap-margin", "-ms-wrap-through", "writing-mode", "-webkit-writing-mode",
400 "writing-mode", "writing-mode",
401 ]
402
403 all_styles = standard_styles + all_prefixed_styles No newline at end of file
@@ -1,499 +1,515 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Renderer for markup languages with ability to parse using rst or markdown
24 24 """
25 25
26 26 import re
27 27 import os
28 28 import lxml
29 29 import logging
30 30 import urlparse
31 import bleach
31 32
32 33 from mako.lookup import TemplateLookup
33 34 from mako.template import Template as MakoTemplate
34 35
35 36 from docutils.core import publish_parts
36 37 from docutils.parsers.rst import directives
37 38 from docutils import writers
38 39 from docutils.writers import html4css1
39 40 import markdown
40 41
41 42 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
42 43 from rhodecode.lib.utils2 import (
43 44 safe_str, safe_unicode, md5_safe, MENTIONS_REGEX)
44 45
45 46 log = logging.getLogger(__name__)
46 47
47 48 # default renderer used to generate automated comments
48 49 DEFAULT_COMMENTS_RENDERER = 'rst'
49 50
50 51
51 52 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
52 53 """
53 54 Custom HTML Translator used for sandboxing potential
54 55 JS injections in ref links
55 56 """
56 57
57 58 def visit_reference(self, node):
58 59 if 'refuri' in node.attributes:
59 60 refuri = node['refuri']
60 61 if ':' in refuri:
61 62 prefix, link = refuri.lstrip().split(':', 1)
62 63 if prefix == 'javascript':
63 64 # we don't allow javascript type of refs...
64 65 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
65 66
66 67 # old style class requires this...
67 68 return html4css1.HTMLTranslator.visit_reference(self, node)
68 69
69 70
70 71 class RhodeCodeWriter(writers.html4css1.Writer):
71 72 def __init__(self):
72 73 writers.Writer.__init__(self)
73 74 self.translator_class = CustomHTMLTranslator
74 75
75 76
76 77 def relative_links(html_source, server_paths):
77 78 if not html_source:
78 79 return html_source
79 80
80 81 try:
81 82 from lxml.html import fromstring
82 83 from lxml.html import tostring
83 84 except ImportError:
84 85 log.exception('Failed to import lxml')
85 86 return html_source
86 87
87 88 try:
88 89 doc = lxml.html.fromstring(html_source)
89 90 except Exception:
90 91 return html_source
91 92
92 93 for el in doc.cssselect('img, video'):
93 94 src = el.attrib.get('src')
94 95 if src:
95 96 el.attrib['src'] = relative_path(src, server_paths['raw'])
96 97
97 98 for el in doc.cssselect('a:not(.gfm)'):
98 99 src = el.attrib.get('href')
99 100 if src:
100 101 raw_mode = el.attrib['href'].endswith('?raw=1')
101 102 if raw_mode:
102 103 el.attrib['href'] = relative_path(src, server_paths['raw'])
103 104 else:
104 105 el.attrib['href'] = relative_path(src, server_paths['standard'])
105 106
106 107 return lxml.html.tostring(doc)
107 108
108 109
109 110 def relative_path(path, request_path, is_repo_file=None):
110 111 """
111 112 relative link support, path is a rel path, and request_path is current
112 113 server path (not absolute)
113 114
114 115 e.g.
115 116
116 117 path = '../logo.png'
117 118 request_path= '/repo/files/path/file.md'
118 119 produces: '/repo/files/logo.png'
119 120 """
120 121 # TODO(marcink): unicode/str support ?
121 122 # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:'))
122 123
123 124 def dummy_check(p):
124 125 return True # assume default is a valid file path
125 126
126 127 is_repo_file = is_repo_file or dummy_check
127 128 if not path:
128 129 return request_path
129 130
130 131 path = safe_unicode(path)
131 132 request_path = safe_unicode(request_path)
132 133
133 134 if path.startswith((u'data:', u'javascript:', u'#', u':')):
134 135 # skip data, anchor, invalid links
135 136 return path
136 137
137 138 is_absolute = bool(urlparse.urlparse(path).netloc)
138 139 if is_absolute:
139 140 return path
140 141
141 142 if not request_path:
142 143 return path
143 144
144 145 if path.startswith(u'/'):
145 146 path = path[1:]
146 147
147 148 if path.startswith(u'./'):
148 149 path = path[2:]
149 150
150 151 parts = request_path.split('/')
151 152 # compute how deep we need to traverse the request_path
152 153 depth = 0
153 154
154 155 if is_repo_file(request_path):
155 156 # if request path is a VALID file, we use a relative path with
156 157 # one level up
157 158 depth += 1
158 159
159 160 while path.startswith(u'../'):
160 161 depth += 1
161 162 path = path[3:]
162 163
163 164 if depth > 0:
164 165 parts = parts[:-depth]
165 166
166 167 parts.append(path)
167 168 final_path = u'/'.join(parts).lstrip(u'/')
168 169
169 170 return u'/' + final_path
170 171
171 172
172 173 class MarkupRenderer(object):
173 174 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
174 175
175 176 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
176 177 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
177 178 JUPYTER_PAT = re.compile(r'\.(ipynb)$', re.IGNORECASE)
178 179 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
179 180
180 181 URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
181 182 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
182 183
183 184 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
185 output_format = 'html4'
184 186 markdown_renderer = markdown.Markdown(
185 extensions, safe_mode=True, enable_attributes=False)
187 extensions, enable_attributes=False, output_format=output_format)
186 188
187 189 markdown_renderer_flavored = markdown.Markdown(
188 extensions + [GithubFlavoredMarkdownExtension()], safe_mode=True,
189 enable_attributes=False)
190 extensions + [GithubFlavoredMarkdownExtension()],
191 enable_attributes=False, output_format=output_format)
190 192
191 193 # extension together with weights. Lower is first means we control how
192 194 # extensions are attached to readme names with those.
193 195 PLAIN_EXTS = [
194 196 # prefer no extension
195 197 ('', 0), # special case that renders READMES names without extension
196 198 ('.text', 2), ('.TEXT', 2),
197 199 ('.txt', 3), ('.TXT', 3)
198 200 ]
199 201
200 202 RST_EXTS = [
201 203 ('.rst', 1), ('.rest', 1),
202 204 ('.RST', 2), ('.REST', 2)
203 205 ]
204 206
205 207 MARKDOWN_EXTS = [
206 208 ('.md', 1), ('.MD', 1),
207 209 ('.mkdn', 2), ('.MKDN', 2),
208 210 ('.mdown', 3), ('.MDOWN', 3),
209 211 ('.markdown', 4), ('.MARKDOWN', 4)
210 212 ]
211 213
212 214 def _detect_renderer(self, source, filename=None):
213 215 """
214 216 runs detection of what renderer should be used for generating html
215 217 from a markup language
216 218
217 219 filename can be also explicitly a renderer name
218 220
219 221 :param source:
220 222 :param filename:
221 223 """
222 224
223 225 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
224 226 detected_renderer = 'markdown'
225 227 elif MarkupRenderer.RST_PAT.findall(filename):
226 228 detected_renderer = 'rst'
227 229 elif MarkupRenderer.JUPYTER_PAT.findall(filename):
228 230 detected_renderer = 'jupyter'
229 231 elif MarkupRenderer.PLAIN_PAT.findall(filename):
230 232 detected_renderer = 'plain'
231 233 else:
232 234 detected_renderer = 'plain'
233 235
234 236 return getattr(MarkupRenderer, detected_renderer)
235 237
236 238 @classmethod
239 def bleach_clean(cls, text):
240 from .bleach_whitelist import markdown_attrs, markdown_tags
241 allowed_tags = markdown_tags
242 allowed_attrs = markdown_attrs
243 return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs)
244
245 @classmethod
237 246 def renderer_from_filename(cls, filename, exclude):
238 247 """
239 248 Detect renderer markdown/rst from filename and optionally use exclude
240 249 list to remove some options. This is mostly used in helpers.
241 250 Returns None when no renderer can be detected.
242 251 """
243 252 def _filter(elements):
244 253 if isinstance(exclude, (list, tuple)):
245 254 return [x for x in elements if x not in exclude]
246 255 return elements
247 256
248 257 if filename.endswith(
249 258 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
250 259 return 'markdown'
251 260 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
252 261 return 'rst'
253 262
254 263 return None
255 264
256 265 def render(self, source, filename=None):
257 266 """
258 267 Renders a given filename using detected renderer
259 268 it detects renderers based on file extension or mimetype.
260 269 At last it will just do a simple html replacing new lines with <br/>
261 270
262 271 :param file_name:
263 272 :param source:
264 273 """
265 274
266 275 renderer = self._detect_renderer(source, filename)
267 276 readme_data = renderer(source)
268 277 return readme_data
269 278
270 279 @classmethod
271 280 def _flavored_markdown(cls, text):
272 281 """
273 282 Github style flavored markdown
274 283
275 284 :param text:
276 285 """
277 286
278 287 # Extract pre blocks.
279 288 extractions = {}
280 289
281 290 def pre_extraction_callback(matchobj):
282 291 digest = md5_safe(matchobj.group(0))
283 292 extractions[digest] = matchobj.group(0)
284 293 return "{gfm-extraction-%s}" % digest
285 294 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
286 295 text = re.sub(pattern, pre_extraction_callback, text)
287 296
288 297 # Prevent foo_bar_baz from ending up with an italic word in the middle.
289 298 def italic_callback(matchobj):
290 299 s = matchobj.group(0)
291 300 if list(s).count('_') >= 2:
292 301 return s.replace('_', r'\_')
293 302 return s
294 303 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
295 304
296 305 # Insert pre block extractions.
297 306 def pre_insert_callback(matchobj):
298 307 return '\n\n' + extractions[matchobj.group(1)]
299 308 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
300 309 pre_insert_callback, text)
301 310
302 311 return text
303 312
304 313 @classmethod
305 314 def urlify_text(cls, text):
306 315 def url_func(match_obj):
307 316 url_full = match_obj.groups()[0]
308 317 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
309 318
310 319 return cls.URL_PAT.sub(url_func, text)
311 320
312 321 @classmethod
313 322 def plain(cls, source, universal_newline=True):
314 323 source = safe_unicode(source)
315 324 if universal_newline:
316 325 newline = '\n'
317 326 source = newline.join(source.splitlines())
318 327
319 328 source = cls.urlify_text(source)
320 329 return '<br />' + source.replace("\n", '<br />')
321 330
322 331 @classmethod
323 def markdown(cls, source, safe=True, flavored=True, mentions=False):
324 # It does not allow to insert inline HTML. In presence of HTML tags, it
325 # will replace them instead with [HTML_REMOVED]. This is controlled by
326 # the safe_mode=True parameter of the markdown method.
332 def markdown(cls, source, safe=True, flavored=True, mentions=False,
333 clean_html=True):
334 """
335 returns markdown rendered code cleaned by the bleach library
336 """
327 337
328 338 if flavored:
329 339 markdown_renderer = cls.markdown_renderer_flavored
330 340 else:
331 341 markdown_renderer = cls.markdown_renderer
332 342
333 343 if mentions:
334 344 mention_pat = re.compile(MENTIONS_REGEX)
335 345
336 346 def wrapp(match_obj):
337 347 uname = match_obj.groups()[0]
338 348 return ' **@%(uname)s** ' % {'uname': uname}
339 349 mention_hl = mention_pat.sub(wrapp, source).strip()
340 350 # we extracted mentions render with this using Mentions false
341 351 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
342 352 mentions=False)
343 353
344 354 source = safe_unicode(source)
355
345 356 try:
346 357 if flavored:
347 358 source = cls._flavored_markdown(source)
348 return markdown_renderer.convert(source)
359 rendered = markdown_renderer.convert(source)
360 if clean_html:
361 rendered = cls.bleach_clean(rendered)
362 return rendered
349 363 except Exception:
350 364 log.exception('Error when rendering Markdown')
351 365 if safe:
352 366 log.debug('Fallback to render in plain mode')
353 367 return cls.plain(source)
354 368 else:
355 369 raise
356 370
357 371 @classmethod
358 def rst(cls, source, safe=True, mentions=False):
372 def rst(cls, source, safe=True, mentions=False, clean_html=False):
359 373 if mentions:
360 374 mention_pat = re.compile(MENTIONS_REGEX)
361 375
362 376 def wrapp(match_obj):
363 377 uname = match_obj.groups()[0]
364 378 return ' **@%(uname)s** ' % {'uname': uname}
365 379 mention_hl = mention_pat.sub(wrapp, source).strip()
366 380 # we extracted mentions render with this using Mentions false
367 381 return cls.rst(mention_hl, safe=safe, mentions=False)
368 382
369 383 source = safe_unicode(source)
370 384 try:
371 385 docutils_settings = dict(
372 386 [(alias, None) for alias in
373 387 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
374 388
375 docutils_settings.update({'input_encoding': 'unicode',
376 'report_level': 4})
389 docutils_settings.update({
390 'input_encoding': 'unicode', 'report_level': 4})
377 391
378 392 for k, v in docutils_settings.iteritems():
379 393 directives.register_directive(k, v)
380 394
381 395 parts = publish_parts(source=source,
382 396 writer=RhodeCodeWriter(),
383 397 settings_overrides=docutils_settings)
384
385 return parts['html_title'] + parts["fragment"]
398 rendered = parts["fragment"]
399 if clean_html:
400 rendered = cls.bleach_clean(rendered)
401 return parts['html_title'] + rendered
386 402 except Exception:
387 403 log.exception('Error when rendering RST')
388 404 if safe:
389 405 log.debug('Fallbacking to render in plain mode')
390 406 return cls.plain(source)
391 407 else:
392 408 raise
393 409
394 410 @classmethod
395 411 def jupyter(cls, source, safe=True):
396 412 from rhodecode.lib import helpers
397 413
398 414 from traitlets.config import Config
399 415 import nbformat
400 416 from nbconvert import HTMLExporter
401 417 from nbconvert.preprocessors import Preprocessor
402 418
403 419 class CustomHTMLExporter(HTMLExporter):
404 420 def _template_file_default(self):
405 421 return 'basic'
406 422
407 423 class Sandbox(Preprocessor):
408 424
409 425 def preprocess(self, nb, resources):
410 426 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
411 427 for cell in nb['cells']:
412 428 if safe and 'outputs' in cell:
413 429 for cell_output in cell['outputs']:
414 430 if 'data' in cell_output:
415 431 if 'application/javascript' in cell_output['data']:
416 432 cell_output['data']['text/plain'] = sandbox_text
417 433 cell_output['data'].pop('application/javascript', None)
418 434 return nb, resources
419 435
420 436 def _sanitize_resources(resources):
421 437 """
422 438 Skip/sanitize some of the CSS generated and included in jupyter
423 439 so it doesn't messes up UI so much
424 440 """
425 441
426 442 # TODO(marcink): probably we should replace this with whole custom
427 443 # CSS set that doesn't screw up, but jupyter generated html has some
428 444 # special markers, so it requires Custom HTML exporter template with
429 445 # _default_template_path_default, to achieve that
430 446
431 447 # strip the reset CSS
432 448 resources[0] = resources[0][resources[0].find('/*! Source'):]
433 449 return resources
434 450
435 451 def as_html(notebook):
436 452 conf = Config()
437 453 conf.CustomHTMLExporter.preprocessors = [Sandbox]
438 454 html_exporter = CustomHTMLExporter(config=conf)
439 455
440 456 (body, resources) = html_exporter.from_notebook_node(notebook)
441 457 header = '<!-- ## IPYTHON NOTEBOOK RENDERING ## -->'
442 458 js = MakoTemplate(r'''
443 459 <!-- Load mathjax -->
444 460 <!-- MathJax configuration -->
445 461 <script type="text/x-mathjax-config">
446 462 MathJax.Hub.Config({
447 463 jax: ["input/TeX","output/HTML-CSS", "output/PreviewHTML"],
448 464 extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "[Contrib]/a11y/accessibility-menu.js"],
449 465 TeX: {
450 466 extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
451 467 },
452 468 tex2jax: {
453 469 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
454 470 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
455 471 processEscapes: true,
456 472 processEnvironments: true
457 473 },
458 474 // Center justify equations in code and markdown cells. Elsewhere
459 475 // we use CSS to left justify single line equations in code cells.
460 476 displayAlign: 'center',
461 477 "HTML-CSS": {
462 478 styles: {'.MathJax_Display': {"margin": 0}},
463 479 linebreaks: { automatic: true },
464 480 availableFonts: ["STIX", "TeX"]
465 481 },
466 482 showMathMenu: false
467 483 });
468 484 </script>
469 485 <!-- End of mathjax configuration -->
470 486 <script src="${h.asset('js/src/math_jax/MathJax.js')}"></script>
471 487 ''').render(h=helpers)
472 488
473 489 css = '<style>{}</style>'.format(
474 490 ''.join(_sanitize_resources(resources['inlining']['css'])))
475 491
476 492 body = '\n'.join([header, css, js, body])
477 493 return body, resources
478 494
479 495 notebook = nbformat.reads(source, as_version=4)
480 496 (body, resources) = as_html(notebook)
481 497 return body
482 498
483 499
484 500 class RstTemplateRenderer(object):
485 501
486 502 def __init__(self):
487 503 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
488 504 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
489 505 self.template_store = TemplateLookup(
490 506 directories=rst_template_dirs,
491 507 input_encoding='utf-8',
492 508 imports=['from rhodecode.lib import helpers as h'])
493 509
494 510 def _get_template(self, templatename):
495 511 return self.template_store.get_template(templatename)
496 512
497 513 def render(self, template_name, **kwargs):
498 514 template = self._get_template(template_name)
499 515 return template.render(**kwargs)
@@ -1,258 +1,678 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib.markup_renderer import (
24 24 MarkupRenderer, RstTemplateRenderer, relative_path, relative_links)
25 25
26 26
27 27 @pytest.mark.parametrize(
28 28 "filename, expected_renderer",
29 29 [
30 30 ('readme.md', 'markdown'),
31 31 ('readme.Md', 'markdown'),
32 32 ('readme.MdoWn', 'markdown'),
33 33 ('readme.rst', 'rst'),
34 34 ('readme.Rst', 'rst'),
35 35 ('readme.rest', 'rst'),
36 36 ('readme.rest', 'rst'),
37 37
38 38 ('markdown.xml', 'plain'),
39 39 ('rest.xml', 'plain'),
40 40 ('readme.xml', 'plain'),
41 41
42 42 ('readme', 'plain'),
43 43 ('README', 'plain'),
44 44 ('readme.mdx', 'plain'),
45 45 ('readme.rstx', 'plain'),
46 46 ('readmex', 'plain'),
47 47 ])
48 48 def test_detect_renderer(filename, expected_renderer):
49 49 detected_renderer = MarkupRenderer()._detect_renderer(
50 50 '', filename=filename).__name__
51 51 assert expected_renderer == detected_renderer
52 52
53 53
54 54 def test_markdown_xss_link():
55 55 xss_md = "[link](javascript:alert('XSS: pwned!'))"
56 56 rendered_html = MarkupRenderer.markdown(xss_md)
57 57 assert 'href="javascript:alert(\'XSS: pwned!\')"' not in rendered_html
58 58
59 59
60 60 def test_markdown_xss_inline_html():
61 61 xss_md = '\n'.join([
62 62 '> <a name="n"',
63 63 '> href="javascript:alert(\'XSS: pwned!\')">link</a>'])
64 64 rendered_html = MarkupRenderer.markdown(xss_md)
65 65 assert 'href="javascript:alert(\'XSS: pwned!\')">' not in rendered_html
66 66
67 67
68 68 def test_markdown_inline_html():
69 69 xss_md = '\n'.join(['> <a name="n"',
70 '> href="https://rhodecode.com">link</a>'])
70 '> onload="javascript:alert()" href="https://rhodecode.com">link</a>'])
71 71 rendered_html = MarkupRenderer.markdown(xss_md)
72 assert '[HTML_REMOVED]link[HTML_REMOVED]' in rendered_html
72 assert '<a href="https://rhodecode.com" name="n">link</a>' in rendered_html
73
74
75 def test_markdown_bleach_renders_correct():
76 test_md = """
77 This is intended as a quick reference and showcase. For more complete info, see [John Gruber's original spec](http://daringfireball.net/projects/markdown/) and the [Github-flavored Markdown info page](http://github.github.com/github-flavored-markdown/).
78
79 Note that there is also a [Cheatsheet specific to Markdown Here](./Markdown-Here-Cheatsheet) if that's what you're looking for. You can also check out [more Markdown tools](./Other-Markdown-Tools).
80
81 ##### Table of Contents
82 [Headers](#headers)
83 [Emphasis](#emphasis)
84 [Lists](#lists)
85 [Links](#links)
86 [Images](#images)
87 [Code and Syntax Highlighting](#code)
88 [Tables](#tables)
89 [Blockquotes](#blockquotes)
90 [Inline HTML](#html)
91 [Horizontal Rule](#hr)
92 [Line Breaks](#lines)
93 [Youtube videos](#videos)
94
95
96 ## Headers
97
98 ```no-highlight
99 # H1
100 ## H2
101 ### H3
102 #### H4
103 ##### H5
104 ###### H6
105
106 Alternatively, for H1 and H2, an underline-ish style:
107
108 Alt-H1
109 ======
110
111 Alt-H2
112 ------
113 ```
114
115 # H1
116 ## H2
117 ### H3
118 #### H4
119 ##### H5
120 ###### H6
121
122 Alternatively, for H1 and H2, an underline-ish style:
123
124 Alt-H1
125 ======
126
127 Alt-H2
128 ------
129
130 ## Emphasis
131
132 ```no-highlight
133 Emphasis, aka italics, with *asterisks* or _underscores_.
134
135 Strong emphasis, aka bold, with **asterisks** or __underscores__.
136
137 Combined emphasis with **asterisks and _underscores_**.
138
139 Strikethrough uses two tildes. ~~Scratch this.~~
140 ```
141
142 Emphasis, aka italics, with *asterisks* or _underscores_.
143
144 Strong emphasis, aka bold, with **asterisks** or __underscores__.
145
146 Combined emphasis with **asterisks and _underscores_**.
147
148 Strikethrough uses two tildes. ~~Scratch this.~~
149
150
151 ## Lists
152
153 (In this example, leading and trailing spaces are shown with with dots: ⋅)
154
155 ```no-highlight
156 1. First ordered list item
157 2. Another item
158 ⋅⋅* Unordered sub-list.
159 1. Actual numbers don't matter, just that it's a number
160 ⋅⋅1. Ordered sub-list
161 4. And another item.
162
163 ⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
164
165 ⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅
166 ⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅
167 ⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
168
169 * Unordered list can use asterisks
170 - Or minuses
171 + Or pluses
172 ```
173
174 1. First ordered list item
175 2. Another item
176 * Unordered sub-list.
177 1. Actual numbers don't matter, just that it's a number
178 1. Ordered sub-list
179 4. And another item.
180
181 You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
182
183 To have a line break without a paragraph, you will need to use two trailing spaces.
184 Note that this line is separate, but within the same paragraph.
185 (This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
186
187 * Unordered list can use asterisks
188 - Or minuses
189 + Or pluses
190
191
192 ## Links
193
194 There are two ways to create links.
195
196 ```no-highlight
197 [I'm an inline-style link](https://www.google.com)
198
199 [I'm an inline-style link with title](https://www.google.com "Google's Homepage")
200
201 [I'm a reference-style link][Arbitrary case-insensitive reference text]
202
203 [I'm a relative reference to a repository file (LICENSE)](./LICENSE)
204
205 [I'm a relative reference to a repository file (IMAGE)](./img/logo.png)
206
207 [I'm a relative reference to a repository file (IMAGE2)](img/logo.png)
208
209 [You can use numbers for reference-style link definitions][1]
210
211 Or leave it empty and use the [link text itself].
212
213 URLs and URLs in angle brackets will automatically get turned into links.
214 http://www.example.com or <http://www.example.com> and sometimes
215 example.com (but not on Github, for example).
216
217 Some text to show that the reference links can follow later.
218
219 [arbitrary case-insensitive reference text]: https://www.mozilla.org
220 [1]: http://slashdot.org
221 [link text itself]: http://www.reddit.com
222 ```
223
224 [I'm an inline-style link](https://www.google.com)
225
226 [I'm an inline-style link with title](https://www.google.com "Google's Homepage")
227
228 [I'm a reference-style link][Arbitrary case-insensitive reference text]
229
230 [I'm a relative reference to a repository file (LICENSE)](./LICENSE)
231
232 [I'm a relative reference to a repository file (IMAGE)](./img/logo.png)
233
234 [I'm a relative reference to a repository file (IMAGE2)](img/logo.png)
235
236 [You can use numbers for reference-style link definitions][1]
237
238 Or leave it empty and use the [link text itself].
239
240 URLs and URLs in angle brackets will automatically get turned into links.
241 http://www.example.com or <http://www.example.com> and sometimes
242 example.com (but not on Github, for example).
243
244 Some text to show that the reference links can follow later.
245
246 [arbitrary case-insensitive reference text]: https://www.mozilla.org
247 [1]: http://slashdot.org
248 [link text itself]: http://www.reddit.com
249
250
251 ## Images
252
253 ```no-highlight
254 Here's our logo (hover to see the title text):
255
256 Inline-style:
257 ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1")
258
259 relative-src-style:
260 ![alt text](img/logo.png)
261
262 Reference-style:
263 ![alt text][logo]
264
265 [logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2"
266 ```
267
268 Here's our logo (hover to see the title text):
269
270 Inline-style:
271 ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1")
272
273 relative-src-style:
274 ![alt text](img/logo.png)
275
276 relative-src-style:
277 ![alt text](./img/logo.png)
278
279 Reference-style:
280 ![alt text][logo]
281
282 [logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2"
283
284
285 ## Code and Syntax Highlighting
286
287 Code blocks are part of the Markdown spec, but syntax highlighting isn't. However, many renderers -- like Github's and *Markdown Here* -- support syntax highlighting. Which languages are supported and how those language names should be written will vary from renderer to renderer. *Markdown Here* supports highlighting for dozens of languages (and not-really-languages, like diffs and HTTP headers); to see the complete list, and how to write the language names, see the [highlight.js demo page](http://softwaremaniacs.org/media/soft/highlight/test.html).
288
289 ```no-highlight
290 Inline `code` has `back-ticks around` it.
291 ```
292
293 Inline `code` has `back-ticks around` it.
294
295 Blocks of code are either fenced by lines with three back-ticks <code>```</code>, or are indented with four spaces. I recommend only using the fenced code blocks -- they're easier and only they support syntax highlighting.
296
297 ```javascript
298 var s = "JavaScript syntax highlighting";
299 console.log(s);
300 ```
301
302 ```python
303 s = "Python syntax highlighting"
304 print s
305 ```
306
307 ```
308 No language indicated, so no syntax highlighting.
309 But let's throw in a &lt;b&gt;tag&lt;/b&gt;.
310 ```
311
312
313 ```javascript
314 var s = "JavaScript syntax highlighting";
315 alert(s);
316 ```
317
318 ```python
319 s = "Python syntax highlighting"
320 print s
321 ```
322
323 ```
324 No language indicated, so no syntax highlighting in Markdown Here (varies on Github).
325 But let's throw in a <b>tag</b>.
326 ```
327
328
329 ## Tables
330
331 Tables aren't part of the core Markdown spec, but they are part of GFM and *Markdown Here* supports them. They are an easy way of adding tables to your email -- a task that would otherwise require copy-pasting from another application.
332
333 ```no-highlight
334 Colons can be used to align columns.
335
336 | Tables | Are | Cool |
337 | ------------- |:-------------:| -----:|
338 | col 3 is | right-aligned | $1600 |
339 | col 2 is | centered | $12 |
340 | zebra stripes | are neat | $1 |
341
342 There must be at least 3 dashes separating each header cell.
343 The outer pipes (|) are optional, and you don't need to make the
344 raw Markdown line up prettily. You can also use inline Markdown.
345
346 Markdown | Less | Pretty
347 --- | --- | ---
348 *Still* | `renders` | **nicely**
349 1 | 2 | 3
350 ```
351
352 Colons can be used to align columns.
353
354 | Tables | Are | Cool |
355 | ------------- |:-------------:| -----:|
356 | col 3 is | right-aligned | $1600 |
357 | col 2 is | centered | $12 |
358 | zebra stripes | are neat | $1 |
359
360 There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
361
362 Markdown | Less | Pretty
363 --- | --- | ---
364 *Still* | `renders` | **nicely**
365 1 | 2 | 3
366
367
368 ## Blockquotes
369
370 ```no-highlight
371 > Blockquotes are very handy in email to emulate reply text.
372 > This line is part of the same quote.
373
374 Quote break.
375
376 > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
377 ```
378
379 > Blockquotes are very handy in email to emulate reply text.
380 > This line is part of the same quote.
381
382 Quote break.
383
384 > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
385
386
387 ## Inline HTML
388
389 You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
390
391 ```no-highlight
392 <dl>
393 <dt>Definition list</dt>
394 <dd>Is something people use sometimes.</dd>
395
396 <dt>Markdown in HTML</dt>
397 <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
398 </dl>
399 ```
400
401 <dl>
402 <dt>Definition list</dt>
403 <dd>Is something people use sometimes.</dd>
404
405 <dt>Markdown in HTML</dt>
406 <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
407 </dl>
408
409
410 ## Horizontal Rule
411
412 ```
413 Three or more...
414
415 ---
416
417 Hyphens
418
419 ***
420
421 Asterisks
422
423 ___
424
425 Underscores
426 ```
427
428 Three or more...
429
430 ---
431
432 Hyphens
433
434 ***
435
436 Asterisks
437
438 ___
439
440 Underscores
441
442
443 ## Line Breaks
444
445 My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
446
447 Here are some things to try out:
448
449 ```
450 Here's a line for us to start with.
451
452 This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
453
454 This line is also a separate paragraph, but...
455 This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
456 ```
457
458 Here's a line for us to start with.
459
460 This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
461
462 This line is also begins a separate paragraph, but...
463 This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
464
465 (Technical note: *Markdown Here* uses GFM line breaks, so there's no need to use MD's two-space line breaks.)
466
467
468 ## Youtube videos
469
470 They can't be added directly but you can add an image with a link to the video like this:
471
472 ```no-highlight
473 <a href="http://www.youtube.com/watch?feature=player_embedded&v=YOUTUBE_VIDEO_ID_HERE
474 " target="_blank"><img src="http://img.youtube.com/vi/YOUTUBE_VIDEO_ID_HERE/0.jpg"
475 alt="IMAGE ALT TEXT HERE" width="240" height="180" border="10" /></a>
476 ```
477
478 Or, in pure Markdown, but losing the image sizing and border:
479
480 ```no-highlight
481 [![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/YOUTUBE_VIDEO_ID_HERE/0.jpg)](http://www.youtube.com/watch?v=YOUTUBE_VIDEO_ID_HERE)
482 ```
483
484 Referencing a bug by #bugID in your git commit links it to the slip. For example #1.
485
486 ---
487
488 License: [CC-BY](https://creativecommons.org/licenses/by/3.0/)
489 """
490 raw_rendered_html = MarkupRenderer.markdown(test_md, clean_html=False)
491 bleached_rendered_html = MarkupRenderer.markdown(test_md, clean_html=True)
492 assert raw_rendered_html == bleached_rendered_html
73 493
74 494
75 495 def test_rst_xss_link():
76 496 xss_rst = "`Link<javascript:alert('XSS: pwned!')>`_"
77 497 rendered_html = MarkupRenderer.rst(xss_rst)
78 498 assert "href=javascript:alert('XSS: pwned!')" not in rendered_html
79 499
80 500
81 501 @pytest.mark.xfail(reason='Bug in docutils. Waiting answer from the author')
82 502 def test_rst_xss_inline_html():
83 503 xss_rst = '<a href="javascript:alert(\'XSS: pwned!\')">link</a>'
84 504 rendered_html = MarkupRenderer.rst(xss_rst)
85 505 assert 'href="javascript:alert(' not in rendered_html
86 506
87 507
88 508 def test_rst_xss_raw_directive():
89 509 xss_rst = '\n'.join([
90 510 '.. raw:: html',
91 511 '',
92 512 ' <a href="javascript:alert(\'XSS: pwned!\')">link</a>'])
93 513 rendered_html = MarkupRenderer.rst(xss_rst)
94 514 assert 'href="javascript:alert(' not in rendered_html
95 515
96 516
97 517 def test_render_rst_template_without_files():
98 518 expected = u'''\
99 519 Pull request updated. Auto status change to |under_review|
100 520
101 521 .. role:: added
102 522 .. role:: removed
103 523 .. parsed-literal::
104 524
105 525 Changed commits:
106 526 * :added:`2 added`
107 527 * :removed:`3 removed`
108 528
109 529 No file changes found
110 530
111 531 .. |under_review| replace:: *"NEW STATUS"*'''
112 532
113 533 params = {
114 534 'under_review_label': 'NEW STATUS',
115 535 'added_commits': ['a', 'b'],
116 536 'removed_commits': ['a', 'b', 'c'],
117 537 'changed_files': [],
118 538 'added_files': [],
119 539 'modified_files': [],
120 540 'removed_files': [],
121 541 }
122 542 renderer = RstTemplateRenderer()
123 543 rendered = renderer.render('pull_request_update.mako', **params)
124 544 assert expected == rendered
125 545
126 546
127 547 def test_render_rst_template_with_files():
128 548 expected = u'''\
129 549 Pull request updated. Auto status change to |under_review|
130 550
131 551 .. role:: added
132 552 .. role:: removed
133 553 .. parsed-literal::
134 554
135 555 Changed commits:
136 556 * :added:`1 added`
137 557 * :removed:`3 removed`
138 558
139 559 Changed files:
140 560 * `A /path/a.py <#a_c--68ed34923b68>`_
141 561 * `A /path/b.js <#a_c--64f90608b607>`_
142 562 * `M /path/d.js <#a_c--85842bf30c6e>`_
143 563 * `M /path/ę.py <#a_c--d713adf009cd>`_
144 564 * R /path/ź.py
145 565
146 566 .. |under_review| replace:: *"NEW STATUS"*'''
147 567
148 568 added = ['/path/a.py', '/path/b.js']
149 569 modified = ['/path/d.js', u'/path/ę.py']
150 570 removed = [u'/path/ź.py']
151 571
152 572 params = {
153 573 'under_review_label': 'NEW STATUS',
154 574 'added_commits': ['a'],
155 575 'removed_commits': ['a', 'b', 'c'],
156 576 'changed_files': added + modified + removed,
157 577 'added_files': added,
158 578 'modified_files': modified,
159 579 'removed_files': removed,
160 580 }
161 581 renderer = RstTemplateRenderer()
162 582 rendered = renderer.render('pull_request_update.mako', **params)
163 583
164 584 assert expected == rendered
165 585
166 586
167 587 def test_render_rst_auto_status_template():
168 588 expected = u'''\
169 589 Auto status change to |new_status|
170 590
171 591 .. |new_status| replace:: *"NEW STATUS"*'''
172 592
173 593 params = {
174 594 'new_status_label': 'NEW STATUS',
175 595 'pull_request': None,
176 596 'commit_id': None,
177 597 }
178 598 renderer = RstTemplateRenderer()
179 599 rendered = renderer.render('auto_status_change.mako', **params)
180 600 assert expected == rendered
181 601
182 602
183 603 @pytest.mark.parametrize(
184 604 "src_path, server_path, is_path, expected",
185 605 [
186 606 ('source.png', '/repo/files/path', lambda p: False,
187 607 '/repo/files/path/source.png'),
188 608
189 609 ('source.png', 'mk/git/blob/master/README.md', lambda p: True,
190 610 '/mk/git/blob/master/source.png'),
191 611
192 612 ('./source.png', 'mk/git/blob/master/README.md', lambda p: True,
193 613 '/mk/git/blob/master/source.png'),
194 614
195 615 ('/source.png', 'mk/git/blob/master/README.md', lambda p: True,
196 616 '/mk/git/blob/master/source.png'),
197 617
198 618 ('./source.png', 'repo/files/path/source.md', lambda p: True,
199 619 '/repo/files/path/source.png'),
200 620
201 621 ('./source.png', '/repo/files/path/file.md', lambda p: True,
202 622 '/repo/files/path/source.png'),
203 623
204 624 ('../source.png', '/repo/files/path/file.md', lambda p: True,
205 625 '/repo/files/source.png'),
206 626
207 627 ('./../source.png', '/repo/files/path/file.md', lambda p: True,
208 628 '/repo/files/source.png'),
209 629
210 630 ('./source.png', '/repo/files/path/file.md', lambda p: True,
211 631 '/repo/files/path/source.png'),
212 632
213 633 ('../../../source.png', 'path/file.md', lambda p: True,
214 634 '/source.png'),
215 635
216 636 ('../../../../../source.png', '/path/file.md', None,
217 637 '/source.png'),
218 638
219 639 ('../../../../../source.png', 'files/path/file.md', None,
220 640 '/source.png'),
221 641
222 642 ('../../../../../https://google.com/image.png', 'files/path/file.md', None,
223 643 '/https://google.com/image.png'),
224 644
225 645 ('https://google.com/image.png', 'files/path/file.md', None,
226 646 'https://google.com/image.png'),
227 647
228 648 ('://foo', '/files/path/file.md', None,
229 649 '://foo'),
230 650
231 651 (u'한글.png', '/files/path/file.md', None,
232 652 u'/files/path/한글.png'),
233 653
234 654 ('my custom image.png', '/files/path/file.md', None,
235 655 '/files/path/my custom image.png'),
236 656 ])
237 657 def test_relative_path(src_path, server_path, is_path, expected):
238 658 path = relative_path(src_path, server_path, is_path)
239 659 assert path == expected
240 660
241 661
242 662 @pytest.mark.parametrize(
243 663 "src_html, expected_html",
244 664 [
245 665 ('<div></div>', '<div></div>'),
246 666 ('<img src="/file.png"></img>', '<img src="/path/raw/file.png">'),
247 667 ('<img src="data:abcd"/>', '<img src="data:abcd">'),
248 668 ('<a href="/file.png?raw=1"></a>', '<a href="/path/raw/file.png?raw=1"></a>'),
249 669 ('<a href="/file.png"></a>', '<a href="/path/file.png"></a>'),
250 670 ('<a href="#anchor"></a>', '<a href="#anchor"></a>'),
251 671 ('<a href="./README.md?raw=1"></a>', '<a href="/path/raw/README.md?raw=1"></a>'),
252 672 ('<a href="./README.md"></a>', '<a href="/path/README.md"></a>'),
253 673 ('<a href="../README.md"></a>', '<a href="/README.md"></a>'),
254 674
255 675 ])
256 676 def test_relative_links(src_html, expected_html):
257 677 server_paths = {'raw': '/path/raw/file.md', 'standard': '/path/file.md'}
258 678 assert relative_links(src_html, server_paths=server_paths) == expected_html
General Comments 0
You need to be logged in to leave comments. Login now