зеркало из https://github.com/mozilla/kitsune.git
[614115] Prevent infinite recursion of templates and includes.
This commit is contained in:
Родитель
894bf429c4
Коммит
14b4457d24
|
@ -539,7 +539,8 @@ class Revision(ModelBase):
|
|||
@property
|
||||
def content_parsed(self):
|
||||
from wiki.parser import wiki_to_html
|
||||
return wiki_to_html(self.content, self.document.locale)
|
||||
return wiki_to_html(self.content, locale=self.document.locale,
|
||||
doc_id=self.document.id)
|
||||
|
||||
|
||||
# FirefoxVersion and OperatingSystem map many ints to one Document. The
|
||||
|
|
|
@ -27,9 +27,11 @@ VIDEO_PARAMS = ['height', 'width', 'modal', 'title', 'placeholder']
|
|||
TEMPLATE_ARG_REGEX = re.compile('{{{([^{]+?)}}}')
|
||||
|
||||
|
||||
def wiki_to_html(wiki_markup, locale=settings.WIKI_DEFAULT_LANGUAGE):
|
||||
def wiki_to_html(wiki_markup, locale=settings.WIKI_DEFAULT_LANGUAGE,
|
||||
doc_id=None):
|
||||
"""Wiki Markup -> HTML with the wiki app's enhanced parser"""
|
||||
return WikiParser().parse(wiki_markup, show_toc=False, locale=locale)
|
||||
return WikiParser(doc_id=doc_id).parse(wiki_markup, show_toc=False,
|
||||
locale=locale)
|
||||
|
||||
|
||||
def _format_template_content(content, params):
|
||||
|
@ -273,15 +275,28 @@ class ForParser(object):
|
|||
return cls._PARSED_STRIPPED_FOR_CLOSER.sub(u'</for>', html)
|
||||
|
||||
|
||||
RECURSION_MESSAGE = _lazy('[Recursive inclusion of "%s"]')
|
||||
|
||||
|
||||
class WikiParser(sumo.parser.WikiParser):
|
||||
"""An extension of the parser from the forums adding more crazy features
|
||||
|
||||
{for} tags, inclusions, and templates--oh my!
|
||||
|
||||
"""
|
||||
def __init__(self, base_url=None):
|
||||
|
||||
def __init__(self, base_url=None, doc_id=None):
|
||||
"""
|
||||
doc_id -- If you want to be nice, pass the ID of the Document you are
|
||||
rendering. This will make recursive inclusions fail immediately
|
||||
rather than after the first round of recursion.
|
||||
|
||||
"""
|
||||
super(WikiParser, self).__init__(base_url)
|
||||
|
||||
# Stack of document IDs to prevent Include or Template recursion:
|
||||
self.inclusions = [doc_id] if doc_id else []
|
||||
|
||||
# The wiki has additional hooks not used elsewhere
|
||||
self.registerInternalLinkHook('Include', self._hook_include)
|
||||
self.registerInternalLinkHook('I', self._hook_include)
|
||||
|
@ -315,11 +330,19 @@ class WikiParser(sumo.parser.WikiParser):
|
|||
def _hook_include(self, parser, space, title):
|
||||
"""Returns the document's parsed content."""
|
||||
from wiki.models import Document
|
||||
message = _lazy('The document "%s" does not exist.') % title
|
||||
message = _('The document "%s" does not exist.') % title
|
||||
t = get_object_fallback(Document, title, locale=self.locale)
|
||||
if not t or not t.current_revision:
|
||||
return message
|
||||
return t.current_revision.content_parsed
|
||||
|
||||
if t.id in parser.inclusions:
|
||||
return RECURSION_MESSAGE % title
|
||||
else:
|
||||
parser.inclusions.append(t.id)
|
||||
ret = parser.parse(t.current_revision.content, show_toc=False,
|
||||
locale=self.locale)
|
||||
parser.inclusions.pop()
|
||||
return ret
|
||||
|
||||
# Wiki templates are documents that receive arguments.
|
||||
#
|
||||
|
@ -343,11 +366,16 @@ class WikiParser(sumo.parser.WikiParser):
|
|||
if not t or not t.current_revision:
|
||||
return message
|
||||
|
||||
if t.id in parser.inclusions:
|
||||
return RECURSION_MESSAGE % template_title
|
||||
else:
|
||||
parser.inclusions.append(t.id)
|
||||
c = t.current_revision.content.rstrip()
|
||||
# Note: this completely ignores the allowed attributes passed to the
|
||||
# WikiParser.parse() method, and defaults to ALLOWED_ATTRIBUTES
|
||||
# WikiParser.parse() method and defaults to ALLOWED_ATTRIBUTES.
|
||||
parsed = parser.parse(c, show_toc=False, attributes=ALLOWED_ATTRIBUTES,
|
||||
locale=self.locale)
|
||||
parser.inclusions.pop()
|
||||
|
||||
# Special case for inline templates
|
||||
if '\n' not in c:
|
||||
|
|
|
@ -9,7 +9,7 @@ from gallery.models import Video
|
|||
from gallery.tests import image, video
|
||||
from sumo.tests import TestCase
|
||||
import sumo.tests.test_parser
|
||||
from wiki.parser import (WikiParser, ForParser, PATTERNS,
|
||||
from wiki.parser import (WikiParser, ForParser, PATTERNS, RECURSION_MESSAGE,
|
||||
_build_template_params as _btp,
|
||||
_format_template_content as _ftc, _key_split)
|
||||
from wiki.tests import document, revision
|
||||
|
@ -302,6 +302,33 @@ class TestWikiTemplate(TestCase):
|
|||
eq_(0, doc('div.caption').length)
|
||||
eq_(0, doc('div.img').length)
|
||||
|
||||
def test_direct_recursion(self):
|
||||
"""Make sure direct recursion is caught on the very first nesting."""
|
||||
d = document(title='Template:Boo')
|
||||
d.save()
|
||||
|
||||
# Twice so the second revision sees content identical to itself:
|
||||
for i in range(2):
|
||||
revision(document=d, content='Fine [[Template:Boo]] Fellows',
|
||||
is_approved=True).save()
|
||||
|
||||
eq_('<p>Fine %s Fellows\n</p>' % (RECURSION_MESSAGE % 'Template:Boo'),
|
||||
d.content_parsed)
|
||||
|
||||
def test_indirect_recursion(self):
|
||||
"""Make sure indirect recursion is caught."""
|
||||
boo = document(title='Template:Boo')
|
||||
boo.save()
|
||||
yah = document(title='Template:Yah')
|
||||
yah.save()
|
||||
revision(document=boo, content='Paper [[Template:Yah]] Cups',
|
||||
is_approved=True).save()
|
||||
revision(document=yah, content='Wooden [[Template:Boo]] Bats',
|
||||
is_approved=True).save()
|
||||
recursion_message = RECURSION_MESSAGE % 'Template:Boo'
|
||||
eq_('<p>Paper Wooden %s Bats\n Cups\n</p>' % recursion_message,
|
||||
boo.content_parsed)
|
||||
|
||||
|
||||
class TestWikiInclude(TestCase):
|
||||
fixtures = ['users.json']
|
||||
|
@ -338,6 +365,37 @@ class TestWikiInclude(TestCase):
|
|||
doc = pq(p.parse('[[Include:Test title]]', locale='fr'))
|
||||
eq_('French content', doc.text())
|
||||
|
||||
def test_direct_recursion(self):
|
||||
"""Make sure direct recursion is caught on the very first nesting."""
|
||||
d = document(title='Boo')
|
||||
d.save()
|
||||
|
||||
# Twice so the second revision sees content identical to itself:
|
||||
for i in range(2):
|
||||
revision(document=d, content='Fine [[Include:Boo]] Fellows',
|
||||
is_approved=True).save()
|
||||
|
||||
eq_('<p>Fine %s Fellows\n</p>' % (RECURSION_MESSAGE % 'Boo'),
|
||||
d.content_parsed)
|
||||
|
||||
def test_indirect_recursion(self):
|
||||
"""Make sure indirect recursion is caught."""
|
||||
boo = document(title='Boo')
|
||||
boo.save()
|
||||
yah = document(title='Yah')
|
||||
yah.save()
|
||||
revision(document=boo, content='Paper [[Include:Yah]] Cups',
|
||||
is_approved=True).save()
|
||||
revision(document=yah, content='Wooden [[Include:Boo]] Bats',
|
||||
is_approved=True).save()
|
||||
recursion_message = RECURSION_MESSAGE % 'Boo'
|
||||
|
||||
# boo.content_parsed is something like <p>Paper </p><p>Wooden
|
||||
# [Recursive inclusion of "Boo"] Bats\n</p> Cups\n<p></p>.
|
||||
eq_('Paper Wooden %s Bats Cups' % recursion_message,
|
||||
boo.content_parsed.replace('</p>', '').replace('<p>',
|
||||
'').replace('\n', ''))
|
||||
|
||||
|
||||
class TestWikiVideo(TestCase):
|
||||
"""Video hook."""
|
||||
|
|
|
@ -274,7 +274,7 @@ def edit_document(request, document_slug, revision_id=None):
|
|||
def preview_revision(request):
|
||||
"""Create an HTML fragment preview of the posted wiki syntax."""
|
||||
wiki_content = request.POST.get('content', '')
|
||||
data = {'content': wiki_to_html(wiki_content, request.locale)}
|
||||
data = {'content': wiki_to_html(wiki_content, request.locale)} # TODO: Get doc ID from JSON.
|
||||
data.update(SHOWFOR_DATA)
|
||||
return jingo.render(request, 'wiki/preview.html', data)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче