diff --git a/src/calibre/ebooks/conversion/plugins/epub_output.py b/src/calibre/ebooks/conversion/plugins/epub_output.py index bcf93728e4..6b1c55cca4 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_output.py +++ b/src/calibre/ebooks/conversion/plugins/epub_output.py @@ -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 diff --git a/src/calibre/ebooks/oeb/polish/container.py b/src/calibre/ebooks/oeb/polish/container.py index 4bb53e8bd1..21722a5310 100644 --- a/src/calibre/ebooks/oeb/polish/container.py +++ b/src/calibre/ebooks/oeb/polish/container.py @@ -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"]'): diff --git a/src/calibre/ebooks/oeb/polish/upgrade.py b/src/calibre/ebooks/oeb/polish/upgrade.py index 4658beee04..ecd85ee2fd 100644 --- a/src/calibre/ebooks/oeb/polish/upgrade.py +++ b/src/calibre/ebooks/oeb/polish/upgrade.py @@ -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)