Skip to content

Commit fa6d425

Browse files
eric-wieserAA-Turnertk0miya
authored
URI-escape image filenames (#10268)
Without this change, local images with `#` in their name result in incorrect URLs There is already a similar call to `urllib.parse.quote` for file downloads, suggesting this is a sensible approach. Co-authored-by: Adam Turner <[email protected]> Co-authored-by: Takeshi KOMIYA <[email protected]>
1 parent e008e16 commit fa6d425

File tree

10 files changed

+28
-9
lines changed

10 files changed

+28
-9
lines changed

CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Features added
2020
``:option:`--module[=foobar]``` or ``:option:`--module foobar```.
2121
Patch by Martin Liska.
2222
* #10881: autosectionlabel: Record the generated section label to the debug log.
23+
* #10268: Correctly URI-escape image filenames.
2324

2425
Bugs fixed
2526
----------

sphinx/builders/_epub_base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
from os import path
77
from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple
8+
from urllib.parse import quote
89
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
910

1011
from docutils import nodes
@@ -524,7 +525,7 @@ def build_content(self) -> None:
524525
type='epub', subtype='unknown_project_files')
525526
continue
526527
filename = filename.replace(os.sep, '/')
527-
item = ManifestItem(html.escape(filename),
528+
item = ManifestItem(html.escape(quote(filename)),
528529
html.escape(self.make_id(filename)),
529530
html.escape(self.media_types[ext]))
530531
metadata['manifest_items'].append(item)

sphinx/writers/html.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ def visit_image(self, node: Element) -> None:
620620
# rewrite the URI if the environment knows about it
621621
if olduri in self.builder.images:
622622
node['uri'] = posixpath.join(self.builder.imgpath,
623-
self.builder.images[olduri])
623+
urllib.parse.quote(self.builder.images[olduri]))
624624

625625
if 'scale' in node:
626626
# Try to figure out image height and width. Docutils does that too,

sphinx/writers/html5.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ def visit_image(self, node: Element) -> None:
567567
# rewrite the URI if the environment knows about it
568568
if olduri in self.builder.images:
569569
node['uri'] = posixpath.join(self.builder.imgpath,
570-
self.builder.images[olduri])
570+
urllib.parse.quote(self.builder.images[olduri]))
571571

572572
if 'scale' in node:
573573
# Try to figure out image height and width. Docutils does that too,

sphinx/writers/latex.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1319,14 +1319,17 @@ def visit_image(self, node: Element) -> None:
13191319
if include_graphics_options:
13201320
options = '[%s]' % ','.join(include_graphics_options)
13211321
base, ext = path.splitext(uri)
1322+
13221323
if self.in_title and base:
13231324
# Lowercase tokens forcely because some fncychap themes capitalize
13241325
# the options of \sphinxincludegraphics unexpectedly (ex. WIDTH=...).
1325-
self.body.append(r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' %
1326-
(options, base, ext))
1326+
cmd = r'\lowercase{\sphinxincludegraphics%s}{{%s}%s}' % (options, base, ext)
13271327
else:
1328-
self.body.append(r'\sphinxincludegraphics%s{{%s}%s}' %
1329-
(options, base, ext))
1328+
cmd = r'\sphinxincludegraphics%s{{%s}%s}' % (options, base, ext)
1329+
# escape filepath for includegraphics, https://tex.stackexchange.com/a/202714/41112
1330+
if '#' in base:
1331+
cmd = r'{\catcode`\#=12' + cmd + '}'
1332+
self.body.append(cmd)
13301333
self.body.extend(post)
13311334

13321335
def depart_image(self, node: Element) -> None:

tests/roots/test-image-escape/conf.py

Whitespace-only changes.
64.7 KB
Loading
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Sphinx image handling
2+
=====================
3+
4+
.. an image with a character that is valid in a local file path but not a URL
5+
.. image:: img_#1.png

tests/test_build_html.py

+9
Original file line numberDiff line numberDiff line change
@@ -1397,6 +1397,15 @@ def test_html_remote_images(app, status, warning):
13971397
assert not (app.outdir / 'python-logo.png').exists()
13981398

13991399

1400+
@pytest.mark.sphinx('html', testroot='image-escape')
1401+
def test_html_encoded_image(app, status, warning):
1402+
app.builder.build_all()
1403+
1404+
result = (app.outdir / 'index.html').read_text()
1405+
assert ('<img alt="_images/img_%231.png" src="_images/img_%231.png" />' in result)
1406+
assert (app.outdir / '_images/img_#1.png').exists()
1407+
1408+
14001409
@pytest.mark.sphinx('html', testroot='remote-logo')
14011410
def test_html_remote_logo(app, status, warning):
14021411
app.builder.build_all()

tests/test_build_latex.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def compile_latex_document(app, filename='python.tex'):
5959
except OSError as exc: # most likely the latex executable was not found
6060
raise pytest.skip.Exception from exc
6161
except CalledProcessError as exc:
62-
print(exc.stdout)
63-
print(exc.stderr)
62+
print(exc.stdout.decode('utf8'))
63+
print(exc.stderr.decode('utf8'))
6464
raise AssertionError('%s exited with return code %s' % (app.config.latex_engine,
6565
exc.returncode))
6666

0 commit comments

Comments
 (0)