mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-07 09:01:38 -04:00
EPUB to EPUB conversions: Preserve font encryption
This commit is contained in:
parent
5238aab3f8
commit
68f0f892e4
@ -3,7 +3,7 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, re, uuid
|
import os, uuid
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@ -19,8 +19,7 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
|
|
||||||
recommendations = set([('page_breaks_before', '/', OptionRecommendation.MED)])
|
recommendations = set([('page_breaks_before', '/', OptionRecommendation.MED)])
|
||||||
|
|
||||||
@classmethod
|
def decrypt_font(self, key, path):
|
||||||
def decrypt_font(cls, key, path):
|
|
||||||
raw = open(path, 'rb').read()
|
raw = open(path, 'rb').read()
|
||||||
crypt = raw[:1024]
|
crypt = raw[:1024]
|
||||||
key = cycle(iter(key))
|
key = cycle(iter(key))
|
||||||
@ -29,13 +28,18 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
f.write(decrypt)
|
f.write(decrypt)
|
||||||
f.write(raw[1024:])
|
f.write(raw[1024:])
|
||||||
|
|
||||||
@classmethod
|
def process_encryption(self, encfile, opf, log):
|
||||||
def process_encryption(cls, encfile, opf, log):
|
|
||||||
key = None
|
key = None
|
||||||
m = re.search(r'(?i)(urn:uuid:[0-9a-f-]+)', open(opf, 'rb').read())
|
for item in opf.identifier_iter():
|
||||||
if m:
|
scheme = None
|
||||||
key = m.group(1)
|
for key in item.attrib.keys():
|
||||||
|
if key.endswith('scheme'):
|
||||||
|
scheme = item.get(key)
|
||||||
|
if (scheme and scheme.lower() == 'uuid') or \
|
||||||
|
(item.text and item.text.startswith('urn:uuid:')):
|
||||||
|
key = str(item.text).rpartition(':')[-1]
|
||||||
key = list(map(ord, uuid.UUID(key).bytes))
|
key = list(map(ord, uuid.UUID(key).bytes))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
root = etree.parse(encfile)
|
root = etree.parse(encfile)
|
||||||
for em in root.xpath('descendant::*[contains(name(), "EncryptionMethod")]'):
|
for em in root.xpath('descendant::*[contains(name(), "EncryptionMethod")]'):
|
||||||
@ -46,7 +50,8 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
uri = cr.get('URI')
|
uri = cr.get('URI')
|
||||||
path = os.path.abspath(os.path.join(os.path.dirname(encfile), '..', *uri.split('/')))
|
path = os.path.abspath(os.path.join(os.path.dirname(encfile), '..', *uri.split('/')))
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
cls.decrypt_font(key, path)
|
self._encrypted_font_uris.append(uri)
|
||||||
|
self.decrypt_font(key, path)
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
@ -115,14 +120,17 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
if opf is None:
|
if opf is None:
|
||||||
raise ValueError('%s is not a valid EPUB file'%path)
|
raise ValueError('%s is not a valid EPUB file'%path)
|
||||||
|
|
||||||
if os.path.exists(encfile):
|
|
||||||
if not self.process_encryption(encfile, opf, log):
|
|
||||||
raise DRMError(os.path.basename(path))
|
|
||||||
|
|
||||||
opf = os.path.relpath(opf, os.getcwdu())
|
opf = os.path.relpath(opf, os.getcwdu())
|
||||||
parts = os.path.split(opf)
|
parts = os.path.split(opf)
|
||||||
opf = OPF(opf, os.path.dirname(os.path.abspath(opf)))
|
opf = OPF(opf, os.path.dirname(os.path.abspath(opf)))
|
||||||
|
|
||||||
|
self._encrypted_font_uris = []
|
||||||
|
if os.path.exists(encfile):
|
||||||
|
if not self.process_encryption(encfile, opf, log):
|
||||||
|
raise DRMError(os.path.basename(path))
|
||||||
|
self.encrypted_fonts = self._encrypted_font_uris
|
||||||
|
|
||||||
|
|
||||||
if len(parts) > 1 and parts[0]:
|
if len(parts) > 1 and parts[0]:
|
||||||
delta = '/'.join(parts[:-1])+'/'
|
delta = '/'.join(parts[:-1])+'/'
|
||||||
for elem in opf.itermanifest():
|
for elem in opf.itermanifest():
|
||||||
|
@ -12,8 +12,9 @@ from urllib import unquote
|
|||||||
from calibre.customize.conversion import OutputFormatPlugin
|
from calibre.customize.conversion import OutputFormatPlugin
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
from calibre import strftime, guess_type, prepare_string_for_xml
|
from calibre import strftime, guess_type, prepare_string_for_xml, CurrentDir
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
|
from calibre.constants import filesystem_encoding
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -170,6 +171,19 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
|
|
||||||
self.workaround_sony_quirks()
|
self.workaround_sony_quirks()
|
||||||
|
|
||||||
|
from calibre.ebooks.oeb.base import OPF
|
||||||
|
identifiers = oeb.metadata['identifier']
|
||||||
|
uuid = None
|
||||||
|
for x in identifiers:
|
||||||
|
if x.get(OPF('scheme'), None).lower() == 'uuid' or unicode(x).startswith('urn:uuid:'):
|
||||||
|
uuid = unicode(x).split(':')[-1]
|
||||||
|
break
|
||||||
|
if uuid is None:
|
||||||
|
self.log.warn('No UUID identifier found')
|
||||||
|
from uuid import uuid4
|
||||||
|
uuid = str(uuid4())
|
||||||
|
oeb.metadata.add('identifier', uuid, scheme='uuid', id=uuid)
|
||||||
|
|
||||||
with TemporaryDirectory('_epub_output') as tdir:
|
with TemporaryDirectory('_epub_output') as tdir:
|
||||||
from calibre.customize.ui import plugin_for_output_format
|
from calibre.customize.ui import plugin_for_output_format
|
||||||
oeb_output = plugin_for_output_format('oeb')
|
oeb_output = plugin_for_output_format('oeb')
|
||||||
@ -177,10 +191,16 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
opf = [x for x in os.listdir(tdir) if x.endswith('.opf')][0]
|
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)\
|
self.condense_ncx([os.path.join(tdir, x) for x in os.listdir(tdir)\
|
||||||
if x.endswith('.ncx')][0])
|
if x.endswith('.ncx')][0])
|
||||||
|
encrypted_fonts = getattr(input_plugin, 'encrypted_fonts', [])
|
||||||
|
encryption = None
|
||||||
|
if encrypted_fonts:
|
||||||
|
encryption = self.encrypt_fonts(encrypted_fonts, tdir, uuid)
|
||||||
|
|
||||||
from calibre.ebooks.epub import initialize_container
|
from calibre.ebooks.epub import initialize_container
|
||||||
epub = initialize_container(output_path, os.path.basename(opf))
|
epub = initialize_container(output_path, os.path.basename(opf))
|
||||||
epub.add_dir(tdir)
|
epub.add_dir(tdir)
|
||||||
|
if encryption is not None:
|
||||||
|
epub.writestr('META-INF/encryption.xml', encryption)
|
||||||
if opts.extract_to is not None:
|
if opts.extract_to is not None:
|
||||||
if os.path.exists(opts.extract_to):
|
if os.path.exists(opts.extract_to):
|
||||||
shutil.rmtree(opts.extract_to)
|
shutil.rmtree(opts.extract_to)
|
||||||
@ -189,6 +209,52 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
self.log.info('EPUB extracted to', opts.extract_to)
|
self.log.info('EPUB extracted to', opts.extract_to)
|
||||||
epub.close()
|
epub.close()
|
||||||
|
|
||||||
|
def encrypt_fonts(self, uris, tdir, uuid):
|
||||||
|
from binascii import unhexlify
|
||||||
|
|
||||||
|
key = re.sub(r'[^a-fA-F0-9]', '', uuid)
|
||||||
|
if len(key) < 16:
|
||||||
|
raise ValueError('UUID identifier %r is invalid'%uuid)
|
||||||
|
key = unhexlify((key + key)[:32])
|
||||||
|
key = tuple(map(ord, key))
|
||||||
|
paths = []
|
||||||
|
with CurrentDir(tdir):
|
||||||
|
paths = [os.path.join(*x.split('/')) for x in uris]
|
||||||
|
uris = dict(zip(uris, paths))
|
||||||
|
fonts = []
|
||||||
|
for uri in list(uris.keys()):
|
||||||
|
path = uris[uri]
|
||||||
|
if isinstance(path, unicode):
|
||||||
|
path = path.encode(filesystem_encoding)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
uris.pop(uri)
|
||||||
|
continue
|
||||||
|
self.log.debug('Encrypting font:', uri)
|
||||||
|
with open(path, 'r+b') as f:
|
||||||
|
data = f.read(1024)
|
||||||
|
f.seek(0)
|
||||||
|
for i in range(1024):
|
||||||
|
f.write(chr(ord(data[i]) ^ key[i%16]))
|
||||||
|
if not isinstance(uri, unicode):
|
||||||
|
uri = uri.decode('utf-8')
|
||||||
|
fonts.append(u'''
|
||||||
|
<enc:EncryptedData>
|
||||||
|
<enc:EncryptionMethod Algorithm="http://ns.adobe.com/pdf/enc#RC"/>
|
||||||
|
<enc:CipherData>
|
||||||
|
<enc:CipherReference URI="%s"/>
|
||||||
|
</enc:CipherData>
|
||||||
|
</enc:EncryptedData>
|
||||||
|
'''%(uri.replace('"', '\\"')))
|
||||||
|
if fonts:
|
||||||
|
ans = '''<encryption
|
||||||
|
xmlns="urn:oasis:names:tc:opendocument:xmlns:container"
|
||||||
|
xmlns:enc="http://www.w3.org/2001/04/xmlenc#"
|
||||||
|
xmlns:deenc="http://ns.adobe.com/digitaleditions/enc">
|
||||||
|
'''
|
||||||
|
ans += (u'\n'.join(fonts)).encode('utf-8')
|
||||||
|
ans += '\n</encryption>'
|
||||||
|
return ans
|
||||||
|
|
||||||
def default_cover(self):
|
def default_cover(self):
|
||||||
'''
|
'''
|
||||||
Create a generic cover for books that dont have a cover
|
Create a generic cover for books that dont have a cover
|
||||||
|
@ -779,6 +779,9 @@ class OPF(object):
|
|||||||
self.set_text(matches[0], unicode(val))
|
self.set_text(matches[0], unicode(val))
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def identifier_iter(self):
|
||||||
|
for item in self.identifier_path(self.metadata):
|
||||||
|
yield item
|
||||||
|
|
||||||
def guess_cover(self):
|
def guess_cover(self):
|
||||||
'''
|
'''
|
||||||
|
Loading…
x
Reference in New Issue
Block a user