[614115] Prevent infinite recursion of templates and includes.

This commit is contained in:
Erik Rose 2010-11-22 20:45:18 -05:00
Родитель 894bf429c4
Коммит 14b4457d24
4 изменённых файлов: 96 добавлений и 9 удалений

Просмотреть файл

@ -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)