diff --git a/_includes/action_bar.html b/_includes/action_bar.html index 751a07c1089..09f629753d4 100644 --- a/_includes/action_bar.html +++ b/_includes/action_bar.html @@ -10,9 +10,9 @@ role="button" tabindex="0" data-conf-id="{{ conf_id }}" - data-conf-name="{{ include.conf_name }}" + data-conf-name="{{ include.conf_name | escape }}" data-conf-cfp="{{ include.conf_cfp }}" - data-conf-place="{{ include.conf_place }}" + data-conf-place="{{ include.conf_place | escape }}" aria-label="Conference actions"> diff --git a/_includes/calendar.js b/_includes/calendar.js index b84620792a8..f720dd9d529 100644 --- a/_includes/calendar.js +++ b/_includes/calendar.js @@ -124,15 +124,16 @@ function load_conference_list() { {% assign extended = "(extended)" %} {% else %} {% assign cfp = conf.cfp %} + {% assign extended = "" %} {% endif %} {% capture conf_date %}{%- translate_file dates/pretty_dates.html start=conf.start end=conf.end -%}{% endcapture %} // add deadlines in red conf_list_all.push({ id: "{{conf.conference | slugify: "latin"}}-{{conf.year}}-deadline", abbreviation: "{{conf.conference | slugify: "latin"}}-{{conf.year}}", - name: "{{conf.conference}} {{conf.year}} CfP {{extended}}", + name: {{ conf.conference | append: " " | append: conf.year | append: " CfP " | append: extended | jsonify }}, color: "red", - location: "{{conf.place}}", + location: {{ conf.place | jsonify }}, date: "{{conf_date | strip}}", subject: "{{conf.sub}}", startDate: Date.parse("{{cfp}}"), @@ -142,9 +143,9 @@ function load_conference_list() { conf_list_all.push({ id: "{{conf.conference | slugify: "latin"}}-{{conf.year}}-deadline", abbreviation: "{{conf.conference | slugify: "latin"}}-{{conf.year}}", - name: "{{conf.conference}} {{conf.year}} Workshop Deadline", + name: {{ conf.conference | append: " " | append: conf.year | append: " Workshop Deadline" | jsonify }}, color: "red", - location: "{{conf.place}}", + location: {{ conf.place | jsonify }}, date: "{{ conf_date | strip }}", subject: "{{conf.sub}}", startDate: Date.parse("{{conf.workshop_deadline}}"), @@ -155,9 +156,9 @@ function load_conference_list() { conf_list_all.push({ id: "{{conf.conference | slugify: "latin"}}-{{conf.year}}-deadline", abbreviation: "{{conf.conference | slugify: "latin"}}-{{conf.year}}", - name: "{{conf.conference}} {{conf.year}} Tutorial Deadline", + name: {{ conf.conference | append: " " | append: conf.year | append: " Tutorial Deadline" | jsonify }}, color: "red", - location: "{{conf.place}}", + location: {{ conf.place | jsonify }}, date: "{{ conf_date | strip }}", subject: "{{conf.sub}}", startDate: Date.parse("{{cfp}}"), @@ -177,9 +178,9 @@ function load_conference_list() { conf_list_all.push({ id: "{{conf.conference | slugify: "latin"}}-{{conf.year}}-conference", abbreviation: "{{conf.conference | slugify: "latin"}}-{{conf.year}}", - name: "{{conf.conference}} {{conf.year}}", + name: {{ conf.conference | append: " " | append: conf.year | jsonify }}, color: color, - location: "{{conf.place}}", + location: {{ conf.place | jsonify }}, date: "{{ conf_date | strip }}", subject: "{{conf.sub}}", startDate: Date.parse("{{conf.start}}"), diff --git a/_includes/conference_detail_actions.html b/_includes/conference_detail_actions.html index 08a616cf77d..81a7bfafb80 100644 --- a/_includes/conference_detail_actions.html +++ b/_includes/conference_detail_actions.html @@ -5,17 +5,17 @@
{{ conf.place }}
{% endif %}", 0), + ], +) +def test_checker_flags_unescaped_free_text(tmp_path, snippet, expected): + """The checker flags free text in scripts and HTML tags unless a matching filter escapes it.""" + path = tmp_path / "page.html" + + assert len(violations(path, f"{{{{ conf.place }}}}
\n{snippet}\n")) == expected + + +def test_checker_follows_include_parameters(tmp_path): + """A field passed into an include under another name is still free text inside the include.""" + caller = (tmp_path / "index.html", "{% include bar.html conf_name=conf.conference conf_id=conf_id %}") + include = tmp_path / "_includes" / "bar.html" + text = '' + + assert len(violations(include, text, attribute_parameters=include_parameters([caller], ATTRIBUTE_FIELDS))) == 1 + + +def test_checker_reads_templates(): + """The checker finds the jsonify'd conference data the site already injects.""" + head = ROOT / "_includes" / "head.html" + tags = [ + tag.group(1) + for _, source in script_regions(head, head.read_text(encoding="utf-8")) + for tag in OUTPUT_TAG.finditer(source) + ] + + assert any("conf.place" in tag and "jsonify" in tag for tag in tags) + + +def test_checker_reads_attributes(): + """The checker finds the conference names the front page puts into data attributes.""" + index = ROOT / "index.html" + tags = [tag.group(1) for tag in attribute_tags(index, index.read_text(encoding="utf-8"))] + + assert any("conf.conference" in tag for tag in tags) + + +def test_free_text_reaches_templates_escaped(): + """Conference text in scripts goes through jsonify, and in HTML tags through escape or url_encode.""" + templates = [(path, path.read_text(encoding="utf-8")) for path in template_files()] + script_parameters = include_parameters(templates, FREE_TEXT_FIELDS) + attribute_parameters = include_parameters(templates, ATTRIBUTE_FIELDS) + found = [ + message + for path, text in templates + for message in violations(path, text, script_parameters, attribute_parameters) + ] + + assert not found, "Unescaped Liquid output:\n" + "\n".join(found)