Ticket #14057: 14057-context-parameter-2.diff

File 14057-context-parameter-2.diff, 11.5 KB (added by Will Hardy, 14 years ago)

Custom autoescape (all default filters should now be safe)

  • django/utils/html.py

     
    4747    """Converts newlines into <p> and <br />s."""
    4848    value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
    4949    paras = re.split('\n{2,}', value)
     50    _escape = callable(autoescape) and autoescape or escape
    5051    if autoescape:
    51         paras = [u'<p>%s</p>' % escape(p).replace('\n', '<br />') for p in paras]
     52        paras = [u'<p>%s</p>' % _escape(p).replace('\n', '<br />') for p in paras]
    5253    else:
    5354        paras = [u'<p>%s</p>' % p.replace('\n', '<br />') for p in paras]
    5455    return u'\n\n'.join(paras)
     
    9192
    9293    If autoescape is True, the link text and URLs will get autoescaped.
    9394    """
     95    _escape = callable(autoescape) and autoescape or escape
    9496    trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
    9597    safe_input = isinstance(text, SafeData)
    9698    words = word_split_re.split(force_unicode(text))
     
    116118            if url:
    117119                trimmed = trim_url(middle)
    118120                if autoescape and not safe_input:
    119                     lead, trail = escape(lead), escape(trail)
    120                     url, trimmed = escape(url), escape(trimmed)
     121                    lead, trail = _escape(lead), _escape(trail)
     122                    url, trimmed = _escape(url), _escape(trimmed)
    121123                middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
    122124                words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
    123125            else:
    124126                if safe_input:
    125127                    words[i] = mark_safe(word)
    126128                elif autoescape:
    127                     words[i] = escape(word)
     129                    words[i] = _escape(word)
    128130        elif safe_input:
    129131            words[i] = mark_safe(word)
    130132        elif autoescape:
    131             words[i] = escape(word)
     133            words[i] = _escape(word)
    132134    return u''.join(words)
    133135urlize = allow_lazy(urlize, unicode)
    134136
  • django/utils/safestring.py

     
    117117        return EscapeUnicode(s)
    118118    return EscapeString(str(s))
    119119
     120def conditional_custom_escape(escape_function):
     121    """ Return a new function that conditionally escapes its input using the
     122        given escape function.
     123    """
     124    def _escape(value):
     125        if isinstance(value, SafeData):
     126            return value
     127        else:
     128            return escape_function(value)
     129    for attr in ('autoescape_context', 'safe_filters'):
     130        if hasattr(escape_function, attr):
     131            setattr(_escape, attr, getattr(escape_function, attr))
     132    return _escape
     133
  • django/template/defaultfilters.py

     
    3232        if args:
    3333            args = list(args)
    3434            args[0] = force_unicode(args[0])
    35             if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
    36                 return mark_safe(func(*args, **kwargs))
    3735        return func(*args, **kwargs)
    3836
    3937    # Include a reference to the real function (used to check original
     
    163161        return input_val
    164162
    165163    if not m and p < 0:
    166         return mark_safe(formats.number_format(u'%d' % (int(d)), 0))
     164        return formats.number_format(u'%d' % (int(d)), 0)
    167165
    168166    if p == 0:
    169167        exp = Decimal(1)
    170168    else:
    171169        exp = Decimal('1.0') / (Decimal(10) ** abs(p))
    172170    try:
    173         return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))
     171        return formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p))
    174172    except InvalidOperation:
    175173        return input_val
    176174floatformat.is_safe = True
     
    183181
    184182def linenumbers(value, autoescape=None):
    185183    """Displays text with line numbers."""
    186     from django.utils.html import escape
     184    if callable(autoescape):
     185        _escape = autoescape
     186    else:
     187        from django.utils.html import escape as _escape
    187188    lines = value.split(u'\n')
    188189    # Find the maximum width of the line count, for use with zero padding
    189190    # string format command
     
    193194            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
    194195    else:
    195196        for i, line in enumerate(lines):
    196             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
     197            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, _escape(line))
    197198    return mark_safe(u'\n'.join(lines))
    198199linenumbers.is_safe = True
    199200linenumbers.needs_autoescape = True
     
    224225    import unicodedata
    225226    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    226227    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    227     return mark_safe(re.sub('[-\s]+', '-', value))
     228    return re.sub('[-\s]+', '-', value)
    228229slugify.is_safe = True
    229230slugify = stringfilter(slugify)
    230231
     
    373374center.is_safe = True
    374375center = stringfilter(center)
    375376
    376 def cut(value, arg):
     377def cut(value, arg, autoescape=None):
    377378    """
    378379    Removes all values of arg from the given string.
    379380    """
    380381    safe = isinstance(value, SafeData)
    381382    value = value.replace(arg, u'')
    382     if safe and arg != ';':
     383    if safe and arg != ';' and not callable(autoescape):
    383384        return mark_safe(value)
    384385    return value
    385386cut = stringfilter(cut)
     387cut.needs_autoescape = True
    386388
    387389###################
    388390# HTML STRINGS    #
     
    397399escape.is_safe = True
    398400escape = stringfilter(escape)
    399401
    400 def force_escape(value):
     402def force_escape(value, autoescape=None):
    401403    """
    402404    Escapes a string's HTML. This returns a new string containing the escaped
    403405    characters (as opposed to "escape", which marks the content for later
    404406    possible escaping).
    405407    """
    406     from django.utils.html import escape
    407     return mark_safe(escape(value))
     408    if callable(autoescape):
     409        return mark_safe(autoescape(value))
     410    else:
     411        from django.utils.html import escape
     412        return mark_safe(_escape(value))
    408413force_escape = stringfilter(force_escape)
    409414force_escape.is_safe = True
     415force_escape.needs_autoescape = True
    410416
    411417def linebreaks(value, autoescape=None):
    412418    """
     
    415421    followed by a blank line becomes a paragraph break (``</p>``).
    416422    """
    417423    from django.utils.html import linebreaks
    418     autoescape = autoescape and not isinstance(value, SafeData)
     424    autoescape = not isinstance(value, SafeData) and autoescape
    419425    return mark_safe(linebreaks(value, autoescape))
    420426linebreaks.is_safe = True
    421427linebreaks.needs_autoescape = True
     
    427433    (``<br />``).
    428434    """
    429435    if autoescape and not isinstance(value, SafeData):
    430         from django.utils.html import escape
    431         value = escape(value)
     436        if callable(autoescape):
     437            value = autoescape(value)
     438        else:
     439            from django.utils.html import escape
     440            value = escape(value)
    432441    return mark_safe(value.replace('\n', '<br />'))
    433442linebreaksbr.is_safe = True
    434443linebreaksbr.needs_autoescape = True
     
    510519    Joins a list with a string, like Python's ``str.join(list)``.
    511520    """
    512521    value = map(force_unicode, value)
     522    _escape = callable(autoescape) and autoescape.conditional or conditional_escape
    513523    if autoescape:
    514         value = [conditional_escape(v) for v in value]
     524        value = [_escape(v) for v in value]
    515525    try:
    516         data = conditional_escape(arg).join(value)
     526        data = _escape(arg).join(value)
    517527    except AttributeError: # fail silently but nicely
    518528        return value
    519529    return mark_safe(data)
     
    591601        </ul>
    592602        </li>
    593603    """
    594     if autoescape:
     604    if callable(autoescape):
     605        escaper = autoescape.conditional
     606    elif autoescape:
    595607        from django.utils.html import conditional_escape
    596608        escaper = conditional_escape
    597609    else:
  • django/template/__init__.py

     
    566566                        obj = settings.TEMPLATE_STRING_IF_INVALID
    567567        else:
    568568            obj = self.var
     569
     570        # Custom autoescape functions can define a context other than html
     571        # to prevent html functions from marking output as safe
     572        ae_context = getattr(context.autoescape, 'autoescape_context', 'html')
     573        # Custom autoescape functions can define a list of safe filter names
     574        ae_safe_filters = getattr(context.autoescape, 'safe_filters', set())
     575
    569576        for func, args in self.filters:
     577            func_ae_context = getattr(func, 'autoescape_context', 'html')
    570578            arg_vals = []
    571579            for lookup, arg in args:
    572580                if not lookup:
    573581                    arg_vals.append(mark_safe(arg))
    574582                else:
    575583                    arg_vals.append(arg.resolve(context))
     584
     585
    576586            if getattr(func, 'needs_autoescape', False):
    577587                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
    578588            else:
    579589                new_obj = func(obj, *arg_vals)
    580             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     590            if (getattr(func, 'is_safe', False) and isinstance(obj, SafeData)
     591                    and (ae_context == func_ae_context or func.__name__ in ae_safe_filters)):
    581592                obj = mark_safe(new_obj)
    582593            elif isinstance(obj, EscapeData):
    583594                obj = mark_for_escaping(new_obj)
     
    828839    value = localize(value, use_l10n=context.use_l10n)
    829840    value = force_unicode(value)
    830841    if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
    831         return escape(value)
     842        if callable(context.autoescape):
     843            return context.autoescape(value)
     844        else:
     845            return escape(value)
    832846    else:
    833847        return value
    834848
  • django/template/context.py

     
    11from django.core.exceptions import ImproperlyConfigured
    22from django.utils.importlib import import_module
     3from django.utils.safestring import conditional_custom_escape
    34
    45# Cache of actual callables.
    56_standard_context_processors = None
     
    6768class Context(BaseContext):
    6869    "A stack container for variable context"
    6970    def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
     71        if callable(autoescape):
     72            autoescape.conditional = conditional_custom_escape(autoescape)
    7073        self.autoescape = autoescape
    7174        self.use_l10n = use_l10n
    7275        self.current_app = current_app
  • django/template/debug.py

     
    9797        except UnicodeDecodeError:
    9898            return ''
    9999        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
    100             return escape(output)
     100            if callable(context.autoescape):
     101                return context.autoescape(output)
     102            else:
     103                return escape(output)
    101104        else:
    102105            return output
Back to Top