Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 9d48711

Browse files
committed
Fix: Check divid against spec.
1 parent a08e850 commit 9d48711

File tree

1 file changed

+35
-8
lines changed

1 file changed

+35
-8
lines changed

runestone/common/runestonedirective.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
from docutils.parsers.rst import Directive
2626
from docutils.utils import get_source_line
2727
from docutils.statemachine import ViewList
28-
2928
from sphinx import application
3029
from sphinx.errors import ExtensionError
30+
from sphinx.util import logging
31+
3132

3233
UNNUMBERED_DIRECTIVES = [
3334
# "activecode",
@@ -44,6 +45,8 @@
4445
"disqus",
4546
]
4647

48+
logger = logging.getLogger(__name__)
49+
4750

4851
# Provide a class which all Runestone nodes will inherit from.
4952
class RunestoneNode(nodes.Node):
@@ -265,6 +268,29 @@ def __init__(self, *args, **kwargs):
265268

266269
self.explain_text = []
267270

271+
# Check for a `valid HTML5 divid <https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute>`_.
272+
def validate_divid(self, divid):
273+
if (
274+
# Per the spec, a divid must not contain `whitespace <https://infra.spec.whatwg.org/#ascii-whitespace>`_ (see also `Python string escape sequences <https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals>`_).
275+
"\t" in divid
276+
or "\n" in divid
277+
or "\f" in divid
278+
or "\r" in divid
279+
or " " in divid
280+
or
281+
# Also avoid characters that need escaping for CSS to avoid problems there.
282+
"." in divid
283+
or "#" in divid
284+
or ":" in divid
285+
or
286+
# It must also be at least one character long. This is probably taken care of by the existence of ``self.arguments[0]``, but here's a bit of paranoia:
287+
len(divid) == 0
288+
):
289+
logger.error(
290+
f"Invalid divid '{divid}'.",
291+
location=self.state_machine.get_source_and_line(self.lineno),
292+
)
293+
268294

269295
# This is a base class for all Runestone directives which require a divid as their first parameter.
270296
class RunestoneIdDirective(RunestoneDirective):
@@ -318,9 +344,10 @@ def run(self):
318344
# Make sure the runestone directive at least requires an ID.
319345
assert self.required_arguments >= 1
320346
if "divid" not in self.options:
321-
id_ = self.options["divid"] = self.arguments[0]
347+
divid = self.options["divid"] = self.arguments[0]
322348
else:
323-
id_ = self.options["divid"]
349+
divid = self.options["divid"]
350+
self.validate_divid(divid)
324351

325352
self.options["qnumber"] = self.getNumber()
326353
# print(f"{id_} is number {self.options['qnumber']}")
@@ -331,19 +358,19 @@ def run(self):
331358
id_to_page = runestone_data.id_to_page
332359
page_to_id = runestone_data.page_to_id
333360
# See if this ID already exists.
334-
if id_ in id_to_page:
335-
page = id_to_page[id_]
361+
if divid in id_to_page:
362+
page = id_to_page[divid]
336363
# If it's not simply an update to an existing ID, complain.
337364
if page.docname != env.docname or page.lineno != self.lineno:
338365
raise self.error(
339366
"Duplicate ID -- see {}, line {}".format(page.docname, page.lineno)
340367
)
341368
# Make sure our data structure is consistent.
342-
assert id_ in page_to_id[page.docname]
369+
assert divid in page_to_id[page.docname]
343370
else:
344371
# Add a new entry.
345-
id_to_page[id_] = Struct(docname=env.docname, lineno=self.lineno)
346-
page_to_id[env.docname].add(id_)
372+
id_to_page[divid] = Struct(docname=env.docname, lineno=self.lineno)
373+
page_to_id[env.docname].add(divid)
347374

348375
self.in_exam = getattr(env, "in_timed", "")
349376

0 commit comments

Comments
 (0)