Attachment 'Color2-1.9.9.py'
Download 1 """
2 MoinMoin - Color2 Macro
3
4 @copyright: 2006 by Clif Kussmaul <clif@kussmaul.org>
5 2008 by Clif Kussmaul, Dave Hein (MoinMoin:DaveHein)
6 2011 by Clif Kussmaul, Dave Hein (MoinMoin:DaveHein), Gregor Mirai
7 @license: GNU GPL, see COPYING for details
8
9 Usage: <<Color2(text,col=color,bcol=bgcolor,font=_font_)>>
10 <<Color2(text,bcol=bgcolor)>>
11 <<Color2(text,color)>>
12
13 History:
14 - 2017.05.17: [Moin 1.9.9] mitigate CSS injection attacks; by Dave Hein
15 Issue reported by Paul Wise; fix reviewed by Gregor Mirai
16 and Paul Wise.
17 - 2011.02.23: [Moin 1.9] updated for Moin 1.9 by Gregor Mirai,
18 code simplified due to new parameter parsing and lots of other improvements in Moin code.
19 MiniPage functionality for the text parameter has been preserved.
20 - 2008.01.25: [Moin 1.6] updated for Moin 1.6 by Dave Hein,
21 no functional changes.
22 - 2006: [Moin 1.5] written by Clif Kussmaul
23 - originally based on Color Macro
24 Copyright (c) 2002 by Markus Gritsch <gritsch@iue.tuwien.ac.at>
25 """
26
27 from MoinMoin import wikiutil
28 from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
29
30 """
31 Parameters that macro accepts are:
32 - text (the text to be colored)
33 - col (optional text color)
34 - bcol (optional background text color)
35 - font (optional font)
36
37 Examples:
38
39 <<Color2(Hello World!,col=red,bcol=blue,font=18px courier)>>
40 <<Color2(Hello World!,bcol=blue)>>
41 <<Color2(Hello World!,orange)>>
42 """
43
44 # used to cache the string lists and regular expressions that are
45 # used by the _color2_is_valid_*() methods to validate CSS property values.
46 #
47 _color2_validations = {}
48
49 def _color2_initialize_validations(macro):
50 """Initialize _color2_validations with lists and regular expressions.
51
52 The Color2 macro uses the lists and regular expressions in the
53 _color2_validations dictionary to validate CSS color and font property
54 values in order to mitigate CSS injection leading to XSS attacks
55 and defacement attacks.
56
57 Args:
58 none
59
60 """
61
62 import re
63
64 # From https://www.w3.org/TR/css3-color/#colorunits
65 #
66 _color2_validations['css_basic_color_keywords'] = set([x.lower() for x in [
67 "black", "silver", "gray", "white", "maroon", "red", "purple",
68 "fuchsia", "green", "lime", "olive", "yellow", "navy", "blue",
69 "teal", "aqua"]])
70 _color2_validations['css_special_color_keywords'] = set([x.lower() for x in [
71 "transparent", "currentColor" ]])
72 _color2_validations['css_extended_color_keywords'] = set([x.lower() for x in [
73 "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
74 "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
75 "burlywood", "cadetblue", "chartreuse", "chocolate", "coral",
76 "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue",
77 "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey",
78 "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange",
79 "darkorchid", "darkred", "darksalmon", "darkseagreen",
80 "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise",
81 "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey",
82 "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia",
83 "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green",
84 "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo",
85 "ivory", "khaki", "lavender", "lavenderblush", "lawngreen",
86 "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
87 "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey",
88 "lightpink", "lightsalmon", "lightseagreen", "lightskyblue",
89 "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow",
90 "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine",
91 "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen",
92 "mediumslateblue", "mediumspringgreen", "mediumturquoise",
93 "mediumvioletred", "midnightblue", "mintcream", "mistyrose",
94 "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab",
95 "orange", "orangered", "orchid", "palegoldenrod", "palegreen",
96 "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
97 "pink", "plum", "powderblue", "purple", "red", "rosybrown",
98 "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
99 "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray",
100 "slategrey", "snow", "springgreen", "steelblue", "tan", "teal",
101 "thistle", "tomato", "turquoise", "violet", "wheat", "white",
102 "whitesmoke", "yellow", "yellowgreen"]])
103 _color2_validations['css_system_color_keywords'] = set([x.lower() for x in [
104 "ActiveBorder", "ActiveCaption", "AppWorkspace", "Background",
105 "ButtonFace", "ButtonHighlight", "ButtonShadow", "ButtonText",
106 "CaptionText", "GrayText", "Highlight", "HighlightText",
107 "InactiveBorder", "InactiveCaption", "InactiveCaptionText",
108 "InfoBackground", "InfoText", "Menu", "MenuText", "Scrollbar",
109 "ThreeDDarkShadow", "ThreeDFace", "ThreeDHighlight",
110 "ThreeDLightShadow", "ThreeDShadow", "Window", "WindowFrame",
111 "WindowText"]])
112
113 _color2_validations['re_rgb_3'] = re.compile("^#[0-9a-fA-F]{3}$")
114
115 _color2_validations['re_rgb_6'] = re.compile("^#[0-9a-fA-F]{6}$")
116
117 res_int_or_pct = "\\s*-?[0-9]{1,3}(?:(?:\\.[0-9]*)?%)?"
118 res_rgb = "^rgb\\(%(a0)s\\s*,%(a0)s\\s*,%(a0)s\\s*\\)$" \
119 % { 'a0': res_int_or_pct }
120 _color2_validations['re_rgb'] = re.compile(res_rgb)
121
122 res_float = "\\s*(?:0?.)?[0-9]+"
123 res_rgba = "^rgba\\(%(a0)s\\s*,%(a0)s\\s*,%(a0)s\\s*,%(a1)s\\s*\\)$" \
124 % { 'a0': res_int_or_pct, 'a1': res_float }
125 _color2_validations['re_rgba'] = re.compile(res_rgba)
126
127 res_int = "\\s*[0-9]{1,3}"
128 res_hue = "\\s*[0-9]{1,3}(?:.[0-9]*)?"
129 res_pct = "\\s*[0-9]{1,3}(?:.[0-9]*)?%"
130 res_hsl = "^hsl\\(%(a0)s\\s*,%(a1)s\\s*,%(a1)s\\s*\\)$" \
131 % { 'a0': res_hue, 'a1': res_pct }
132 _color2_validations['re_hsl'] = re.compile(res_hsl)
133
134 res_hsla = "^hsla\\(%(a0)s\\s*,%(a1)s\\s*,%(a1)s\\s*,%(a2)s\\s*\\)$" \
135 % { 'a0': res_hue, 'a1': res_pct, 'a2': res_float }
136 _color2_validations['re_hsla'] = re.compile(res_hsla)
137
138 # From https://www.w3.org/TR/2013/CR-css-fonts-3-20131003/#propdef-font
139 #
140 _color2_validations['css_system_fonts'] = set([x.lower() for x in [
141 "caption", "icon", "menu", "message-box", "small-caption",
142 "status-bar"]])
143 _color2_validations['css_font_style'] = set([x.lower() for x in [
144 "normal", "italic", "oblique" ]])
145 _color2_validations['css_font_variant_css21'] = set([x.lower() for x in [
146 "normal", "small-caps" ]])
147 _color2_validations['css_font_weight'] = set([x.lower() for x in [
148 "normal", "bold", "bolder", "lighter", "100", "200", "300", "400",
149 "500", "600", "700", "800", "900"]])
150 _color2_validations['css_font_stretch'] = set([x.lower() for x in [
151 "normal", "ultra-condensed", "extra-condensed", "condensed",
152 "semi-condensed", "semi-expanded", "expanded", "extra-expanded",
153 "ultra-expanded"]])
154 _color2_validations['css_font_absolute_size'] = set([x.lower() for x in [
155 "xx-small", "x-small", "small", "medium", "large", "x-large",
156 "xx-large" ]])
157 _color2_validations['css_font_relative_size'] = set([x.lower() for x in [
158 "larger", "smaller" ]])
159 _color2_validations['css_line_height'] = set([x.lower() for x in [
160 "normal", "inherit" ]])
161 _color2_validations['re_length'] = re.compile(
162 "^[0-9]+(?:.[0-9]*)?[a-zA-Z]{0,4}$")
163 _color2_validations['re_length2'] = re.compile(
164 "^[0-9]*(?:.[0-9]+)[a-zA-Z]{0,4}$")
165 _color2_validations['re_length_pct'] = re.compile("^[0-9]+%$")
166 # Expect that font family names do not start with number or decimal point;
167 # required to distinguish lengths from names
168 #
169 _color2_validations['re_font_family'] = re.compile("^[^0-9.;\\\"',-][^;\\\"',]*,?$")
170 _color2_validations['re_font_family_quoted'] = re.compile(
171 "^\\\"[^;\\\"']+\\\",?$")
172 _color2_validations['re_font_family_squoted'] = re.compile(
173 "^'[^;\\\"']+',?$")
174
175 return
176
177 def _color2_is_valid_color(macro, col=None):
178 """Whether 'col' is a valid CSS color property value.
179
180 The Color2 macro checks the 'col' and 'bcol' arguments to ensure
181 a valid color value is specified. This is done to mitigate CSS
182 injection leading to XSS attacks and defacement attacks.
183
184 Args:
185 macro: the macro argument passed to Color2.
186 col: the CSS property value to be validated.
187
188 Returns:
189 True if the 'col' argument is a valid CSS color property value;
190 otherwise, False.
191
192 """
193
194 import re
195
196 # Initialize the validations dictionary if it is empty
197 #
198 if 0 == len(_color2_validations):
199 _color2_initialize_validations(macro);
200
201 # Validate
202 #
203 if col is None:
204 return False
205
206 t_col = col.lower()
207 if t_col in _color2_validations['css_basic_color_keywords']:
208 return True
209 if t_col in _color2_validations['css_special_color_keywords']:
210 return True
211 if t_col in _color2_validations['css_extended_color_keywords']:
212 return True
213 if t_col in _color2_validations['css_system_color_keywords']:
214 return True
215 if _color2_validations['re_rgb_3'].match(col) is not None:
216 return True
217 if _color2_validations['re_rgb_6'].match(col) is not None:
218 return True
219 if _color2_validations['re_rgb'].match(col) is not None:
220 return True
221 if _color2_validations['re_rgba'].match(col) is not None:
222 return True
223 if _color2_validations['re_hsl'].match(col) is not None:
224 return True
225 if _color2_validations['re_hsla'].match(col) is not None:
226 return True
227
228 # Did not match any expected color specification string
229 #
230 return False
231
232 def _color2_is_valid_font(macro, font=None):
233 """Whether 'font' is a valid CSS font property value.
234
235 The Color2 macro checks the 'font' argument to ensure a valid
236 CSS font property value is specified. This is done to mitigate CSS
237 injection leading to XSS attacks and defacement attacks.
238
239 This validation is approximate. It should filter out injection attacks
240 and wildly improper CSS, but doesn't validate font family names and
241 token ordering or length units (i.e., it is not a strict validation).
242
243 Args:
244 macro: the macro argument passed to Color2.
245 font: the CSS property value to be validated.
246
247 Returns:
248 True if the 'font' argument is an approximately valid CSS font
249 property value; otherwise, False.
250
251 """
252
253 import re
254
255 # Initialize the validations dictionary if it is empty
256 #
257 if 0 == len(_color2_validations):
258 _color2_initialize_validations(macro);
259
260 # Validate
261 #
262
263 if font is None:
264 return False
265
266 t_font = font.lower()
267 if t_font in _color2_validations['css_system_fonts']:
268 return True
269
270 # split property value into tokens, recombining
271 # quoted string tokens
272 #
273 parts_naive = font.split()
274 parts = []
275 part_accum = ""
276 pfx = ""
277 for part in parts_naive:
278 if 0 == len(part_accum) and not part.startswith(("\"", "'")):
279 parts.append(part)
280 continue
281 if 0 == len(part_accum):
282 pfx = part[0]
283 if part.endswith((pfx, pfx + ",")):
284 parts.append(part)
285 pfx = ""
286 else:
287 part_accum += part
288 continue
289 if not part.endswith((pfx, pfx + ",")):
290 part_accum += part
291 continue
292 part_accum += part
293 parts.append(part_accum)
294 part_accum = ""
295 pfx = ""
296 if 0 != len(part_accum):
297 if not part_accum.endswith(pfx):
298 # Unmatched quote
299 #
300 return False;
301 parts.append(part_accum)
302 part_accum = ""
303 pfx = ""
304
305 # Validate each of the tokens
306 #
307 for part in parts:
308 t_part = part.lower()
309 if t_part in _color2_validations['css_font_style']:
310 continue
311 if t_part in _color2_validations['css_font_variant_css21']:
312 continue
313 if t_part in _color2_validations['css_font_weight']:
314 continue
315 if t_part in _color2_validations['css_font_stretch']:
316 continue
317 if t_part in _color2_validations['css_font_absolute_size']:
318 continue
319 if t_part in _color2_validations['css_font_relative_size']:
320 continue
321 if not part.startswith("\"") and "/" in part:
322 subparts = part.split('/')
323 if 2 != len(subparts):
324 return False
325 if subparts[0] not in _color2_validations['css_font_absolute_size'] \
326 and subparts[0] not in _color2_validations['css_font_relative_size'] \
327 and _color2_validations['re_length'].match(subparts[0]) is None \
328 and _color2_validations['re_length2'].match(subparts[0]) is None \
329 and _color2_validations['re_length_pct'].match(subparts[0]) is None:
330 return False
331 if _color2_validations['re_length'].match(subparts[1]) is None \
332 and _color2_validations['re_length2'].match(subparts[1]) is None \
333 and _color2_validations['re_length_pct'].match(subparts[1]) is None:
334 return False
335 continue
336 if _color2_validations['re_length'].match(part) is not None:
337 continue;
338 if _color2_validations['re_length2'].match(part) is not None:
339 continue;
340 if _color2_validations['re_length_pct'].match(part) is not None:
341 continue;
342 if _color2_validations['re_font_family'].match(part) is not None:
343 continue;
344 if _color2_validations['re_font_family_quoted'].match(part) is not None:
345 continue;
346 if _color2_validations['re_font_family_squoted'].match(part) is not None:
347 continue;
348
349 # Did not match any CSS 'font' token
350 #
351 return False
352
353 # All tokens match expectations for CSS font property
354 #
355 return True
356
357 def macro_Color2(macro, text=None, col=None, bcol=None, font=None):
358 """Wrap text in a span element with inline color and font styles.
359
360 Note that CSS requires that a shorthand font property value include
361 both a font-size value and a font-family value. The other font style
362 properties are optional.
363
364 Security considerations: text argument value is escaped to prevent
365 various injection attacks. Each style argument is validated to avoid
366 CSS injection attackes; if the style argument fails validation then
367 it is ignored (not included in the result string).
368
369 Args:
370 text: the text string to be styled.
371 col: the CSS color property value to be applied. Optional.
372 bcol: the CSS background-color property value to be applied. Optional.
373 font: the CSS shorthand font property value. Optional.
374
375 Returns:
376 The text string wrapped in a span element with a style attribute.
377
378 """
379 f = macro.formatter
380
381 if not text:
382 return f.strong(1) + \
383 f.text('Color2 examples : ') + \
384 f.text('<<Color2(Hello World!,red,blue,18px courier)>>, ') + \
385 f.text('<<Color2(Hello World!,col=red,bcol=blue,font="18px courier")>>, ') + \
386 f.text('<<Color2(Hello World!,#8844AA)>>') + \
387 f.strong(0) + f.linebreak(0)
388
389 # Escape CSS values
390 #
391 if col is not None:
392 col = col.strip()
393 if not _color2_is_valid_color(macro, col):
394 col = None
395 if bcol is not None:
396 bcol = bcol.strip()
397 if not _color2_is_valid_color(macro, bcol):
398 bcol = None
399 if font is not None:
400 font = font.strip()
401 if not _color2_is_valid_font(macro, font):
402 font = None
403
404 # Assemble CSS style string
405 #
406 style = ''
407 if col:
408 style += 'color: %s; ' % col
409 if bcol:
410 style += 'background-color: %s; ' % bcol
411 if font:
412 style += 'font: %s; ' % font
413
414 # Escape HTML stuff.
415 text = wikiutil.escape(text)
416 text = wikiutil.renderText(macro.request, WikiParser, text)
417 text = text.strip()
418
419 # Get out
420 #
421 return f.rawHTML('<span style="%s">' % style) + text + f.rawHTML('</span>')
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.