EPUB Output: Fix converting a document with obfuscated fonts to EPUB 3 not working. Fixes #1990185 [Private bug](https://bugs.launchpad.net/calibre/+bug/1990185)

This commit is contained in:
Kovid Goyal 2022-09-26 19:02:34 +05:30
parent 1f13e36547
commit 19cb5117f0
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 77 additions and 23 deletions

View File

@ -257,11 +257,11 @@ class EPUBOutput(OutputFormatPlugin):
opf = [x for x in os.listdir(tdir) if x.endswith('.opf')][0]
self.condense_ncx([os.path.join(tdir, x) for x in os.listdir(tdir)
if x.endswith('.ncx')][0])
if self.opts.epub_version == '3':
self.upgrade_to_epub3(tdir, opf)
encryption = None
if encrypted_fonts:
encryption = self.encrypt_fonts(encrypted_fonts, tdir, uuid)
if self.opts.epub_version == '3':
encryption = self.upgrade_to_epub3(tdir, opf, encryption)
from calibre.ebooks.epub import initialize_container
with initialize_container(output_path, os.path.basename(opf),
@ -284,7 +284,7 @@ class EPUBOutput(OutputFormatPlugin):
zf.extractall(path=opts.extract_to)
self.log.info('EPUB extracted to', opts.extract_to)
def upgrade_to_epub3(self, tdir, opf):
def upgrade_to_epub3(self, tdir, opf, encryption=None):
self.log.info('Upgrading to EPUB 3...')
from calibre.ebooks.epub import simple_container_xml
from calibre.ebooks.oeb.polish.cover import fix_conversion_titlepage_links_in_nav
@ -294,6 +294,9 @@ class EPUBOutput(OutputFormatPlugin):
pass
with open(os.path.join(tdir, 'META-INF', 'container.xml'), 'wb') as f:
f.write(simple_container_xml(os.path.basename(opf)).encode('utf-8'))
if encryption is not None:
with open(os.path.join(tdir, 'META-INF', 'encryption.xml'), 'wb') as ef:
ef.write(as_bytes(encryption))
from calibre.ebooks.oeb.polish.container import EpubContainer
container = EpubContainer(tdir, self.log)
from calibre.ebooks.oeb.polish.upgrade import epub_2_to_3
@ -304,10 +307,14 @@ class EPUBOutput(OutputFormatPlugin):
fix_conversion_titlepage_links_in_nav(container)
container.commit()
os.remove(f.name)
if encryption is not None:
encryption = open(ef.name, 'rb').read()
os.remove(ef.name)
try:
os.rmdir(os.path.join(tdir, 'META-INF'))
except OSError:
pass
return encryption
def encrypt_fonts(self, uris, tdir, uuid): # {{{
from polyglot.binary import from_hex_bytes

View File

@ -1283,24 +1283,7 @@ class EpubContainer(Container):
self.dirty('META-INF/encryption.xml')
super().remove_item(name, remove_from_guide=remove_from_guide)
def process_encryption(self):
fonts = {}
enc = self.parsed('META-INF/encryption.xml')
for em in enc.xpath('//*[local-name()="EncryptionMethod" and @Algorithm]'):
alg = em.get('Algorithm')
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
raise DRMError()
try:
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
except (IndexError, ValueError, KeyError):
continue
name = self.href_to_name(cr.get('URI'))
path = self.name_path_map.get(name, None)
if path is not None:
fonts[name] = alg
if not fonts:
return
def read_raw_unique_identifier(self):
package_id = raw_unique_identifier = idpf_key = None
for attrib, val in iteritems(self.opf.attrib):
if attrib.endswith('unique-identifier'):
@ -1315,6 +1298,32 @@ class EpubContainer(Container):
idpf_key = raw_unique_identifier
idpf_key = re.sub('[\u0020\u0009\u000d\u000a]', '', idpf_key)
idpf_key = hashlib.sha1(idpf_key.encode('utf-8')).digest()
return package_id, raw_unique_identifier, idpf_key
def iter_encryption_entries(self):
if 'META-INF/encryption.xml' in self.name_path_map:
enc = self.parsed('META-INF/encryption.xml')
for em in enc.xpath('//*[local-name()="EncryptionMethod" and @Algorithm]'):
try:
cr = em.getparent().xpath('descendant::*[local-name()="CipherReference" and @URI]')[0]
except Exception:
cr = None
yield em, cr
def process_encryption(self):
fonts = {}
for em, cr in self.iter_encryption_entries():
alg = em.get('Algorithm')
if alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
raise DRMError()
if cr is None:
continue
name = self.href_to_name(cr.get('URI'))
path = self.name_path_map.get(name, None)
if path is not None:
fonts[name] = alg
package_id, raw_unique_identifier, idpf_key = self.read_raw_unique_identifier()
key = None
for item in self.opf_xpath('//*[local-name()="metadata"]/*'
'[local-name()="identifier"]'):

View File

@ -4,14 +4,19 @@
import sys
from calibre.ebooks.conversion.plugins.epub_input import (
ADOBE_OBFUSCATION, IDPF_OBFUSCATION
)
from calibre.ebooks.metadata.opf3 import XPath
from calibre.ebooks.metadata.opf_2_to_3 import upgrade_metadata
from calibre.ebooks.oeb.base import EPUB_NS, OEB_DOCS, xpath
from calibre.ebooks.oeb.base import DC, EPUB_NS, OEB_DOCS, xpath
from calibre.ebooks.oeb.parse_utils import ensure_namespace_prefixes
from calibre.ebooks.oeb.polish.utils import OEB_FONTS
from calibre.ebooks.oeb.polish.opf import get_book_language
from calibre.ebooks.oeb.polish.toc import (
commit_nav_toc, find_existing_ncx_toc, get_landmarks, get_toc
)
from calibre.ebooks.oeb.polish.utils import OEB_FONTS
from calibre.utils.short_uuid import uuid4
def add_properties(item, *props):
@ -31,6 +36,38 @@ def fix_font_mime_types(container):
return changed
def migrate_obfuscated_fonts(container):
if not container.obfuscated_fonts:
return
name_to_elem_map = {}
for em, cr in container.iter_encryption_entries():
alg = em.get('Algorithm')
if cr is None or alg not in {ADOBE_OBFUSCATION, IDPF_OBFUSCATION}:
continue
name = container.href_to_name(cr.get('URI'))
name_to_elem_map[name] = em, cr
package_id, raw_unique_identifier, idpf_key = container.read_raw_unique_identifier()
if not idpf_key:
if not package_id:
package_id = uuid4()
container.opf.set('unique-identifier', package_id)
metadata = XPath('./opf:metadata')(container.opf)[0]
ident = metadata.makeelement(DC('identifier'))
ident.text = uuid4()
metadata.append(ident)
package_id, raw_unique_identifier, idpf_key = container.read_raw_unique_identifier()
for name in tuple(container.obfuscated_fonts):
try:
em, cr = name_to_elem_map[name]
except KeyError:
container.obfuscated_fonts.pop(name)
continue
em.set('Algorithm', IDPF_OBFUSCATION)
cr.set('URI', container.name_to_href(name))
container.obfuscated_fonts[name] = (IDPF_OBFUSCATION, idpf_key)
container.commit_item('META-INF/encryption.xml')
def collect_properties(container):
for item in container.opf_xpath('//opf:manifest/opf:item[@href and @media-type]'):
mt = item.get('media-type') or ''
@ -128,6 +165,7 @@ def epub_2_to_3(container, report, previous_nav=None, remove_ncx=True):
container.opf.set('version', '3.0')
if fix_font_mime_types(container):
container.refresh_mime_map()
migrate_obfuscated_fonts(container)
container.dirty(container.opf_name)