From e6f219494874503caf17be01d96be8be8bbca47c Mon Sep 17 00:00:00 2001 From: Kartik Naik Date: Sun, 26 Jul 2026 17:50:47 +0530 Subject: [PATCH 1/2] escape urls and file names interpolated into display html attributes --- IPython/core/display.py | 4 ++-- IPython/lib/display.py | 40 ++++++++++++++++++++++++++-------------- tests/test_display.py | 24 ++++++++++++++++++++++++ tests/test_display_2.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 16 deletions(-) diff --git a/IPython/core/display.py b/IPython/core/display.py index 2af0e6c6418..dbe70d8653c 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -1065,7 +1065,7 @@ def _repr_html_(self): if self.alt: alt = ' alt="%s"' % html.escape(self.alt) return ''.format( - url=self.url, + url=html.escape(self.url or ""), width=width, height=height, klass=klass, @@ -1228,7 +1228,7 @@ def _repr_html_(self): url = self.url if self.url is not None else self.filename output = """""".format(url, self.html_attributes, width, height) + """.format(html.escape(url or ""), self.html_attributes, width, height) return output # Embedded videos are base64-encoded. diff --git a/IPython/lib/display.py b/IPython/lib/display.py index 72486198e7f..afe1882cf97 100644 --- a/IPython/lib/display.py +++ b/IPython/lib/display.py @@ -240,7 +240,7 @@ def src_attr(self): return """data:{type};base64,{base64}""".format(type=self.mimetype, base64=data) elif self.url is not None: - return self.url + return html_escape(self.url) else: return "" @@ -252,7 +252,7 @@ def autoplay_attr(self): def element_id_attr(self): if (self.element_id): - return f'id="{self.element_id}"' + return f'id="{html_escape(self.element_id)}"' else: return '' @@ -292,9 +292,9 @@ def _repr_html_(self): else: params = "" return self.iframe.format( - src=self.src, - width=self.width, - height=self.height, + src=html_escape(self.src), + width=html_escape(str(self.width)), + height=html_escape(str(self.height)), params=params, extras=" ".join(self.extras), ) @@ -513,7 +513,12 @@ def __init__(self, self.recursive = recursive def _get_display_formatter( - self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None + self, + dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner=None, + escape_names=False, ): """generate built-in formatter function @@ -531,7 +536,11 @@ def _get_display_formatter( fp_format: string to use for formatting filepaths, must contain exactly two "%s" and the dirname will be substituted for the first and fname will be substituted for the second + escape_names: whether directory and file names must be HTML-escaped + before being substituted, as they are for the notebook formatter """ + escape = html_escape if escape_names else str + def f(dirname, fnames, included_suffixes=None): result = [] # begin by figuring out which filenames, if any, @@ -550,18 +559,18 @@ def f(dirname, fnames, included_suffixes=None): else: # otherwise print the formatted directory name followed by # the formatted filenames - dirname_output_line = dirname_output_format % dirname + dirname_output_line = dirname_output_format % escape(dirname) result.append(dirname_output_line) for fname in display_fnames: - fp = fp_format % (dirname,fname) + fp = fp_format % (escape(dirname), escape(fname)) if fp_cleaner is not None: fp = fp_cleaner(fp) try: # output can include both a filepath and a filename... - fname_output_line = fname_output_format % (fp, fname) + fname_output_line = fname_output_format % (fp, escape(fname)) except TypeError: # ... or just a single filepath - fname_output_line = fname_output_format % fname + fname_output_line = fname_output_format % escape(fname) result.append(fname_output_line) return result return f @@ -586,10 +595,13 @@ def fp_cleaner(fp): else: fp_cleaner = None - return self._get_display_formatter(dirname_output_format, - fname_output_format, - fp_format, - fp_cleaner) + return self._get_display_formatter( + dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner, + escape_names=True, + ) def _get_terminal_display_formatter(self, spacer=" "): diff --git a/tests/test_display.py b/tests/test_display.py index b262c515c27..6d959d3c29c 100644 --- a/tests/test_display.py +++ b/tests/test_display.py @@ -188,11 +188,35 @@ def test_recursive_FileLinks(): assert len(actual) == 2, actual +def test_escaped_names_FileLinks(): + """FileLinks: html metacharacters in file names are escaped""" + td = mkdtemp() + with open(pjoin(td, "a'>