Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/downloads/search_indexes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Haystack search indexes for the downloads app."""

import datetime
import html

from django.template.defaultfilters import striptags, truncatewords_html
from django.utils import timezone
Expand Down Expand Up @@ -43,7 +44,7 @@ def prepare_version(self, obj):

def prepare_description(self, obj):
"""Return a truncated plain-text description."""
return striptags(truncatewords_html(obj.content.rendered, 50))
return html.unescape(striptags(truncatewords_html(obj.content.rendered, 50)))

def prepare(self, obj):
"""Boost recent releases."""
Expand Down
22 changes: 22 additions & 0 deletions apps/downloads/tests/test_search_indexes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from types import SimpleNamespace

from django.template.loader import render_to_string
from django.test import SimpleTestCase

from apps.downloads.search_indexes import ReleaseIndex


class ReleaseSearchIndexTests(SimpleTestCase):
def test_prepare_description_returns_plain_text(self):
obj = SimpleNamespace(content=SimpleNamespace(rendered="<p>Data &amp; AI role <script>alert(1)</script></p>"))

self.assertEqual(ReleaseIndex().prepare_description(obj), "Data & AI role alert(1)")

def test_result_template_escapes_description(self):
rendered = render_to_string(
"search/includes/downloads.release.html",
{"result": {"description": "<script>alert(1)</script>"}},
)

self.assertNotIn("<script>", rendered)
self.assertIn("&lt;script&gt;", rendered)
6 changes: 4 additions & 2 deletions apps/events/search_indexes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Haystack search indexes for the events app."""

import html

from django.template.defaultfilters import striptags, truncatewords_html
from haystack import indexes

Expand Down Expand Up @@ -28,7 +30,7 @@ def prepare_path(self, obj):

def prepare_description(self, obj):
"""Return a truncated plain-text description."""
return striptags(truncatewords_html(obj.description, 50))
return html.unescape(striptags(truncatewords_html(obj.description, 50)))

def prepare_include_template(self, obj):
"""Return the search result template path."""
Expand Down Expand Up @@ -65,7 +67,7 @@ def prepare_path(self, obj):

def prepare_description(self, obj):
"""Return a truncated plain-text description."""
return striptags(truncatewords_html(obj.description.rendered, 50))
return html.unescape(striptags(truncatewords_html(obj.description.rendered, 50)))

def prepare_venue(self, obj):
"""Return the venue name or None if no venue is set."""
Expand Down
31 changes: 31 additions & 0 deletions apps/events/tests/test_search_indexes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from types import SimpleNamespace

from django.template.loader import render_to_string
from django.test import SimpleTestCase

from apps.events.search_indexes import CalendarIndex, EventIndex


class EventSearchIndexTests(SimpleTestCase):
def test_event_prepare_description_returns_plain_text(self):
obj = SimpleNamespace(
description=SimpleNamespace(rendered="<p>Data &amp; AI role <script>alert(1)</script></p>")
)

self.assertEqual(EventIndex().prepare_description(obj), "Data & AI role alert(1)")

def test_calendar_prepare_description_returns_plain_text(self):
obj = SimpleNamespace(description="<p>Data &amp; AI role <script>alert(1)</script></p>")

self.assertEqual(CalendarIndex().prepare_description(obj), "Data & AI role alert(1)")

def test_result_templates_escape_description(self):
for template in (
"search/includes/events.event.html",
"search/includes/events.calendar.html",
):
with self.subTest(template=template):
rendered = render_to_string(template, {"result": {"description": "<script>alert(1)</script>"}})

