##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Renderer for markup languages with ability to parse using rst or markdown
23 Renderer for markup languages with ability to parse using rst or markdown
24 """
24 """
25
25
26 import re
26 import re
27 import os
27 import os
28 import lxml
28 import lxml
29 import logging
29 import logging
30 import urlparse
30 import urlparse
31 import bleach
31
32
32 from mako.lookup import TemplateLookup
33 from mako.lookup import TemplateLookup
33 from mako.template import Template as MakoTemplate
34 from mako.template import Template as MakoTemplate
34
35
35 from docutils.core import publish_parts
36 from docutils.core import publish_parts
36 from docutils.parsers.rst import directives
37 from docutils.parsers.rst import directives
37 from docutils import writers
38 from docutils import writers
38 from docutils.writers import html4css1
39 from docutils.writers import html4css1
39 import markdown
40 import markdown
40
41
41 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
42 from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension
42 from rhodecode.lib.utils2 import (
43 from rhodecode.lib.utils2 import (
43 safe_str, safe_unicode, md5_safe, MENTIONS_REGEX)
44 safe_str, safe_unicode, md5_safe, MENTIONS_REGEX)
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47 # default renderer used to generate automated comments
48 # default renderer used to generate automated comments
48 DEFAULT_COMMENTS_RENDERER = 'rst'
49 DEFAULT_COMMENTS_RENDERER = 'rst'
49
50
50
51
51 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
52 class CustomHTMLTranslator(writers.html4css1.HTMLTranslator):
52 """
53 """
53 Custom HTML Translator used for sandboxing potential
54 Custom HTML Translator used for sandboxing potential
54 JS injections in ref links
55 JS injections in ref links
55 """
56 """
56
57
57 def visit_reference(self, node):
58 def visit_reference(self, node):
58 if 'refuri' in node.attributes:
59 if 'refuri' in node.attributes:
59 refuri = node['refuri']
60 refuri = node['refuri']
60 if ':' in refuri:
61 if ':' in refuri:
61 prefix, link = refuri.lstrip().split(':', 1)
62 prefix, link = refuri.lstrip().split(':', 1)
62 if prefix == 'javascript':
63 if prefix == 'javascript':
63 # we don't allow javascript type of refs...
64 # we don't allow javascript type of refs...
64 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
65 node['refuri'] = 'javascript:alert("SandBoxedJavascript")'
65
66
66 # old style class requires this...
67 # old style class requires this...
67 return html4css1.HTMLTranslator.visit_reference(self, node)
68 return html4css1.HTMLTranslator.visit_reference(self, node)
68
69
69
70
70 class RhodeCodeWriter(writers.html4css1.Writer):
71 class RhodeCodeWriter(writers.html4css1.Writer):
71 def __init__(self):
72 def __init__(self):
72 writers.Writer.__init__(self)
73 writers.Writer.__init__(self)
73 self.translator_class = CustomHTMLTranslator
74 self.translator_class = CustomHTMLTranslator
74
75
75
76
76 def relative_links(html_source, server_paths):
77 def relative_links(html_source, server_paths):
77 if not html_source:
78 if not html_source:
78 return html_source
79 return html_source
79
80
80 try:
81 try:
81 from lxml.html import fromstring
82 from lxml.html import fromstring
82 from lxml.html import tostring
83 from lxml.html import tostring
83 except ImportError:
84 except ImportError:
84 log.exception('Failed to import lxml')
85 log.exception('Failed to import lxml')
85 return html_source
86 return html_source
86
87
87 try:
88 try:
88 doc = lxml.html.fromstring(html_source)
89 doc = lxml.html.fromstring(html_source)
89 except Exception:
90 except Exception:
90 return html_source
91 return html_source
91
92
92 for el in doc.cssselect('img, video'):
93 for el in doc.cssselect('img, video'):
93 src = el.attrib.get('src')
94 src = el.attrib.get('src')
94 if src:
95 if src:
95 el.attrib['src'] = relative_path(src, server_paths['raw'])
96 el.attrib['src'] = relative_path(src, server_paths['raw'])
96
97
97 for el in doc.cssselect('a:not(.gfm)'):
98 for el in doc.cssselect('a:not(.gfm)'):
98 src = el.attrib.get('href')
99 src = el.attrib.get('href')
99 if src:
100 if src:
100 raw_mode = el.attrib['href'].endswith('?raw=1')
101 raw_mode = el.attrib['href'].endswith('?raw=1')
101 if raw_mode:
102 if raw_mode:
102 el.attrib['href'] = relative_path(src, server_paths['raw'])
103 el.attrib['href'] = relative_path(src, server_paths['raw'])
103 else:
104 else:
104 el.attrib['href'] = relative_path(src, server_paths['standard'])
105 el.attrib['href'] = relative_path(src, server_paths['standard'])
105
106
106 return lxml.html.tostring(doc)
107 return lxml.html.tostring(doc)
107
108
108
109
109 def relative_path(path, request_path, is_repo_file=None):
110 def relative_path(path, request_path, is_repo_file=None):
110 """
111 """
111 relative link support, path is a rel path, and request_path is current
112 relative link support, path is a rel path, and request_path is current
112 server path (not absolute)
113 server path (not absolute)
113
114
114 e.g.
115 e.g.
115
116
116 path = '../logo.png'
117 path = '../logo.png'
117 request_path= '/repo/files/path/file.md'
118 request_path= '/repo/files/path/file.md'
118 produces: '/repo/files/logo.png'
119 produces: '/repo/files/logo.png'
119 """
120 """
120 # TODO(marcink): unicode/str support ?
121 # TODO(marcink): unicode/str support ?
121 # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:'))
122 # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:'))
122
123
123 def dummy_check(p):
124 def dummy_check(p):
124 return True # assume default is a valid file path
125 return True # assume default is a valid file path
125
126
126 is_repo_file = is_repo_file or dummy_check
127 is_repo_file = is_repo_file or dummy_check
127 if not path:
128 if not path:
128 return request_path
129 return request_path
129
130
130 path = safe_unicode(path)
131 path = safe_unicode(path)
131 request_path = safe_unicode(request_path)
132 request_path = safe_unicode(request_path)
132
133
133 if path.startswith((u'data:', u'javascript:', u'#', u':')):
134 if path.startswith((u'data:', u'javascript:', u'#', u':')):
134 # skip data, anchor, invalid links
135 # skip data, anchor, invalid links
135 return path
136 return path
136
137
137 is_absolute = bool(urlparse.urlparse(path).netloc)
138 is_absolute = bool(urlparse.urlparse(path).netloc)
138 if is_absolute:
139 if is_absolute:
139 return path
140 return path
140
141
141 if not request_path:
142 if not request_path:
142 return path
143 return path
143
144
144 if path.startswith(u'/'):
145 if path.startswith(u'/'):
145 path = path[1:]
146 path = path[1:]
146
147
147 if path.startswith(u'./'):
148 if path.startswith(u'./'):
148 path = path[2:]
149 path = path[2:]
149
150
150 parts = request_path.split('/')
151 parts = request_path.split('/')
151 # compute how deep we need to traverse the request_path
152 # compute how deep we need to traverse the request_path
152 depth = 0
153 depth = 0
153
154
154 if is_repo_file(request_path):
155 if is_repo_file(request_path):
155 # if request path is a VALID file, we use a relative path with
156 # if request path is a VALID file, we use a relative path with
156 # one level up
157 # one level up
157 depth += 1
158 depth += 1
158
159
159 while path.startswith(u'../'):
160 while path.startswith(u'../'):
160 depth += 1
161 depth += 1
161 path = path[3:]
162 path = path[3:]
162
163
163 if depth > 0:
164 if depth > 0:
164 parts = parts[:-depth]
165 parts = parts[:-depth]
165
166
166 parts.append(path)
167 parts.append(path)
167 final_path = u'/'.join(parts).lstrip(u'/')
168 final_path = u'/'.join(parts).lstrip(u'/')
168
169
169 return u'/' + final_path
170 return u'/' + final_path
170
171
171
172
172 class MarkupRenderer(object):
173 class MarkupRenderer(object):
173 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
174 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
174
175
175 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
176 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
176 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
177 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
177 JUPYTER_PAT = re.compile(r'\.(ipynb)$', re.IGNORECASE)
178 JUPYTER_PAT = re.compile(r'\.(ipynb)$', re.IGNORECASE)
178 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
179 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
179
180
180 URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
181 URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
181 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
182 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
182
183
183 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
184 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
185 output_format = 'html4'
184 markdown_renderer = markdown.Markdown(
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 markdown_renderer_flavored = markdown.Markdown(
189 markdown_renderer_flavored = markdown.Markdown(
188 extensions + [GithubFlavoredMarkdownExtension()], safe_mode=True,
190 extensions + [GithubFlavoredMarkdownExtension()],
189 enable_attributes=False)
191 enable_attributes=False, output_format=output_format)
190
192
191 # extension together with weights. Lower is first means we control how
193 # extension together with weights. Lower is first means we control how
192 # extensions are attached to readme names with those.
194 # extensions are attached to readme names with those.
193 PLAIN_EXTS = [
195 PLAIN_EXTS = [
194 # prefer no extension
196 # prefer no extension
195 ('', 0), # special case that renders READMES names without extension
197 ('', 0), # special case that renders READMES names without extension
196 ('.text', 2), ('.TEXT', 2),
198 ('.text', 2), ('.TEXT', 2),
197 ('.txt', 3), ('.TXT', 3)
199 ('.txt', 3), ('.TXT', 3)
198 ]
200 ]
199
201
200 RST_EXTS = [
202 RST_EXTS = [
201 ('.rst', 1), ('.rest', 1),
203 ('.rst', 1), ('.rest', 1),
202 ('.RST', 2), ('.REST', 2)
204 ('.RST', 2), ('.REST', 2)
203 ]
205 ]
204
206
205 MARKDOWN_EXTS = [
207 MARKDOWN_EXTS = [
206 ('.md', 1), ('.MD', 1),
208 ('.md', 1), ('.MD', 1),
207 ('.mkdn', 2), ('.MKDN', 2),
209 ('.mkdn', 2), ('.MKDN', 2),
208 ('.mdown', 3), ('.MDOWN', 3),
210 ('.mdown', 3), ('.MDOWN', 3),
209 ('.markdown', 4), ('.MARKDOWN', 4)
211 ('.markdown', 4), ('.MARKDOWN', 4)
210 ]
212 ]
211
213
212 def _detect_renderer(self, source, filename=None):
214 def _detect_renderer(self, source, filename=None):
213 """
215 """
214 runs detection of what renderer should be used for generating html
216 runs detection of what renderer should be used for generating html
215 from a markup language
217 from a markup language
216
218
217 filename can be also explicitly a renderer name
219 filename can be also explicitly a renderer name
218
220
219 :param source:
221 :param source:
220 :param filename:
222 :param filename:
221 """
223 """
222
224
223 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
225 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
224 detected_renderer = 'markdown'
226 detected_renderer = 'markdown'
225 elif MarkupRenderer.RST_PAT.findall(filename):
227 elif MarkupRenderer.RST_PAT.findall(filename):
226 detected_renderer = 'rst'
228 detected_renderer = 'rst'
227 elif MarkupRenderer.JUPYTER_PAT.findall(filename):
229 elif MarkupRenderer.JUPYTER_PAT.findall(filename):
228 detected_renderer = 'jupyter'
230 detected_renderer = 'jupyter'
229 elif MarkupRenderer.PLAIN_PAT.findall(filename):
231 elif MarkupRenderer.PLAIN_PAT.findall(filename):
230 detected_renderer = 'plain'
232 detected_renderer = 'plain'
231 else:
233 else:
232 detected_renderer = 'plain'
234 detected_renderer = 'plain'
233
235
234 return getattr(MarkupRenderer, detected_renderer)
236 return getattr(MarkupRenderer, detected_renderer)
235
237
236 @classmethod
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 def renderer_from_filename(cls, filename, exclude):
246 def renderer_from_filename(cls, filename, exclude):
238 """
247 """
239 Detect renderer markdown/rst from filename and optionally use exclude
248 Detect renderer markdown/rst from filename and optionally use exclude
240 list to remove some options. This is mostly used in helpers.
249 list to remove some options. This is mostly used in helpers.
241 Returns None when no renderer can be detected.
250 Returns None when no renderer can be detected.
242 """
251 """
243 def _filter(elements):
252 def _filter(elements):
244 if isinstance(exclude, (list, tuple)):
253 if isinstance(exclude, (list, tuple)):
245 return [x for x in elements if x not in exclude]
254 return [x for x in elements if x not in exclude]
246 return elements
255 return elements
247
256
248 if filename.endswith(
257 if filename.endswith(
249 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
258 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
250 return 'markdown'
259 return 'markdown'
251 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
260 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
252 return 'rst'
261 return 'rst'
253
262
254 return None
263 return None
255
264
256 def render(self, source, filename=None):
265 def render(self, source, filename=None):
257 """
266 """
258 Renders a given filename using detected renderer
267 Renders a given filename using detected renderer
259 it detects renderers based on file extension or mimetype.
268 it detects renderers based on file extension or mimetype.
260 At last it will just do a simple html replacing new lines with <br/>
269 At last it will just do a simple html replacing new lines with <br/>
261
270
262 :param file_name:
271 :param file_name:
263 :param source:
272 :param source:
264 """
273 """
265
274
266 renderer = self._detect_renderer(source, filename)
275 renderer = self._detect_renderer(source, filename)
267 readme_data = renderer(source)
276 readme_data = renderer(source)
268 return readme_data
277 return readme_data
269
278
270 @classmethod
279 @classmethod
271 def _flavored_markdown(cls, text):
280 def _flavored_markdown(cls, text):
272 """
281 """
273 Github style flavored markdown
282 Github style flavored markdown
274
283
275 :param text:
284 :param text:
276 """
285 """
277
286
278 # Extract pre blocks.
287 # Extract pre blocks.
279 extractions = {}
288 extractions = {}
280
289
281 def pre_extraction_callback(matchobj):
290 def pre_extraction_callback(matchobj):
282 digest = md5_safe(matchobj.group(0))
291 digest = md5_safe(matchobj.group(0))
283 extractions[digest] = matchobj.group(0)
292 extractions[digest] = matchobj.group(0)
284 return "{gfm-extraction-%s}" % digest
293 return "{gfm-extraction-%s}" % digest
285 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
294 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
286 text = re.sub(pattern, pre_extraction_callback, text)
295 text = re.sub(pattern, pre_extraction_callback, text)
287
296
288 # Prevent foo_bar_baz from ending up with an italic word in the middle.
297 # Prevent foo_bar_baz from ending up with an italic word in the middle.
289 def italic_callback(matchobj):
298 def italic_callback(matchobj):
290 s = matchobj.group(0)
299 s = matchobj.group(0)
291 if list(s).count('_') >= 2:
300 if list(s).count('_') >= 2:
292 return s.replace('_', r'\_')
301 return s.replace('_', r'\_')
293 return s
302 return s
294 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
303 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
295
304
296 # Insert pre block extractions.
305 # Insert pre block extractions.
297 def pre_insert_callback(matchobj):
306 def pre_insert_callback(matchobj):
298 return '\n\n' + extractions[matchobj.group(1)]
307 return '\n\n' + extractions[matchobj.group(1)]
299 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
308 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
300 pre_insert_callback, text)
309 pre_insert_callback, text)
301
310
302 return text
311 return text
303
312
304 @classmethod
313 @classmethod
305 def urlify_text(cls, text):
314 def urlify_text(cls, text):
306 def url_func(match_obj):
315 def url_func(match_obj):
307 url_full = match_obj.groups()[0]
316 url_full = match_obj.groups()[0]
308 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
317 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
309
318
310 return cls.URL_PAT.sub(url_func, text)
319 return cls.URL_PAT.sub(url_func, text)
311
320
312 @classmethod
321 @classmethod
313 def plain(cls, source, universal_newline=True):
322 def plain(cls, source, universal_newline=True):
314 source = safe_unicode(source)
323 source = safe_unicode(source)
315 if universal_newline:
324 if universal_newline:
316 newline = '\n'
325 newline = '\n'
317 source = newline.join(source.splitlines())
326 source = newline.join(source.splitlines())
318
327
319 source = cls.urlify_text(source)
328 source = cls.urlify_text(source)
320 return '<br />' + source.replace("\n", '<br />')
329 return '<br />' + source.replace("\n", '<br />')
321
330
322 @classmethod
331 @classmethod
323 def markdown(cls, source, safe=True, flavored=True, mentions=False):
332 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
333 clean_html=True):
325 # will replace them instead with [HTML_REMOVED]. This is controlled by
334 """
326 # the safe_mode=True parameter of the markdown method.
335 returns markdown rendered code cleaned by the bleach library
336 """
327
337
328 if flavored:
338 if flavored:
329 markdown_renderer = cls.markdown_renderer_flavored
339 markdown_renderer = cls.markdown_renderer_flavored
330 else:
340 else:
331 markdown_renderer = cls.markdown_renderer
341 markdown_renderer = cls.markdown_renderer
332
342
333 if mentions:
343 if mentions:
334 mention_pat = re.compile(MENTIONS_REGEX)
344 mention_pat = re.compile(MENTIONS_REGEX)
335
345
336 def wrapp(match_obj):
346 def wrapp(match_obj):
337 uname = match_obj.groups()[0]
347 uname = match_obj.groups()[0]
338 return ' **@%(uname)s** ' % {'uname': uname}
348 return ' **@%(uname)s** ' % {'uname': uname}
339 mention_hl = mention_pat.sub(wrapp, source).strip()
349 mention_hl = mention_pat.sub(wrapp, source).strip()
340 # we extracted mentions render with this using Mentions false
350 # we extracted mentions render with this using Mentions false
341 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
351 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
342 mentions=False)
352 mentions=False)
343
353
344 source = safe_unicode(source)
354 source = safe_unicode(source)
355
345 try:
356 try:
346 if flavored:
357 if flavored:
347 source = cls._flavored_markdown(source)
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 except Exception:
363 except Exception:
350 log.exception('Error when rendering Markdown')
364 log.exception('Error when rendering Markdown')
351 if safe:
365 if safe:
352 log.debug('Fallback to render in plain mode')
366 log.debug('Fallback to render in plain mode')
353 return cls.plain(source)
367 return cls.plain(source)
354 else:
368 else:
355 raise
369 raise
356
370
357 @classmethod
371 @classmethod
358 def rst(cls, source, safe=True, mentions=False):
372 def rst(cls, source, safe=True, mentions=False, clean_html=False):
359 if mentions:
373 if mentions:
360 mention_pat = re.compile(MENTIONS_REGEX)
374 mention_pat = re.compile(MENTIONS_REGEX)
361
375
362 def wrapp(match_obj):
376 def wrapp(match_obj):
363 uname = match_obj.groups()[0]
377 uname = match_obj.groups()[0]
364 return ' **@%(uname)s** ' % {'uname': uname}
378 return ' **@%(uname)s** ' % {'uname': uname}
365 mention_hl = mention_pat.sub(wrapp, source).strip()
379 mention_hl = mention_pat.sub(wrapp, source).strip()
366 # we extracted mentions render with this using Mentions false
380 # we extracted mentions render with this using Mentions false
367 return cls.rst(mention_hl, safe=safe, mentions=False)
381 return cls.rst(mention_hl, safe=safe, mentions=False)
368
382
369 source = safe_unicode(source)
383 source = safe_unicode(source)
370 try:
384 try:
371 docutils_settings = dict(
385 docutils_settings = dict(
372 [(alias, None) for alias in
386 [(alias, None) for alias in
373 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
387 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
374
388
375 docutils_settings.update({'input_encoding': 'unicode',
389 docutils_settings.update({
376 'report_level': 4})
390 'input_encoding': 'unicode', 'report_level': 4})
377
391
378 for k, v in docutils_settings.iteritems():
392 for k, v in docutils_settings.iteritems():
379 directives.register_directive(k, v)
393 directives.register_directive(k, v)
380
394
381 parts = publish_parts(source=source,
395 parts = publish_parts(source=source,
382 writer=RhodeCodeWriter(),
396 writer=RhodeCodeWriter(),
383 settings_overrides=docutils_settings)
397 settings_overrides=docutils_settings)
384
398 rendered = parts["fragment"]
385 return parts['html_title'] + parts["fragment"]
399 if clean_html:
400 rendered = cls.bleach_clean(rendered)
401 return parts['html_title'] + rendered
386 except Exception:
402 except Exception:
387 log.exception('Error when rendering RST')
403 log.exception('Error when rendering RST')
388 if safe:
404 if safe:
389 log.debug('Fallbacking to render in plain mode')
405 log.debug('Fallbacking to render in plain mode')
390 return cls.plain(source)
406 return cls.plain(source)
391 else:
407 else:
392 raise
408 raise
393
409
394 @classmethod
410 @classmethod
395 def jupyter(cls, source, safe=True):
411 def jupyter(cls, source, safe=True):
396 from rhodecode.lib import helpers
412 from rhodecode.lib import helpers
397
413
398 from traitlets.config import Config
414 from traitlets.config import Config
399 import nbformat
415 import nbformat
400 from nbconvert import HTMLExporter
416 from nbconvert import HTMLExporter
401 from nbconvert.preprocessors import Preprocessor
417 from nbconvert.preprocessors import Preprocessor
402
418
403 class CustomHTMLExporter(HTMLExporter):
419 class CustomHTMLExporter(HTMLExporter):
404 def _template_file_default(self):
420 def _template_file_default(self):
405 return 'basic'
421 return 'basic'
406
422
407 class Sandbox(Preprocessor):
423 class Sandbox(Preprocessor):
408
424
409 def preprocess(self, nb, resources):
425 def preprocess(self, nb, resources):
410 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
426 sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
411 for cell in nb['cells']:
427 for cell in nb['cells']:
412 if safe and 'outputs' in cell:
428 if safe and 'outputs' in cell:
413 for cell_output in cell['outputs']:
429 for cell_output in cell['outputs']:
414 if 'data' in cell_output:
430 if 'data' in cell_output:
415 if 'application/javascript' in cell_output['data']:
431 if 'application/javascript' in cell_output['data']:
416 cell_output['data']['text/plain'] = sandbox_text
432 cell_output['data']['text/plain'] = sandbox_text
417 cell_output['data'].pop('application/javascript', None)
433 cell_output['data'].pop('application/javascript', None)
418 return nb, resources
434 return nb, resources
419
435
420 def _sanitize_resources(resources):
436 def _sanitize_resources(resources):
421 """
437 """
422 Skip/sanitize some of the CSS generated and included in jupyter
438 Skip/sanitize some of the CSS generated and included in jupyter
423 so it doesn't messes up UI so much
439 so it doesn't messes up UI so much
424 """
440 """
425
441
426 # TODO(marcink): probably we should replace this with whole custom
442 # TODO(marcink): probably we should replace this with whole custom
427 # CSS set that doesn't screw up, but jupyter generated html has some
443 # CSS set that doesn't screw up, but jupyter generated html has some
428 # special markers, so it requires Custom HTML exporter template with
444 # special markers, so it requires Custom HTML exporter template with
429 # _default_template_path_default, to achieve that
445 # _default_template_path_default, to achieve that
430
446
431 # strip the reset CSS
447 # strip the reset CSS
432 resources[0] = resources[0][resources[0].find('/*! Source'):]
448 resources[0] = resources[0][resources[0].find('/*! Source'):]
433 return resources
449 return resources
434
450
435 def as_html(notebook):
451 def as_html(notebook):
436 conf = Config()
452 conf = Config()
437 conf.CustomHTMLExporter.preprocessors = [Sandbox]
453 conf.CustomHTMLExporter.preprocessors = [Sandbox]
438 html_exporter = CustomHTMLExporter(config=conf)
454 html_exporter = CustomHTMLExporter(config=conf)
439
455
440 (body, resources) = html_exporter.from_notebook_node(notebook)
456 (body, resources) = html_exporter.from_notebook_node(notebook)
441 header = '<!-- ## IPYTHON NOTEBOOK RENDERING ## -->'
457 header = '<!-- ## IPYTHON NOTEBOOK RENDERING ## -->'
442 js = MakoTemplate(r'''
458 js = MakoTemplate(r'''
443 <!-- Load mathjax -->
459 <!-- Load mathjax -->
444 <!-- MathJax configuration -->
460 <!-- MathJax configuration -->
445 <script type="text/x-mathjax-config">
461 <script type="text/x-mathjax-config">
446 MathJax.Hub.Config({
462 MathJax.Hub.Config({
447 jax: ["input/TeX","output/HTML-CSS", "output/PreviewHTML"],
463 jax: ["input/TeX","output/HTML-CSS", "output/PreviewHTML"],
448 extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "[Contrib]/a11y/accessibility-menu.js"],
464 extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "fast-preview.js", "AssistiveMML.js", "[Contrib]/a11y/accessibility-menu.js"],
449 TeX: {
465 TeX: {
450 extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
466 extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
451 },
467 },
452 tex2jax: {
468 tex2jax: {
453 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
469 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
454 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
470 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
455 processEscapes: true,
471 processEscapes: true,
456 processEnvironments: true
472 processEnvironments: true
457 },
473 },
458 // Center justify equations in code and markdown cells. Elsewhere
474 // Center justify equations in code and markdown cells. Elsewhere
459 // we use CSS to left justify single line equations in code cells.
475 // we use CSS to left justify single line equations in code cells.
460 displayAlign: 'center',
476 displayAlign: 'center',
461 "HTML-CSS": {
477 "HTML-CSS": {
462 styles: {'.MathJax_Display': {"margin": 0}},
478 styles: {'.MathJax_Display': {"margin": 0}},
463 linebreaks: { automatic: true },
479 linebreaks: { automatic: true },
464 availableFonts: ["STIX", "TeX"]
480 availableFonts: ["STIX", "TeX"]
465 },
481 },
466 showMathMenu: false
482 showMathMenu: false
467 });
483 });
468 </script>
484 </script>
469 <!-- End of mathjax configuration -->
485 <!-- End of mathjax configuration -->
470 <script src="${h.asset('js/src/math_jax/MathJax.js')}"></script>
486 <script src="${h.asset('js/src/math_jax/MathJax.js')}"></script>
471 ''').render(h=helpers)
487 ''').render(h=helpers)
472
488
473 css = '<style>{}</style>'.format(
489 css = '<style>{}</style>'.format(
474 ''.join(_sanitize_resources(resources['inlining']['css'])))
490 ''.join(_sanitize_resources(resources['inlining']['css'])))
475
491
476 body = '\n'.join([header, css, js, body])
492 body = '\n'.join([header, css, js, body])
477 return body, resources
493 return body, resources
478
494
479 notebook = nbformat.reads(source, as_version=4)
495 notebook = nbformat.reads(source, as_version=4)
480 (body, resources) = as_html(notebook)
496 (body, resources) = as_html(notebook)
481 return body
497 return body
482
498
483
499
484 class RstTemplateRenderer(object):
500 class RstTemplateRenderer(object):
485
501
486 def __init__(self):
502 def __init__(self):
487 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
503 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
488 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
504 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
489 self.template_store = TemplateLookup(
505 self.template_store = TemplateLookup(
490 directories=rst_template_dirs,
506 directories=rst_template_dirs,
491 input_encoding='utf-8',
507 input_encoding='utf-8',
492 imports=['from rhodecode.lib import helpers as h'])
508 imports=['from rhodecode.lib import helpers as h'])
493
509
494 def _get_template(self, templatename):
510 def _get_template(self, templatename):
495 return self.template_store.get_template(templatename)
511 return self.template_store.get_template(templatename)
496
512
497 def render(self, template_name, **kwargs):
513 def render(self, template_name, **kwargs):
498 template = self._get_template(template_name)
514 template = self._get_template(template_name)
499 return template.render(**kwargs)
515 return template.render(**kwargs)
@@ -1,258 +1,678 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.markup_renderer import (
23 from rhodecode.lib.markup_renderer import (
24 MarkupRenderer, RstTemplateRenderer, relative_path, relative_links)
24 MarkupRenderer, RstTemplateRenderer, relative_path, relative_links)
25
25
26
26
27 @pytest.mark.parametrize(
27 @pytest.mark.parametrize(
28 "filename, expected_renderer",
28 "filename, expected_renderer",
29 [
29 [
30 ('readme.md', 'markdown'),
30 ('readme.md', 'markdown'),
31 ('readme.Md', 'markdown'),
31 ('readme.Md', 'markdown'),
32 ('readme.MdoWn', 'markdown'),
32 ('readme.MdoWn', 'markdown'),
33 ('readme.rst', 'rst'),
33 ('readme.rst', 'rst'),
34 ('readme.Rst', 'rst'),
34 ('readme.Rst', 'rst'),
35 ('readme.rest', 'rst'),
35 ('readme.rest', 'rst'),
36 ('readme.rest', 'rst'),
36 ('readme.rest', 'rst'),
37
37
38 ('markdown.xml', 'plain'),
38 ('markdown.xml', 'plain'),
39 ('rest.xml', 'plain'),
39 ('rest.xml', 'plain'),
40 ('readme.xml', 'plain'),
40 ('readme.xml', 'plain'),
41
41
42 ('readme', 'plain'),
42 ('readme', 'plain'),
43 ('README', 'plain'),
43 ('README', 'plain'),
44 ('readme.mdx', 'plain'),
44 ('readme.mdx', 'plain'),
45 ('readme.rstx', 'plain'),
45 ('readme.rstx', 'plain'),
46 ('readmex', 'plain'),
46 ('readmex', 'plain'),
47 ])
47 ])
48 def test_detect_renderer(filename, expected_renderer):
48 def test_detect_renderer(filename, expected_renderer):
49 detected_renderer = MarkupRenderer()._detect_renderer(
49 detected_renderer = MarkupRenderer()._detect_renderer(
50 '', filename=filename).__name__
50 '', filename=filename).__name__
51 assert expected_renderer == detected_renderer
51 assert expected_renderer == detected_renderer
52
52
53
53
54 def test_markdown_xss_link():
54 def test_markdown_xss_link():
55 xss_md = "[link](javascript:alert('XSS: pwned!'))"
55 xss_md = "[link](javascript:alert('XSS: pwned!'))"
56 rendered_html = MarkupRenderer.markdown(xss_md)
56 rendered_html = MarkupRenderer.markdown(xss_md)
57 assert 'href="javascript:alert(\'XSS: pwned!\')"' not in rendered_html
57 assert 'href="javascript:alert(\'XSS: pwned!\')"' not in rendered_html
58
58
59
59
60 def test_markdown_xss_inline_html():
60 def test_markdown_xss_inline_html():
61 xss_md = '\n'.join([
61 xss_md = '\n'.join([
62 '> <a name="n"',
62 '> <a name="n"',
63 '> href="javascript:alert(\'XSS: pwned!\')">link</a>'])
63 '> href="javascript:alert(\'XSS: pwned!\')">link</a>'])
64 rendered_html = MarkupRenderer.markdown(xss_md)
64 rendered_html = MarkupRenderer.markdown(xss_md)
65 assert 'href="javascript:alert(\'XSS: pwned!\')">' not in rendered_html
65 assert 'href="javascript:alert(\'XSS: pwned!\')">' not in rendered_html
66
66
67
67
68 def test_markdown_inline_html():
68 def test_markdown_inline_html():
69 xss_md = '\n'.join(['> <a name="n"',
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 rendered_html = MarkupRenderer.markdown(xss_md)
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 def test_rst_xss_link():
495 def test_rst_xss_link():
76 xss_rst = "`Link<javascript:alert('XSS: pwned!')>`_"
496 xss_rst = "`Link<javascript:alert('XSS: pwned!')>`_"
77 rendered_html = MarkupRenderer.rst(xss_rst)
497 rendered_html = MarkupRenderer.rst(xss_rst)
78 assert "href=javascript:alert('XSS: pwned!')" not in rendered_html
498 assert "href=javascript:alert('XSS: pwned!')" not in rendered_html
79
499
80
500
81 @pytest.mark.xfail(reason='Bug in docutils. Waiting answer from the author')
501 @pytest.mark.xfail(reason='Bug in docutils. Waiting answer from the author')
82 def test_rst_xss_inline_html():
502 def test_rst_xss_inline_html():
83 xss_rst = '<a href="javascript:alert(\'XSS: pwned!\')">link</a>'
503 xss_rst = '<a href="javascript:alert(\'XSS: pwned!\')">link</a>'
84 rendered_html = MarkupRenderer.rst(xss_rst)
504 rendered_html = MarkupRenderer.rst(xss_rst)
85 assert 'href="javascript:alert(' not in rendered_html
505 assert 'href="javascript:alert(' not in rendered_html
86
506
87
507
88 def test_rst_xss_raw_directive():
508 def test_rst_xss_raw_directive():
89 xss_rst = '\n'.join([
509 xss_rst = '\n'.join([
90 '.. raw:: html',
510 '.. raw:: html',
91 '',
511 '',
92 ' <a href="javascript:alert(\'XSS: pwned!\')">link</a>'])
512 ' <a href="javascript:alert(\'XSS: pwned!\')">link</a>'])
93 rendered_html = MarkupRenderer.rst(xss_rst)
513 rendered_html = MarkupRenderer.rst(xss_rst)
94 assert 'href="javascript:alert(' not in rendered_html
514 assert 'href="javascript:alert(' not in rendered_html
95
515
96
516
97 def test_render_rst_template_without_files():
517 def test_render_rst_template_without_files():
98 expected = u'''\
518 expected = u'''\
99 Pull request updated. Auto status change to |under_review|
519 Pull request updated. Auto status change to |under_review|
100
520
101 .. role:: added
521 .. role:: added
102 .. role:: removed
522 .. role:: removed
103 .. parsed-literal::
523 .. parsed-literal::
104
524
105 Changed commits:
525 Changed commits:
106 * :added:`2 added`
526 * :added:`2 added`
107 * :removed:`3 removed`
527 * :removed:`3 removed`
108
528
109 No file changes found
529 No file changes found
110
530
111 .. |under_review| replace:: *"NEW STATUS"*'''
531 .. |under_review| replace:: *"NEW STATUS"*'''
112
532
113 params = {
533 params = {
114 'under_review_label': 'NEW STATUS',
534 'under_review_label': 'NEW STATUS',
115 'added_commits': ['a', 'b'],
535 'added_commits': ['a', 'b'],
116 'removed_commits': ['a', 'b', 'c'],
536 'removed_commits': ['a', 'b', 'c'],
117 'changed_files': [],
537 'changed_files': [],
118 'added_files': [],
538 'added_files': [],
119 'modified_files': [],
539 'modified_files': [],
120 'removed_files': [],
540 'removed_files': [],
121 }
541 }
122 renderer = RstTemplateRenderer()
542 renderer = RstTemplateRenderer()
123 rendered = renderer.render('pull_request_update.mako', **params)
543 rendered = renderer.render('pull_request_update.mako', **params)
124 assert expected == rendered
544 assert expected == rendered
125
545
126
546
127 def test_render_rst_template_with_files():
547 def test_render_rst_template_with_files():
128 expected = u'''\
548 expected = u'''\
129 Pull request updated. Auto status change to |under_review|
549 Pull request updated. Auto status change to |under_review|
130
550
131 .. role:: added
551 .. role:: added
132 .. role:: removed
552 .. role:: removed
133 .. parsed-literal::
553 .. parsed-literal::
134
554
135 Changed commits:
555 Changed commits:
136 * :added:`1 added`
556 * :added:`1 added`
137 * :removed:`3 removed`
557 * :removed:`3 removed`
138
558
139 Changed files:
559 Changed files:
140 * `A /path/a.py <#a_c--68ed34923b68>`_
560 * `A /path/a.py <#a_c--68ed34923b68>`_
141 * `A /path/b.js <#a_c--64f90608b607>`_
561 * `A /path/b.js <#a_c--64f90608b607>`_
142 * `M /path/d.js <#a_c--85842bf30c6e>`_
562 * `M /path/d.js <#a_c--85842bf30c6e>`_
143 * `M /path/Δ™.py <#a_c--d713adf009cd>`_
563 * `M /path/Δ™.py <#a_c--d713adf009cd>`_
144 * R /path/ΕΊ.py
564 * R /path/ΕΊ.py
145
565
146 .. |under_review| replace:: *"NEW STATUS"*'''
566 .. |under_review| replace:: *"NEW STATUS"*'''
147
567
148 added = ['/path/a.py', '/path/b.js']
568 added = ['/path/a.py', '/path/b.js']
149 modified = ['/path/d.js', u'/path/Δ™.py']
569 modified = ['/path/d.js', u'/path/Δ™.py']
150 removed = [u'/path/ΕΊ.py']
570 removed = [u'/path/ΕΊ.py']
151
571
152 params = {
572 params = {
153 'under_review_label': 'NEW STATUS',
573 'under_review_label': 'NEW STATUS',
154 'added_commits': ['a'],
574 'added_commits': ['a'],
155 'removed_commits': ['a', 'b', 'c'],
575 'removed_commits': ['a', 'b', 'c'],
156 'changed_files': added + modified + removed,
576 'changed_files': added + modified + removed,
157 'added_files': added,
577 'added_files': added,
158 'modified_files': modified,
578 'modified_files': modified,
159 'removed_files': removed,
579 'removed_files': removed,
160 }
580 }
161 renderer = RstTemplateRenderer()
581 renderer = RstTemplateRenderer()
162 rendered = renderer.render('pull_request_update.mako', **params)
582 rendered = renderer.render('pull_request_update.mako', **params)
163
583
164 assert expected == rendered
584 assert expected == rendered
165
585
166
586
167 def test_render_rst_auto_status_template():
587 def test_render_rst_auto_status_template():
168 expected = u'''\
588 expected = u'''\
169 Auto status change to |new_status|
589 Auto status change to |new_status|
170
590
171 .. |new_status| replace:: *"NEW STATUS"*'''
591 .. |new_status| replace:: *"NEW STATUS"*'''
172
592
173 params = {
593 params = {
174 'new_status_label': 'NEW STATUS',
594 'new_status_label': 'NEW STATUS',
175 'pull_request': None,
595 'pull_request': None,
176 'commit_id': None,
596 'commit_id': None,
177 }
597 }
178 renderer = RstTemplateRenderer()
598 renderer = RstTemplateRenderer()
179 rendered = renderer.render('auto_status_change.mako', **params)
599 rendered = renderer.render('auto_status_change.mako', **params)
180 assert expected == rendered
600 assert expected == rendered
181
601
182
602
183 @pytest.mark.parametrize(
603 @pytest.mark.parametrize(
184 "src_path, server_path, is_path, expected",
604 "src_path, server_path, is_path, expected",
185 [
605 [
186 ('source.png', '/repo/files/path', lambda p: False,
606 ('source.png', '/repo/files/path', lambda p: False,
187 '/repo/files/path/source.png'),
607 '/repo/files/path/source.png'),
188
608
189 ('source.png', 'mk/git/blob/master/README.md', lambda p: True,
609 ('source.png', 'mk/git/blob/master/README.md', lambda p: True,
190 '/mk/git/blob/master/source.png'),
610 '/mk/git/blob/master/source.png'),
191
611
192 ('./source.png', 'mk/git/blob/master/README.md', lambda p: True,
612 ('./source.png', 'mk/git/blob/master/README.md', lambda p: True,
193 '/mk/git/blob/master/source.png'),
613 '/mk/git/blob/master/source.png'),
194
614
195 ('/source.png', 'mk/git/blob/master/README.md', lambda p: True,
615 ('/source.png', 'mk/git/blob/master/README.md', lambda p: True,
196 '/mk/git/blob/master/source.png'),
616 '/mk/git/blob/master/source.png'),
197
617
198 ('./source.png', 'repo/files/path/source.md', lambda p: True,
618 ('./source.png', 'repo/files/path/source.md', lambda p: True,
199 '/repo/files/path/source.png'),
619 '/repo/files/path/source.png'),
200
620
201 ('./source.png', '/repo/files/path/file.md', lambda p: True,
621 ('./source.png', '/repo/files/path/file.md', lambda p: True,
202 '/repo/files/path/source.png'),
622 '/repo/files/path/source.png'),
203
623
204 ('../source.png', '/repo/files/path/file.md', lambda p: True,
624 ('../source.png', '/repo/files/path/file.md', lambda p: True,
205 '/repo/files/source.png'),
625 '/repo/files/source.png'),
206
626
207 ('./../source.png', '/repo/files/path/file.md', lambda p: True,
627 ('./../source.png', '/repo/files/path/file.md', lambda p: True,
208 '/repo/files/source.png'),
628 '/repo/files/source.png'),
209
629
210 ('./source.png', '/repo/files/path/file.md', lambda p: True,
630 ('./source.png', '/repo/files/path/file.md', lambda p: True,
211 '/repo/files/path/source.png'),
631 '/repo/files/path/source.png'),
212
632
213 ('../../../source.png', 'path/file.md', lambda p: True,
633 ('../../../source.png', 'path/file.md', lambda p: True,
214 '/source.png'),
634 '/source.png'),
215
635
216 ('../../../../../source.png', '/path/file.md', None,
636 ('../../../../../source.png', '/path/file.md', None,
217 '/source.png'),
637 '/source.png'),
218
638
219 ('../../../../../source.png', 'files/path/file.md', None,
639 ('../../../../../source.png', 'files/path/file.md', None,
220 '/source.png'),
640 '/source.png'),
221
641
222 ('../../../../../https://google.com/image.png', 'files/path/file.md', None,
642 ('../../../../../https://google.com/image.png', 'files/path/file.md', None,
223 '/https://google.com/image.png'),
643 '/https://google.com/image.png'),
224
644
225 ('https://google.com/image.png', 'files/path/file.md', None,
645 ('https://google.com/image.png', 'files/path/file.md', None,
226 'https://google.com/image.png'),
646 'https://google.com/image.png'),
227
647
228 ('://foo', '/files/path/file.md', None,
648 ('://foo', '/files/path/file.md', None,
229 '://foo'),
649 '://foo'),
230
650
231 (u'ν•œκΈ€.png', '/files/path/file.md', None,
651 (u'ν•œκΈ€.png', '/files/path/file.md', None,
232 u'/files/path/ν•œκΈ€.png'),
652 u'/files/path/ν•œκΈ€.png'),
233
653
234 ('my custom image.png', '/files/path/file.md', None,
654 ('my custom image.png', '/files/path/file.md', None,
235 '/files/path/my custom image.png'),
655 '/files/path/my custom image.png'),
236 ])
656 ])
237 def test_relative_path(src_path, server_path, is_path, expected):
657 def test_relative_path(src_path, server_path, is_path, expected):
238 path = relative_path(src_path, server_path, is_path)
658 path = relative_path(src_path, server_path, is_path)
239 assert path == expected
659 assert path == expected
240
660
241
661
242 @pytest.mark.parametrize(
662 @pytest.mark.parametrize(
243 "src_html, expected_html",
663 "src_html, expected_html",
244 [
664 [
245 ('<div></div>', '<div></div>'),
665 ('<div></div>', '<div></div>'),
246 ('<img src="/file.png"></img>', '<img src="/path/raw/file.png">'),
666 ('<img src="/file.png"></img>', '<img src="/path/raw/file.png">'),
247 ('<img src="data:abcd"/>', '<img src="data:abcd">'),
667 ('<img src="data:abcd"/>', '<img src="data:abcd">'),
248 ('<a href="/file.png?raw=1"></a>', '<a href="/path/raw/file.png?raw=1"></a>'),
668 ('<a href="/file.png?raw=1"></a>', '<a href="/path/raw/file.png?raw=1"></a>'),
249 ('<a href="/file.png"></a>', '<a href="/path/file.png"></a>'),
669 ('<a href="/file.png"></a>', '<a href="/path/file.png"></a>'),
250 ('<a href="#anchor"></a>', '<a href="#anchor"></a>'),
670 ('<a href="#anchor"></a>', '<a href="#anchor"></a>'),
251 ('<a href="./README.md?raw=1"></a>', '<a href="/path/raw/README.md?raw=1"></a>'),
671 ('<a href="./README.md?raw=1"></a>', '<a href="/path/raw/README.md?raw=1"></a>'),
252 ('<a href="./README.md"></a>', '<a href="/path/README.md"></a>'),
672 ('<a href="./README.md"></a>', '<a href="/path/README.md"></a>'),
253 ('<a href="../README.md"></a>', '<a href="/README.md"></a>'),
673 ('<a href="../README.md"></a>', '<a href="/README.md"></a>'),
254
674
255 ])
675 ])
256 def test_relative_links(src_html, expected_html):
676 def test_relative_links(src_html, expected_html):
257 server_paths = {'raw': '/path/raw/file.md', 'standard': '/path/file.md'}
677 server_paths = {'raw': '/path/raw/file.md', 'standard': '/path/file.md'}
258 assert relative_links(src_html, server_paths=server_paths) == expected_html
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