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.
  • [get | view] (2008-01-26 14:27:02, 4.9 KB) [[attachment:Color2-1.6.py]]
  • [get | view] (2011-03-01 17:09:31, 2.3 KB) [[attachment:Color2-1.9.3-1.py]]
  • [get | view] (2011-02-23 16:33:05, 2.1 KB) [[attachment:Color2-1.9.3.py]]
  • [get | view] (2017-05-18 01:43:16, 16.3 KB) [[attachment:Color2-1.9.9.py]]
  • [get | view] (2006-09-19 15:41:58, 4.7 KB) [[attachment:Color2.py]]
  • [get | view] (2008-08-11 16:41:33, 9.8 KB) [[attachment:Color2_Example.png]]
  • [get | view] (2008-08-11 16:42:00, 20.6 KB) [[attachment:Color2_ExampleTables.png]]
  • [get | view] (2009-08-08 00:18:45, 4.9 KB) [[attachment:Color2_bar.py]]
  • [get | view] (2009-08-08 02:07:07, 1.0 KB) [[attachment:Color2_bar_example.png]]
  • [get | view] (2017-05-18 02:28:07, 80.2 KB) [[attachment:Color2_example-1.9.9-at-2017-05-17.png]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.