diff --git a/h/accounts/schemas.py b/h/accounts/schemas.py index 5d75d3fb11e..81d74aa6d44 100644 --- a/h/accounts/schemas.py +++ b/h/accounts/schemas.py @@ -4,7 +4,7 @@ import colander import deform -from jinja2 import Markup +from markupsafe import Markup from h import i18n, models from h.models.user import ( diff --git a/h/form.py b/h/form.py index 6b47aa7fdb1..016f3d448ac 100644 --- a/h/form.py +++ b/h/form.py @@ -5,8 +5,8 @@ form templates in preference to the defaults. """ import deform -import jinja2 import pyramid_jinja2 +from markupsafe import Markup from pyramid import httpexceptions from pyramid.path import AssetResolver @@ -45,7 +45,7 @@ def __call__(self, template_name, **kwargs): context = self._system.copy() context.update(kwargs) - return jinja2.Markup(template.render(context)) + return Markup(template.render(context)) def create_environment(base): diff --git a/h/presenters/annotation_html.py b/h/presenters/annotation_html.py index 9bd38c286e8..0309fa9f5a5 100644 --- a/h/presenters/annotation_html.py +++ b/h/presenters/annotation_html.py @@ -1,5 +1,5 @@ -import jinja2 from dateutil import parser +from markupsafe import Markup, escape from h.presenters.document_html import DocumentHTMLPresenter @@ -16,7 +16,7 @@ def __init__(self, annotation): @property def uri(self): - return jinja2.escape(self.annotation.target_uri) + return escape(self.annotation.target_uri) @property def text_rendered(self): @@ -28,15 +28,15 @@ def text_rendered(self): care of all necessary escaping. """ if self.annotation.text_rendered: - return jinja2.Markup(self.annotation.text_rendered) - return jinja2.Markup("") + return Markup(self.annotation.text_rendered) + return Markup("") @property def quote(self): """Get the text in the document which this annotation refers to.""" selection = self._get_selection() if selection: - return jinja2.escape(selection) + return escape(selection) return "" @@ -55,12 +55,12 @@ def description(self): selection = self._get_selection() if selection: - selection = jinja2.escape(selection) + selection = escape(selection) description += f"<blockquote>{selection}</blockquote>" text = self.annotation.text if text: - text = jinja2.escape(text) + text = escape(text) description += f"{text}" return description @@ -74,7 +74,7 @@ def created_day_string(self): date. """ - created_string = jinja2.escape(self.annotation.created) + created_string = escape(self.annotation.created) return parser.parse(created_string).strftime("%Y-%m-%d") @property diff --git a/h/presenters/document_html.py b/h/presenters/document_html.py index f6864d27805..fd8f1ec0074 100644 --- a/h/presenters/document_html.py +++ b/h/presenters/document_html.py @@ -1,6 +1,6 @@ from urllib.parse import unquote, urlparse -import jinja2 +import markupsafe class DocumentHTMLPresenter: @@ -24,7 +24,7 @@ def filename(self): """ if self.uri.lower().startswith("file:///"): - return jinja2.escape(self.uri.split("/")[-1]) + return markupsafe.escape(self.uri.split("/")[-1]) return "" @property @@ -44,7 +44,7 @@ def href(self): """ if self.document.web_uri: - return jinja2.escape(self.document.web_uri) + return markupsafe.escape(self.document.web_uri) return "" @property @@ -64,14 +64,14 @@ def hostname_or_filename(self): object so that it doesn't get double-escaped. """ if self.filename: - return jinja2.escape(unquote(self.filename)) + return markupsafe.escape(unquote(self.filename)) hostname = urlparse(self.uri).hostname # urlparse()'s .hostname is sometimes None. hostname = hostname or "" - return jinja2.escape(hostname) + return markupsafe.escape(hostname) @property def link(self): @@ -129,7 +129,7 @@ def link_text(self): Markup object so it doesn't get double-escaped. """ - title = jinja2.escape(self.title) + title = markupsafe.escape(self.title) # Sometimes self.title is the annotated document's URI (if the document # has no title). In those cases we want to remove the http(s):// from @@ -160,17 +160,17 @@ def title(self): # Convert non-string titles into strings. # We're assuming that title cannot be a byte string. title = str(title) - return jinja2.escape(title) + return markupsafe.escape(title) if self.filename: - return jinja2.escape(unquote(self.filename)) + return markupsafe.escape(unquote(self.filename)) - return jinja2.escape(unquote(self.uri)) + return markupsafe.escape(unquote(self.uri)) @property def uri(self): if self.document.document_uris: - return jinja2.escape(self.document.document_uris[0].uri) + return markupsafe.escape(self.document.document_uris[0].uri) return "" @property @@ -205,7 +205,7 @@ def truncate(content, length=55): if len(content) <= length: return content - return content[:length] + jinja2.Markup("…") + return content[:length] + markupsafe.Markup("…") host_or_filename = truncate(host_or_filename) link_text = truncate(link_text) @@ -220,10 +220,10 @@ def truncate(content, length=55): link += "
{host_or_filename}" link = link.format( - href=jinja2.escape(href), - title=jinja2.escape(title), - link_text=jinja2.escape(link_text), - host_or_filename=jinja2.escape(host_or_filename), + href=markupsafe.escape(href), + title=markupsafe.escape(title), + link_text=markupsafe.escape(link_text), + host_or_filename=markupsafe.escape(host_or_filename), ) - return jinja2.Markup(link) + return markupsafe.Markup(link) diff --git a/h/views/accounts.py b/h/views/accounts.py index 5d3abfbf1e2..b368c3c5463 100644 --- a/h/views/accounts.py +++ b/h/views/accounts.py @@ -4,7 +4,7 @@ import colander import deform -import jinja2 +from markupsafe import Markup from pyramid import httpexceptions, security from pyramid.exceptions import BadCSRFToken from pyramid.view import view_config, view_defaults @@ -285,7 +285,7 @@ def _reset_password(self, user, password): svc.update_password(user, password) self.request.session.flash( - jinja2.Markup( + Markup( _( "Your password has been reset. You can now log in with " "your new password." @@ -321,7 +321,7 @@ def get_when_not_logged_in(self): activation = models.Activation.get_by_code(self.request.db, code) if activation is None: self.request.session.flash( - jinja2.Markup( + Markup( _( "We didn't recognize that activation link. " "Have you already activated your account? " @@ -340,7 +340,7 @@ def get_when_not_logged_in(self): user.activate() self.request.session.flash( - jinja2.Markup( + Markup( _( "Your account has been activated! " "You can now log in using the password you provided." @@ -369,14 +369,12 @@ def get_when_logged_in(self): # The user is already logged in to the account (so the account # must already be activated). self.request.session.flash( - jinja2.Markup( - _("Your account has been activated and you're logged in.") - ), + Markup(_("Your account has been activated and you're logged in.")), "success", ) else: self.request.session.flash( - jinja2.Markup( + Markup( _( "You're already logged in to a different account. " 'Log out and open the activation link ' diff --git a/h/views/activity.py b/h/views/activity.py index 0041bd5d5e8..6ad5e426f6d 100644 --- a/h/views/activity.py +++ b/h/views/activity.py @@ -2,7 +2,7 @@ from urllib.parse import urlparse -from jinja2 import Markup +from markupsafe import Markup from pyramid import httpexceptions from pyramid.view import view_config, view_defaults diff --git a/h/views/admin/groups.py b/h/views/admin/groups.py index e1a0c18b32e..f8719d94339 100644 --- a/h/views/admin/groups.py +++ b/h/views/admin/groups.py @@ -1,4 +1,4 @@ -from jinja2 import Markup +from markupsafe import Markup from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config, view_defaults diff --git a/h/views/admin/organizations.py b/h/views/admin/organizations.py index 8217e1098fa..07c292e6496 100644 --- a/h/views/admin/organizations.py +++ b/h/views/admin/organizations.py @@ -1,4 +1,4 @@ -from jinja2 import Markup +from markupsafe import Markup from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config, view_defaults from sqlalchemy import func diff --git a/h/views/admin/users.py b/h/views/admin/users.py index 91fe83d956e..08ddd8eb161 100644 --- a/h/views/admin/users.py +++ b/h/views/admin/users.py @@ -1,4 +1,4 @@ -import jinja2 +from markupsafe import Markup from pyramid import httpexceptions from pyramid.view import view_config @@ -70,7 +70,7 @@ def users_activate(request): request.session.flash( # pylint:disable=consider-using-f-string - jinja2.Markup(_("User {name} has been activated!".format(name=user.username))), + Markup(_("User {name} has been activated!".format(name=user.username))), "success", ) @@ -145,7 +145,7 @@ def users_delete(request): @view_config(context=UserNotFoundError) def user_not_found(exc, request): # pragma: no cover - request.session.flash(jinja2.Markup(_(exc.message)), "error") + request.session.flash(Markup(_(exc.message)), "error") return httpexceptions.HTTPFound(location=request.route_path("admin.users")) diff --git a/tests/unit/h/presenters/annotation_html_test.py b/tests/unit/h/presenters/annotation_html_test.py index d3abbe45ae3..e31e7a2ad52 100644 --- a/tests/unit/h/presenters/annotation_html_test.py +++ b/tests/unit/h/presenters/annotation_html_test.py @@ -1,7 +1,7 @@ import datetime import pytest -from jinja2 import Markup +from markupsafe import Markup from h.presenters.annotation_html import AnnotationHTMLPresenter diff --git a/tests/unit/h/presenters/document_html_test.py b/tests/unit/h/presenters/document_html_test.py index 38fbafc97ee..d107546155d 100644 --- a/tests/unit/h/presenters/document_html_test.py +++ b/tests/unit/h/presenters/document_html_test.py @@ -1,7 +1,7 @@ from unittest import mock -import jinja2 import pytest +from markupsafe import Markup, escape from h.presenters.document_html import DocumentHTMLPresenter @@ -26,7 +26,7 @@ def test_filename_returns_Markup(self): document_uris=[mock.Mock(uri="file:///home/seanh/MyFile.pdf")] ) - assert isinstance(presenter.filename, jinja2.Markup) + assert isinstance(presenter.filename, Markup) def test_filename_with_FILE_uri(self): presenter = self.presenter( @@ -65,7 +65,7 @@ def test_href_returns_empty_string_for_document_with_no_web_uri(self): def test_href_returns_Markup(self): web_uri = "http://www.example.com/example.html" - assert isinstance(self.presenter(web_uri=web_uri).href, jinja2.Markup) + assert isinstance(self.presenter(web_uri=web_uri).href, Markup) link_text_fixtures = pytest.mark.usefixtures("title") @@ -105,12 +105,12 @@ def test_link_text_with_https_title(self, title): @link_text_fixtures def test_link_text_returns_Markup_if_title_returns_Markup(self, title): for title_ in ( - jinja2.Markup("Example Document"), - jinja2.Markup("http://www.example.com/example.html"), - jinja2.Markup("https://www.example.com/example.html"), + Markup("Example Document"), + Markup("http://www.example.com/example.html"), + Markup("https://www.example.com/example.html"), ): title.return_value = title_ - assert isinstance(self.presenter().link_text, jinja2.Markup) + assert isinstance(self.presenter().link_text, Markup) hostname_or_filename_fixtures = pytest.mark.usefixtures("uri", "filename") @@ -122,9 +122,9 @@ def test_hostname_or_filename_returns_filename_for_files(self, filename): @hostname_or_filename_fixtures def test_hostname_or_filename_returns_Markup_if_filename_does(self, filename): - filename.return_value = jinja2.Markup("MyFile.pdf") + filename.return_value = Markup("MyFile.pdf") - assert isinstance(self.presenter().hostname_or_filename, jinja2.Markup) + assert isinstance(self.presenter().hostname_or_filename, Markup) @hostname_or_filename_fixtures def test_hostname_or_filename_unquotes_filenames(self, filename): @@ -142,9 +142,9 @@ def test_hostname_or_filename_returns_hostname_for_non_files(self, uri, filename @hostname_or_filename_fixtures def test_hostname_or_filename_returns_Markup_when_uri_does(self, uri, filename): filename.return_value = "" - uri.return_value = jinja2.Markup("http://www.example.com/example.html") + uri.return_value = Markup("http://www.example.com/example.html") - assert isinstance(self.presenter().hostname_or_filename, jinja2.Markup) + assert isinstance(self.presenter().hostname_or_filename, Markup) @hostname_or_filename_fixtures def test_hostname_or_filename_with_empty_string_for_uri(self, uri, filename): @@ -178,10 +178,10 @@ def test_title_escapes_html_in_document_titles(self): title = self.presenter(title=spam_link).title - assert jinja2.escape(spam_link) in title + assert escape(spam_link) in title for char in ["<", ">", '"', "'"]: assert char not in title - assert isinstance(title, jinja2.Markup) + assert isinstance(title, Markup) @title_fixtures def test_title_with_file_uri(self, filename): @@ -193,9 +193,9 @@ def test_title_with_file_uri(self, filename): @title_fixtures def test_title_returns_Markup_when_filename_returns_Markup(self, filename): - filename.return_value = jinja2.Markup("MyFile.pdf") + filename.return_value = Markup("MyFile.pdf") - assert isinstance(self.presenter(title=None).title, jinja2.Markup) + assert isinstance(self.presenter(title=None).title, Markup) @title_fixtures def test_title_unquotes_uris(self, uri, filename): @@ -207,9 +207,9 @@ def test_title_unquotes_uris(self, uri, filename): @title_fixtures def test_title_returns_Markup_when_uri_returns_Markup(self, uri, filename): filename.return_value = "" # This is not a file:// URI. - uri.return_value = jinja2.Markup("http://example.com/example.html") + uri.return_value = Markup("http://example.com/example.html") - assert isinstance(self.presenter(title=None).title, jinja2.Markup) + assert isinstance(self.presenter(title=None).title, Markup) @title_fixtures def test_title_when_document_has_None_for_title(self, uri, filename):