--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:calibre
User: [email protected]
Usertags: pu
[ Reason ]
Fix these CVEs.
* CVE-2026-25635
* CVE-2026-25636
* CVE-2026-25731
[ Impact ]
* CVE-2026-25635: path traversal vulnerability
* CVE-2026-25636: path traversal vulnerability
* CVE-2026-25731: Server-Side Template Injection (SSTI) vulnerability
[ Tests ]
Automated build-time test was successful.
[ Risks ]
Not well tested on real machine.
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
[ Changes ]
Backport and apply upstream fixes.
[ Other info ]
Fixes are examine from online:
https://github.com/debian-
calibre/calibre/compare/debian/6.13.0+repack-2+deb12u5...bookworm-update
diff -Nru calibre-6.13.0+repack/debian/calibre.install
calibre-6.13.0+repack/debian/calibre.install
--- calibre-6.13.0+repack/debian/calibre.install 2025-09-22
00:51:57.000000000 +0900
+++ calibre-6.13.0+repack/debian/calibre.install 2026-02-09
00:14:55.000000000 +0900
@@ -7,7 +7,6 @@
#
usr/lib/calibre/odf
usr/lib/calibre/qt
-usr/lib/calibre/templite
usr/lib/calibre/tinycss
usr/lib/calibre/css_selectors
usr/lib/calibre/polyglot
diff -Nru calibre-6.13.0+repack/debian/changelog
calibre-6.13.0+repack/debian/changelog
--- calibre-6.13.0+repack/debian/changelog 2025-11-09 18:15:24.000000000
+0900
+++ calibre-6.13.0+repack/debian/changelog 2026-02-09 00:25:09.000000000
+0900
@@ -1,3 +1,16 @@
+calibre (6.13.0+repack-2+deb12u6) bookworm; urgency=medium
+
+ * CVE-2026-25635: CHM Input: Ignore internal files that have paths that
+ end up outside the container
+ * CVE-2026-25636: DRYer
+ * CVE-2026-25731: ZIP Output: Change the template engine used for HTML
+ templating from templite to Mustache, for greater safety and
+ performance. Note that this is a breaking change if you use custom
+ templates with ZIP output.
+ * Use pystache instead of templite to fix CVE-2026-25731
+
+ -- YOKOTA Hiroshi <[email protected]> Mon, 09 Feb 2026 00:25:09 +0900
+
calibre (6.13.0+repack-2+deb12u5) bookworm; urgency=medium
* Fix CVE-2025-64486
diff -Nru calibre-6.13.0+repack/debian/control
calibre-6.13.0+repack/debian/control
--- calibre-6.13.0+repack/debian/control 2025-11-09 18:15:24.000000000
+0900
+++ calibre-6.13.0+repack/debian/control 2026-02-09 00:14:55.000000000
+0900
@@ -63,6 +63,7 @@
python3-pyqt6.qtsvg,
python3-pyqt6.qtwebengine,
python3-pyqtbuild,
+ python3-pystache,
python3-regex,
python3-routes,
python3-setuptools,
diff -Nru
calibre-6.13.0+repack/debian/patches/0037-CVE-2026-25635-CHM-Input-Ignore-internal-files-that-.patch
calibre-6.13.0+repack/debian/patches/0037-CVE-2026-25635-CHM-Input-Ignore-internal-files-that-.patch
---
calibre-6.13.0+repack/debian/patches/0037-CVE-2026-25635-CHM-Input-Ignore-internal-files-that-.patch
1970-01-01 09:00:00.000000000 +0900
+++
calibre-6.13.0+repack/debian/patches/0037-CVE-2026-25635-CHM-Input-Ignore-internal-files-that-.patch
2026-02-09 00:15:48.000000000 +0900
@@ -0,0 +1,81 @@
+From: Kovid Goyal <[email protected]>
+Date: Wed, 4 Feb 2026 09:39:54 +0530
+Subject: CVE-2026-25635: CHM Input: Ignore internal files that have paths
+ that end up outside the container
+
+Forwarded: not-needed
+Bug:
https://github.com/kovidgoyal/calibre/security/advisories/GHSA-32vh-whvh-9fxr
+Origin: backport,
https://github.com/kovidgoyal/calibre/commit/9739232fcb029ac15dfe52ccd4fdb4a07ebb6ce9
+
+Also, allow extraction of long filenames
+
+Signed-off-by: YOKOTA Hiroshi <[email protected]>
+---
+ src/calibre/ebooks/chm/reader.py | 33 ++++++++++++++++-----------------
+ 1 file changed, 16 insertions(+), 17 deletions(-)
+
+diff --git a/src/calibre/ebooks/chm/reader.py
b/src/calibre/ebooks/chm/reader.py
+index 102f439..de5deaf 100644
+--- a/src/calibre/ebooks/chm/reader.py
++++ b/src/calibre/ebooks/chm/reader.py
+@@ -12,6 +12,7 @@ from calibre.constants import filesystem_encoding, iswindows
+ from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString
+ from calibre.ebooks.chardet import xml_to_unicode
+ from calibre.ebooks.metadata.toc import TOC
++from calibre.utils.filenames import make_long_path_useable
+ from chm.chm import CHMFile, chmlib
+ from polyglot.builtins import as_unicode
+
+@@ -159,37 +160,35 @@ class CHMReader(CHMFile):
+
+ def ExtractFiles(self, output_dir=os.getcwd(), debug_dump=False):
+ html_files = set()
++ base = output_dir = os.path.abspath(output_dir)
++ if not base.endswith(os.sep):
++ base += os.sep
+ for path in self.Contents():
+- fpath = path
+- lpath = os.path.join(output_dir, fpath)
++ fpath = path.partition(';')[0] # fix file names with ";<junk>"
at the end, see _reformat()
++ fpath = fpath.replace('/', os.sep)
++ lpath = os.path.abspath(os.path.join(output_dir, fpath))
++ if os.path.commonprefix((lpath, base)) != base:
++ self.log.warn(f'{path!r} outside container, skipping')
++ continue
+ self._ensure_dir(lpath)
+ try:
+ data = self.GetFile(path)
+ except:
+ self.log.exception('Failed to extract %s from CHM,
ignoring'%path)
+ continue
+- if lpath.find(';') != -1:
+- # fix file names with ";<junk>" at the end, see _reformat()
+- lpath = lpath.split(';')[0]
++ with open(make_long_path_useable(lpath), 'wb') as f:
++ f.write(data)
+ try:
+- with open(lpath, 'wb') as f:
+- f.write(data)
+- try:
+- if 'html' in guess_mimetype(path)[0]:
+- html_files.add(lpath)
+- except:
+- pass
++ if 'html' in guess_mimetype(os.path.basename(lpath))[0]:
++ html_files.add(lpath)
+ except:
+- if iswindows and len(lpath) > 250:
+- self.log.warn('%r filename too long, skipping'%path)
+- continue
+- raise
++ pass
+
+ if debug_dump:
+ import shutil
+ shutil.copytree(output_dir, os.path.join(debug_dump,
'debug_dump'))
+ for lpath in html_files:
+- with open(lpath, 'r+b') as f:
++ with open(make_long_path_useable(lpath), 'r+b') as f:
+ data = f.read()
+ data = self._reformat(data, lpath)
+ if isinstance(data, str):
diff -Nru calibre-6.13.0+repack/debian/patches/0038-CVE-2026-25636-DRYer.patch
calibre-6.13.0+repack/debian/patches/0038-CVE-2026-25636-DRYer.patch
--- calibre-6.13.0+repack/debian/patches/0038-CVE-2026-25636-DRYer.patch
1970-01-01 09:00:00.000000000 +0900
+++ calibre-6.13.0+repack/debian/patches/0038-CVE-2026-25636-DRYer.patch
2026-02-09 00:15:48.000000000 +0900
@@ -0,0 +1,37 @@
+From: Kovid Goyal <[email protected]>
+Date: Mon, 2 Feb 2026 11:25:09 +0530
+Subject: CVE-2026-25636: DRYer
+
+Forwarded: not-needed
+Bug:
https://github.com/kovidgoyal/calibre/security/advisories/GHSA-8r26-m7j5-hm29
+Origin: backport,
https://github.com/kovidgoyal/calibre/commit/9484ea82c6ab226c18e6ca5aa000fa16de598726
+
+Signed-off-by: YOKOTA Hiroshi <[email protected]>
+---
+ src/calibre/ebooks/conversion/plugins/epub_input.py | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/src/calibre/ebooks/conversion/plugins/epub_input.py
b/src/calibre/ebooks/conversion/plugins/epub_input.py
+index 9915c2b..87c0e7d 100644
+--- a/src/calibre/ebooks/conversion/plugins/epub_input.py
++++ b/src/calibre/ebooks/conversion/plugins/epub_input.py
+@@ -62,15 +62,17 @@ class EPUBInput(InputFormatPlugin):
+
+ try:
+ root = etree.parse(encfile)
++ base = os.path.dirname(encfile)
++ container_base = os.path.dirname(base)
+ for em in root.xpath('descendant::*[contains(name(),
"EncryptionMethod")]'):
+ algorithm = em.get('Algorithm', '')
+ if algorithm not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
+ return False
+ cr = em.getparent().xpath('descendant::*[contains(name(),
"CipherReference")]')[0]
+ uri = cr.get('URI')
+- path = os.path.abspath(os.path.join(os.path.dirname(encfile),
'..', *uri.split('/')))
++ path = os.path.abspath(os.path.join(base, '..',
*uri.split('/')))
+ tkey = (key if algorithm == ADOBE_OBFUSCATION else idpf_key)
+- if (tkey and os.path.exists(path)):
++ if (tkey and is_existing_subpath(path, container_base)):
+ self._encrypted_font_uris.append(uri)
+ decrypt_font(tkey, path, algorithm)
+ return True
diff -Nru
calibre-6.13.0+repack/debian/patches/0039-CVE-2026-25731-ZIP-Output-Change-the-template-engine.patch
calibre-6.13.0+repack/debian/patches/0039-CVE-2026-25731-ZIP-Output-Change-the-template-engine.patch
---
calibre-6.13.0+repack/debian/patches/0039-CVE-2026-25731-ZIP-Output-Change-the-template-engine.patch
1970-01-01 09:00:00.000000000 +0900
+++
calibre-6.13.0+repack/debian/patches/0039-CVE-2026-25731-ZIP-Output-Change-the-template-engine.patch
2026-02-09 00:15:48.000000000 +0900
@@ -0,0 +1,541 @@
+From: Kovid Goyal <[email protected]>
+Date: Thu, 5 Feb 2026 14:21:25 +0530
+Subject: CVE-2026-25731: ZIP Output: Change the template engine used for HTML
+ templating from templite to Mustache,
+ for greater safety and performance. Note that this is a breaking change if
+ you use custom templates with ZIP output.
+
+Forwarded: not-needed
+Bug:
https://github.com/kovidgoyal/calibre/security/advisories/GHSA-xrh9-w7qx-3gcc
+Origin: backport,
https://github.com/kovidgoyal/calibre/commit/f0649b27512e987b95fcab2e1e0a3bcdafc23379
+
+Signed-off-by: YOKOTA Hiroshi <[email protected]>
+---
+ COPYRIGHT | 6 --
+ resources/templates/html_export_default.mustache | 70 +++++++++++++++++
+ resources/templates/html_export_default.tmpl | 74 ------------------
+ .../templates/html_export_default_index.mustache | 55 +++++++++++++
+ resources/templates/html_export_default_index.tmpl | 61 ---------------
+ .../ebooks/conversion/plugins/html_output.py | 60 +++++++++------
+ src/templite/__init__.py | 89 ----------------------
+ 7 files changed, 163 insertions(+), 252 deletions(-)
+ create mode 100644 resources/templates/html_export_default.mustache
+ delete mode 100644 resources/templates/html_export_default.tmpl
+ create mode 100644 resources/templates/html_export_default_index.mustache
+ delete mode 100644 resources/templates/html_export_default_index.tmpl
+ delete mode 100644 src/templite/__init__.py
+
+diff --git a/COPYRIGHT b/COPYRIGHT
+index 3425d82..702ced2 100644
+--- a/COPYRIGHT
++++ b/COPYRIGHT
+@@ -12,12 +12,6 @@ Files: resources/rapydscript/*
+ Copyright: Various
+ License: BSD
+
+-Files: src/templite/*
+-Copyright: Copyright (c) 2009 joonis new media, Thimo Kraemer
+-License: GPL-2+
+- The full text of the GPL is distributed as in
+- /usr/share/common-licenses/GPL-2 on Debian systems.
+-
+ Files: src/calibre/devices/bambook/*
+ Copyright: 2010, Li Fanxi
+ License: GPL-3
+diff --git a/resources/templates/html_export_default.mustache
b/resources/templates/html_export_default.mustache
+new file mode 100644
+index 0000000..1c8691a
+--- /dev/null
++++ b/resources/templates/html_export_default.mustache
+@@ -0,0 +1,70 @@
++<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
++<html xmlns="http://www.w3.org/1999/xhtml">
++<head>
++{{{head_content}}}
++
++<link href="{{css_link}}" type="text/css" rel="stylesheet" />
++
++</head>
++<body>
++
++<div class="calibreMeta">
++ <div class="calibreMetaTitle">
++ {{#meta.titles}}
++ {{#is_first}}
++ <h1><a href="{{toc_url}}">{{title}}</a> </h1>
++ {{/is_first}}
++ {{^is_first}}
++ <div class="calibreMetaSubtitle">{{title}}</div>
++ {{/is_first}}
++ {{/meta.titles}}
++ </div>
++ <div class="calibreMetaAuthor">{{meta.creators}}</div>
++</div>
++
++<div class="calibreMain">
++
++ <div class="calibreEbookContent">
++ {{#has_link}}
++ <div class="calibreEbNavTop">
++ {{#prev_link}}
++ <a href="{{prev_link}}" class="calibreAPrev">{{prev_page}}</a>
++ {{/prev_link}}
++ {{^prev_link}}
++ <a href="{{toc_url}}" class="calibreAPrev">{{prev_page}}</a>
++ {{/prev_link}}
++ {{#next_link}}
++ <a href="{{next_link}}" class="calibreANext">{{next_page}}</a>
++ {{/next_link}}
++ </div>
++ {{/has_link}}
++
++ {{{ebook_content}}}
++ </div>
++
++ {{#has_toc}}
++ <div class="calibreToc">
++ <h2><a href="{{toc_url}}">{{table_of_contents}}</a></h2>
++ {{{toc}}}
++ </div>
++ {{/has_toc}}
++
++ <div class="calibreEbNav">
++ {{#prev_link}}
++ <a href="{{prev_link}}" class="calibreAPrev">{{prev_page}}</a>
++ {{/prev_link}}
++ {{^prev_link}}
++ <a href="{{toc_url}}" class="calibreAPrev">{{prev_page}}</a>
++ {{/prev_link}}
++
++ <a href="{{toc_url}}" class="calibreAHome">{{start}}</a>
++
++ {{#next_link}}
++ <a href="{{next_link}}" class="calibreANext">{{next_page}}</a>
++ {{/next_link}}
++ </div>
++
++</div>
++
++</body>
++</html>
+diff --git a/resources/templates/html_export_default.tmpl
b/resources/templates/html_export_default.tmpl
+deleted file mode 100644
+index 7aac247..0000000
+--- a/resources/templates/html_export_default.tmpl
++++ /dev/null
+@@ -1,74 +0,0 @@
+-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+-<html xmlns="http://www.w3.org/1999/xhtml">
+-<head>
+-${head_content}$
+-
+-<link href="${cssLink}$" type="text/css" rel="stylesheet" />
+-
+-</head>
+-<body>
+-
+-<div class="calibreMeta">
+- <div class="calibreMetaTitle">
+- ${pos1=1}$
+- ${for title in meta.titles():}$
+- ${if pos1:}$
+- <h1>
+- <a href="${tocUrl}$">${print(title)}$</a>
+- </h1>
+- ${:else:}$
+- <div class="calibreMetaSubtitle">${print(title)}$</div>
+- ${:endif}$
+- ${pos1=0}$
+- ${:endfor}$
+- </div>
+- <div class="calibreMetaAuthor">
+- ${print(', '.join(meta.creators()))}$
+- </div>
+-</div>
+-
+-<div class="calibreMain">
+-
+- <div class="calibreEbookContent">
+- ${if prevLink or nextLink:}$
+- <div class="calibreEbNavTop">
+- ${if prevLink:}$
+- <a href="${prevLink}$" class="calibreAPrev">${print(_('previous
page'))}$</a>
+- ${:else:}$
+- <a href="${tocUrl}$" class="calibreAPrev">${print(_('previous
page'))}$</a>
+- ${:endif}$
+-
+- ${if nextLink:}$
+- <a href="${nextLink}$" class="calibreANext">${print(_('next
page'))}$</a>
+- ${:endif}$
+- </div>
+- ${:endif}$
+-
+- ${ebookContent}$
+- </div>
+-
+- ${if has_toc:}$
+- <div class="calibreToc">
+- <h2><a href="${tocUrl}$">${print( _('Table of contents'))}$</a></h2>
+- ${print(toc())}$
+- </div>
+- ${:endif}$
+-
+- <div class="calibreEbNav">
+- ${if prevLink:}$
+- <a href="${prevLink}$" class="calibreAPrev">${print(_('previous
page'))}$</a>
+- ${:else:}$
+- <a href="${tocUrl}$" class="calibreAPrev">${print(_('previous
page'))}$</a>
+- ${:endif}$
+-
+- <a href="${tocUrl}$" class="calibreAHome">${print(_('start'))}$</a>
+-
+- ${if nextLink:}$
+- <a href="${nextLink}$" class="calibreANext">${print(_('next
page'))}$</a>
+- ${:endif}$
+- </div>
+-
+-</div>
+-
+-</body>
+-</html>
+diff --git a/resources/templates/html_export_default_index.mustache
b/resources/templates/html_export_default_index.mustache
+new file mode 100644
+index 0000000..aa1bc4d
+--- /dev/null
++++ b/resources/templates/html_export_default_index.mustache
+@@ -0,0 +1,55 @@
++<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
++<html xmlns="http://www.w3.org/1999/xhtml">
++<head>
++<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
++
++<link rel="schema.DC" href="http://purl.org/dc/elements/1.1/" />
++<link rel="schema.DCTERMS" href="http://purl.org/dc/terms/" />
++
++<title>{{meta.creators}} - {{meta.first_title}}</title>
++
++{{#meta.items}}
++ <meta name="DC.{{name}}" content="{{value}}" />
++{{/meta.items}}
++
++<link href="{{css_link}}" type="text/css" rel="stylesheet" />
++</head>
++<body>
++
++<div class="calibreMeta">
++ <div class="calibreMetaTitle">
++ {{#meta.titles}}
++ {{#is_first}}
++ <h1><a href="{{toc_url}}">{{title}}</a> </h1>
++ {{/is_first}}
++ {{^is_first}}
++ <div class="calibreMetaSubtitle">{{title}}</div>
++ {{/is_first}}
++ {{/meta.titles}}
++ </div>
++ <div class="calibreMetaAuthor">{{meta.creators}}</div>
++</div>
++
++<div class="calibreMain">
++ <div class="calibreEbookContent">
++ {{#has_toc}}
++ <div class="calibreTocIndex">
++ <h2>{{table_of_contents}}</h2>
++ {{{toc}}}
++ </div>
++ {{/has_toc}}
++ {{^has_toc}}
++ <h2>{{no_toc}}</h2>
++ <div><strong><a
href="{{next_link}}">{{begin_to_read}}</a></strong></div>
++ {{/has_toc}}
++ </div>
++
++ <div class="calibreEbNav">
++ {{#next_link}}
++ <a href="{{next_link}}" class="calibreANext">{{next_page}}</a>
++ {{/next_link}}
++ </div>
++</div>
++
++</body>
++</html>
+diff --git a/resources/templates/html_export_default_index.tmpl
b/resources/templates/html_export_default_index.tmpl
+deleted file mode 100644
+index f0665ad..0000000
+--- a/resources/templates/html_export_default_index.tmpl
++++ /dev/null
+@@ -1,61 +0,0 @@
+-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+-<html xmlns="http://www.w3.org/1999/xhtml">
+-<head>
+-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+-
+-<link rel="schema.DC" href="http://purl.org/dc/elements/1.1/" />
+-<link rel="schema.DCTERMS" href="http://purl.org/dc/terms/" />
+-
+-<title>${print(', '.join(meta.creators()))}$ - ${print(next(meta.titles()));
print(meta.titles().close())}$</title>
+-
+-${for item in meta:}$
+- <meta ${print('name="DC.'+item['name']+'"')}$
${print('content="'+item['value']+'"')}$ />
+-${:endfor}$
+-
+-<link href="${cssLink}$" type="text/css" rel="stylesheet" />
+-</head>
+-<body>
+-
+-<div class="calibreMeta">
+- <div class="calibreMetaTitle">
+- ${pos1=1}$
+- ${for title in meta.titles():}$
+- ${if pos1:}$
+- <h1>
+- <a href="${tocUrl}$">${print(title)}$</a>
+- </h1>
+- ${:else:}$
+- <div class="calibreMetaSubtitle">${print(title)}$</div>
+- ${:endif}$
+- ${pos1=0}$
+- ${:endfor}$
+- </div>
+- <div class="calibreMetaAuthor">
+- ${print(', '.join(meta.creators()))}$
+- </div>
+-</div>
+-
+-<div class="calibreMain">
+- <div class="calibreEbookContent">
+-
+- ${if has_toc:}$
+- <div class="calibreTocIndex">
+- <h2>${print(_('Table of contents'))}$</h2>
+- ${toc}$
+- </div>
+- ${:else:}$
+- <h2>${print(_('No table of contents present'))}$</h2>
+- <div><strong><a href="${nextLink}$">${print(_('begin to
read'))}$</a></strong></div>
+- ${:endif}$
+-
+- </div>
+-
+- <div class="calibreEbNav">
+- ${if nextLink:}$
+- <a href="${nextLink}$" class="calibreANext">${print(_('next
page'))}$</a>
+- ${:endif}$
+- </div>
+-</div>
+-
+-</body>
+-</html>
+diff --git a/src/calibre/ebooks/conversion/plugins/html_output.py
b/src/calibre/ebooks/conversion/plugins/html_output.py
+index d8e2f18..2d311d1 100644
+--- a/src/calibre/ebooks/conversion/plugins/html_output.py
++++ b/src/calibre/ebooks/conversion/plugins/html_output.py
+@@ -26,13 +26,13 @@ class HTMLOutput(OutputFormatPlugin):
+
+ options = {
+ OptionRecommendation(name='template_css',
+- help=_('CSS file used for the output instead of the default
file')),
++ help=_('CSS file used for the output instead of the default
CSS.')),
+
+ OptionRecommendation(name='template_html_index',
+- help=_('Template used for generation of the HTML index file
instead of the default file')),
++ help=_('Template used for generation of the HTML index file
instead of the default template. In Mustache format.')),
+
+ OptionRecommendation(name='template_html',
+- help=_('Template used for the generation of the HTML contents of
the book instead of the default file')),
++ help=_('Template used for the generation of the HTML contents of
the book instead of the default template. In Mustache format.')),
+
+ OptionRecommendation(name='extract_to',
+ help=_('Extract the contents of the generated ZIP file to the '
+@@ -84,6 +84,7 @@ class HTMLOutput(OutputFormatPlugin):
+ xml_declaration=False)
+
+ def convert(self, oeb_book, output_path, input_plugin, opts, log):
++ import pystache
+ from lxml import etree
+
+ from calibre.ebooks.html.meta import EasyMeta
+@@ -96,7 +97,7 @@ class HTMLOutput(OutputFormatPlugin):
+ with open(opts.template_html_index, 'rb') as f:
+ template_html_index_data = f.read()
+ else:
+- template_html_index_data =
P('templates/html_export_default_index.tmpl', data=True)
++ template_html_data = P('templates/html_export_default.mustache',
data=True)
+
+ if opts.template_html is not None:
+ with open(opts.template_html, 'rb') as f:
+@@ -110,9 +111,10 @@ class HTMLOutput(OutputFormatPlugin):
+ else:
+ template_css_data = P('templates/html_export_default.css',
data=True)
+
+- template_html_index_data = template_html_index_data.decode('utf-8')
+- template_html_data = template_html_data.decode('utf-8')
++ template_html_index =
pystache.parse(template_html_index_data.decode('utf-8'))
++ template_html = pystache.parse(template_html_data.decode('utf-8'))
+ template_css_data = template_css_data.decode('utf-8')
++ has_toc = bool(oeb_book.toc.count())
+
+ self.log = log
+ self.opts = opts
+@@ -129,18 +131,31 @@ class HTMLOutput(OutputFormatPlugin):
+ css_path = output_dir+os.sep+'calibreHtmlOutBasicCss.css'
+ with open(css_path, 'wb') as f:
+ f.write(template_css_data.encode('utf-8'))
++ meta_dict = {
++ 'titles': [{'title': x, 'is_first': i == 0} for i, x in
enumerate(meta.titles())],
++ 'creators': authors_to_string(tuple(meta.creators())),
++ 'items': list(meta),
++ }
++ meta_dict['first_title'] = meta_dict['titles'][0]['title'] if
meta_dict['titles'] else ''
++ basic_template_vars = {
++ 'meta': meta_dict, 'has_toc': has_toc,
++ 'table_of_contents': _('Table of contents'), 'no_toc': _('No
table of contents present'),
++ 'begin_to_read': _('begin to read'), 'start': _('start'),
++ 'prev_page': _('previous page'), 'next_page': _('next page'),
++ }
+
+ with open(output_file, 'wb') as f:
+- html_toc = self.generate_html_toc(oeb_book, output_file,
output_dir)
+- templite = Templite(template_html_index_data)
+ nextLink = oeb_book.spine[0].href
+ nextLink = relpath(output_dir+os.sep+nextLink,
dirname(output_file))
+ cssLink = relpath(abspath(css_path), dirname(output_file))
+ tocUrl = relpath(output_file, dirname(output_file))
+- t = templite.render(has_toc=bool(oeb_book.toc.count()),
+- toc=html_toc, meta=meta, nextLink=nextLink,
+- tocUrl=tocUrl, cssLink=cssLink,
+- firstContentPageLink=nextLink)
++ toc_as_html = self.generate_html_toc(oeb_book, output_file,
output_dir) if has_toc else ''
++ v = basic_template_vars.copy()
++ v.update({
++ 'toc': toc_as_html, 'css_link': cssLink, 'toc_url': tocUrl,
'next_link': nextLink,
++ 'first_content_page_link': nextLink,
++ })
++ t = pystache.render(template_html_index, v)
+ if isinstance(t, str):
+ t = t.encode('utf-8')
+ f.write(t)
+@@ -196,17 +211,18 @@ class HTMLOutput(OutputFormatPlugin):
+ firstContentPageLink = oeb_book.spine[0].href
+
+ # render template
+- templite = Templite(template_html_data)
+-
+ def toc():
+- return self.generate_html_toc(oeb_book, path, output_dir)
+- t = templite.render(ebookContent=ebook_content,
+- prevLink=prevLink, nextLink=nextLink,
+- has_toc=bool(oeb_book.toc.count()), toc=toc,
+- tocUrl=tocUrl, head_content=head_content,
+- meta=meta, cssLink=cssLink,
+- firstContentPageLink=firstContentPageLink)
+-
++ return
++ toc_as_html = self.generate_html_toc(oeb_book, path,
output_dir) if has_toc else ''
++ v = basic_template_vars.copy()
++ v.update({
++ 'has_link': prevLink or nextLink,
++ 'prev_link': prevLink, 'next_link': nextLink, 'toc_url':
tocUrl,
++ 'head_content': head_content, 'ebook_content':
ebook_content,
++ 'css_link': cssLink, 'toc': toc_as_html,
++ 'first_content_page_link': firstContentPageLink,
++ })
++ t = pystache.render(template_html, v)
+ # write html to file
+ with open(path, 'wb') as f:
+ f.write(t.encode('utf-8'))
+diff --git a/src/templite/__init__.py b/src/templite/__init__.py
+deleted file mode 100644
+index f46e777..0000000
+--- a/src/templite/__init__.py
++++ /dev/null
+@@ -1,89 +0,0 @@
+-#!/usr/bin/env python
+-#
+-# Templite+
+-# A light-weight, fully functional, general purpose templating engine
+-#
+-# Copyright (c) 2009 joonis new media
+-# Author: Thimo Kraemer <[email protected]>
+-#
+-# Based on Templite - Tomer Filiba
+-# http://code.activestate.com/recipes/496702/
+-#
+-# This program is free software; you can redistribute it and/or modify
+-# it under the terms of the GNU General Public License as published by
+-# the Free Software Foundation; either version 2 of the License, or
+-# (at your option) any later version.
+-#
+-# This program is distributed in the hope that it will be useful,
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-# GNU General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program; if not, write to the Free Software
+-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+-# MA 02110-1301, USA.
+-#
+-
+-import sys, re
+-
+-from polyglot.builtins import unicode_type
+-
+-class Templite:
+- auto_emit = re.compile('(^[\'\"])|(^[a-zA-Z0-9_\[\]\'\"]+$)')
+-
+- def __init__(self, template, start='${', end='}$'):
+- if len(start) != 2 or len(end) != 2:
+- raise ValueError('each delimiter must be two characters long')
+- delimiter = re.compile('%s(.*?)%s' % (re.escape(start),
re.escape(end)), re.DOTALL)
+- offset = 0
+- tokens = []
+- for i, part in enumerate(delimiter.split(template)):
+- part = part.replace('\\'.join(list(start)), start)
+- part = part.replace('\\'.join(list(end)), end)
+- if i % 2 == 0:
+- if not part: continue
+- part = part.replace('\\', '\\\\').replace('"', '\\"')
+- part = '\t' * offset + 'emit("""%s""")' % part
+- else:
+- part = part.rstrip()
+- if not part: continue
+- if part.lstrip().startswith(':'):
+- if not offset:
+- raise SyntaxError('no block statement to terminate:
${%s}$' % part)
+- offset -= 1
+- part = part.lstrip()[1:]
+- if not part.endswith(':'): continue
+- elif self.auto_emit.match(part.lstrip()):
+- part = 'emit(%s)' % part.lstrip()
+- lines = part.splitlines()
+- margin = min(len(l) - len(l.lstrip()) for l in lines if
l.strip())
+- part = '\n'.join('\t' * offset + l[margin:] for l in lines)
+- if part.endswith(':'):
+- offset += 1
+- tokens.append(part)
+- if offset:
+- raise SyntaxError('%i block statement(s) not terminated' % offset)
+- self.__code = compile('\n'.join(tokens), '<templite %r>' %
template[:20], 'exec')
+-
+- def render(self, __namespace=None, **kw):
+- """
+- renders the template according to the given namespace.
+- __namespace - a dictionary serving as a namespace for evaluation
+- **kw - keyword arguments which are added to the namespace
+- """
+- namespace = {}
+- if __namespace: namespace.update(__namespace)
+- if kw: namespace.update(kw)
+- namespace['emit'] = self.write
+-
+- __stdout = sys.stdout
+- sys.stdout = self
+- self.__output = []
+- eval(self.__code, namespace)
+- sys.stdout = __stdout
+- return ''.join(self.__output)
+-
+- def write(self, *args):
+- for a in args:
+- self.__output.append(unicode_type(a))
diff -Nru calibre-6.13.0+repack/debian/patches/series
calibre-6.13.0+repack/debian/patches/series
--- calibre-6.13.0+repack/debian/patches/series 2025-11-09 18:15:24.000000000
+0900
+++ calibre-6.13.0+repack/debian/patches/series 2026-02-09 00:15:48.000000000
+0900
@@ -34,3 +34,6 @@
0034-Fix-2075128-Private-bug-https-bugs.launchpad.net-cal.patch
0035-Fix-2076515-calibredb-list-command-ignores-fields-op.patch
0036-Fix-CVE-2025-64486.patch
+0037-CVE-2026-25635-CHM-Input-Ignore-internal-files-that-.patch
+0038-CVE-2026-25636-DRYer.patch
+0039-CVE-2026-25731-ZIP-Output-Change-the-template-engine.patch
--- End Message ---