self.assertNotIn("<script>", rendered)
self.assertIn("&lt;script&gt;", rendered)
4 changes: 3 additions & 1 deletion apps/jobs/search_indexes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Haystack search indexes for the jobs app."""

import html

from django.template.defaultfilters import striptags, truncatewords_html
from django.urls import reverse
from haystack import indexes
Expand Down Expand Up @@ -101,7 +103,7 @@ def prepare_include_template(self, obj):

def prepare_description(self, obj):
"""Return a truncated plain-text job description."""
return striptags(truncatewords_html(obj.description.rendered, 50))
return html.unescape(striptags(truncatewords_html(obj.description.rendered, 50)))

def prepare_path(self, obj):
"""Return the URL for this job listing."""
Expand Down
24 changes: 24 additions & 0 deletions apps/jobs/tests/test_search_indexes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from types import SimpleNamespace

from django.template.loader import render_to_string
from django.test import SimpleTestCase

from apps.jobs.search_indexes import JobIndex


class JobSearchIndexTests(SimpleTestCase):
def test_prepare_description_returns_plain_text(self):
obj = SimpleNamespace(
description=SimpleNamespace(rendered="<p>Data &amp; AI role <script>alert(1)</script></p>")
)

self.assertEqual(JobIndex().prepare_description(obj), "Data & AI role alert(1)")

def test_result_template_escapes_description(self):
rendered = render_to_string(
"search/includes/jobs.job.html",
{"result": {"description": "<script>alert(1)</script>"}},
)

self.assertNotIn("<script>", rendered)
self.assertIn("&lt;script&gt;", rendered)
4 changes: 3 additions & 1 deletion apps/pages/search_indexes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Haystack search indexes for the pages app."""

import html

from django.template.defaultfilters import striptags, truncatewords_html
from haystack import indexes

Expand Down Expand Up @@ -27,7 +29,7 @@ def prepare_description(self, obj):
"""Create a description if none exists."""
if obj.description:
return obj.description
return striptags(truncatewords_html(obj.content.rendered, 50))
return html.unescape(striptags(truncatewords_html(obj.content.rendered, 50)))

def index_queryset(self, using=None):
"""Only index published pages."""
Expand Down
20 changes: 20 additions & 0 deletions apps/pages/tests/test_search_indexes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from types import SimpleNamespace

from django.test import SimpleTestCase

from apps.pages.search_indexes import PageIndex


class PageSearchIndexTests(SimpleTestCase):
def test_prepare_description_falls_back_to_plain_text_content(self):
obj = SimpleNamespace(
description="",
content=SimpleNamespace(rendered="<p>Data &amp; AI role <script>alert(1)</script></p>"),
)

self.assertEqual(PageIndex().prepare_description(obj), "Data & AI role alert(1)")

def test_prepare_description_keeps_existing_description(self):
obj = SimpleNamespace(description="Kept &amp; raw", content=None)

self.assertEqual(PageIndex().prepare_description(obj), "Kept &amp; raw")
2 changes: 1 addition & 1 deletion templates/search/includes/downloads.release.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h3>Release &ndash; <a href="{{ result.path }}">{{ result.name }}</a></h3>
<p>Version: {{ result.version }}</p>
<p>Released: {{ result.release_date|date }}</p>
<p>{{ result.description|safe }}</p>
<p>{{ result.description }}</p>
{% if result.release_notes_url %}
<p><a href="{{ result.release_notes_url }}">View Release Notes</a></p>
{% endif %}
2 changes: 1 addition & 1 deletion templates/search/includes/events.calendar.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h3>Calendar &ndash; <a href="/{{ result.path }}">{{ result.name }}</a></h3>
<p>{{ result.description|safe }}</p>
<p>{{ result.description }}</p>
{% if result.rss %}
<p><a href="{{ result.rss }}">RSS Feed</a></p>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion templates/search/includes/events.event.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ <h3>Event &ndash; <a href="{{ result.path }}">{{ result.name }}</a></h3>
<p>Location: {{ result.venue }}</p>
{% endif %}

<p>{{ result.description|safe }}</p>
<p>{{ result.description }}</p>
2 changes: 1 addition & 1 deletion templates/search/includes/jobs.job.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ <h3><a href="{{ result.path }}">Job - {{ result.name }}</a></h3>
<p>Location: {{ result.city }}{% if result.region %}, {{ result.region }}{% endif %} {{ result.country }}</p>

{% if result.telecommuting %}<p>Telecommuting: Yes</p>{% endif %}
<p>{{ result.description|safe }}</p>
<p>{{ result.description }}</p>