为什么这个Jinja nl2br过滤器逃避了?而不是<p>?

chr*_*kso 7 python regex jinja2 flask

我正在尝试实现这个 Jinja nl2br过滤器.它正常工作,除了<br>它添加的它被转义.这对我来说很奇怪,因为<p>它们没有被转义并且它们都在同一个字符串中.

我正在使用烧瓶,因此autoescape启用了Jinja .当我发现这个人autoescape并且escape(value)可能导致双重逃逸时,我真的很有希望,但删除escape()没有帮助.

这是我修改过的代码及其输出:

@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
    _paragraph_re = re.compile(r'(?:\r\n|\r(?!\n)|\n){2,}')
    result = u'\n\n'.join(u'<p>%s</p>' % escape(p.replace(u'\r\n', u'<br>\n')) for p in _paragraph_re.split(value))
    if eval_ctx.autoescape:
        result = Markup(result)
    return result
Run Code Online (Sandbox Code Playgroud)

输入:

u'1\r\n2\r\n\r\n3\r\n4\r\n\r\n5\r\n6\r\n7'
Run Code Online (Sandbox Code Playgroud)

输出:

<p>1&lt;br&gt;
2</p>

<p>3&lt;br&gt;
4</p>

<p>5&lt;br&gt;
6&lt;br&gt;
7</p>
Run Code Online (Sandbox Code Playgroud)

期望的输出:

<p>1<br>2</p>

<p>3<br>4</p>

<p>5<br>6<br>7</p>
Run Code Online (Sandbox Code Playgroud)

什么可能导致<br>逃脱,但允许<p>的?

joe*_*ler 5

nl2br过滤器不能正确处理标记的对象.如果value是标记,那么插入的<br>标签将被转义.要修复它,<br>标记也必须是标记:

@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
    _paragraph_re = re.compile(r'(?:\r\n|\r(?!\n)|\n){2,}')
    result = u'\n\n'.join(u'<p>%s</p>' % p.replace(u'\n', Markup('<br>\n'))
                                         for p in _paragraph_re.split(value))
    if eval_ctx.autoescape:
        result = Markup(result)
    return result
Run Code Online (Sandbox Code Playgroud)

注意:我将行结尾标准化为\n.

以下是对正在发生的事情的更长解释:

拆分Markup对象,产生许多Markup对象:

>>> Markup("hello there").split()
[Markup(u'hello'), Markup(u'there')]
Run Code Online (Sandbox Code Playgroud)

根据Jinja的Markup文档:

标记字符串上的操作是标记识别的,这意味着所有参数都通过escape()函数传递.

回顾主要转型nl2br,我们可以看到发生了什么以及为什么它不起作用:

result = u'\n\n'.join(u'<p>%s</p>' % p.replace(u'\n', u'<br>\n')
                                     for p in _paragraph_re.split(value))
Run Code Online (Sandbox Code Playgroud)

u'\n\n'u'<br>\n'是Unicode字符串,但pMarkup已经从分裂value,这是一个标记对象.p.replace尝试向Markup对象添加unicode字符串p,但Markup对象首先正确截取并转义字符串.

<p>由于Python组装了最终字符串,因此标签不会被转义,因为%格式化方法是在unicode字符串上调用的,它使用传递给它的元素的unicode表示.Markup元素已被声明为安全,因此它们不会被进一步转义.result最终成为一个unicode字符串.


Dav*_*Xia 5

在我写这篇文章的时候,另外两个答案不会逃避<br/>标签,但它们很容易受到XSS的攻击.使用此输入字符串测试它:

';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//";
alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--
></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>
Run Code Online (Sandbox Code Playgroud)

Dan Jacob的原始nl2br jinja片段几乎就在那里:

import re

from jinja2 import evalcontextfilter, Markup, escape

_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')

app = Flask(__name__)

@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
    result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n') \
        for p in _paragraph_re.split(escape(value)))
    if eval_ctx.autoescape:
        result = Markup(result)
    return result
Run Code Online (Sandbox Code Playgroud)

只要value只是一个字符串,上面的代码就可以工作了.它不但不能,如果value已经一个Markup对象,自那时以来,.replace()调用导致'<br>'串得到逃过一劫.这是从Jinja2通常处理逃逸的方式得出的; Markup假定对象是安全的,正常的字符串对象被认为是不安全的,因此将两者结合起来的操作会自动调用正常字符串对象上的转义.

要解决这个问题,只需将其与@ joemaller创建Markup('<br/>\n')对象的答案结合起来即可.即:

result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', Markup('<br/>\n')) \
    for p in _paragraph_re.split(escape(value)))
Run Code Online (Sandbox Code Playgroud)

  • 这真的,真的很难过,因为在他改编的[官方例子](http://jinja.pocoo.org/docs/dev/api/#custom-filters)中它实际上是正确的. (2认同)

oll*_*_uk 4

您是否尝试过移除逃生功能?因为下面的内容对我有用吗?

@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
   _paragraph_re = re.compile(r'(?:\r\n|\r(?!\n)|\n){2,}')
   result = u'\n\n'.join(u'<p>%s</p>' % p.replace(u'\r\n', u'<br/>') for p in _paragraph_re.split(value))
   if eval_ctx.autoescape:
       result = Markup(result)
   return result
Run Code Online (Sandbox Code Playgroud)

当在如下模板中使用时:

{{ '1\r\n2\r\n\r\n3\r\n4\r\n\r\n5\r\n6\r\n7' | nl2br}}
Run Code Online (Sandbox Code Playgroud)

给我下面的输出

<p>1<br/>2</p>

<p>3<br/>4</p>

<p>5<br/>6<br/>7</p>
Run Code Online (Sandbox Code Playgroud)