view data/plugin/macro/span.py @ 537:61621f711b18

span macro: avoid empty style (if all was crap)
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Tue, 22 Jun 2010 17:18:59 +0200
parents c937dca76e96
children 6cb5b60276b1
line wrap: on
line source
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - span generating macro

    Supported span attrs:
        class (use css_class param)
        id
        lang
        dir
        title
    
    Partially supported attrs:
        if SUPPORT_STYLE_ATTR is True:
        style - only support safe properties that are on whitelist,  others
                could contain javascript and thus, be dangerous (XSS etc.)!
        
        if SUPPORT_STYLE_ATTR is False:
        style - macro argument is accepted, but will be silently ignored, it
                won't create style attribute output.

    Unsupported attrs:
        event attrs - unsafe, can contain javascript, XSS danger
        align - deprecated by the W3C (use css classes)

    Usage:
    <<span(red)>>some text contained in a span with css class red<<span>>
    <<span(css_class=red)>>same as above<<span>>
    <<span(id=foobar)>>some text in a span with id foobar<<span>>
    <<span(title="read this!")>>some text with a mouseover title<<span>>

    if SUPPORT_STYLE_ATTR is True, this also works:
    <<span(style="color: red; font: 20pt sans-serif;")>>20pt sans-serif red<<span>>

    @copyright: 2010 MoinMoin:ThomasWaldmann
    @license: GNU GPL, see COPYING for details.
"""

SUPPORT_STYLE_ATTR = True  # True should be safe, False is safer :)

Dependencies = []


def make_style_safe(style, whitelist=None):
    """
    make html 'style' attribute value safe, only accept property names in whitelist,
    if whitelist is None, use builtin WHITELIST
    """
    # whitelist of safe style attributes, taken from:
    # http://validator.w3.org/feed/docs/warning/DangerousStyleAttr.html
    WHITELIST = ("azimuth,background,background-color,border,border-bottom,"
                 "border-bottom-color,border-bottom-style,border-bottom-width,"
                 "border-collapse,border-color,border-left,border-left-color,"
                 "border-left-style,border-left-width,border-right,border-right-color,"
                 "border-right-style,border-right-width,border-spacing,border-style,"
                 "border-top,border-top-color,border-top-style,border-top-width,"
                 "border-width,clear,color,cursor,direction,display,elevation,"
                 "float,font,font-family,font-size,font-style,font-variant,"
                 "font-weight,height,letter-spacing,line-height,margin,"
                 "margin-bottom,margin-left,margin-right,margin-top,overflow,"
                 "padding,padding-bottom,padding-left,padding-right,padding-top,"
                 "pause,pause-after,pause-before,pitch,pitch-range,richness,"
                 "speak,speak-header,speak-numeral,speak-punctuation,speech-rate,"
                 "stress,text-align,text-decoration,text-indent,unicode-bidi,"
                 "vertical-align,voice-family,volume,white-space,width").split(',')

    def style_split(style):
        """
        split style into a list of declarations,
        split the declarations into property, value tuples,
        remove all surrounding whitespace
        """
        decls = []
        for decl in style.split(u';'):
            decl = decl.split(u':', 1)
            if len(decl) == 2:
                prop = decl[0].strip()
                val = decl[1].strip()
                decls.append((prop, val))
        return decls

    def style_join(decls):
        """
        join a list of prop, value tuples into a style declaration
        """
        decls = [u'%s: %s' % (prop, val) for prop, val in decls]
        style = u'; '.join(decls)
        return style

    def decl_filter(decls, whitelist):
        """
        filter a list of prop, value tuples, only let whitelisted props through
        """
        whitelist = whitelist or WHITELIST
        return [(prop, val) for prop, val in decls if prop in whitelist]

    decls = style_split(style)
    decls = decl_filter(decls, whitelist)
    style = style_join(decls)
    return style


def macro_span(macro,
               # first the stuff we can directly give to span formatter:
               css_class=u'',
               id=u'',
               lang=u'',
               dir=u'',
               title=u'',
               # deprecated by W3C:
               #align=u'',
               style=u'',
              ):
    attrs = {}
    for key, value in [
        ('css_class', css_class),
        ('id', id),
        ('lang', lang),
        ('dir', dir),
        ('title', title),
        #('align', align),
        ]:
        if value:
            attrs[key] = value

    if SUPPORT_STYLE_ATTR:
        style = make_style_safe(style)
        if style:
            attrs['style'] = style

    if attrs:
        return macro.formatter.span(True, **attrs)
    else:
        return macro.formatter.span(False)