Starting on Version 7.0 using the work done by others. Completely untested. I will be testing things, but I thought I'd get this base version up for others to give pull requests.

THIS IS ON THE MASTER BRANCH. The Master branch will be Python 3.0 from now on. While Python 2.7 support will not be deliberately broken, all efforts should now focus on Python 3.0 compatibility.

I can see a lot of work has been done. There's more to do. I've bumped the version number of everything I came across to the next major number for Python 3.0 compatibility indication.

Thanks everyone. I hope to update here at least once a week until we have a stable 7.0 release for calibre 5.0
pull/1248/head
Apprentice Harper 4 years ago
parent 4868a7460e
commit afa4ac5716

@ -7,7 +7,7 @@ from __future__ import with_statement
# Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '6.8.0' __version__ = '7.0.0'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@ -71,17 +71,19 @@ __docformat__ = 'restructuredtext en'
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018 # 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
# 6.7.0 - Handle new library in calibre. # 6.7.0 - Handle new library in calibre.
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+) # 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
# 7.0.0 - Switched to Python 3 for calibre 5.0. Thanks to all who comtibuted
""" """
Decrypt DRMed ebooks. Decrypt DRMed ebooks.
""" """
PLUGIN_NAME = u"DeDRM" PLUGIN_NAME = u"DeDRM"
PLUGIN_VERSION_TUPLE = (6, 8, 0) PLUGIN_VERSION_TUPLE = (7, 0, 0)
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE]) PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name. # Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
import codecs
import sys, os, re import sys, os, re
import time import time
import zipfile import zipfile
@ -107,7 +109,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
try: try:
self.stream.write(data) self.stream.write(data)
@ -165,7 +167,7 @@ class DeDRM(FileTypePlugin):
else: else:
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"] names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
lib_dict = self.load_resources(names) lib_dict = self.load_resources(names)
print u"{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
for entry, data in lib_dict.items(): for entry, data in lib_dict.items():
file_path = os.path.join(self.alfdir, entry) file_path = os.path.join(self.alfdir, entry)
@ -177,7 +179,7 @@ class DeDRM(FileTypePlugin):
try: try:
open(file_path,'wb').write(data) open(file_path,'wb').write(data)
except: except:
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION))
traceback.print_exc() traceback.print_exc()
pass pass
@ -187,7 +189,7 @@ class DeDRM(FileTypePlugin):
# mark that this version has been initialized # mark that this version has been initialized
os.mkdir(self.verdir) os.mkdir(self.verdir)
except Exception, e: except Exception as e:
traceback.print_exc() traceback.print_exc()
raise raise
@ -198,11 +200,11 @@ class DeDRM(FileTypePlugin):
inf = self.temporary_file(u".epub") inf = self.temporary_file(u".epub")
try: try:
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION))
fr = zipfix.fixZip(path_to_ebook, inf.name) fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix() fr.fix()
except Exception, e: except Exception as e:
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) print(u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
raise Exception(e) raise Exception(e)
# import the decryption keys # import the decryption keys
@ -215,19 +217,19 @@ class DeDRM(FileTypePlugin):
#check the book #check the book
if ignobleepub.ignobleBook(inf.name): if ignobleepub.ignobleBook(inf.name):
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) print("{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in dedrmprefs['bandnkeys'].items(): for keyname, userkey in dedrmprefs['bandnkeys'].items():
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
of = self.temporary_file(u".epub") of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
result = ignobleepub.decryptBook(userkey, inf.name, of.name) result = ignobleepub.decryptBook(userkey, inf.name, of.name)
except: except:
print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
@ -238,10 +240,10 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
# perhaps we should see if we can get a key from a log file # perhaps we should see if we can get a key from a log file
print u"{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# get the default NOOK Study keys # get the default NOOK Study keys
defaultkeys = [] defaultkeys = []
@ -258,7 +260,7 @@ class DeDRM(FileTypePlugin):
defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix']) defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix'])
except: except:
print u"{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
newkeys = [] newkeys = []
@ -269,7 +271,7 @@ class DeDRM(FileTypePlugin):
if len(newkeys) > 0: if len(newkeys) > 0:
try: try:
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".epub") of = self.temporary_file(u".epub")
@ -277,7 +279,7 @@ class DeDRM(FileTypePlugin):
try: try:
result = ignobleepub.decryptBook(userkey, inf.name, of.name) result = ignobleepub.decryptBook(userkey, inf.name, of.name)
except: except:
print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
@ -286,59 +288,59 @@ class DeDRM(FileTypePlugin):
if result == 0: if result == 0:
# Decryption was a success # Decryption was a success
# Store the new successful key in the defaults # Store the new successful key in the defaults
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try: try:
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue) dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except: except:
print u"{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception, e: except Exception as e:
pass pass
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# import the Adobe Adept ePub handler # import the Adobe Adept ePub handler
import calibre_plugins.dedrm.ineptepub as ineptepub import calibre_plugins.dedrm.ineptepub as ineptepub
if ineptepub.adeptBook(inf.name): if ineptepub.adeptBook(inf.name):
print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) print("{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
userkey = userkeyhex.decode('hex') userkey = codecs.decode(userkeyhex, 'hex')
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) print(u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(u".epub") of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
result = ineptepub.decryptBook(userkey, inf.name, of.name) result = ineptepub.decryptBook(userkey, inf.name, of.name)
except: except:
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
try: try:
of.close() of.close()
except: except:
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
if result == 0: if result == 0:
# Decryption was successful. # Decryption was successful.
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime) print("{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime) print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
# perhaps we need to get a new default ADE key # perhaps we need to get a new default ADE key
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# get the default Adobe keys # get the default Adobe keys
defaultkeys = [] defaultkeys = []
@ -356,7 +358,7 @@ class DeDRM(FileTypePlugin):
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
self.default_key = u"" self.default_key = u""
@ -368,14 +370,14 @@ class DeDRM(FileTypePlugin):
if len(newkeys) > 0: if len(newkeys) > 0:
try: try:
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".epub") of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
result = ineptepub.decryptBook(userkey, inf.name, of.name) result = ineptepub.decryptBook(userkey, inf.name, of.name)
except: except:
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
@ -384,31 +386,31 @@ class DeDRM(FileTypePlugin):
if result == 0: if result == 0:
# Decryption was a success # Decryption was a success
# Store the new successful key in the defaults # Store the new successful key in the defaults
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try: try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except: except:
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
print u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception, e: except Exception as e:
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print(u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
pass pass
# Something went wrong with decryption. # Something went wrong with decryption.
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
# Not a Barnes & Noble nor an Adobe Adept # Not a Barnes & Noble nor an Adobe Adept
# Import the fixed epub. # Import the fixed epub.
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) print("{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
raise DeDRMError(u"{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
def PDFDecrypt(self,path_to_ebook): def PDFDecrypt(self,path_to_ebook):
@ -417,17 +419,17 @@ class DeDRM(FileTypePlugin):
dedrmprefs = prefs.DeDRM_Prefs() dedrmprefs = prefs.DeDRM_Prefs()
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
userkey = userkeyhex.decode('hex') userkey = codecs.decode(userkeyhex, 'hex')
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
of = self.temporary_file(u".pdf") of = self.temporary_file(u".pdf")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
except: except:
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
@ -438,10 +440,10 @@ class DeDRM(FileTypePlugin):
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime) print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
# perhaps we need to get a new default ADE key # perhaps we need to get a new default ADE key
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
# get the default Adobe keys # get the default Adobe keys
defaultkeys = [] defaultkeys = []
@ -459,7 +461,7 @@ class DeDRM(FileTypePlugin):
self.default_key = defaultkeys[0] self.default_key = defaultkeys[0]
except: except:
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
self.default_key = u"" self.default_key = u""
@ -471,14 +473,14 @@ class DeDRM(FileTypePlugin):
if len(newkeys) > 0: if len(newkeys) > 0:
try: try:
for i,userkey in enumerate(newkeys): for i,userkey in enumerate(newkeys):
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
of = self.temporary_file(u".pdf") of = self.temporary_file(u".pdf")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
try: try:
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
except: except:
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
result = 1 result = 1
@ -487,23 +489,23 @@ class DeDRM(FileTypePlugin):
if result == 0: if result == 0:
# Decryption was a success # Decryption was a success
# Store the new successful key in the defaults # Store the new successful key in the defaults
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION) print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try: try:
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except: except:
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
# Return the modified PersistentTemporary file to calibre. # Return the modified PersistentTemporary file to calibre.
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print(u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except Exception, e: except Exception as e:
pass pass
# Something went wrong with decryption. # Something went wrong with decryption.
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
@ -530,12 +532,12 @@ class DeDRM(FileTypePlugin):
try: try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime) book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
except Exception, e: except Exception as e:
decoded = False decoded = False
# perhaps we need to get a new default Kindle for Mac/PC key # perhaps we need to get a new default Kindle for Mac/PC key
defaultkeys = [] defaultkeys = []
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]) print("{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]))
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
try: try:
if iswindows or isosx: if iswindows or isosx:
@ -548,7 +550,7 @@ class DeDRM(FileTypePlugin):
scriptpath = os.path.join(self.alfdir,u"kindlekey.py") scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix']) defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
except: except:
print u"{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime) print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc() traceback.print_exc()
pass pass
@ -558,20 +560,20 @@ class DeDRM(FileTypePlugin):
if keyvalue not in dedrmprefs['kindlekeys'].values(): if keyvalue not in dedrmprefs['kindlekeys'].values():
newkeys[keyname] = keyvalue newkeys[keyname] = keyvalue
if len(newkeys) > 0: if len(newkeys) > 0:
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys"))
try: try:
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime) book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
decoded = True decoded = True
# store the new successful keys in the defaults # store the new successful keys in the defaults
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys") print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys"))
for keyvalue in newkeys.values(): for keyvalue in newkeys.values():
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue) dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
except Exception, e: except Exception as e:
pass pass
if not decoded: if not decoded:
#if you reached here then no luck raise and exception #if you reached here then no luck raise and exception
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
of = self.temporary_file(book.getBookExtension()) of = self.temporary_file(book.getBookExtension())
@ -590,7 +592,7 @@ class DeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key (generated or provided). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in dedrmprefs['ereaderkeys'].items(): for keyname, userkey in dedrmprefs['ereaderkeys'].items():
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname) keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked) print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
of = self.temporary_file(u".pmlz") of = self.temporary_file(u".pmlz")
# Give the userkey, ebook and TemporaryPersistent file to the decryption function. # Give the userkey, ebook and TemporaryPersistent file to the decryption function.
@ -601,12 +603,12 @@ class DeDRM(FileTypePlugin):
# Decryption was successful return the modified PersistentTemporary # Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process. # file to Calibre's import process.
if result == 0: if result == 0:
print u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) print("{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
return of.name return of.name
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime) print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
@ -616,7 +618,7 @@ class DeDRM(FileTypePlugin):
sys.stdout=SafeUnbuffered(sys.stdout) sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr) sys.stderr=SafeUnbuffered(sys.stderr)
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)) print("{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
self.starttime = time.time() self.starttime = time.time()
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:] booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
@ -635,9 +637,9 @@ class DeDRM(FileTypePlugin):
# Adobe Adept or B&N ePub # Adobe Adept or B&N ePub
decrypted_ebook = self.ePubDecrypt(path_to_ebook) decrypted_ebook = self.ePubDecrypt(path_to_ebook)
else: else:
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype) print("Unknown booktype {0}. Passing back to calibre unchanged".format(booktype))
return path_to_ebook return path_to_ebook
print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime) print("{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
return decrypted_ebook return decrypted_ebook
def is_customizable(self): def is_customizable(self):

@ -48,14 +48,16 @@ from __future__ import with_statement
# 5.8 - Added getkey interface for Windows DeDRM application # 5.8 - Added getkey interface for Windows DeDRM application
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 6.0 - Work if TkInter is missing # 6.0 - Work if TkInter is missing
# 7.0 - Python 3 compatible for calibre 5
from __future__ import print_function
""" """
Retrieve Adobe ADEPT user key. Retrieve Adobe ADEPT user key.
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '6.0' __version__ = '7.0'
import sys, os, struct, getopt import sys, os, struct, getopt
@ -118,7 +120,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return arg
class ADEPTError(Exception): class ADEPTError(Exception):
pass pass
@ -497,7 +499,7 @@ def cli_main():
try: try:
opts, args = getopt.getopt(argv[1:], "h") opts, args = getopt.getopt(argv[1:], "h")
except getopt.GetoptError, err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print(u"Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
@ -586,7 +588,7 @@ def gui_main():
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
except ADEPTError, e: except ADEPTError as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')

@ -13,6 +13,8 @@
CryptoPy Artisitic License Version 1.0 CryptoPy Artisitic License Version 1.0
See the wonderful pure python package cryptopy-1.2.5 See the wonderful pure python package cryptopy-1.2.5
and read its LICENSE.txt for complete license details. and read its LICENSE.txt for complete license details.
Adjusted for Python 3 compatibility, September 2020
""" """
class CryptoError(Exception): class CryptoError(Exception):
@ -101,7 +103,7 @@ class BlockCipher:
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
if more == None: # no more calls to decrypt, should have all the data if more == None: # no more calls to decrypt, should have all the data
if numExtraBytes != 0: if numExtraBytes != 0:
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
# hold back some bytes in case last decrypt has zero len # hold back some bytes in case last decrypt has zero len
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
@ -143,7 +145,7 @@ class padWithPadLen(Pad):
def removePad(self, paddedBinaryString, blockSize): def removePad(self, paddedBinaryString, blockSize):
""" Remove padding from a binary string """ """ Remove padding from a binary string """
if not(0<len(paddedBinaryString)): if not(0<len(paddedBinaryString)):
raise DecryptNotBlockAlignedError, 'Expected More Data' raise DecryptNotBlockAlignedError('Expected More Data')
return paddedBinaryString[:-ord(paddedBinaryString[-1])] return paddedBinaryString[:-ord(paddedBinaryString[-1])]
class noPadding(Pad): class noPadding(Pad):
@ -451,7 +453,7 @@ class AES(Rijndael):
def __init__(self, key = None, padding = padWithPadLen(), keySize=16): def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
""" Initialize AES, keySize is in bytes """ """ Initialize AES, keySize is in bytes """
if not (keySize == 16 or keySize == 24 or keySize == 32) : if not (keySize == 16 or keySize == 24 or keySize == 32) :
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )

@ -178,13 +178,13 @@ def _load_python_alfcrypto():
if len(key)!=16: if len(key)!=16:
raise Exception('Pukall_Cipher: Bad key length.') raise Exception('Pukall_Cipher: Bad key length.')
wkey = [] wkey = []
for i in xrange(8): for i in range(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = "" dst = ""
for i in xrange(len(src)): for i in range(len(src)):
temp1 = 0; temp1 = 0;
byteXorVal = 0; byteXorVal = 0;
for j in xrange(8): for j in range(8):
temp1 ^= wkey[j] temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1 sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF sum1 = (temp1*346)&0xFFFF
@ -197,7 +197,7 @@ def _load_python_alfcrypto():
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption: if decryption:
keyXorVal = curByte * 257; keyXorVal = curByte * 257;
for j in xrange(8): for j in range(8):
wkey[j] ^= keyXorVal; wkey[j] ^= keyXorVal;
dst+=chr(curByte) dst+=chr(curByte)
return dst return dst

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function
# androidkindlekey.py # androidkindlekey.py
# Copyright © 2013-15 by Thom and Apprentice Harper # Copyright © 2013-15 by Thom and Apprentice Harper
@ -17,14 +18,14 @@ from __future__ import with_statement
# 1.3 - added in TkInter interface, output to a file # 1.3 - added in TkInter interface, output to a file
# 1.4 - Fix some problems identified by Aldo Bleeker # 1.4 - Fix some problems identified by Aldo Bleeker
# 1.5 - Fix another problem identified by Aldo Bleeker # 1.5 - Fix another problem identified by Aldo Bleeker
# 2.0 - Add Python 3 compatibility
""" """
Retrieve Kindle for Android Serial Number. Retrieve Kindle for Android Serial Number.
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '1.5' __version__ = '2.0'
import os import os
import sys import sys
@ -34,7 +35,10 @@ import tempfile
import zlib import zlib
import tarfile import tarfile
from hashlib import md5 from hashlib import md5
from cStringIO import StringIO try:
from cStringIO import StringIO
except ImportError:
from io import BytesIO as StringIO
from binascii import a2b_hex, b2a_hex from binascii import a2b_hex, b2a_hex
# Routines common to Mac and PC # Routines common to Mac and PC
@ -49,7 +53,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -90,7 +94,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"kindlekey.py"] return [u"kindlekey.py"]
@ -98,7 +102,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
class DrmException(Exception): class DrmException(Exception):
pass pass
@ -248,7 +252,7 @@ def get_serials2(path=STORAGE2):
traceback.print_exc() traceback.print_exc()
pass pass
tokens = list(set(tokens)) tokens = list(set(tokens))
serials = [] serials = []
for x in dsns: for x in dsns:
serials.append(x) serials.append(x)
@ -313,7 +317,7 @@ __all__ = [ 'get_serials', 'getkey']
def getkey(outfile, inpath): def getkey(outfile, inpath):
keys = get_serials(inpath) keys = get_serials(inpath)
if len(keys) > 0: if len(keys) > 0:
with file(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
for key in keys: for key in keys:
keyfileout.write(key) keyfileout.write(key)
keyfileout.write("\n") keyfileout.write("\n")
@ -340,7 +344,7 @@ def cli_main():
try: try:
opts, args = getopt.getopt(argv[1:], "hb:") opts, args = getopt.getopt(argv[1:], "hb:")
except getopt.GetoptError, err: except getopt.GetoptError as err:
usage(progname) usage(progname)
print(u"\nError in options or arguments: {0}".format(err.args[0])) print(u"\nError in options or arguments: {0}".format(err.args[0]))
return 2 return 2
@ -444,11 +448,11 @@ def gui_main():
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with file(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
except Exception, e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
self.status['text'] = u"Select backup.ab file" self.status['text'] = u"Select backup.ab file"

@ -42,7 +42,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return arg
def add_cp65001_codec(): def add_cp65001_codec():

@ -29,6 +29,8 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE. # DEALINGS IN THE SOFTWARE.
# Adjusted for Python 3, September 2020
""" """
AskFolder(...) -- Ask the user to select a folder Windows specific AskFolder(...) -- Ask the user to select a folder Windows specific
""" """
@ -164,15 +166,15 @@ def AskFolder(
def BrowseCallback(hwnd, uMsg, lParam, lpData): def BrowseCallback(hwnd, uMsg, lParam, lpData):
if uMsg == BFFM_INITIALIZED: if uMsg == BFFM_INITIALIZED:
if actionButtonLabel: if actionButtonLabel:
label = unicode(actionButtonLabel, errors='replace') label = actionButtonLabel.decode('utf-8', 'replace')
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label) user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
if cancelButtonLabel: if cancelButtonLabel:
label = unicode(cancelButtonLabel, errors='replace') label = cancelButtonLabel.decode('utf-8', 'replace')
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL) cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
if cancelButton: if cancelButton:
user32.SetWindowTextW(cancelButton, label) user32.SetWindowTextW(cancelButton, label)
if windowTitle: if windowTitle:
title = unicode(windowTitle, erros='replace') title = windowTitle.decode('utf-8', 'replace')
user32.SetWindowTextW(hwnd, title) user32.SetWindowTextW(hwnd, title)
if defaultLocation: if defaultLocation:
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\')) user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))

@ -6,6 +6,8 @@ from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
# Added Python 3 compatibility, September 2020
# Standard Python modules. # Standard Python modules.
import os, traceback, json import os, traceback, json
@ -22,7 +24,7 @@ try:
from PyQt5 import Qt as QtGui from PyQt5 import Qt as QtGui
except ImportError: except ImportError:
from PyQt4 import QtGui from PyQt4 import QtGui
from zipfile import ZipFile from zipfile import ZipFile
# calibre modules and constants. # calibre modules and constants.
@ -123,7 +125,7 @@ class ConfigWidget(QWidget):
def kindle_serials(self): def kindle_serials(self):
d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog) d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
d.exec_() d.exec_()
def kindle_android(self): def kindle_android(self):
d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a') d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
d.exec_() d.exec_()
@ -289,7 +291,7 @@ class ManageKeysDialog(QDialog):
def getwineprefix(self): def getwineprefix(self):
if self.wineprefix is not None: if self.wineprefix is not None:
return unicode(self.wp_lineedit.text()).strip() return self.wp_lineedit.text().strip()
return u"" return u""
def populate_list(self): def populate_list(self):
@ -338,7 +340,7 @@ class ManageKeysDialog(QDialog):
if d.result() != d.Accepted: if d.result() != d.Accepted:
# rename cancelled or moot. # rename cancelled or moot.
return return
keyname = unicode(self.listy.currentItem().text()) keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
return return
self.plugin_keys[d.key_name] = self.plugin_keys[keyname] self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
@ -350,7 +352,7 @@ class ManageKeysDialog(QDialog):
def delete_key(self): def delete_key(self):
if not self.listy.currentItem(): if not self.listy.currentItem():
return return
keyname = unicode(self.listy.currentItem().text()) keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
return return
if type(self.plugin_keys) == dict: if type(self.plugin_keys) == dict:
@ -388,7 +390,7 @@ class ManageKeysDialog(QDialog):
with open(fpath,'rb') as keyfile: with open(fpath,'rb') as keyfile:
new_key_value = keyfile.read() new_key_value = keyfile.read()
if self.binary_file: if self.binary_file:
new_key_value = new_key_value.encode('hex') new_key_value = new_key_value.hex()
elif self.json_file: elif self.json_file:
new_key_value = json.loads(new_key_value) new_key_value = json.loads(new_key_value)
elif self.android_file: elif self.android_file:
@ -412,7 +414,7 @@ class ManageKeysDialog(QDialog):
else: else:
counter += 1 counter += 1
self.plugin_keys[new_key_name] = new_key_value self.plugin_keys[new_key_name] = new_key_value
msg = u"" msg = u""
if counter+skipped > 1: if counter+skipped > 1:
if counter > 0: if counter > 0:
@ -434,7 +436,7 @@ class ManageKeysDialog(QDialog):
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
return return
keyname = unicode(self.listy.currentItem().text()) keyname = self.listy.currentItem().text()
unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
caption = u"Save {0} File as...".format(self.key_type_name) caption = u"Save {0} File as...".format(self.key_type_name)
filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])] filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
@ -485,7 +487,7 @@ class RenameKeyDialog(QDialog):
self.resize(self.sizeHint()) self.resize(self.sizeHint())
def accept(self): def accept(self):
if not unicode(self.key_ledit.text()) or unicode(self.key_ledit.text()).isspace(): if not self.key_ledit.text() or self.key_ledit.text().isspace():
errmsg = u"Key name field cannot be empty!" errmsg = u"Key name field cannot be empty!"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
_(errmsg), show=True, show_copy_button=False) _(errmsg), show=True, show_copy_button=False)
@ -506,7 +508,7 @@ class RenameKeyDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@ -589,19 +591,19 @@ class AddBandNKeyDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
return unicode(self.key_display.text()).strip() return self.key_display.text().strip()
@property @property
def user_name(self): def user_name(self):
return unicode(self.name_ledit.text()).strip().lower().replace(' ','') return self.name_ledit.text().strip().lower().replace(' ','')
@property @property
def cc_number(self): def cc_number(self):
return unicode(self.cc_ledit.text()).strip() return self.cc_ledit.text().strip()
def retrieve_key(self): def retrieve_key(self):
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
@ -675,7 +677,7 @@ class AddEReaderDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
@ -684,11 +686,11 @@ class AddEReaderDialog(QDialog):
@property @property
def user_name(self): def user_name(self):
return unicode(self.name_ledit.text()).strip().lower().replace(' ','') return self.name_ledit.text().strip().lower().replace(' ','')
@property @property
def cc_number(self): def cc_number(self):
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','') return self.cc_ledit.text().strip().replace(' ', '').replace('-','')
def accept(self): def accept(self):
@ -758,7 +760,7 @@ class AddAdeptDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
@ -819,7 +821,7 @@ class AddKindleDialog(QDialog):
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
default_key_error.setAlignment(Qt.AlignHCenter) default_key_error.setAlignment(Qt.AlignHCenter)
layout.addWidget(default_key_error) layout.addWidget(default_key_error)
# if no default, both buttons do the same # if no default, both buttons do the same
self.button_box.accepted.connect(self.reject) self.button_box.accepted.connect(self.reject)
@ -830,7 +832,7 @@ class AddKindleDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
@ -876,11 +878,11 @@ class AddSerialDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
return unicode(self.key_ledit.text()).replace(' ', '') return self.key_ledit.text().replace(' ', '')
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():
@ -916,7 +918,7 @@ class AddAndroidDialog(QDialog):
self.selected_file_name = QLabel(u"",self) self.selected_file_name = QLabel(u"",self)
self.selected_file_name.setAlignment(Qt.AlignHCenter) self.selected_file_name.setAlignment(Qt.AlignHCenter)
file_group.addWidget(self.selected_file_name) file_group.addWidget(self.selected_file_name)
key_group = QHBoxLayout() key_group = QHBoxLayout()
data_group_box_layout.addLayout(key_group) data_group_box_layout.addLayout(key_group)
key_group.addWidget(QLabel(u"Unique Key Name:", self)) key_group.addWidget(QLabel(u"Unique Key Name:", self))
@ -926,7 +928,7 @@ class AddAndroidDialog(QDialog):
#key_label = QLabel(_(''), self) #key_label = QLabel(_(''), self)
#key_label.setAlignment(Qt.AlignHCenter) #key_label.setAlignment(Qt.AlignHCenter)
#data_group_box_layout.addWidget(key_label) #data_group_box_layout.addWidget(key_label)
self.button_box.accepted.connect(self.accept) self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject) self.button_box.rejected.connect(self.reject)
layout.addWidget(self.button_box) layout.addWidget(self.button_box)
@ -934,16 +936,16 @@ class AddAndroidDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def file_name(self): def file_name(self):
return unicode(self.selected_file_name.text()).strip() return self.selected_file_name.text().strip()
@property @property
def key_value(self): def key_value(self):
return self.serials_from_file return self.serials_from_file
def get_android_file(self): def get_android_file(self):
unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
caption = u"Select Kindle for Android backup file to add" caption = u"Select Kindle for Android backup file to add"
@ -961,7 +963,7 @@ class AddAndroidDialog(QDialog):
file_name = os.path.basename(self.filename) file_name = os.path.basename(self.filename)
self.serials_from_file.extend(file_serials) self.serials_from_file.extend(file_serials)
self.selected_file_name.setText(file_name) self.selected_file_name.setText(file_name)
def accept(self): def accept(self):
if len(self.file_name) == 0 or len(self.key_value) == 0: if len(self.file_name) == 0 or len(self.key_value) == 0:
@ -1004,11 +1006,11 @@ class AddPIDDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():

@ -1,6 +1,7 @@
#! /usr/bin/python #! /usr/bin/python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# For use with Topaz Scripts Version 2.6 # For use with Topaz Scripts Version 2.6
# Added Python 3 compatibility, September 2020
from __future__ import print_function from __future__ import print_function
class Unbuffered: class Unbuffered:
@ -107,7 +108,7 @@ def readString(file):
def convert(i): def convert(i):
result = '' result = ''
val = encodeNumber(i) val = encodeNumber(i)
for j in xrange(len(val)): for j in range(len(val)):
c = ord(val[j:j+1]) c = ord(val[j:j+1])
result += '%02x' % c result += '%02x' % c
return result return result
@ -121,10 +122,10 @@ class Dictionary(object):
def __init__(self, dictFile): def __init__(self, dictFile):
self.filename = dictFile self.filename = dictFile
self.size = 0 self.size = 0
self.fo = file(dictFile,'rb') self.fo = open(dictFile,'rb')
self.stable = [] self.stable = []
self.size = readEncodedNumber(self.fo) self.size = readEncodedNumber(self.fo)
for i in xrange(self.size): for i in range(self.size):
self.stable.append(self.escapestr(readString(self.fo))) self.stable.append(self.escapestr(readString(self.fo)))
self.pos = 0 self.pos = 0
@ -151,7 +152,7 @@ class Dictionary(object):
return self.pos return self.pos
def dumpDict(self): def dumpDict(self):
for i in xrange(self.size): for i in range(self.size):
print("%d %s %s" % (i, convert(i), self.stable[i])) print("%d %s %s" % (i, convert(i), self.stable[i]))
return return
@ -161,7 +162,7 @@ class Dictionary(object):
class PageParser(object): class PageParser(object):
def __init__(self, filename, dict, debug, flat_xml): def __init__(self, filename, dict, debug, flat_xml):
self.fo = file(filename,'rb') self.fo = open(filename,'rb')
self.id = os.path.basename(filename).replace('.dat','') self.id = os.path.basename(filename).replace('.dat','')
self.dict = dict self.dict = dict
self.debug = debug self.debug = debug
@ -392,7 +393,7 @@ class PageParser(object):
'startID' : (0, 'number', 1, 1), 'startID' : (0, 'number', 1, 1),
'startID.page' : (1, 'number', 0, 0), 'startID.page' : (1, 'number', 0, 0),
'startID.id' : (1, 'number', 0, 0), 'startID.id' : (1, 'number', 0, 0),
'median_d' : (1, 'number', 0, 0), 'median_d' : (1, 'number', 0, 0),
'median_h' : (1, 'number', 0, 0), 'median_h' : (1, 'number', 0, 0),
'median_firsty' : (1, 'number', 0, 0), 'median_firsty' : (1, 'number', 0, 0),
@ -420,7 +421,7 @@ class PageParser(object):
def get_tagpath(self, i): def get_tagpath(self, i):
cnt = len(self.tagpath) cnt = len(self.tagpath)
if i < cnt : result = self.tagpath[i] if i < cnt : result = self.tagpath[i]
for j in xrange(i+1, cnt) : for j in range(i+1, cnt) :
result += '.' + self.tagpath[j] result += '.' + self.tagpath[j]
return result return result
@ -472,7 +473,7 @@ class PageParser(object):
if self.debug : print('Processing: ', self.get_tagpath(0)) if self.debug : print('Processing: ', self.get_tagpath(0))
cnt = self.tagpath_len() cnt = self.tagpath_len()
for j in xrange(cnt): for j in range(cnt):
tkn = self.get_tagpath(j) tkn = self.get_tagpath(j)
if tkn in self.token_tags : if tkn in self.token_tags :
num_args = self.token_tags[tkn][0] num_args = self.token_tags[tkn][0]
@ -497,7 +498,7 @@ class PageParser(object):
if (subtags == 1): if (subtags == 1):
ntags = readEncodedNumber(self.fo) ntags = readEncodedNumber(self.fo)
if self.debug : print('subtags: ' + token + ' has ' + str(ntags)) if self.debug : print('subtags: ' + token + ' has ' + str(ntags))
for j in xrange(ntags): for j in range(ntags):
val = readEncodedNumber(self.fo) val = readEncodedNumber(self.fo)
subtagres.append(self.procToken(self.dict.lookup(val))) subtagres.append(self.procToken(self.dict.lookup(val)))
@ -511,7 +512,7 @@ class PageParser(object):
argres = self.decodeCMD(arg,argtype) argres = self.decodeCMD(arg,argtype)
else : else :
# num_arg scalar arguments # num_arg scalar arguments
for i in xrange(num_args): for i in range(num_args):
argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
# build the return tag # build the return tag
@ -546,7 +547,7 @@ class PageParser(object):
result += 'of the document is indicated by snippet number sets at the\n' result += 'of the document is indicated by snippet number sets at the\n'
result += 'end of each snippet. \n' result += 'end of each snippet. \n'
print(result) print(result)
for i in xrange(cnt): for i in range(cnt):
if self.debug: print('Snippet:',str(i)) if self.debug: print('Snippet:',str(i))
snippet = [] snippet = []
snippet.append(i) snippet.append(i)
@ -565,12 +566,12 @@ class PageParser(object):
adj = readEncodedNumber(self.fo) adj = readEncodedNumber(self.fo)
mode = mode >> 1 mode = mode >> 1
x = [] x = []
for i in xrange(cnt): for i in range(cnt):
x.append(readEncodedNumber(self.fo) - adj) x.append(readEncodedNumber(self.fo) - adj)
for i in xrange(mode): for i in range(mode):
for j in xrange(1, cnt): for j in range(1, cnt):
x[j] = x[j] + x[j - 1] x[j] = x[j] + x[j - 1]
for i in xrange(cnt): for i in range(cnt):
result.append(self.formatArg(x[i],argtype)) result.append(self.formatArg(x[i],argtype))
return result return result
@ -844,7 +845,7 @@ def main(argv):
try: try:
opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
except getopt.GetoptError, err: except getopt.GetoptError as err:
# print help information and exit: # print help information and exit:
print(str(err)) # will print something like "option -a not recognized" print(str(err)) # will print something like "option -a not recognized"

@ -10,6 +10,7 @@
# Changelog epubtest # Changelog epubtest
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf # 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
# 1.01 - Added routine for use by Windows DeDRM # 1.01 - Added routine for use by Windows DeDRM
# 2.00 - Added Python 3 compatibility, September 2020
# #
# Written in 2011 by Paul Durrant # Written in 2011 by Paul Durrant
# Released with unlicense. See http://unlicense.org/ # Released with unlicense. See http://unlicense.org/
@ -47,7 +48,7 @@
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function from __future__ import print_function
__version__ = '1.01' __version__ = '2.0'
import sys, struct, os, traceback import sys, struct, os, traceback
import zlib import zlib
@ -116,7 +117,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return arg
_FILENAME_LEN_OFFSET = 26 _FILENAME_LEN_OFFSET = 26
_EXTRA_LEN_OFFSET = 28 _EXTRA_LEN_OFFSET = 28

@ -67,8 +67,9 @@
# - Ignore sidebars for dictionaries (different format?) # - Ignore sidebars for dictionaries (different format?)
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source # 0.22 - Unicode and plugin support, different image folders for PMLZ and source
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility # 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 1.00 - Added Python 3 compatibility for calibre 5.0
__version__='0.23' __version__='1.00'
import sys, re import sys, re
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
@ -88,7 +89,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -126,7 +127,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return [u"mobidedrm.py"]
@ -134,7 +135,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
Des = None Des = None
if iswindows: if iswindows:
@ -200,7 +201,7 @@ class Sectionizer(object):
bkType = "Book" bkType = "Book"
def __init__(self, filename, ident): def __init__(self, filename, ident):
self.contents = file(filename, 'rb').read() self.contents = open(filename, 'rb').read()
self.header = self.contents[0:72] self.header = self.contents[0:72]
self.num_sections, = struct.unpack('>H', self.contents[76:78]) self.num_sections, = struct.unpack('>H', self.contents[76:78])
# Dictionary or normal content (TODO: Not hard-coded) # Dictionary or normal content (TODO: Not hard-coded)
@ -210,7 +211,7 @@ class Sectionizer(object):
else: else:
raise ValueError('Invalid file format') raise ValueError('Invalid file format')
self.sections = [] self.sections = []
for i in xrange(self.num_sections): for i in range(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8]) offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4 flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) ) self.sections.append( (offset, flags, val) )
@ -233,7 +234,7 @@ def sanitizeFileName(name):
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip() name = re.sub(r"\s", u" ", name).strip()
# remove leading dots # remove leading dots
while len(name)>0 and name[0] == u".": while len(name)>0 and name[0] == u".":
name = name[1:] name = name[1:]
@ -250,7 +251,7 @@ def fixKey(key):
def deXOR(text, sp, table): def deXOR(text, sp, table):
r='' r=''
j = sp j = sp
for i in xrange(len(text)): for i in range(len(text)):
r += chr(ord(table[j]) ^ ord(text[i])) r += chr(ord(table[j]) ^ ord(text[i]))
j = j + 1 j = j + 1
if j == len(table): if j == len(table):
@ -276,7 +277,7 @@ class EreaderProcessor(object):
def unshuff(data, shuf): def unshuff(data, shuf):
r = [''] * len(data) r = [''] * len(data)
j = 0 j = 0
for i in xrange(len(data)): for i in range(len(data)):
j = (j + shuf) % len(data) j = (j + shuf) % len(data)
r[j] = data[i] r[j] = data[i]
assert len("".join(r)) == len(data) assert len("".join(r)) == len(data)
@ -330,7 +331,7 @@ class EreaderProcessor(object):
self.flags = struct.unpack('>L', r[4:8])[0] self.flags = struct.unpack('>L', r[4:8])[0]
reqd_flags = (1<<9) | (1<<7) | (1<<10) reqd_flags = (1<<9) | (1<<7) | (1<<10)
if (self.flags & reqd_flags) != reqd_flags: if (self.flags & reqd_flags) != reqd_flags:
print "Flags: 0x%X" % self.flags print("Flags: 0x%X" % self.flags)
raise ValueError('incompatible eReader file') raise ValueError('incompatible eReader file')
des = Des(fixKey(user_key)) des = Des(fixKey(user_key))
if version == 259: if version == 259:
@ -361,7 +362,7 @@ class EreaderProcessor(object):
sect = self.section_reader(self.first_image_page + i) sect = self.section_reader(self.first_image_page + i)
name = sect[4:4+32].strip('\0') name = sect[4:4+32].strip('\0')
data = sect[62:] data = sect[62:]
return sanitizeFileName(unicode(name,'windows-1252')), data return sanitizeFileName(name.decode('windows-1252')), data
# def getChapterNamePMLOffsetData(self): # def getChapterNamePMLOffsetData(self):
@ -410,7 +411,7 @@ class EreaderProcessor(object):
def getText(self): def getText(self):
des = Des(fixKey(self.content_key)) des = Des(fixKey(self.content_key))
r = '' r = ''
for i in xrange(self.num_text_pages): for i in range(self.num_text_pages):
logging.debug('get page %d', i) logging.debug('get page %d', i)
r += zlib.decompress(des.decrypt(self.section_reader(1 + i))) r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
@ -422,7 +423,7 @@ class EreaderProcessor(object):
fnote_ids = deXOR(sect, 0, self.xortable) fnote_ids = deXOR(sect, 0, self.xortable)
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
des = Des(fixKey(self.content_key)) des = Des(fixKey(self.content_key))
for i in xrange(1,self.num_footnote_pages): for i in range(1,self.num_footnote_pages):
logging.debug('get footnotepage %d', i) logging.debug('get footnotepage %d', i)
id_len = ord(fnote_ids[2]) id_len = ord(fnote_ids[2])
id = fnote_ids[3:3+id_len] id = fnote_ids[3:3+id_len]
@ -446,7 +447,7 @@ class EreaderProcessor(object):
sbar_ids = deXOR(sect, 0, self.xortable) sbar_ids = deXOR(sect, 0, self.xortable)
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated # the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
des = Des(fixKey(self.content_key)) des = Des(fixKey(self.content_key))
for i in xrange(1,self.num_sidebar_pages): for i in range(1,self.num_sidebar_pages):
id_len = ord(sbar_ids[2]) id_len = ord(sbar_ids[2])
id = sbar_ids[3:3+id_len] id = sbar_ids[3:3+id_len]
smarker = '<sidebar id="%s">\n' % id smarker = '<sidebar id="%s">\n' % id
@ -460,7 +461,7 @@ class EreaderProcessor(object):
def cleanPML(pml): def cleanPML(pml):
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml pml2 = pml
for k in xrange(128,256): for k in range(128,256):
badChar = chr(k) badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k) pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2 return pml2
@ -480,26 +481,26 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
try: try:
if not os.path.exists(outdir): if not os.path.exists(outdir):
os.makedirs(outdir) os.makedirs(outdir)
print u"Decoding File" print(u"Decoding File")
sect = Sectionizer(infile, 'PNRdPPrs') sect = Sectionizer(infile, 'PNRdPPrs')
er = EreaderProcessor(sect, user_key) er = EreaderProcessor(sect, user_key)
if er.getNumImages() > 0: if er.getNumImages() > 0:
print u"Extracting images" print(u"Extracting images")
if not os.path.exists(imagedirpath): if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath) os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()): for i in range(er.getNumImages()):
name, contents = er.getImage(i) name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents) open(os.path.join(imagedirpath, name), 'wb').write(contents)
print u"Extracting pml" print(u"Extracting pml")
pml_string = er.getText() pml_string = er.getText()
pmlfilename = bookname + ".pml" pmlfilename = bookname + ".pml"
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string)) open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
if pmlzname is not None: if pmlzname is not None:
import zipfile import zipfile
import shutil import shutil
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname)) print(u"Creating PMLZ file {0}".format(os.path.basename(pmlzname)))
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False) myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir) list = os.listdir(outdir)
for filename in list: for filename in list:
@ -518,33 +519,33 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
myZipFile.close() myZipFile.close()
# remove temporary directory # remove temporary directory
shutil.rmtree(outdir, True) shutil.rmtree(outdir, True)
print u"Output is {0}".format(pmlzname) print(u"Output is {0}".format(pmlzname))
else : else :
print u"Output is in {0}".format(outdir) print(u"Output is in {0}".format(outdir))
print "done" print("done")
except ValueError, e: except ValueError as e:
print u"Error: {0}".format(e) print(u"Error: {0}".format(e))
traceback.print_exc() traceback.print_exc()
return 1 return 1
return 0 return 0
def usage(): def usage():
print u"Converts DRMed eReader books to PML Source" print(u"Converts DRMed eReader books to PML Source")
print u"Usage:" print(u"Usage:")
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number" print(u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number")
print u" " print(u" ")
print u"Options: " print(u"Options: ")
print u" -h prints this message" print(u" -h prints this message")
print u" -p create PMLZ instead of source folder" print(u" -p create PMLZ instead of source folder")
print u" --make-pmlz create PMLZ instead of source folder" print(u" --make-pmlz create PMLZ instead of source folder")
print u" " print(u" ")
print u"Note:" print(u"Note:")
print u" if outpath is ommitted, creates source in 'infile_Source' folder" print(u" if outpath is ommitted, creates source in 'infile_Source' folder")
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'" print(u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'")
print u" if source folder created, images are in infile_img folder" print(u" if source folder created, images are in infile_img folder")
print u" if pmlz file created, images are in images folder" print(u" if pmlz file created, images are in images folder")
print u" It's enough to enter the last 8 digits of the credit card number" print(u" It's enough to enter the last 8 digits of the credit card number")
return return
def getuser_key(name,cc): def getuser_key(name,cc):
@ -553,13 +554,13 @@ def getuser_key(name,cc):
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff) return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
def cli_main(): def cli_main():
print u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__) print(u"eRdr2Pml v{0}. Copyright © 20092012 The Dark Reverser et al.".format(__version__))
argv=unicode_argv() argv=unicode_argv()
try: try:
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"]) opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
except getopt.GetoptError, err: except getopt.GetoptError as err:
print err.args[0] print(err.args[0])
usage() usage()
return 1 return 1
make_pmlz = False make_pmlz = False
@ -585,7 +586,7 @@ def cli_main():
elif len(args)==4: elif len(args)==4:
infile, outpath, name, cc = args infile, outpath, name, cc = args
print getuser_key(name,cc).encode('hex') print(getuser_key(name,cc).encode('hex'))
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc)) return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))

@ -1,5 +1,6 @@
#! /usr/bin/python #! /usr/bin/python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
# Added Python 3 compatibility for calibre 5.0
from __future__ import print_function from __future__ import print_function
from .convert2xml import encodeNumber from .convert2xml import encodeNumber
@ -87,9 +88,9 @@ def readString(file):
def getMetaArray(metaFile): def getMetaArray(metaFile):
# parse the meta file # parse the meta file
result = {} result = {}
fo = file(metaFile,'rb') fo = open(metaFile,'rb')
size = readEncodedNumber(fo) size = readEncodedNumber(fo)
for i in xrange(size): for i in range(size):
tag = readString(fo) tag = readString(fo)
value = readString(fo) value = readString(fo)
result[tag] = value result[tag] = value
@ -103,10 +104,10 @@ class Dictionary(object):
def __init__(self, dictFile): def __init__(self, dictFile):
self.filename = dictFile self.filename = dictFile
self.size = 0 self.size = 0
self.fo = file(dictFile,'rb') self.fo = open(dictFile,'rb')
self.stable = [] self.stable = []
self.size = readEncodedNumber(self.fo) self.size = readEncodedNumber(self.fo)
for i in xrange(self.size): for i in range(self.size):
self.stable.append(self.escapestr(readString(self.fo))) self.stable.append(self.escapestr(readString(self.fo)))
self.pos = 0 self.pos = 0
def escapestr(self, str): def escapestr(self, str):
@ -142,7 +143,7 @@ class PageDimParser(object):
else: else:
end = min(cnt,end) end = min(cnt,end)
foundat = -1 foundat = -1
for j in xrange(pos, end): for j in range(pos, end):
item = docList[j] item = docList[j]
if item.find('=') >= 0: if item.find('=') >= 0:
(name, argres) = item.split('=') (name, argres) = item.split('=')
@ -195,7 +196,7 @@ class GParser(object):
def getData(self, path): def getData(self, path):
result = None result = None
cnt = len(self.flatdoc) cnt = len(self.flatdoc)
for j in xrange(cnt): for j in range(cnt):
item = self.flatdoc[j] item = self.flatdoc[j]
if item.find('=') >= 0: if item.find('=') >= 0:
(name, argt) = item.split('=') (name, argt) = item.split('=')
@ -207,7 +208,7 @@ class GParser(object):
result = argres result = argres
break break
if (len(argres) > 0) : if (len(argres) > 0) :
for j in xrange(0,len(argres)): for j in range(0,len(argres)):
argres[j] = int(argres[j]) argres[j] = int(argres[j])
return result return result
def getGlyphDim(self, gly): def getGlyphDim(self, gly):
@ -223,7 +224,7 @@ class GParser(object):
tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]] tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]] ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
p = 0 p = 0
for k in xrange(self.glen[gly], self.glen[gly+1]): for k in range(self.glen[gly], self.glen[gly+1]):
if (p == 0): if (p == 0):
zx = tx[0:self.vlen[k]+1] zx = tx[0:self.vlen[k]+1]
zy = ty[0:self.vlen[k]+1] zy = ty[0:self.vlen[k]+1]
@ -322,17 +323,17 @@ def generateBook(bookDir, raw, fixedimage):
imgname = filename.replace('color','img') imgname = filename.replace('color','img')
sfile = os.path.join(spath,filename) sfile = os.path.join(spath,filename)
dfile = os.path.join(dpath,imgname) dfile = os.path.join(dpath,imgname)
imgdata = file(sfile,'rb').read() imgdata = open(sfile,'rb').read()
file(dfile,'wb').write(imgdata) open(dfile,'wb').write(imgdata)
print("Creating cover.jpg") print("Creating cover.jpg")
isCover = False isCover = False
cpath = os.path.join(bookDir,'img') cpath = os.path.join(bookDir,'img')
cpath = os.path.join(cpath,'img0000.jpg') cpath = os.path.join(cpath,'img0000.jpg')
if os.path.isfile(cpath): if os.path.isfile(cpath):
cover = file(cpath, 'rb').read() cover = open(cpath, 'rb').read()
cpath = os.path.join(bookDir,'cover.jpg') cpath = os.path.join(bookDir,'cover.jpg')
file(cpath, 'wb').write(cover) open(cpath, 'wb').write(cover)
isCover = True isCover = True
@ -361,7 +362,7 @@ def generateBook(bookDir, raw, fixedimage):
mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n') mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
metastr = "".join(mlst) metastr = "".join(mlst)
mlst = None mlst = None
file(xname, 'wb').write(metastr) open(xname, 'wb').write(metastr)
print('Processing StyleSheet') print('Processing StyleSheet')
@ -424,10 +425,10 @@ def generateBook(bookDir, raw, fixedimage):
# now get the css info # now get the css info
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
file(xname, 'wb').write(cssstr) open(xname, 'wb').write(cssstr)
if buildXML: if buildXML:
xname = os.path.join(xmlDir, 'other0000.xml') xname = os.path.join(xmlDir, 'other0000.xml')
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) open(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
print('Processing Glyphs') print('Processing Glyphs')
gd = GlyphDict() gd = GlyphDict()
@ -449,10 +450,10 @@ def generateBook(bookDir, raw, fixedimage):
if buildXML: if buildXML:
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) open(xname, 'wb').write(convert2xml.getXML(dict, fname))
gp = GParser(flat_xml) gp = GParser(flat_xml)
for i in xrange(0, gp.count): for i in range(0, gp.count):
path = gp.getPath(i) path = gp.getPath(i)
maxh, maxw = gp.getGlyphDim(i) maxh, maxw = gp.getGlyphDim(i)
fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh) fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh)
@ -507,7 +508,7 @@ def generateBook(bookDir, raw, fixedimage):
if buildXML: if buildXML:
xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
file(xname, 'wb').write(convert2xml.getXML(dict, fname)) open(xname, 'wb').write(convert2xml.getXML(dict, fname))
# first get the html # first get the html
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
@ -518,7 +519,7 @@ def generateBook(bookDir, raw, fixedimage):
hlst.append('</body>\n</html>\n') hlst.append('</body>\n</html>\n')
htmlstr = "".join(hlst) htmlstr = "".join(hlst)
hlst = None hlst = None
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) open(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
print(" ") print(" ")
print('Extracting Table of Contents from Amazon OCR') print('Extracting Table of Contents from Amazon OCR')
@ -564,7 +565,7 @@ def generateBook(bookDir, raw, fixedimage):
tlst.append('</body>\n') tlst.append('</body>\n')
tlst.append('</html>\n') tlst.append('</html>\n')
tochtml = "".join(tlst) tochtml = "".join(tlst)
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) open(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
# now create index_svg.xhtml that points to all required files # now create index_svg.xhtml that points to all required files
@ -619,7 +620,7 @@ def generateBook(bookDir, raw, fixedimage):
slst.append('</body>\n</html>\n') slst.append('</body>\n</html>\n')
svgindex = "".join(slst) svgindex = "".join(slst)
slst = None slst = None
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) open(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
print(" ") print(" ")
@ -668,7 +669,7 @@ def generateBook(bookDir, raw, fixedimage):
olst.append('</package>\n') olst.append('</package>\n')
opfstr = "".join(olst) opfstr = "".join(olst)
olst = None olst = None
file(opfname, 'wb').write(opfstr) open(opfname, 'wb').write(opfstr)
print('Processing Complete') print('Processing Complete')
@ -694,7 +695,7 @@ def main(argv):
try: try:
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"]) opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
except getopt.GetoptError, err: except getopt.GetoptError as err:
print(str(err)) print(str(err))
usage() usage()
return 1 return 1

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function
# ignobleepub.pyw, version 4.1 # ignobleepub.pyw, version 4.1
# Copyright © 2009-2010 by i♥cabbages # Copyright © 2009-2010 by i♥cabbages
@ -37,14 +38,14 @@ from __future__ import with_statement
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 4.0 - Work if TkInter is missing # 4.0 - Work if TkInter is missing
# 4.1 - Import tkFileDialog, don't assume something else will import it. # 4.1 - Import tkFileDialog, don't assume something else will import it.
# 5.0 - Added Python 3 compatibility for calibre 5.0
""" """
Decrypt Barnes & Noble encrypted ePub books. Decrypt Barnes & Noble encrypted ePub books.
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "4.1" __version__ = "5.0"
import sys import sys
import os import os
@ -65,7 +66,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -106,13 +107,13 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
return [u"ineptepub.py"] return [u"ineptepub.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
class IGNOBLEError(Exception): class IGNOBLEError(Exception):
@ -434,7 +435,7 @@ def gui_main():
self.status['text'] = u"Decrypting..." self.status['text'] = u"Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:

@ -2,9 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function
# ignoblekey.py # ignoblekey.py
# Copyright © 2015 Apprentice Alf and Apprentice Harper # Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf # Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
@ -14,11 +15,11 @@ from __future__ import with_statement
# Revision history: # Revision history:
# 1.0 - Initial release # 1.0 - Initial release
# 1.1 - remove duplicates and return last key as single key # 1.1 - remove duplicates and return last key as single key
# 2.0 - Added Python 3 compatibility for calibre 5.0
""" """
Get Barnes & Noble EPUB user key from nook Studio log file Get Barnes & Noble EPUB user key from nook Studio log file
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "1.1" __version__ = "1.1"
@ -39,7 +40,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -80,7 +81,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekey.py"] return [u"ignoblekey.py"]
@ -88,7 +89,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
class DrmException(Exception): class DrmException(Exception):
pass pass
@ -210,7 +211,7 @@ def getkey(outpath, files=[]):
if len(keys) > 0: if len(keys) > 0:
if not os.path.isdir(outpath): if not os.path.isdir(outpath):
outfile = outpath outfile = outpath
with file(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(keys[-1]) keyfileout.write(keys[-1])
print(u"Saved a key to {0}".format(outfile)) print(u"Saved a key to {0}".format(outfile))
else: else:
@ -221,7 +222,7 @@ def getkey(outpath, files=[]):
outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount)) outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with file(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
print(u"Saved a key to {0}".format(outfile)) print(u"Saved a key to {0}".format(outfile))
return True return True
@ -244,7 +245,7 @@ def cli_main():
try: try:
opts, args = getopt.getopt(argv[1:], "hk:") opts, args = getopt.getopt(argv[1:], "hk:")
except getopt.GetoptError, err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print(u"Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
@ -315,11 +316,11 @@ def gui_main():
if not os.path.exists(outfile): if not os.path.exists(outfile):
break break
with file(outfile, 'w') as keyfileout: with open(outfile, 'w') as keyfileout:
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
except DrmException, e: except DrmException as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')

@ -2,9 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function
# ignoblekeyfetch.pyw, version 1.1 # ignoblekeyfetch.pyw, version 2.0
# Copyright © 2015 Apprentice Harper # Copyright © 2015-2020 Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
@ -24,11 +25,11 @@ from __future__ import with_statement
# Revision history: # Revision history:
# 1.0 - Initial version # 1.0 - Initial version
# 1.1 - Try second URL if first one fails # 1.1 - Try second URL if first one fails
# 2.0 - Added Python 3 compatibility for calibre 5.0
""" """
Fetch Barnes & Noble EPUB user key from B&N servers using email and password Fetch Barnes & Noble EPUB user key from B&N servers using email and password
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "1.1" __version__ = "1.1"
@ -46,7 +47,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -87,7 +88,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekeyfetch.py"] return [u"ignoblekeyfetch.py"]
@ -95,7 +96,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
class IGNOBLEError(Exception): class IGNOBLEError(Exception):
@ -103,9 +104,9 @@ class IGNOBLEError(Exception):
def fetch_key(email, password): def fetch_key(email, password):
# change email and password to utf-8 if unicode # change email and password to utf-8 if unicode
if type(email)==unicode: if type(email)==bytes:
email = email.encode('utf-8') email = email.encode('utf-8')
if type(password)==unicode: if type(password)==bytes:
password = password.encode('utf-8') password = password.encode('utf-8')
import random import random
@ -236,7 +237,7 @@ def gui_main():
self.status['text'] = u"Fetching..." self.status['text'] = u"Fetching..."
try: try:
userkey = fetch_key(email, password) userkey = fetch_key(email, password)
except Exception, e: except Exception as e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if len(userkey) == 28: if len(userkey) == 28:

@ -2,15 +2,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import print_function
# ignoblekeygen.pyw, version 2.5 # ignoblekeygen.pyw
# Copyright © 2009-2010 i♥cabbages # Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python. # Windows users: Before running this program, you must first install Python.
# We recommend ActiveState Python 2.7.X for Windows (x86) from # We recommend ActiveState Python 2.7.X for Windows (x86) from
# http://www.activestate.com/activepython/downloads. # http://www.activestate.com/activepython/downloads.
@ -34,14 +33,14 @@ from __future__ import with_statement
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility # 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 2.7 - Work if TkInter is missing # 2.7 - Work if TkInter is missing
# 2.8 - Fix bug in stand-alone use (import tkFileDialog) # 2.8 - Fix bug in stand-alone use (import tkFileDialog)
# 3.0 - Added Python 3 compatibility for calibre 5.0
""" """
Generate Barnes & Noble EPUB user key from name and credit card number. Generate Barnes & Noble EPUB user key from name and credit card number.
""" """
from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "2.8" __version__ = "3.0"
import sys import sys
import os import os
@ -57,7 +56,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -98,7 +97,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"ignoblekeygen.py"] return [u"ignoblekeygen.py"]
@ -106,7 +105,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
class IGNOBLEError(Exception): class IGNOBLEError(Exception):
@ -199,9 +198,9 @@ def normalize_name(name):
def generate_key(name, ccn): def generate_key(name, ccn):
# remove spaces and case from name and CC numbers. # remove spaces and case from name and CC numbers.
if type(name)==unicode: if type(name)==bytes:
name = name.encode('utf-8') name = name.encode('utf-8')
if type(ccn)==unicode: if type(ccn)==bytes:
ccn = ccn.encode('utf-8') ccn = ccn.encode('utf-8')
name = normalize_name(name) + '\x00' name = normalize_name(name) + '\x00'
@ -306,7 +305,7 @@ def gui_main():
self.status['text'] = u"Generating..." self.status['text'] = u"Generating..."
try: try:
userkey = generate_key(name, ccn) userkey = generate_key(name, ccn)
except Exception, e: except Exception as e:
self.status['text'] = u"Error: (0}".format(e.args[0]) self.status['text'] = u"Error: (0}".format(e.args[0])
return return
open(keypath,'wb').write(userkey) open(keypath,'wb').write(userkey)

@ -2,18 +2,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
from __future__ import absolute_import
from __future__ import print_function
# ineptepub.pyw, version 6.6 # ineptepub.pyw
# Copyright © 2009-2020 by Apprentice Harper et al. # Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# Original script by i♥cabbages
# Modified 20102013 by some_updates, DiapDealer and Apprentice Alf
# Modified 20152020 by Apprentice Harper et al.
# Revision history: # Revision history:
# 1 - Initial release # 1 - Initial release
@ -36,17 +31,16 @@ from __future__ import print_function
# 6.4 - Remove erroneous check on DER file sanity # 6.4 - Remove erroneous check on DER file sanity
# 6.5 - Completely remove erroneous check on DER file sanity # 6.5 - Completely remove erroneous check on DER file sanity
# 6.6 - Import tkFileDialog, don't assume something else will import it. # 6.6 - Import tkFileDialog, don't assume something else will import it.
# 6.7 - Add Python 3 compatibility while still working with Python 2.7 # 7.0 - Add Python 3 compatibility for calibre 5.0
""" """
Decrypt Adobe Digital Editions encrypted ePub books. Decrypt Adobe Digital Editions encrypted ePub books.
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "6.7" __version__ = "7.0"
import six import codecs
from six.moves import range
import sys import sys
import os import os
import traceback import traceback
@ -55,7 +49,6 @@ import zipfile
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing from contextlib import closing
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import base64
# Wrap a stream so that output gets flushed immediately # Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get # and also make sure that any unicode strings get
@ -67,7 +60,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,six.text_type): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -114,7 +107,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == six.text_type) else six.text_type(arg,argvencoding) for arg in sys.argv] return argv
class ADEPTError(Exception): class ADEPTError(Exception):
@ -418,9 +411,7 @@ def decryptBook(userkey, inpath, outpath):
if len(bookkey) != 172: if len(bookkey) != 172:
print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))) print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
return 1 return 1
bookkey = bookkey.encode('ascii') bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
bookkey = base64.b64decode(bookkey)
bookkey = rsa.decrypt(bookkey)
# Padded as per RSAES-PKCS1-v1_5 # Padded as per RSAES-PKCS1-v1_5
if bookkey[-17] != '\x00' and bookkey[-17] != 0: if bookkey[-17] != '\x00' and bookkey[-17] != 0:
print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))) print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
@ -486,79 +477,79 @@ def cli_main():
def gui_main(): def gui_main():
try: try:
import six.moves.tkinter import Tkinter
import six.moves.tkinter_constants import Tkconstants
import six.moves.tkinter_filedialog import tkFileDialog
import six.moves.tkinter_messagebox import tkMessageBox
import traceback import traceback
except: except:
return cli_main() return cli_main()
class DecryptionDialog(six.moves.tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
six.moves.tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = six.moves.tkinter.Label(self, text=u"Select files for decryption") self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=six.moves.tkinter_constants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = six.moves.tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=six.moves.tkinter_constants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = six.moves.tkinter_constants.E + six.moves.tkinter_constants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
six.moves.tkinter.Label(body, text=u"Key file").grid(row=0) Tkinter.Label(body, text=u"Key file").grid(row=0)
self.keypath = six.moves.tkinter.Entry(body, width=30) self.keypath = Tkinter.Entry(body, width=30)
self.keypath.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
if os.path.exists(u"adeptkey.der"): if os.path.exists(u"adeptkey.der"):
self.keypath.insert(0, u"adeptkey.der") self.keypath.insert(0, u"adeptkey.der")
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_keypath) button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=0, column=2) button.grid(row=0, column=2)
six.moves.tkinter.Label(body, text=u"Input file").grid(row=1) Tkinter.Label(body, text=u"Input file").grid(row=1)
self.inpath = six.moves.tkinter.Entry(body, width=30) self.inpath = Tkinter.Entry(body, width=30)
self.inpath.grid(row=1, column=1, sticky=sticky) self.inpath.grid(row=1, column=1, sticky=sticky)
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_inpath) button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
button.grid(row=1, column=2) button.grid(row=1, column=2)
six.moves.tkinter.Label(body, text=u"Output file").grid(row=2) Tkinter.Label(body, text=u"Output file").grid(row=2)
self.outpath = six.moves.tkinter.Entry(body, width=30) self.outpath = Tkinter.Entry(body, width=30)
self.outpath.grid(row=2, column=1, sticky=sticky) self.outpath.grid(row=2, column=1, sticky=sticky)
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_outpath) button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
button.grid(row=2, column=2) button.grid(row=2, column=2)
buttons = six.moves.tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = six.moves.tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Decrypt", width=10, command=self.decrypt) buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=six.moves.tkinter_constants.LEFT) botton.pack(side=Tkconstants.LEFT)
six.moves.tkinter.Frame(buttons, width=10).pack(side=six.moves.tkinter_constants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = six.moves.tkinter.Button( button = Tkinter.Button(
buttons, text=u"Quit", width=10, command=self.quit) buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=six.moves.tkinter_constants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = six.moves.tkinter_filedialog.askopenfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select Adobe Adept \'.der\' key file", parent=None, title=u"Select Adobe Adept \'.der\' key file",
defaultextension=u".der", defaultextension=u".der",
filetypes=[('Adobe Adept DER-encoded files', '.der'), filetypes=[('Adobe Adept DER-encoded files', '.der'),
('All Files', '.*')]) ('All Files', '.*')])
if keypath: if keypath:
keypath = os.path.normpath(keypath) keypath = os.path.normpath(keypath)
self.keypath.delete(0, six.moves.tkinter_constants.END) self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath) self.keypath.insert(0, keypath)
return return
def get_inpath(self): def get_inpath(self):
inpath = six.moves.tkinter_filedialog.askopenfilename( inpath = tkFileDialog.askopenfilename(
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt", parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if inpath: if inpath:
inpath = os.path.normpath(inpath) inpath = os.path.normpath(inpath)
self.inpath.delete(0, six.moves.tkinter_constants.END) self.inpath.delete(0, Tkconstants.END)
self.inpath.insert(0, inpath) self.inpath.insert(0, inpath)
return return
def get_outpath(self): def get_outpath(self):
outpath = six.moves.tkinter_filedialog.asksaveasfilename( outpath = tkFileDialog.asksaveasfilename(
parent=None, title=u"Select unencrypted ePub file to produce", parent=None, title=u"Select unencrypted ePub file to produce",
defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
if outpath: if outpath:
outpath = os.path.normpath(outpath) outpath = os.path.normpath(outpath)
self.outpath.delete(0, six.moves.tkinter_constants.END) self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath) self.outpath.insert(0, outpath)
return return
@ -590,11 +581,11 @@ def gui_main():
else: else:
self.status['text'] = u"The was an error decrypting the file." self.status['text'] = u"The was an error decrypting the file."
root = six.moves.tkinter.Tk() root = Tkinter.Tk()
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__)) root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False) root.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=six.moves.tkinter_constants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop() root.mainloop()
return 0 return 0

@ -4,13 +4,11 @@
from __future__ import with_statement from __future__ import with_statement
# ineptpdf.py # ineptpdf.py
# Copyright © 2009-2017 by Apprentice Harper et al. # Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf
# Modified 2015-2017 by Apprentice Harper et al.
# Revision history: # Revision history:
# 1 - Initial release # 1 - Initial release
@ -49,14 +47,14 @@ from __future__ import with_statement
# 8.0.4 - Completely remove erroneous check on DER file sanity # 8.0.4 - Completely remove erroneous check on DER file sanity
# 8.0.5 - Do not process DRM-free documents # 8.0.5 - Do not process DRM-free documents
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog # 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
# 9.0.0 - Add Python 3 compatibility for calibre 5
""" """
Decrypts Adobe ADEPT-encrypted PDF files. Decrypts Adobe ADEPT-encrypted PDF files.
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "8.0.6" __version__ = "9.0.0"
import sys import sys
import os import os
@ -64,8 +62,8 @@ import re
import zlib import zlib
import struct import struct
import hashlib import hashlib
from decimal import * from decimal import Decimal
from itertools import chain, islice import itertools
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
# Wrap a stream so that output gets flushed immediately # Wrap a stream so that output gets flushed immediately
@ -75,10 +73,10 @@ class SafeUnbuffered:
def __init__(self, stream): def __init__(self, stream):
self.stream = stream self.stream = stream
self.encoding = stream.encoding self.encoding = stream.encoding
if self.encoding == None: if self.encoding is None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -116,13 +114,13 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
return [u"ineptpdf.py"] return [u"ineptpdf.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding is None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return sys.argv
class ADEPTError(Exception): class ADEPTError(Exception):
@ -378,12 +376,12 @@ def _load_crypto_pycrypto():
class RSA(object): class RSA(object):
def __init__(self, der): def __init__(self, der):
key = ASN1Parser([ord(x) for x in der]) key = ASN1Parser([ord(x) for x in der])
key = [key.getChild(x).value for x in xrange(1, 4)] key = [key.getChild(x).value for x in range(1, 4)]
key = [self.bytesToNumber(v) for v in key] key = [self.bytesToNumber(v) for v in key]
self._rsa = _RSA.construct(key) self._rsa = _RSA.construct(key)
def bytesToNumber(self, bytes): def bytesToNumber(self, bytes):
total = 0L total = 0
for byte in bytes: for byte in bytes:
total = (total << 8) + byte total = (total << 8) + byte
return total return total
@ -411,7 +409,10 @@ ARC4, RSA, AES = _load_crypto()
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO try:
from StringIO import StringIO
except ImportError:
from io import StringIO
# Do we generate cross reference streams on output? # Do we generate cross reference streams on output?
@ -444,11 +445,11 @@ def nunpack(s, default=0):
if not l: if not l:
return default return default
elif l == 1: elif l == 1:
return ord(s) return s
elif l == 2: elif l == 2:
return struct.unpack('>H', s)[0] return struct.unpack('>H', s)[0]
elif l == 3: elif l == 3:
return struct.unpack('>L', '\x00'+s)[0] return struct.unpack('>L', b'\x00'+s)[0]
elif l == 4: elif l == 4:
return struct.unpack('>L', s)[0] return struct.unpack('>L', s)[0]
else: else:
@ -487,9 +488,9 @@ class PSLiteral(PSObject):
name = [] name = []
for char in self.name: for char in self.name:
if not char.isalnum(): if not char.isalnum():
char = '#%02x' % ord(char) char = b'#%02x' % char
name.append(char) name.append(char)
return '/%s' % ''.join(name) return b'/%s' % ''.join(name)
# PSKeyword # PSKeyword
class PSKeyword(PSObject): class PSKeyword(PSObject):
@ -716,7 +717,7 @@ class PSBaseParser(object):
except ValueError: except ValueError:
pass pass
return (self.parse_main, j) return (self.parse_main, j)
def parse_decimal(self, s, i): def parse_decimal(self, s, i):
m = END_NUMBER.search(s, i) m = END_NUMBER.search(s, i)
if not m: if not m:
@ -949,7 +950,7 @@ class PSStackParser(PSBaseParser):
try: try:
(pos, objs) = self.end_type('d') (pos, objs) = self.end_type('d')
if len(objs) % 2 != 0: if len(objs) % 2 != 0:
print "Incomplete dictionary construct" print("Incomplete dictionary construct")
objs.append("") # this isn't necessary. objs.append("") # this isn't necessary.
# temporary fix. is this due to rental books? # temporary fix. is this due to rental books?
# raise PSSyntaxError( # raise PSSyntaxError(
@ -1106,18 +1107,18 @@ def stream_value(x):
# ascii85decode(data) # ascii85decode(data)
def ascii85decode(data): def ascii85decode(data):
n = b = 0 n = b = 0
out = '' out = b''
for c in data: for c in data:
if '!' <= c and c <= 'u': if b'!' <= c and c <= b'u':
n += 1 n += 1
b = b*85+(ord(c)-33) b = b*85+(c-33)
if n == 5: if n == 5:
out += struct.pack('>L',b) out += struct.pack('>L',b)
n = b = 0 n = b = 0
elif c == 'z': elif c == b'z':
assert n == 0 assert n == 0
out += '\0\0\0\0' out += b'\0\0\0\0'
elif c == '~': elif c == b'~':
if n: if n:
for _ in range(5-n): for _ in range(5-n):
b = b*85+84 b = b*85+84
@ -1138,7 +1139,7 @@ class PDFStream(PDFObject):
cutdiv = len(rawdata) // 16 cutdiv = len(rawdata) // 16
rawdata = rawdata[:16*cutdiv] rawdata = rawdata[:16*cutdiv]
else: else:
if eol in ('\r', '\n', '\r\n'): if eol in (b'\r', b'\n', b'\r\n'):
rawdata = rawdata[:length] rawdata = rawdata[:length]
self.dic = dic self.dic = dic
@ -1206,14 +1207,14 @@ class PDFStream(PDFObject):
raise PDFValueError( raise PDFValueError(
'Columns undefined for predictor=12') 'Columns undefined for predictor=12')
columns = int_value(params['Columns']) columns = int_value(params['Columns'])
buf = '' buf = b''
ent0 = '\x00' * columns ent0 = b'\x00' * columns
for i in xrange(0, len(data), columns+1): for i in range(0, len(data), columns+1):
pred = data[i] pred = data[i]
ent1 = data[i+1:i+1+columns] ent1 = data[i+1:i+1+columns]
if pred == '\x02': if pred == b'\x02':
ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \ ent1 = ''.join(bytes([(a+b) & 255]) \
for (a,b) in zip(ent0,ent1)) for (a,b) in zip(ent0,ent1))
buf += ent1 buf += ent1
ent0 = ent1 ent0 = ent1
data = buf data = buf
@ -1290,7 +1291,7 @@ class PDFXRef(object):
(start, nobjs) = map(int, f) (start, nobjs) = map(int, f)
except ValueError: except ValueError:
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line)) raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
for objid in xrange(start, start+nobjs): for objid in range(start, start+nobjs):
try: try:
(_, line) = parser.nextline() (_, line) = parser.nextline()
except PSEOF: except PSEOF:
@ -1342,7 +1343,7 @@ class PDFXRefStream(object):
def objids(self): def objids(self):
for first, size in self.index: for first, size in self.index:
for objid in xrange(first, first + size): for objid in range(first, first + size):
yield objid yield objid
def load(self, parser, debug=0): def load(self, parser, debug=0):
@ -1355,8 +1356,8 @@ class PDFXRefStream(object):
raise PDFNoValidXRef('Invalid PDF stream spec.') raise PDFNoValidXRef('Invalid PDF stream spec.')
size = stream.dic['Size'] size = stream.dic['Size']
index = stream.dic.get('Index', (0,size)) index = stream.dic.get('Index', (0,size))
self.index = zip(islice(index, 0, None, 2), self.index = zip(itertools.islice(index, 0, None, 2),
islice(index, 1, None, 2)) itertools.islice(index, 1, None, 2))
(self.fl1, self.fl2, self.fl3) = stream.dic['W'] (self.fl1, self.fl2, self.fl3) = stream.dic['W']
self.data = stream.get_data() self.data = stream.get_data()
self.entlen = self.fl1+self.fl2+self.fl3 self.entlen = self.fl1+self.fl2+self.fl3
@ -1506,10 +1507,10 @@ class PDFDocument(object):
if plaintext[-16:] != 16 * chr(16): if plaintext[-16:] != 16 * chr(16):
raise ADEPTError('Offlinekey cannot be decrypted, aborting ...') raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol) pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16: if pdrlpol[-1] < 1 or pdrlpol[-1] > 16:
raise ADEPTError('Could not decrypt PDRLPol, aborting ...') raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
else: else:
cutter = -1 * ord(pdrlpol[-1]) cutter = -1 * pdrlpol[-1]
pdrlpol = pdrlpol[:cutter] pdrlpol = pdrlpol[:cutter]
return plaintext[:16] return plaintext[:16]
@ -1551,7 +1552,7 @@ class PDFDocument(object):
hash.update('ffffffff'.decode('hex')) hash.update('ffffffff'.decode('hex'))
if 5 <= R: if 5 <= R:
# 8 # 8
for _ in xrange(50): for _ in range(50):
hash = hashlib.md5(hash.digest()[:length/8]) hash = hashlib.md5(hash.digest()[:length/8])
key = hash.digest()[:length/8] key = hash.digest()[:length/8]
if R == 2: if R == 2:
@ -1562,8 +1563,8 @@ class PDFDocument(object):
hash = hashlib.md5(self.PASSWORD_PADDING) # 2 hash = hashlib.md5(self.PASSWORD_PADDING) # 2
hash.update(docid[0]) # 3 hash.update(docid[0]) # 3
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4 x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
for i in xrange(1,19+1): for i in range(1,19+1):
k = ''.join( chr(ord(c) ^ i) for c in key ) k = ''.join(bytes([c ^ i]) for c in key )
x = ARC4.new(k).decrypt(x) x = ARC4.new(k).decrypt(x)
u1 = x+x # 32bytes total u1 = x+x # 32bytes total
if R == 2: if R == 2:
@ -1585,9 +1586,9 @@ class PDFDocument(object):
if V != 4: if V != 4:
self.decipher = self.decipher_rc4 # XXX may be AES self.decipher = self.decipher_rc4 # XXX may be AES
# aes # aes
elif V == 4 and Length == 128: elif V == 4 and length == 128:
elf.decipher = self.decipher_aes self.decipher = self.decipher_aes
elif V == 4 and Length == 256: elif V == 4 and length == 256:
raise PDFNotImplementedError('AES256 encryption is currently unsupported') raise PDFNotImplementedError('AES256 encryption is currently unsupported')
self.ready = True self.ready = True
return return
@ -1616,18 +1617,18 @@ class PDFDocument(object):
else: else:
V = 2 V = 2
elif len(bookkey) == length + 1: elif len(bookkey) == length + 1:
V = ord(bookkey[0]) V = bookkey[0]
bookkey = bookkey[1:] bookkey = bookkey[1:]
else: else:
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type) print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
print "length is %d and len(bookkey) is %d" % (length, len(bookkey)) print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
print "bookkey[0] is %d" % ord(bookkey[0]) print("bookkey[0] is %d" % bookkey[0])
raise ADEPTError('error decrypting book session key - mismatched length') raise ADEPTError('error decrypting book session key - mismatched length')
else: else:
# proper length unknown try with whatever you have # proper length unknown try with whatever you have
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type) print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
print "length is %d and len(bookkey) is %d" % (length, len(bookkey)) print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
print "bookkey[0] is %d" % ord(bookkey[0]) print("bookkey[0] is %d" % bookkey[0])
if ebx_V == 3: if ebx_V == 3:
V = 3 V = 3
else: else:
@ -1671,7 +1672,7 @@ class PDFDocument(object):
data = data[16:] data = data[16:]
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data) plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
# remove pkcs#5 aes padding # remove pkcs#5 aes padding
cutter = -1 * ord(plaintext[-1]) cutter = -1 * plaintext[-1]
#print cutter #print cutter
plaintext = plaintext[:cutter] plaintext = plaintext[:cutter]
return plaintext return plaintext
@ -1682,7 +1683,7 @@ class PDFDocument(object):
data = data[16:] data = data[16:]
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data) plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
# remove pkcs#5 aes padding # remove pkcs#5 aes padding
cutter = -1 * ord(plaintext[-1]) cutter = -1 * plaintext[-1]
#print cutter #print cutter
plaintext = plaintext[:cutter] plaintext = plaintext[:cutter]
return plaintext return plaintext
@ -2026,7 +2027,7 @@ class PDFSerializer(object):
if not gen_xref_stm: if not gen_xref_stm:
self.write('xref\n') self.write('xref\n')
self.write('0 %d\n' % (maxobj + 1,)) self.write('0 %d\n' % (maxobj + 1,))
for objid in xrange(0, maxobj + 1): for objid in range(0, maxobj + 1):
if objid in xrefs: if objid in xrefs:
# force the genno to be 0 # force the genno to be 0
self.write("%010d 00000 n \n" % xrefs[objid][0]) self.write("%010d 00000 n \n" % xrefs[objid][0])
@ -2135,7 +2136,7 @@ class PDFSerializer(object):
if self.last.isalnum(): if self.last.isalnum():
self.write(' ') self.write(' ')
self.write(str(obj).lower()) self.write(str(obj).lower())
elif isinstance(obj, (int, long)): elif isinstance(obj, int):
if self.last.isalnum(): if self.last.isalnum():
self.write(' ') self.write(' ')
self.write(str(obj)) self.write(str(obj))
@ -2189,8 +2190,8 @@ def decryptBook(userkey, inpath, outpath):
# help construct to make sure the method runs to the end # help construct to make sure the method runs to the end
try: try:
serializer.dump(outf) serializer.dump(outf)
except Exception, e: except Exception as e:
print u"error writing pdf: {0}".format(e.args[0]) print(u"error writing pdf: {0}".format(e.args[0]))
return 2 return 2
return 0 return 0
@ -2201,13 +2202,13 @@ def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname) print(u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname))
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
if result == 0: if result == 0:
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)) print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
return result return result
@ -2309,7 +2310,7 @@ def gui_main():
self.status['text'] = u"Decrypting..." self.status['text'] = u"Decrypting..."
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception as e:
self.status['text'] = u"Error; {0}".format(e.args[0]) self.status['text'] = u"Error; {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:

@ -7,7 +7,7 @@ from __future__ import with_statement
# Copyright © 2013-2020 Apprentice Harper et al. # Copyright © 2013-2020 Apprentice Harper et al.
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '2.0' __version__ = '3.0'
# Revision history: # Revision history:
# Pascal implementation by lulzkabulz. # Pascal implementation by lulzkabulz.
@ -17,7 +17,7 @@ __version__ = '2.0'
# 1.2 - Added pylzma import fallback # 1.2 - Added pylzma import fallback
# 1.3 - Fixed lzma support for calibre 4.6+ # 1.3 - Fixed lzma support for calibre 4.6+
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya. # 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
# 3.0 - Added Python 3 compatibility for calibre 5.0
""" """
Decrypt Kindle KFX files. Decrypt Kindle KFX files.
@ -33,7 +33,10 @@ import struct
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.Util.py3compat import bchr, bord from Crypto.Util.py3compat import bchr, bord

@ -4,10 +4,10 @@
from __future__ import with_statement from __future__ import with_statement
# k4mobidedrm.py # k4mobidedrm.py
# Copyright © 2008-2019 by Apprentice Harper et al. # Copyright © 2008-2020 by Apprentice Harper et al.
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '5.7' __version__ = '6.0'
# Engine to remove drm from Kindle and Mobipocket ebooks # Engine to remove drm from Kindle and Mobipocket ebooks
# for personal use for archiving and converting your ebooks # for personal use for archiving and converting your ebooks
@ -62,6 +62,8 @@ __version__ = '5.7'
# 5.5 - Added GPL v3 licence explicitly. # 5.5 - Added GPL v3 licence explicitly.
# 5.6 - Invoke KFXZipBook to handle zipped KFX files # 5.6 - Invoke KFXZipBook to handle zipped KFX files
# 5.7 - Revamp cleanup_name # 5.7 - Revamp cleanup_name
# 6.0 - Added Python 3 compatibility for calibre 5.0
import sys, os, re import sys, os, re
import csv import csv
@ -69,7 +71,7 @@ import getopt
import re import re
import traceback import traceback
import time import time
import htmlentitydefs import html.entities
import json import json
class DrmException(Exception): class DrmException(Exception):
@ -103,7 +105,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -141,7 +143,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return [u"mobidedrm.py"]
@ -149,7 +151,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
# cleanup unicode filenames # cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py # borrowed from calibre from calibre/src/calibre/__init__.py
@ -161,7 +163,7 @@ def cleanup_name(name):
# substitute filename unfriendly characters # substitute filename unfriendly characters
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"") name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" ").replace(u": ",u" ").replace(u":",u"").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
name = re.sub(ur"\s", u" ", name).strip() name = re.sub(r"\s", u" ", name).strip()
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# delete non-ascii characters # delete non-ascii characters
@ -184,19 +186,19 @@ def unescape(text):
# character reference # character reference
try: try:
if text[:3] == u"&#x": if text[:3] == u"&#x":
return unichr(int(text[3:-1], 16)) return chr(int(text[3:-1], 16))
else: else:
return unichr(int(text[2:-1])) return chr(int(text[2:-1]))
except ValueError: except ValueError:
pass pass
else: else:
# named entity # named entity
try: try:
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) text = chr(htmlentitydefs.name2codepoint[text[1:-1]])
except KeyError: except KeyError:
pass pass
return text # leave as is return text # leave as is
return re.sub(u"&#?\w+;", fixup, text) return re.sub(u"&#?\\w+;", fixup, text)
def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()): def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
# handle the obvious cases at the beginning # handle the obvious cases at the beginning
@ -220,7 +222,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb = topazextract.TopazBook(infile) mb = topazextract.TopazBook(infile)
bookname = unescape(mb.getBookTitle()) bookname = unescape(mb.getBookTitle())
print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()) print(u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
# copy list of pids # copy list of pids
totalpids = list(pids) totalpids = list(pids)
@ -232,7 +234,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases)) totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
# remove any duplicates # remove any duplicates
totalpids = list(set(totalpids)) totalpids = list(set(totalpids))
print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)) print(u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)))
#print totalpids #print totalpids
try: try:
@ -241,7 +243,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
mb.cleanup mb.cleanup
raise raise
print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime) print(u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime))
return mb return mb
@ -255,16 +257,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
with open(dbfile, 'r') as keyfilein: with open(dbfile, 'r') as keyfilein:
kindleDatabase = json.loads(keyfilein.read()) kindleDatabase = json.loads(keyfilein.read())
kDatabases.append([dbfile,kindleDatabase]) kDatabases.append([dbfile,kindleDatabase])
except Exception, e: except Exception as e:
print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e) print(u"Error getting database from file {0:s}: {1:s}".format(dbfile,e))
traceback.print_exc() traceback.print_exc()
try: try:
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime) book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
except Exception, e: except Exception as e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) print(u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime))
traceback.print_exc() traceback.print_exc()
return 1 return 1
@ -287,12 +289,12 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
outfile = os.path.join(outdir, outfilename + book.getBookExtension()) outfile = os.path.join(outdir, outfilename + book.getBookExtension())
book.getFile(outfile) book.getFile(outfile)
print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) print(u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
if book.getBookType()==u"Topaz": if book.getBookType()==u"Topaz":
zipname = os.path.join(outdir, outfilename + u"_SVG.zip") zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
book.getSVGZip(zipname) book.getSVGZip(zipname)
print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename) print(u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
book.cleanup() book.cleanup()
@ -300,9 +302,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
def usage(progname): def usage(progname):
print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks" print(u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
print u"Usage:" print(u"Usage:")
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname) print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname))
# #
# Main # Main
@ -310,12 +312,12 @@ def usage(progname):
def cli_main(): def cli_main():
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__) print(u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "k:p:s:a:") opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
except getopt.GetoptError, err: except getopt.GetoptError as err:
print u"Error in options or arguments: {0}".format(err.args[0]) print(u"Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
if len(args)<2: if len(args)<2:

@ -6,6 +6,9 @@ from __future__ import print_function
# Engine to remove drm from Kindle KFX ebooks # Engine to remove drm from Kindle KFX ebooks
# 2.0 - Added Python 3 compatibility for calibre 5.0
import os import os
import shutil import shutil
import zipfile import zipfile
@ -13,7 +16,10 @@ import zipfile
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO try:
from StringIO import StringIO
except ImportError:
from io import StringIO
try: try:
from calibre_plugins.dedrm import ion from calibre_plugins.dedrm import ion
@ -22,7 +28,7 @@ except ImportError:
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '1.0' __version__ = '2.0'
class KFXZipBook: class KFXZipBook:

@ -5,15 +5,17 @@ from __future__ import with_statement
from __future__ import print_function from __future__ import print_function
# kgenpids.py # kgenpids.py
# Copyright © 2008-2017 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '2.1' __version__ = '3.0'
# Revision history: # Revision history:
# 2.0 - Fix for non-ascii Windows user names # 2.0 - Fix for non-ascii Windows user names
# 2.1 - Actual fix for non-ascii WIndows user names. # 2.1 - Actual fix for non-ascii WIndows user names.
# x.x - Return information needed for KFX decryption # 2.2 - Return information needed for KFX decryption
# 3.0 - Added Python 3 compatibility for calibre 5.0
import sys import sys
import os, csv import os, csv
@ -31,9 +33,9 @@ global charMap3
global charMap4 global charMap4
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' charMap3 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' charMap4 = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
# crypto digestroutines # crypto digestroutines
import hashlib import hashlib
@ -84,7 +86,7 @@ def decode(data,map):
def getTwoBitsFromBitField(bitField,offset): def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4 byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4) bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3 return bitField[byteNumber] >> bitPosition & 3
# Returns the six bits at offset from a bit field # Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset): def getSixBitsFromBitField(bitField,offset):
@ -95,9 +97,9 @@ def getSixBitsFromBitField(bitField,offset):
# 8 bits to six bits encoding from hash to generate PID string # 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash): def encodePID(hash):
global charMap3 global charMap3
PID = '' PID = b''
for position in range (0,8): for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)] PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]])
return PID return PID
# Encryption table used to generate the device PID # Encryption table used to generate the device PID
@ -126,7 +128,7 @@ def generatePidSeed(table,dsn) :
def generateDevicePID(table,dsn,nbRoll): def generateDevicePID(table,dsn,nbRoll):
global charMap4 global charMap4
seed = generatePidSeed(table,dsn) seed = generatePidSeed(table,dsn)
pidAscii = '' pidAscii = b''
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF] pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0 index = 0
for counter in range (0,nbRoll): for counter in range (0,nbRoll):
@ -134,7 +136,7 @@ def generateDevicePID(table,dsn,nbRoll):
index = (index+1) %8 index = (index+1) %8
for counter in range (0,8): for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index] pidAscii += bytes([charMap4[index]])
return pidAscii return pidAscii
def crc32(s): def crc32(s):
@ -150,7 +152,7 @@ def checksumPid(s):
for i in (0,1): for i in (0,1):
b = crc & 0xff b = crc & 0xff
pos = (b // l) ^ (b % l) pos = (b // l) ^ (b % l)
res += charMap4[pos%l] res += bytes([charMap4[pos%l]])
crc >>= 8 crc >>= 8
return res return res
@ -160,15 +162,15 @@ def pidFromSerial(s, l):
global charMap4 global charMap4
crc = crc32(s) crc = crc32(s)
arr1 = [0]*l arr1 = [0]*l
for i in xrange(len(s)): for i in range(len(s)):
arr1[i%l] ^= ord(s[i]) arr1[i%l] ^= s[i]
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
for i in xrange(l): for i in range(l):
arr1[i] ^= crc_bytes[i&3] arr1[i] ^= crc_bytes[i&3]
pid = "" pid = b""
for i in xrange(l): for i in range(l):
b = arr1[i] & 0xff b = arr1[i] & 0xff
pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] pid += bytes([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]])
return pid return pid
@ -179,7 +181,7 @@ def getKindlePids(rec209, token, serialnum):
pids=[] pids=[]
if isinstance(serialnum,unicode): if isinstance(serialnum,str):
serialnum = serialnum.encode('utf-8') serialnum = serialnum.encode('utf-8')
# Compute book PID # Compute book PID
@ -189,7 +191,7 @@ def getKindlePids(rec209, token, serialnum):
pids.append(bookPID) pids.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well # compute fixed pid for old pre 2.5 firmware update pid as well
kindlePID = pidFromSerial(serialnum, 7) + "*" kindlePID = pidFromSerial(serialnum, 7) + b"*"
kindlePID = checksumPid(kindlePID) kindlePID = checksumPid(kindlePID)
pids.append(kindlePID) pids.append(kindlePID)
@ -206,7 +208,7 @@ def getK4Pids(rec209, token, kindleDatabase):
try: try:
# Get the kindle account token, if present # Get the kindle account token, if present
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex') kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens']).decode()
except KeyError: except KeyError:
kindleAccountToken="" kindleAccountToken=""
@ -214,30 +216,30 @@ def getK4Pids(rec209, token, kindleDatabase):
try: try:
# Get the DSN token, if present # Get the DSN token, if present
DSN = (kindleDatabase[1])['DSN'].decode('hex') DSN = bytearray.fromhex((kindleDatabase[1])['DSN']).decode()
print(u"Got DSN key from database {0}".format(kindleDatabase[0])) print(u"Got DSN key from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# See if we have the info to generate the DSN # See if we have the info to generate the DSN
try: try:
# Get the Mazama Random number # Get the Mazama Random number
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex') MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber']).decode()
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0]) #print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
try: try:
# Get the SerialNumber token, if present # Get the SerialNumber token, if present
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex') IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber']).decode()
print(u"Got SerialNumber from database {0}".format(kindleDatabase[0])) print(u"Got SerialNumber from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# Get the IDString we added # Get the IDString we added
IDString = (kindleDatabase[1])['IDString'].decode('hex') IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode()
try: try:
# Get the UsernameHash token, if present # Get the UsernameHash token, if present
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex') encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash']).decode()
print(u"Got UsernameHash from database {0}".format(kindleDatabase[0])) print(u"Got UsernameHash from database {0}".format(kindleDatabase[0]))
except KeyError: except KeyError:
# Get the UserName we added # Get the UserName we added
UserName = (kindleDatabase[1])['UserName'].decode('hex') UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode()
# encode it # encode it
encodedUsername = encodeHash(UserName,charMap1) encodedUsername = encodeHash(UserName,charMap1)
#print u"encodedUsername",encodedUsername.encode('hex') #print u"encodedUsername",encodedUsername.encode('hex')
@ -267,19 +269,19 @@ def getK4Pids(rec209, token, kindleDatabase):
# Compute book PIDs # Compute book PIDs
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN.encode()+kindleAccountToken.encode()+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID) bookPID = checksumPid(bookPID)
pids.append(bookPID) pids.append(bookPID)
# variant 1 # variant 1
pidHash = SHA1(kindleAccountToken+rec209+token) pidHash = SHA1(kindleAccountToken.encode()+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID) bookPID = checksumPid(bookPID)
pids.append(bookPID) pids.append(bookPID)
# variant 2 # variant 2
pidHash = SHA1(DSN+rec209+token) pidHash = SHA1(DSN.encode()+rec209+token)
bookPID = encodePID(pidHash) bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID) bookPID = checksumPid(bookPID)
pids.append(bookPID) pids.append(bookPID)
@ -297,14 +299,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
for kDatabase in kDatabases: for kDatabase in kDatabases:
try: try:
pidlst.extend(getK4Pids(md1, md2, kDatabase)) pidlst.extend(getK4Pids(md1, md2, kDatabase))
except Exception, e: except Exception as e:
print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0])) print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
traceback.print_exc() traceback.print_exc()
for serialnum in serials: for serialnum in serials:
try: try:
pidlst.extend(getKindlePids(md1, md2, serialnum)) pidlst.extend(getKindlePids(md1, md2, serialnum))
except Exception, e: except Exception as e:
print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])) print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
traceback.print_exc() traceback.print_exc()

@ -7,7 +7,7 @@ from __future__ import with_statement
# Copyright © 2008-2020 Apprentice Harper et al. # Copyright © 2008-2020 Apprentice Harper et al.
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = '2.7' __version__ = '3.0'
# Revision history: # Revision history:
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc. # 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
@ -30,6 +30,7 @@ __version__ = '2.7'
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus # 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file # 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya # 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
# 3.0 - Added Python 3 compatibility for calibre 5.0
""" """
@ -37,7 +38,7 @@ Retrieve Kindle for PC/Mac user key.
""" """
import sys, os, re import sys, os, re
from struct import pack, unpack from struct import pack, unpack, unpack_from
import json import json
import getopt import getopt
@ -108,7 +109,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return arg
class DrmException(Exception): class DrmException(Exception):
pass pass
@ -299,7 +300,7 @@ if iswindows:
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
if more == None: # no more calls to decrypt, should have all the data if more == None: # no more calls to decrypt, should have all the data
if numExtraBytes != 0: if numExtraBytes != 0:
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
# hold back some bytes in case last decrypt has zero len # hold back some bytes in case last decrypt has zero len
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
@ -341,7 +342,7 @@ if iswindows:
def removePad(self, paddedBinaryString, blockSize): def removePad(self, paddedBinaryString, blockSize):
""" Remove padding from a binary string """ """ Remove padding from a binary string """
if not(0<len(paddedBinaryString)): if not(0<len(paddedBinaryString)):
raise DecryptNotBlockAlignedError, 'Expected More Data' raise DecryptNotBlockAlignedError('Expected More Data')
return paddedBinaryString[:-ord(paddedBinaryString[-1])] return paddedBinaryString[:-ord(paddedBinaryString[-1])]
class noPadding(Pad): class noPadding(Pad):
@ -649,7 +650,7 @@ if iswindows:
def __init__(self, key = None, padding = padWithPadLen(), keySize=16): def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
""" Initialize AES, keySize is in bytes """ """ Initialize AES, keySize is in bytes """
if not (keySize == 16 or keySize == 24 or keySize == 32) : if not (keySize == 16 or keySize == 24 or keySize == 32) :
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
@ -935,7 +936,6 @@ if iswindows:
# Returns Environmental Variables that contain unicode # Returns Environmental Variables that contain unicode
def getEnvironmentVariable(name): def getEnvironmentVariable(name):
import ctypes import ctypes
name = unicode(name) # make sure string argument is unicode
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0) n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
if n == 0: if n == 0:
return None return None
@ -1166,9 +1166,9 @@ if iswindows:
# store values used in decryption # store values used in decryption
DB['IDString'] = GetIDString() DB['IDString'] = GetIDString()
DB['UserName'] = GetUserName() DB['UserName'] = GetUserName()
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex')) print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex')))
else: else:
print u"Couldn't decrypt file." print(u"Couldn't decrypt file.")
DB = {} DB = {}
return DB return DB
elif isosx: elif isosx:
@ -1649,11 +1649,11 @@ elif isosx:
pass pass
if len(DB)>6: if len(DB)>6:
# store values used in decryption # store values used in decryption
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()) print(u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName()))
DB['IDString'] = IDString DB['IDString'] = IDString
DB['UserName'] = GetUserName() DB['UserName'] = GetUserName()
else: else:
print u"Couldn't decrypt file." print(u"Couldn't decrypt file.")
DB = {} DB = {}
return DB return DB
else: else:
@ -1683,7 +1683,7 @@ def getkey(outpath, files=[]):
outfile = outpath outfile = outpath
with file(outfile, 'w') as keyfileout: with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(keys[0])) keyfileout.write(json.dumps(keys[0]))
print u"Saved a key to {0}".format(outfile) print(u"Saved a key to {0}".format(outfile))
else: else:
keycount = 0 keycount = 0
for key in keys: for key in keys:
@ -1694,16 +1694,16 @@ def getkey(outpath, files=[]):
break break
with file(outfile, 'w') as keyfileout: with file(outfile, 'w') as keyfileout:
keyfileout.write(json.dumps(key)) keyfileout.write(json.dumps(key))
print u"Saved a key to {0}".format(outfile) print(u"Saved a key to {0}".format(outfile))
return True return True
return False return False
def usage(progname): def usage(progname):
print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys." print(u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
print u"Keys are saved to the current directory, or a specified output directory." print(u"Keys are saved to the current directory, or a specified output directory.")
print u"If a file name is passed instead of a directory, only the first key is saved, in that file." print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
print u"Usage:" print(u"Usage:")
print u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname) print(u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
def cli_main(): def cli_main():
@ -1711,12 +1711,12 @@ def cli_main():
sys.stderr=SafeUnbuffered(sys.stderr) sys.stderr=SafeUnbuffered(sys.stderr)
argv=unicode_argv() argv=unicode_argv()
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
print u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__) print(u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
try: try:
opts, args = getopt.getopt(argv[1:], "hk:") opts, args = getopt.getopt(argv[1:], "hk:")
except getopt.GetoptError, err: except getopt.GetoptError as err:
print u"Error in options or arguments: {0}".format(err.args[0]) print(u"Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
sys.exit(2) sys.exit(2)
@ -1745,7 +1745,7 @@ def cli_main():
outpath = os.path.realpath(os.path.normpath(outpath)) outpath = os.path.realpath(os.path.normpath(outpath))
if not getkey(outpath, files): if not getkey(outpath, files):
print u"Could not retrieve Kindle for Mac/PC key." print(u"Could not retrieve Kindle for Mac/PC key.")
return 0 return 0
@ -1789,7 +1789,7 @@ def gui_main():
keyfileout.write(json.dumps(key)) keyfileout.write(json.dumps(key))
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
except DrmException, e: except DrmException as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')

@ -10,6 +10,7 @@
# 0.3 updated for unicode # 0.3 updated for unicode
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs. # 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility # 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
# 1.0 Added Python 3 compatibility for calibre 5.0
from __future__ import print_function from __future__ import print_function
import sys import sys
@ -25,7 +26,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -63,7 +64,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"kindlepid.py"] return [u"kindlepid.py"]
@ -71,11 +72,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = "utf-8" argvencoding = "utf-8"
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return sys.argv
if sys.hexversion >= 0x3000000:
print('This script is incompatible with Python 3.x. Please install Python 2.7.x.')
sys.exit(2)
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
@ -83,7 +80,7 @@ def crc32(s):
return (~binascii.crc32(s,-1))&0xFFFFFFFF return (~binascii.crc32(s,-1))&0xFFFFFFFF
def checksumPid(s): def checksumPid(s):
crc = crc32(s) crc = crc32(s.encode('ascii'))
crc = crc ^ (crc >> 16) crc = crc ^ (crc >> 16)
res = s res = s
l = len(letters) l = len(letters)
@ -99,15 +96,15 @@ def pidFromSerial(s, l):
crc = crc32(s) crc = crc32(s)
arr1 = [0]*l arr1 = [0]*l
for i in xrange(len(s)): for i in range(len(s)):
arr1[i%l] ^= ord(s[i]) arr1[i%l] ^= s[i]
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff] crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
for i in xrange(l): for i in range(l):
arr1[i] ^= crc_bytes[i&3] arr1[i] ^= crc_bytes[i&3]
pid = '' pid = ''
for i in xrange(l): for i in range(l):
b = arr1[i] & 0xff b = arr1[i] & 0xff
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
@ -140,6 +137,6 @@ def cli_main():
if __name__ == "__main__": if __name__ == "__main__":
sys.stdout=SafeUnbuffered(sys.stdout) #sys.stdout=SafeUnbuffered(sys.stdout)
sys.stderr=SafeUnbuffered(sys.stderr) #sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main()) sys.exit(cli_main())

@ -3,11 +3,11 @@
# mobidedrm.py # mobidedrm.py
# Copyright © 2008 The Dark Reverser # Copyright © 2008 The Dark Reverser
# Portions © 20082017 Apprentice Harper et al. # Portions © 20082020 Apprentice Harper et al.
from __future__ import print_function from __future__ import print_function
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = u"0.42" __version__ = u"1.00"
# This is a python script. You need a Python interpreter to run it. # This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows. # For example, ActiveState Python, which exists for windows.
@ -73,6 +73,7 @@ __version__ = u"0.42"
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility # 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 0.41 - Fixed potential unicode problem in command line calls # 0.41 - Fixed potential unicode problem in command line calls
# 0.42 - Added GPL v3 licence. updated/removed some print statements # 0.42 - Added GPL v3 licence. updated/removed some print statements
# 3.00 - Added Python 3 compatibility for calibre 5.0
import sys import sys
import os import os
@ -93,7 +94,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -131,7 +132,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return [u"mobidedrm.py"]
@ -139,7 +140,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = 'utf-8' argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return sys.argv
class DrmException(Exception): class DrmException(Exception):
@ -153,12 +154,12 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
# if we can get it from alfcrypto, use that # if we can get it from alfcrypto, use that
try: #try:
return Pukall_Cipher().PC1(key,src,decryption) # return Pukall_Cipher().PC1(key,src,decryption)
except NameError: #except NameError:
pass # pass
except TypeError: #except TypeError:
pass # pass
# use slow python version, since Pukall_Cipher didn't load # use slow python version, since Pukall_Cipher didn't load
sum1 = 0; sum1 = 0;
@ -167,28 +168,28 @@ def PC1(key, src, decryption=True):
if len(key)!=16: if len(key)!=16:
DrmException (u"PC1: Bad key length") DrmException (u"PC1: Bad key length")
wkey = [] wkey = []
for i in xrange(8): for i in range(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(key[i*2]<<8 | key[i*2+1])
dst = "" dst = b''
for i in xrange(len(src)): for i in range(len(src)):
temp1 = 0; temp1 = 0;
byteXorVal = 0; byteXorVal = 0;
for j in xrange(8): for j in range(8):
temp1 ^= wkey[j] temp1 ^= wkey[j]
sum2 = (sum2+j)*20021 + sum1 sum2 = (sum2+j)*20021 + sum1
sum1 = (temp1*346)&0xFFFF sum1 = (temp1*346)&0xFFFF
sum2 = (sum2+sum1)&0xFFFF sum2 = (sum2+sum1)&0xFFFF
temp1 = (temp1*20021+1)&0xFFFF temp1 = (temp1*20021+1)&0xFFFF
byteXorVal ^= temp1 ^ sum2 byteXorVal ^= temp1 ^ sum2
curByte = ord(src[i]) curByte = src[i]
if not decryption: if not decryption:
keyXorVal = curByte * 257; keyXorVal = curByte * 257;
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
if decryption: if decryption:
keyXorVal = curByte * 257; keyXorVal = curByte * 257;
for j in xrange(8): for j in range(8):
wkey[j] ^= keyXorVal; wkey[j] ^= keyXorVal;
dst+=chr(curByte) dst+=bytes([curByte])
return dst return dst
def checksumPid(s): def checksumPid(s):
@ -200,7 +201,7 @@ def checksumPid(s):
for i in (0,1): for i in (0,1):
b = crc & 0xff b = crc & 0xff
pos = (b // l) ^ (b % l) pos = (b // l) ^ (b % l)
res += letters[pos%l] res += letters[pos%l].encode('ascii')
crc >>= 8 crc >>= 8
return res return res
@ -210,7 +211,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
if size <= 0: if size <= 0:
return result return result
while True: while True:
v = ord(ptr[size-1]) v = ptr[size-1]
result |= (v & 0x7F) << bitpos result |= (v & 0x7F) << bitpos
bitpos += 7 bitpos += 7
size -= 1 size -= 1
@ -226,7 +227,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
# if multibyte data is included in the encryped data, we'll # if multibyte data is included in the encryped data, we'll
# have already cleared this flag. # have already cleared this flag.
if flags & 1: if flags & 1:
num += (ord(ptr[size - num - 1]) & 0x3) + 1 num += (ptr[size - num - 1] & 0x3) + 1
return num return num
@ -253,10 +254,10 @@ class MobiBook:
print(u"AlfCrypto not found. Using python PC1 implementation.") print(u"AlfCrypto not found. Using python PC1 implementation.")
# initial sanity check on file # initial sanity check on file
self.data_file = file(infile, 'rb').read() self.data_file = open(infile, 'rb').read()
self.mobi_data = '' self.mobi_data = ''
self.header = self.data_file[0:78] self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': if self.header[0x3C:0x3C+8] != b'BOOKMOBI' and self.header[0x3C:0x3C+8] != b'TEXtREAd':
raise DrmException(u"Invalid file format") raise DrmException(u"Invalid file format")
self.magic = self.header[0x3C:0x3C+8] self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1 self.crypto_type = -1
@ -264,7 +265,7 @@ class MobiBook:
# build up section offset and flag info # build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78]) self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = [] self.sections = []
for i in xrange(self.num_sections): for i in range(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4 flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) ) self.sections.append( (offset, flags, val) )
@ -304,24 +305,24 @@ class MobiBook:
exth = '' exth = ''
if exth_flag & 0x40: if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:] exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 12) and (exth[:4] == 'EXTH'): if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
nitems, = struct.unpack('>I', exth[8:12]) nitems, = struct.unpack('>I', exth[8:12])
pos = 12 pos = 12
for i in xrange(nitems): for i in range(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8]) type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size] content = exth[pos + 8: pos + size]
self.meta_array[type] = content self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present # reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9: if type == 401 and size == 9:
# set clipping limit to 100% # set clipping limit to 100%
self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8) self.patchSection(0, b'\144', 16 + self.mobi_length + pos + 8)
elif type == 404 and size == 9: elif type == 404 and size == 9:
# make sure text to speech is enabled # make sure text to speech is enabled
self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8) self.patchSection(0, b'\0', 16 + self.mobi_length + pos + 8)
# print type, size, content, content.encode('hex') # print type, size, content, content.encode('hex')
pos += size pos += size
except: except Exception as e:
pass print(u"Cannot set meta_array: Error: {:s}".format(e.args[0]))
def getBookTitle(self): def getBookTitle(self):
codec_map = { codec_map = {
@ -341,19 +342,19 @@ class MobiBook:
codec = codec_map[self.mobi_codepage] codec = codec_map[self.mobi_codepage]
if title == '': if title == '':
title = self.header[:32] title = self.header[:32]
title = title.split('\0')[0] title = title.split(b'\0')[0]
return unicode(title, codec) return title.decode(codec)
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = '' rec209 = b''
token = '' token = b''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token
for i in xrange(0,len(data),5): for i in range(0,len(data),5):
val, = struct.unpack('>I',data[i+1:i+5]) val, = struct.unpack('>I',data[i+1:i+5])
sval = self.meta_array.get(val,'') sval = self.meta_array.get(val,'')
token += sval token += sval
@ -373,13 +374,14 @@ class MobiBook:
def parseDRM(self, data, count, pidlist): def parseDRM(self, data, count, pidlist):
found_key = None found_key = None
keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96' keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
for pid in pidlist: for pid in pidlist:
bigpid = pid.ljust(16,'\0') bigpid = pid.ljust(16,b'\0')
bigpid = bigpid
temp_key = PC1(keyvec1, bigpid, False) temp_key = PC1(keyvec1, bigpid, False)
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(temp_key) & 0xff
found_key = None found_key = None
for i in xrange(count): for i in range(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum: if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie) cookie = PC1(temp_key, cookie)
@ -393,8 +395,8 @@ class MobiBook:
# Then try the default encoding that doesn't require a PID # Then try the default encoding that doesn't require a PID
pid = '00000000' pid = '00000000'
temp_key = keyvec1 temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(temp_key) & 0xff
for i in xrange(count): for i in range(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum: if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie) cookie = PC1(temp_key, cookie)
@ -405,7 +407,7 @@ class MobiBook:
return [found_key,pid] return [found_key,pid]
def getFile(self, outpath): def getFile(self, outpath):
file(outpath,'wb').write(self.mobi_data) open(outpath,'wb').write(self.mobi_data)
def getBookType(self): def getBookType(self):
if self.print_replica: if self.print_replica:
@ -442,6 +444,7 @@ class MobiBook:
raise DrmException(u"Cannot decode library or rented ebooks.") raise DrmException(u"Cannot decode library or rented ebooks.")
goodpids = [] goodpids = []
# print("DEBUG ==== pidlist = ", pidlist)
for pid in pidlist: for pid in pidlist:
if len(pid)==10: if len(pid)==10:
if checksumPid(pid[0:-2]) != pid: if checksumPid(pid[0:-2]) != pid:
@ -452,6 +455,8 @@ class MobiBook:
else: else:
print(u"Warning: PID {0} has wrong number of digits".format(pid)) print(u"Warning: PID {0} has wrong number of digits".format(pid))
# print(u"======= DEBUG good pids = ", goodpids)
if self.crypto_type == 1: if self.crypto_type == 1:
t1_keyvec = 'QDCVEPMU675RUBSZ' t1_keyvec = 'QDCVEPMU675RUBSZ'
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
@ -471,9 +476,9 @@ class MobiBook:
if not found_key: if not found_key:
raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids))) raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids)))
# kill the drm keys # kill the drm keys
self.patchSection(0, '\0' * drm_size, drm_ptr) self.patchSection(0, b'\0' * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8) self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8)
if pid=='00000000': if pid=='00000000':
print(u"File has default encryption, no specific key needed.") print(u"File has default encryption, no specific key needed.")
@ -481,13 +486,13 @@ class MobiBook:
print(u"File is encoded with PID {0}.".format(checksumPid(pid))) print(u"File is encoded with PID {0}.".format(checksumPid(pid)))
# clear the crypto type # clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) self.patchSection(0, b'\0' * 2, 0xC)
# decrypt sections # decrypt sections
print(u"Decrypting. Please wait . . .", end=' ') print(u"Decrypting. Please wait . . .", end=' ')
mobidataList = [] mobidataList = []
mobidataList.append(self.data_file[:self.sections[1][0]]) mobidataList.append(self.data_file[:self.sections[1][0]])
for i in xrange(1, self.records+1): for i in range(1, self.records+1):
data = self.loadSection(i) data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0: if i%100 == 0:
@ -501,7 +506,7 @@ class MobiBook:
mobidataList.append(data[-extra_size:]) mobidataList.append(data[-extra_size:])
if self.num_sections > self.records+1: if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList) self.mobi_data = b''.join(mobidataList)
print(u"done") print(u"done")
return return
@ -531,8 +536,8 @@ def cli_main():
pidlist = [] pidlist = []
try: try:
stripped_file = getUnencryptedBook(infile, pidlist) stripped_file = getUnencryptedBook(infile, pidlist)
file(outfile, 'wb').write(stripped_file) open(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException as e:
print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])) print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
return 1 return 1
return 0 return 0

@ -101,9 +101,9 @@ def convertprefs(always = False):
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:]) keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
keyvalue = generate_key(name, ccn) keyvalue = generate_key(name, ccn)
userkeys.append([keyname,keyvalue]) userkeys.append([keyname,keyvalue])
except Exception, e: except Exception as e:
traceback.print_exc() traceback.print_exc()
print e.args[0] print(e.args[0])
pass pass
return userkeys return userkeys
@ -118,9 +118,9 @@ def convertprefs(always = False):
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:]) keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
keyvalue = getuser_key(name,cc).encode('hex') keyvalue = getuser_key(name,cc).encode('hex')
userkeys.append([keyname,keyvalue]) userkeys.append([keyname,keyvalue])
except Exception, e: except Exception as e:
traceback.print_exc() traceback.print_exc()
print e.args[0] print(e.args[0])
pass pass
return userkeys return userkeys
@ -161,7 +161,7 @@ def convertprefs(always = False):
return return
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION) print(u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION))
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM" IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
EREADERPLUGINNAME = "eReader PDB 2 PML" EREADERPLUGINNAME = "eReader PDB 2 PML"
@ -177,7 +177,7 @@ def convertprefs(always = False):
sc = config['plugin_customization'] sc = config['plugin_customization']
val = sc.pop(IGNOBLEPLUGINNAME, None) val = sc.pop(IGNOBLEPLUGINNAME, None)
if val is not None: if val is not None:
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) print(u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorkeycount = len(dedrmprefs['bandnkeys']) priorkeycount = len(dedrmprefs['bandnkeys'])
userkeys = parseIgnobleString(str(val)) userkeys = parseIgnobleString(str(val))
for keypair in userkeys: for keypair in userkeys:
@ -185,7 +185,7 @@ def convertprefs(always = False):
value = keypair[1] value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -193,7 +193,7 @@ def convertprefs(always = False):
# old string to stored keys... get that personal data out of plain sight. # old string to stored keys... get that personal data out of plain sight.
val = sc.pop(EREADERPLUGINNAME, None) val = sc.pop(EREADERPLUGINNAME, None)
if val is not None: if val is not None:
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) print(u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorkeycount = len(dedrmprefs['ereaderkeys']) priorkeycount = len(dedrmprefs['ereaderkeys'])
userkeys = parseeReaderString(str(val)) userkeys = parseeReaderString(str(val))
for keypair in userkeys: for keypair in userkeys:
@ -201,14 +201,14 @@ def convertprefs(always = False):
value = keypair[1] value = keypair[1]
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value) dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") print(u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
# get old Kindle plugin configuration string # get old Kindle plugin configuration string
val = sc.pop(OLDKINDLEPLUGINNAME, None) val = sc.pop(OLDKINDLEPLUGINNAME, None)
if val is not None: if val is not None:
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION) print(u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
priorpidcount = len(dedrmprefs['pids']) priorpidcount = len(dedrmprefs['pids'])
priorserialcount = len(dedrmprefs['serials']) priorserialcount = len(dedrmprefs['serials'])
pids, serials = parseKindleString(val) pids, serials = parseKindleString(val)
@ -218,7 +218,7 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial) dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount addedpidcount = len(dedrmprefs['pids']) - priorpidcount
addedserialcount = len(dedrmprefs['serials']) - priorserialcount addedserialcount = len(dedrmprefs['serials']) - priorserialcount
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") print(u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -234,7 +234,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
if addedkeycount > 0: if addedkeycount > 0:
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files") print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -247,7 +247,7 @@ def convertprefs(always = False):
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value) dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
if addedkeycount > 0: if addedkeycount > 0:
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles") print(u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -260,7 +260,7 @@ def convertprefs(always = False):
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
# no need to delete old prefs, since they contain no recoverable private data # no need to delete old prefs, since they contain no recoverable private data
if addedkeycount > 0: if addedkeycount > 0:
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys") print(u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys"))
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs(False) dedrmprefs.writeprefs(False)
@ -277,19 +277,19 @@ def convertprefs(always = False):
dedrmprefs.addvaluetoprefs('serials',serial) dedrmprefs.addvaluetoprefs('serials',serial)
addedpidcount = len(dedrmprefs['pids']) - priorpidcount addedpidcount = len(dedrmprefs['pids']) - priorpidcount
if addedpidcount > 0: if addedpidcount > 0:
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs") print(u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs"))
addedserialcount = len(dedrmprefs['serials']) - priorserialcount addedserialcount = len(dedrmprefs['serials']) - priorserialcount
if addedserialcount > 0: if addedserialcount > 0:
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers") print(u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers"))
try: try:
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "": if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix']) dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix']) dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
print u"{0} v{1}: WINEPREFIX (2) imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']) print(u"{0} v{1}: WINEPREFIX (2) imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']))
except: except:
traceback.print_exc() traceback.print_exc()
# Make the json write all the prefs to disk # Make the json write all the prefs to disk
dedrmprefs.writeprefs() dedrmprefs.writeprefs()
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION) print(u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION))

@ -6,13 +6,13 @@ from __future__ import print_function
import sys import sys
import os import os
import re import re
import ineptepub import calibre_plugins.dedrm.ineptepub
import ignobleepub import calibre_plugins.dedrm.ignobleepub
import epubtest import calibre_plugins.dedrm.epubtest
import zipfix import calibre_plugins.dedrm.zipfix
import ineptpdf import calibre_plugins.dedrm.ineptpdf
import erdr2pml import calibre_plugins.dedrm.erdr2pml
import k4mobidedrm import calibre_plugins.dedrm.k4mobidedrm
import traceback import traceback
def decryptepub(infile, outdir, rscpath): def decryptepub(infile, outdir, rscpath):
@ -46,7 +46,7 @@ def decryptepub(infile, outdir, rscpath):
if rv == 0: if rv == 0:
print("Decrypted Adobe ePub with key file {0}".format(filename)) print("Decrypted Adobe ePub with key file {0}".format(filename))
break break
except Exception, e: except Exception as e:
errlog += traceback.format_exc() errlog += traceback.format_exc()
errlog += str(e) errlog += str(e)
rv = 1 rv = 1
@ -66,7 +66,7 @@ def decryptepub(infile, outdir, rscpath):
if rv == 0: if rv == 0:
print("Decrypted B&N ePub with key file {0}".format(filename)) print("Decrypted B&N ePub with key file {0}".format(filename))
break break
except Exception, e: except Exception as e:
errlog += traceback.format_exc() errlog += traceback.format_exc()
errlog += str(e) errlog += str(e)
rv = 1 rv = 1
@ -104,7 +104,7 @@ def decryptpdf(infile, outdir, rscpath):
rv = ineptpdf.decryptBook(userkey, infile, outfile) rv = ineptpdf.decryptBook(userkey, infile, outfile)
if rv == 0: if rv == 0:
break break
except Exception, e: except Exception as e:
errlog += traceback.format_exc() errlog += traceback.format_exc()
errlog += str(e) errlog += str(e)
rv = 1 rv = 1
@ -132,7 +132,7 @@ def decryptpdb(infile, outdir, rscpath):
return 1 return 1
try: try:
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8)) rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
except Exception, e: except Exception as e:
errlog += traceback.format_exc() errlog += traceback.format_exc()
errlog += str(e) errlog += str(e)
rv = 1 rv = 1
@ -193,7 +193,7 @@ def decryptk4mobi(infile, outdir, rscpath):
androidFiles.append(dpath) androidFiles.append(dpath)
try: try:
rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums) rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
except Exception, e: except Exception as e:
errlog += traceback.format_exc() errlog += traceback.format_exc()
errlog += str(e) errlog += str(e)
rv = 1 rv = 1

@ -46,7 +46,7 @@ class SimplePrefs(object):
try : try :
data = file(filepath,'rb').read() data = file(filepath,'rb').read()
self.prefs[key] = data self.prefs[key] = data
except Exception, e: except Exception as e:
pass pass
def getPreferences(self): def getPreferences(self):
@ -71,7 +71,7 @@ class SimplePrefs(object):
else: else:
try: try:
file(filepath,'wb').write(data) file(filepath,'wb').write(data)
except Exception, e: except Exception as e:
pass pass
self.prefs = newprefs self.prefs = newprefs
return return

@ -7,9 +7,10 @@
# Changelog # Changelog
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
# 5.0 - Fixed potential unicode problem with command line interface # 5.0 - Fixed potential unicode problem with command line interface
# 6.0 - Added Python 3 compatibility for calibre 5.0
from __future__ import print_function from __future__ import print_function
__version__ = '5.0' __version__ = '6.0'
import sys import sys
import os, csv, getopt import os, csv, getopt
@ -17,7 +18,7 @@ import zlib, zipfile, tempfile, shutil
import traceback import traceback
from struct import pack from struct import pack
from struct import unpack from struct import unpack
from alfcrypto import Topaz_Cipher from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
class SafeUnbuffered: class SafeUnbuffered:
def __init__(self, stream): def __init__(self, stream):
@ -26,7 +27,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -64,7 +65,7 @@ def unicode_argv():
# Remove Python executable and commands if present # Remove Python executable and commands if present
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] range(start, argc.value)]
# if we don't have any arguments at all, just pass back script name # if we don't have any arguments at all, just pass back script name
# this should never happen # this should never happen
return [u"mobidedrm.py"] return [u"mobidedrm.py"]
@ -72,7 +73,7 @@ def unicode_argv():
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
argvencoding = 'utf-8' argvencoding = 'utf-8'
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] return argv
#global switch #global switch
debug = False debug = False
@ -196,7 +197,7 @@ def decryptDkeyRecords(data,PID):
class TopazBook: class TopazBook:
def __init__(self, filename): def __init__(self, filename):
self.fo = file(filename, 'rb') self.fo = open(filename, 'rb')
self.outdir = tempfile.mkdtemp() self.outdir = tempfile.mkdtemp()
# self.outdir = 'rawdat' # self.outdir = 'rawdat'
self.bookPayloadOffset = 0 self.bookPayloadOffset = 0
@ -319,7 +320,7 @@ class TopazBook:
fixedimage=True fixedimage=True
try: try:
keydata = self.getBookPayloadRecord('dkey', 0) keydata = self.getBookPayloadRecord('dkey', 0)
except DrmException, e: except DrmException as e:
print(u"no dkey record found, book may not be encrypted") print(u"no dkey record found, book may not be encrypted")
print(u"attempting to extrct files without a book key") print(u"attempting to extrct files without a book key")
self.createBookDirectory() self.createBookDirectory()
@ -345,7 +346,7 @@ class TopazBook:
data = keydata data = keydata
try: try:
bookKeys+=decryptDkeyRecords(data,pid) bookKeys+=decryptDkeyRecords(data,pid)
except DrmException, e: except DrmException as e:
pass pass
else: else:
bookKey = bookKeys[0] bookKey = bookKeys[0]
@ -357,7 +358,7 @@ class TopazBook:
self.setBookKey(bookKey) self.setBookKey(bookKey)
self.createBookDirectory() self.createBookDirectory()
self.extractFiles() self.extractFiles()
print(u"Successfully Extracted Topaz contents") print(u"Successfully Extracted Topaz contents")
if inCalibre: if inCalibre:
from calibre_plugins.dedrm import genbook from calibre_plugins.dedrm import genbook
@ -411,7 +412,7 @@ class TopazBook:
print(u".", end=' ') print(u".", end=' ')
record = self.getBookPayloadRecord(name,index) record = self.getBookPayloadRecord(name,index)
if record != '': if record != '':
file(outputFile, 'wb').write(record) open(outputFile, 'wb').write(record)
print(u" ") print(u" ")
def getFile(self, zipname): def getFile(self, zipname):
@ -454,7 +455,7 @@ def cli_main():
try: try:
opts, args = getopt.getopt(argv[1:], "k:p:s:x") opts, args = getopt.getopt(argv[1:], "k:p:s:x")
except getopt.GetoptError, err: except getopt.GetoptError as err:
print(u"Error in options or arguments: {0}".format(err.args[0])) print(u"Error in options or arguments: {0}".format(err.args[0]))
usage(progname) usage(progname)
return 1 return 1
@ -513,7 +514,7 @@ def cli_main():
# removing internal temporary directory of pieces # removing internal temporary directory of pieces
tb.cleanup() tb.cleanup()
except DrmException, e: except DrmException as e:
print(u"Decryption failed\n{0}".format(traceback.format_exc())) print(u"Decryption failed\n{0}".format(traceback.format_exc()))
try: try:
@ -522,8 +523,8 @@ def cli_main():
pass pass
return 1 return 1
except Exception, e: except Exception as e:
print(u"Decryption failed\m{0}".format(traceback.format_exc())) print(u"Decryption failed\n{0}".format(traceback.format_exc()))
try: try:
tb.cleanup() tb.cleanup()
except: except:

@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
from ignoblekeygen import generate_key from calibre_plugins.dedrm.ignoblekeygen import generate_key
__license__ = 'GPL v3' __license__ = 'GPL v3'
@ -21,8 +21,8 @@ DETAILED_MESSAGE = \
def uStrCmp (s1, s2, caseless=False): def uStrCmp (s1, s2, caseless=False):
import unicodedata as ud import unicodedata as ud
str1 = s1 if isinstance(s1, unicode) else unicode(s1) str1 = s1 if isinstance(s1, unicode) else s1.decode('utf-8')
str2 = s2 if isinstance(s2, unicode) else unicode(s2) str2 = s2 if isinstance(s2, unicode) else s2.decode('utf-8')
if caseless: if caseless:
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower()) return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
else: else:

@ -40,7 +40,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait") result = p2.wait("wait")
except Exception, e: except Exception as e:
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
if wineprefix != "" and os.path.exists(wineprefix): if wineprefix != "" and os.path.exists(wineprefix):
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix) cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
@ -52,7 +52,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
cmdline = cmdline.encode(sys.getfilesystemencoding()) cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
result = p2.wait("wait") result = p2.wait("wait")
except Exception, e: except Exception as e:
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
# try finding winekeys anyway, even if above code errored # try finding winekeys anyway, even if above code errored

@ -2,10 +2,12 @@
Read and write ZIP files. Read and write ZIP files.
""" """
import struct, os, time, sys, shutil import struct, os, time, sys, shutil
import binascii, cStringIO, stat import binascii, stat
import io import io
import re import re
from io import BytesIO
try: try:
import zlib # We may need its compression method import zlib # We may need its compression method
crc32 = zlib.crc32 crc32 = zlib.crc32
@ -45,8 +47,8 @@ ZIP_DEFLATED = 8
# The "end of central directory" structure, magic number, size, and indices # The "end of central directory" structure, magic number, size, and indices
# (section V.I in the format document) # (section V.I in the format document)
structEndArchive = "<4s4H2LH" structEndArchive = b"<4s4H2LH"
stringEndArchive = "PK\005\006" stringEndArchive = b"PK\005\006"
sizeEndCentDir = struct.calcsize(structEndArchive) sizeEndCentDir = struct.calcsize(structEndArchive)
_ECD_SIGNATURE = 0 _ECD_SIGNATURE = 0
@ -64,8 +66,8 @@ _ECD_LOCATION = 9
# The "central directory" structure, magic number, size, and indices # The "central directory" structure, magic number, size, and indices
# of entries in the structure (section V.F in the format document) # of entries in the structure (section V.F in the format document)
structCentralDir = "<4s4B4HL2L5H2L" structCentralDir = b"<4s4B4HL2L5H2L"
stringCentralDir = "PK\001\002" stringCentralDir = b"PK\001\002"
sizeCentralDir = struct.calcsize(structCentralDir) sizeCentralDir = struct.calcsize(structCentralDir)
# indexes of entries in the central directory structure # indexes of entries in the central directory structure
@ -91,8 +93,8 @@ _CD_LOCAL_HEADER_OFFSET = 18
# The "local file header" structure, magic number, size, and indices # The "local file header" structure, magic number, size, and indices
# (section V.A in the format document) # (section V.A in the format document)
structFileHeader = "<4s2B4HL2L2H" structFileHeader = b"<4s2B4HL2L2H"
stringFileHeader = "PK\003\004" stringFileHeader = b"PK\003\004"
sizeFileHeader = struct.calcsize(structFileHeader) sizeFileHeader = struct.calcsize(structFileHeader)
_FH_SIGNATURE = 0 _FH_SIGNATURE = 0
@ -109,14 +111,14 @@ _FH_FILENAME_LENGTH = 10
_FH_EXTRA_FIELD_LENGTH = 11 _FH_EXTRA_FIELD_LENGTH = 11
# The "Zip64 end of central directory locator" structure, magic number, and size # The "Zip64 end of central directory locator" structure, magic number, and size
structEndArchive64Locator = "<4sLQL" structEndArchive64Locator = b"<4sLQL"
stringEndArchive64Locator = "PK\x06\x07" stringEndArchive64Locator = b"PK\x06\x07"
sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator) sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
# The "Zip64 end of central directory" record, magic number, size, and indices # The "Zip64 end of central directory" record, magic number, size, and indices
# (section V.G in the format document) # (section V.G in the format document)
structEndArchive64 = "<4sQ2H2L4Q" structEndArchive64 = b"<4sQ2H2L4Q"
stringEndArchive64 = "PK\x06\x06" stringEndArchive64 = b"PK\x06\x06"
sizeEndCentDir64 = struct.calcsize(structEndArchive64) sizeEndCentDir64 = struct.calcsize(structEndArchive64)
_CD64_SIGNATURE = 0 _CD64_SIGNATURE = 0
@ -275,7 +277,7 @@ class ZipInfo (object):
# Terminate the file name at the first null byte. Null bytes in file # Terminate the file name at the first null byte. Null bytes in file
# names are used as tricks by viruses in archives. # names are used as tricks by viruses in archives.
null_byte = filename.find(chr(0)) null_byte = filename.find(b"\0")
if null_byte >= 0: if null_byte >= 0:
filename = filename[0:null_byte] filename = filename[0:null_byte]
# This is used to ensure paths in generated ZIP files always use # This is used to ensure paths in generated ZIP files always use
@ -288,8 +290,8 @@ class ZipInfo (object):
self.date_time = date_time # year, month, day, hour, min, sec self.date_time = date_time # year, month, day, hour, min, sec
# Standard values: # Standard values:
self.compress_type = ZIP_STORED # Type of compression for the file self.compress_type = ZIP_STORED # Type of compression for the file
self.comment = "" # Comment for each file self.comment = b"" # Comment for each file
self.extra = "" # ZIP extra data self.extra = b"" # ZIP extra data
if sys.platform == 'win32': if sys.platform == 'win32':
self.create_system = 0 # System which created ZIP archive self.create_system = 0 # System which created ZIP archive
else: else:
@ -343,23 +345,13 @@ class ZipInfo (object):
return header + filename + extra return header + filename + extra
def _encodeFilenameFlags(self): def _encodeFilenameFlags(self):
if isinstance(self.filename, unicode): if isinstance(self.filename, bytes):
return self.filename, self.flag_bits
else:
try: try:
return self.filename.encode('ascii'), self.flag_bits return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError: except UnicodeEncodeError:
return self.filename.encode('utf-8'), self.flag_bits | 0x800 return self.filename.encode('utf-8'), self.flag_bits | 0x800
else:
return self.filename, self.flag_bits
def _decodeFilename(self):
if self.flag_bits & 0x800:
try:
#print "decoding filename",self.filename
return self.filename.decode('utf-8')
except:
return self.filename
else:
return self.filename
def _decodeExtra(self): def _decodeExtra(self):
# Try to decode the extra field. # Try to decode the extra field.
@ -377,20 +369,20 @@ class ZipInfo (object):
elif ln == 0: elif ln == 0:
counts = () counts = ()
else: else:
raise RuntimeError, "Corrupt extra field %s"%(ln,) raise RuntimeError("Corrupt extra field %s"%(ln,))
idx = 0 idx = 0
# ZIP64 extension (large files and/or large archives) # ZIP64 extension (large files and/or large archives)
if self.file_size in (0xffffffffffffffffL, 0xffffffffL): if self.file_size in (0xffffffffffffffff, 0xffffffff):
self.file_size = counts[idx] self.file_size = counts[idx]
idx += 1 idx += 1
if self.compress_size == 0xFFFFFFFFL: if self.compress_size == 0xFFFFFFFF:
self.compress_size = counts[idx] self.compress_size = counts[idx]
idx += 1 idx += 1
if self.header_offset == 0xffffffffL: if self.header_offset == 0xffffffff:
old = self.header_offset old = self.header_offset
self.header_offset = counts[idx] self.header_offset = counts[idx]
idx+=1 idx+=1
@ -481,9 +473,9 @@ class ZipExtFile(io.BufferedIOBase):
if self._compress_type == ZIP_DEFLATED: if self._compress_type == ZIP_DEFLATED:
self._decompressor = zlib.decompressobj(-15) self._decompressor = zlib.decompressobj(-15)
self._unconsumed = '' self._unconsumed = b''
self._readbuffer = '' self._readbuffer = b''
self._offset = 0 self._offset = 0
self._universal = 'U' in mode self._universal = 'U' in mode
@ -514,10 +506,10 @@ class ZipExtFile(io.BufferedIOBase):
if not self._universal: if not self._universal:
return io.BufferedIOBase.readline(self, limit) return io.BufferedIOBase.readline(self, limit)
line = '' line = b''
while limit < 0 or len(line) < limit: while limit < 0 or len(line) < limit:
readahead = self.peek(2) readahead = self.peek(2)
if readahead == '': if readahead == b'':
return line return line
# #
@ -564,7 +556,7 @@ class ZipExtFile(io.BufferedIOBase):
If the argument is omitted, None, or negative, data is read and returned until EOF is reached.. If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
""" """
buf = '' buf = b''
while n < 0 or n is None or n > len(buf): while n < 0 or n is None or n > len(buf):
data = self.read1(n) data = self.read1(n)
if len(data) == 0: if len(data) == 0:
@ -594,7 +586,7 @@ class ZipExtFile(io.BufferedIOBase):
self._compress_left -= len(data) self._compress_left -= len(data)
if data and self._decrypter is not None: if data and self._decrypter is not None:
data = ''.join(map(self._decrypter, data)) data = b''.join(map(self._decrypter, data))
if self._compress_type == ZIP_STORED: if self._compress_type == ZIP_STORED:
self._readbuffer = self._readbuffer[self._offset:] + data self._readbuffer = self._readbuffer[self._offset:] + data
@ -651,10 +643,10 @@ class ZipFile:
pass pass
elif compression == ZIP_DEFLATED: elif compression == ZIP_DEFLATED:
if not zlib: if not zlib:
raise RuntimeError,\ raise RuntimeError(
"Compression requires the (missing) zlib module" "Compression requires the (missing) zlib module")
else: else:
raise RuntimeError, "That compression method is not supported" raise RuntimeError("That compression method is not supported")
self._allowZip64 = allowZip64 self._allowZip64 = allowZip64
self._didModify = False self._didModify = False
@ -664,10 +656,10 @@ class ZipFile:
self.compression = compression # Method of compression self.compression = compression # Method of compression
self.mode = key = mode.replace('b', '')[0] self.mode = key = mode.replace('b', '')[0]
self.pwd = None self.pwd = None
self.comment = '' self.comment = b''
# Check if we were passed a file-like object # Check if we were passed a file-like object
if isinstance(file, basestring): if isinstance(file, str):
self._filePassed = 0 self._filePassed = 0
self.filename = file self.filename = file
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'} modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
@ -699,7 +691,7 @@ class ZipFile:
if not self._filePassed: if not self._filePassed:
self.fp.close() self.fp.close()
self.fp = None self.fp = None
raise RuntimeError, 'Mode must be "r", "w" or "a"' raise RuntimeError('Mode must be "r", "w" or "a"')
def __enter__(self): def __enter__(self):
return self return self
@ -723,9 +715,9 @@ class ZipFile:
fp = self.fp fp = self.fp
endrec = _EndRecData(fp) endrec = _EndRecData(fp)
if not endrec: if not endrec:
raise BadZipfile, "File is not a zip file" raise BadZipfile("File is not a zip file")
if self.debug > 1: if self.debug > 1:
print endrec print(endrec)
size_cd = endrec[_ECD_SIZE] # bytes in central directory size_cd = endrec[_ECD_SIZE] # bytes in central directory
offset_cd = endrec[_ECD_OFFSET] # offset of central directory offset_cd = endrec[_ECD_OFFSET] # offset of central directory
self.comment = endrec[_ECD_COMMENT] # archive comment self.comment = endrec[_ECD_COMMENT] # archive comment
@ -738,20 +730,20 @@ class ZipFile:
if self.debug > 2: if self.debug > 2:
inferred = concat + offset_cd inferred = concat + offset_cd
print "given, inferred, offset", offset_cd, inferred, concat print("given, inferred, offset", offset_cd, inferred, concat)
# self.start_dir: Position of start of central directory # self.start_dir: Position of start of central directory
self.start_dir = offset_cd + concat self.start_dir = offset_cd + concat
fp.seek(self.start_dir, 0) fp.seek(self.start_dir, 0)
data = fp.read(size_cd) data = fp.read(size_cd)
fp = cStringIO.StringIO(data) fp = BytesIO(data)
total = 0 total = 0
while total < size_cd: while total < size_cd:
centdir = fp.read(sizeCentralDir) centdir = fp.read(sizeCentralDir)
if centdir[0:4] != stringCentralDir: if centdir[0:4] != stringCentralDir:
raise BadZipfile, "Bad magic number for central directory" raise BadZipfile("Bad magic number for central directory")
centdir = struct.unpack(structCentralDir, centdir) centdir = struct.unpack(structCentralDir, centdir)
if self.debug > 2: if self.debug > 2:
print centdir print(centdir)
filename = fp.read(centdir[_CD_FILENAME_LENGTH]) filename = fp.read(centdir[_CD_FILENAME_LENGTH])
# Create ZipInfo instance to store file information # Create ZipInfo instance to store file information
x = ZipInfo(filename) x = ZipInfo(filename)
@ -769,7 +761,6 @@ class ZipFile:
x._decodeExtra() x._decodeExtra()
x.header_offset = x.header_offset + concat x.header_offset = x.header_offset + concat
x.filename = x._decodeFilename()
self.filelist.append(x) self.filelist.append(x)
self.NameToInfo[x.filename] = x self.NameToInfo[x.filename] = x
@ -779,7 +770,7 @@ class ZipFile:
+ centdir[_CD_COMMENT_LENGTH]) + centdir[_CD_COMMENT_LENGTH])
if self.debug > 2: if self.debug > 2:
print "total", total print("total", total)
def namelist(self): def namelist(self):
@ -796,10 +787,10 @@ class ZipFile:
def printdir(self): def printdir(self):
"""Print a table of contents for the zip file.""" """Print a table of contents for the zip file."""
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size") print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"))
for zinfo in self.filelist: for zinfo in self.filelist:
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6] date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size) print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size))
def testzip(self): def testzip(self):
"""Read all the files and check the CRC.""" """Read all the files and check the CRC."""
@ -834,10 +825,10 @@ class ZipFile:
def open(self, name, mode="r", pwd=None): def open(self, name, mode="r", pwd=None):
"""Return file-like object for 'name'.""" """Return file-like object for 'name'."""
if mode not in ("r", "U", "rU"): if mode not in ("r", "U", "rU"):
raise RuntimeError, 'open() requires mode "r", "U", or "rU"' raise RuntimeError('open() requires mode "r", "U", or "rU"')
if not self.fp: if not self.fp:
raise RuntimeError, \ raise RuntimeError(
"Attempt to read ZIP archive that was already closed" "Attempt to read ZIP archive that was already closed")
# Only open a new file for instances where we were not # Only open a new file for instances where we were not
# given a file object in the constructor # given a file object in the constructor
@ -859,7 +850,7 @@ class ZipFile:
# Skip the file header: # Skip the file header:
fheader = zef_file.read(sizeFileHeader) fheader = zef_file.read(sizeFileHeader)
if fheader[0:4] != stringFileHeader: if fheader[0:4] != stringFileHeader:
raise BadZipfile, "Bad magic number for file header" raise BadZipfile("Bad magic number for file header")
fheader = struct.unpack(structFileHeader, fheader) fheader = struct.unpack(structFileHeader, fheader)
fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
@ -867,9 +858,9 @@ class ZipFile:
zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
if fname != zinfo.orig_filename: if fname != zinfo.orig_filename:
raise BadZipfile, \ raise BadZipfile(
'File name in directory "%s" and header "%s" differ.' % ( 'File name in directory "%s" and header "%s" differ.' % (
zinfo.orig_filename, fname) zinfo.orig_filename, fname))
# check for encrypted flag & handle password # check for encrypted flag & handle password
is_encrypted = zinfo.flag_bits & 0x1 is_encrypted = zinfo.flag_bits & 0x1
@ -878,8 +869,8 @@ class ZipFile:
if not pwd: if not pwd:
pwd = self.pwd pwd = self.pwd
if not pwd: if not pwd:
raise RuntimeError, "File %s is encrypted, " \ raise RuntimeError("File %s is encrypted, " \
"password required for extraction" % name "password required for extraction" % name)
zd = _ZipDecrypter(pwd) zd = _ZipDecrypter(pwd)
# The first 12 bytes in the cypher stream is an encryption header # The first 12 bytes in the cypher stream is an encryption header
@ -956,7 +947,7 @@ class ZipFile:
return targetpath return targetpath
source = self.open(member, pwd=pwd) source = self.open(member, pwd=pwd)
target = file(targetpath, "wb") target = open(targetpath, "wb")
shutil.copyfileobj(source, target) shutil.copyfileobj(source, target)
source.close() source.close()
target.close() target.close()
@ -967,18 +958,18 @@ class ZipFile:
"""Check for errors before writing a file to the archive.""" """Check for errors before writing a file to the archive."""
if zinfo.filename in self.NameToInfo: if zinfo.filename in self.NameToInfo:
if self.debug: # Warning for duplicate names if self.debug: # Warning for duplicate names
print "Duplicate name:", zinfo.filename print("Duplicate name:", zinfo.filename)
if self.mode not in ("w", "a"): if self.mode not in ("w", "a"):
raise RuntimeError, 'write() requires mode "w" or "a"' raise RuntimeError('write() requires mode "w" or "a"')
if not self.fp: if not self.fp:
raise RuntimeError, \ raise RuntimeError(
"Attempt to write ZIP archive that was already closed" "Attempt to write ZIP archive that was already closed")
if zinfo.compress_type == ZIP_DEFLATED and not zlib: if zinfo.compress_type == ZIP_DEFLATED and not zlib:
raise RuntimeError, \ raise RuntimeError(
"Compression requires the (missing) zlib module" "Compression requires the (missing) zlib module")
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
raise RuntimeError, \ raise RuntimeError(
"That compression method is not supported" "That compression method is not supported")
if zinfo.file_size > ZIP64_LIMIT: if zinfo.file_size > ZIP64_LIMIT:
if not self._allowZip64: if not self._allowZip64:
raise LargeZipFile("Filesize would require ZIP64 extensions") raise LargeZipFile("Filesize would require ZIP64 extensions")
@ -1006,7 +997,7 @@ class ZipFile:
if isdir: if isdir:
arcname += '/' arcname += '/'
zinfo = ZipInfo(arcname, date_time) zinfo = ZipInfo(arcname, date_time)
zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
if compress_type is None: if compress_type is None:
zinfo.compress_type = self.compression zinfo.compress_type = self.compression
else: else:
@ -1076,7 +1067,7 @@ class ZipFile:
date_time=time.localtime(time.time())[:6]) date_time=time.localtime(time.time())[:6])
zinfo.compress_type = self.compression zinfo.compress_type = self.compression
zinfo.external_attr = 0600 << 16 zinfo.external_attr = 0x0600 << 16
else: else:
zinfo = zinfo_or_arcname zinfo = zinfo_or_arcname
@ -1141,7 +1132,7 @@ class ZipFile:
if zinfo.header_offset > ZIP64_LIMIT: if zinfo.header_offset > ZIP64_LIMIT:
extra.append(zinfo.header_offset) extra.append(zinfo.header_offset)
header_offset = 0xffffffffL header_offset = 0xffffffff
else: else:
header_offset = zinfo.header_offset header_offset = zinfo.header_offset
@ -1169,14 +1160,14 @@ class ZipFile:
0, zinfo.internal_attr, zinfo.external_attr, 0, zinfo.internal_attr, zinfo.external_attr,
header_offset) header_offset)
except DeprecationWarning: except DeprecationWarning:
print >>sys.stderr, (structCentralDir, print(structCentralDir,
stringCentralDir, create_version, stringCentralDir, create_version,
zinfo.create_system, extract_version, zinfo.reserved, zinfo.create_system, extract_version, zinfo.reserved,
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
zinfo.CRC, compress_size, file_size, zinfo.CRC, compress_size, file_size,
len(zinfo.filename), len(extra_data), len(zinfo.comment), len(zinfo.filename), len(extra_data), len(zinfo.comment),
0, zinfo.internal_attr, zinfo.external_attr, 0, zinfo.internal_attr, zinfo.external_attr,
header_offset) header_offset, sys.stderr)
raise raise
self.fp.write(centdir) self.fp.write(centdir)
self.fp.write(filename) self.fp.write(filename)
@ -1250,10 +1241,10 @@ class PyZipFile(ZipFile):
else: else:
basename = name basename = name
if self.debug: if self.debug:
print "Adding package in", pathname, "as", basename print("Adding package in", pathname, "as", basename)
fname, arcname = self._get_codename(initname[0:-3], basename) fname, arcname = self._get_codename(initname[0:-3], basename)
if self.debug: if self.debug:
print "Adding", arcname print("Adding", arcname)
self.write(fname, arcname) self.write(fname, arcname)
dirlist = os.listdir(pathname) dirlist = os.listdir(pathname)
dirlist.remove("__init__.py") dirlist.remove("__init__.py")
@ -1269,12 +1260,12 @@ class PyZipFile(ZipFile):
fname, arcname = self._get_codename(path[0:-3], fname, arcname = self._get_codename(path[0:-3],
basename) basename)
if self.debug: if self.debug:
print "Adding", arcname print("Adding", arcname)
self.write(fname, arcname) self.write(fname, arcname)
else: else:
# This is NOT a package directory, add its files at top level # This is NOT a package directory, add its files at top level
if self.debug: if self.debug:
print "Adding files from directory", pathname print("Adding files from directory", pathname)
for filename in os.listdir(pathname): for filename in os.listdir(pathname):
path = os.path.join(pathname, filename) path = os.path.join(pathname, filename)
root, ext = os.path.splitext(filename) root, ext = os.path.splitext(filename)
@ -1282,15 +1273,15 @@ class PyZipFile(ZipFile):
fname, arcname = self._get_codename(path[0:-3], fname, arcname = self._get_codename(path[0:-3],
basename) basename)
if self.debug: if self.debug:
print "Adding", arcname print("Adding", arcname)
self.write(fname, arcname) self.write(fname, arcname)
else: else:
if pathname[-3:] != ".py": if pathname[-3:] != ".py":
raise RuntimeError, \ raise RuntimeError(
'Files added with writepy() must end with ".py"' 'Files added with writepy() must end with ".py"')
fname, arcname = self._get_codename(pathname[0:-3], basename) fname, arcname = self._get_codename(pathname[0:-3], basename)
if self.debug: if self.debug:
print "Adding file", arcname print("Adding file", arcname)
self.write(fname, arcname) self.write(fname, arcname)
def _get_codename(self, pathname, basename): def _get_codename(self, pathname, basename):
@ -1310,11 +1301,11 @@ class PyZipFile(ZipFile):
os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime: os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
import py_compile import py_compile
if self.debug: if self.debug:
print "Compiling", file_py print("Compiling", file_py)
try: try:
py_compile.compile(file_py, file_pyc, None, True) py_compile.compile(file_py, file_pyc, None, True)
except py_compile.PyCompileError,err: except py_compile.PyCompileError as err:
print err.msg print(err.msg)
fname = file_pyc fname = file_pyc
else: else:
fname = file_pyc fname = file_pyc
@ -1337,12 +1328,12 @@ def main(args = None):
args = sys.argv[1:] args = sys.argv[1:]
if not args or args[0] not in ('-l', '-c', '-e', '-t'): if not args or args[0] not in ('-l', '-c', '-e', '-t'):
print USAGE print(USAGE)
sys.exit(1) sys.exit(1)
if args[0] == '-l': if args[0] == '-l':
if len(args) != 2: if len(args) != 2:
print USAGE print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') zf = ZipFile(args[1], 'r')
zf.printdir() zf.printdir()
@ -1350,15 +1341,15 @@ def main(args = None):
elif args[0] == '-t': elif args[0] == '-t':
if len(args) != 2: if len(args) != 2:
print USAGE print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') zf = ZipFile(args[1], 'r')
zf.testzip() zf.testzip()
print "Done testing" print("Done testing")
elif args[0] == '-e': elif args[0] == '-e':
if len(args) != 3: if len(args) != 3:
print USAGE print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') zf = ZipFile(args[1], 'r')
@ -1378,7 +1369,7 @@ def main(args = None):
elif args[0] == '-c': elif args[0] == '-c':
if len(args) < 3: if len(args) < 3:
print USAGE print(USAGE)
sys.exit(1) sys.exit(1)
def addToZip(zf, path, zippath): def addToZip(zf, path, zippath):

@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# zipfix.py, version 1.1 # zipfix.py
# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf # Copyright © 2010-2020 by some_updates, DiapDealer and Apprentice Alf
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
# <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
@ -10,6 +10,7 @@
# Revision history: # Revision history:
# 1.0 - Initial release # 1.0 - Initial release
# 1.1 - Updated to handle zip file metadata correctly # 1.1 - Updated to handle zip file metadata correctly
# 2.0 - Added Python 3 compatibility for calibre 5.0
""" """
Re-write zip (or ePub) fixing problems with file names (and mimetype entry). Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
@ -21,7 +22,7 @@ __version__ = "1.1"
import sys import sys
import zlib import zlib
import zipfilerugged import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
import os import os
import os.path import os.path
import getopt import getopt
@ -49,7 +50,7 @@ class fixZip:
self.inzip = zipfilerugged.ZipFile(zinput,'r') self.inzip = zipfilerugged.ZipFile(zinput,'r')
self.outzip = zipfilerugged.ZipFile(zoutput,'w') self.outzip = zipfilerugged.ZipFile(zoutput,'w')
# open the input zip for reading only as a raw file # open the input zip for reading only as a raw file
self.bzf = file(zinput,'rb') self.bzf = open(zinput,'rb')
def getlocalname(self, zi): def getlocalname(self, zi):
local_header_offset = zi.header_offset local_header_offset = zi.header_offset
@ -115,7 +116,7 @@ class fixZip:
# if epub write mimetype file first, with no compression # if epub write mimetype file first, with no compression
if self.ztype == 'epub': if self.ztype == 'epub':
# first get a ZipInfo with current time and no compression # first get a ZipInfo with current time and no compression
mimeinfo = ZipInfo('mimetype',compress_type=zipfilerugged.ZIP_STORED) mimeinfo = ZipInfo(b'mimetype',compress_type=zipfilerugged.ZIP_STORED)
mimeinfo.internal_attr = 1 # text file mimeinfo.internal_attr = 1 # text file
try: try:
# if the mimetype is present, get its info, including time-stamp # if the mimetype is present, get its info, including time-stamp
@ -129,7 +130,7 @@ class fixZip:
mimeinfo.create_system = oldmimeinfo.create_system mimeinfo.create_system = oldmimeinfo.create_system
except: except:
pass pass
self.outzip.writestr(mimeinfo, _MIMETYPE) self.outzip.writestr(mimeinfo, _MIMETYPE.encode('ascii'))
# write the rest of the files # write the rest of the files
for zinfo in self.inzip.infolist(): for zinfo in self.inzip.infolist():
@ -171,7 +172,7 @@ def repairBook(infile, outfile):
fr = fixZip(infile, outfile) fr = fixZip(infile, outfile)
fr.fix() fr.fix()
return 0 return 0
except Exception, e: except Exception as e:
print("Error Occurred ", e) print("Error Occurred ", e)
return 2 return 2

@ -6,13 +6,14 @@ __license__ = 'GPL v3'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import codecs
import os, traceback, zipfile import os, traceback, zipfile
try: try:
from PyQt5.Qt import QToolButton, QUrl from PyQt5.Qt import QToolButton, QUrl
except ImportError: except ImportError:
from PyQt4.Qt import QToolButton, QUrl from PyQt4.Qt import QToolButton, QUrl
from calibre.gui2 import open_url, question_dialog from calibre.gui2 import open_url, question_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
@ -24,7 +25,7 @@ from calibre.ebooks.metadata.meta import get_metadata
from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog, from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog,
AddEpubFormatsProgressDialog, ResultsSummaryDialog) AddEpubFormatsProgressDialog, ResultsSummaryDialog)
from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME, from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME,
PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME) PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME)
from calibre_plugins.obok_dedrm.utilities import ( from calibre_plugins.obok_dedrm.utilities import (
get_icon, set_plugin_icon_resources, showErrorDlg, format_plural, get_icon, set_plugin_icon_resources, showErrorDlg, format_plural,
@ -53,7 +54,7 @@ class InterfacePluginAction(InterfaceAction):
def genesis(self): def genesis(self):
icon_resources = self.load_resources(PLUGIN_ICONS) icon_resources = self.load_resources(PLUGIN_ICONS)
set_plugin_icon_resources(PLUGIN_NAME, icon_resources) set_plugin_icon_resources(PLUGIN_NAME, icon_resources)
self.qaction.setIcon(get_icon(PLUGIN_ICONS[0])) self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
self.qaction.triggered.connect(self.launchObok) self.qaction.triggered.connect(self.launchObok)
self.gui.keyboard.finalize() self.gui.keyboard.finalize()
@ -106,10 +107,10 @@ class InterfacePluginAction(InterfaceAction):
# Get a list of Kobo titles # Get a list of Kobo titles
books = self.build_book_list() books = self.build_book_list()
if len(books) < 1: if len(books) < 1:
msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed\configured\synchronized?') msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed/configured/synchronized?')
showErrorDlg(msg, None) showErrorDlg(msg, None)
return return
# Check to see if a key can be retrieved using the legacy obok method. # Check to see if a key can be retrieved using the legacy obok method.
legacy_key = legacy_obok().get_legacy_cookie_id legacy_key = legacy_obok().get_legacy_cookie_id
if legacy_key is not None: if legacy_key is not None:
@ -154,7 +155,7 @@ class InterfacePluginAction(InterfaceAction):
# Close Kobo Library object # Close Kobo Library object
self.library.close() self.library.close()
# If we have decrypted books to work with, feed the list of decrypted books details # If we have decrypted books to work with, feed the list of decrypted books details
# and the callback function (self.add_new_books) to the ProgressDialog dispatcher. # and the callback function (self.add_new_books) to the ProgressDialog dispatcher.
if len(self.books_to_add): if len(self.books_to_add):
d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre', d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre',
@ -212,7 +213,7 @@ class InterfacePluginAction(InterfaceAction):
def get_decrypted_kobo_books(self, book): def get_decrypted_kobo_books(self, book):
''' '''
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books
:param book: A KoboBook object that is to be decrypted. :param book: A KoboBook object that is to be decrypted.
''' '''
print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title)) print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title))
@ -233,7 +234,7 @@ class InterfacePluginAction(InterfaceAction):
''' '''
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre
(It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog) (It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog)
:param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books) :param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books)
''' '''
added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False) added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False)
@ -253,7 +254,7 @@ class InterfacePluginAction(InterfaceAction):
def add_epub_format(self, book_id, mi, path): def add_epub_format(self, book_id, mi, path):
''' '''
This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py
:param book_id: calibre ID of the book to add the encrypted epub to. :param book_id: calibre ID of the book to add the encrypted epub to.
:param mi: calibre metadata object :param mi: calibre metadata object
:param path: path to the decrypted epub (temp file) :param path: path to the decrypted epub (temp file)
@ -281,7 +282,7 @@ class InterfacePluginAction(InterfaceAction):
self.formats_to_add.append((home_id, mi, tmp_file)) self.formats_to_add.append((home_id, mi, tmp_file))
else: else:
self.no_home_for_book.append(mi) self.no_home_for_book.append(mi)
# If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book # If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book
# details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher. # details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher.
if self.formats_to_add: if self.formats_to_add:
d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format) d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format)
@ -306,10 +307,10 @@ class InterfacePluginAction(InterfaceAction):
sd = ResultsSummaryDialog(self.gui, caption, msg, log) sd = ResultsSummaryDialog(self.gui, caption, msg, log)
sd.exec_() sd.exec_()
return return
def ask_about_inserting_epubs(self): def ask_about_inserting_epubs(self):
''' '''
Build question dialog with details about kobo books Build question dialog with details about kobo books
that couldn't be added to calibre as new books. that couldn't be added to calibre as new books.
''' '''
''' Terisa: Improve the message ''' Terisa: Improve the message
@ -327,13 +328,13 @@ class InterfacePluginAction(InterfaceAction):
msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2]) msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2])
msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />') msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />')
msg += _('NOTE: no pre-existing EPUB will be overwritten.') msg += _('NOTE: no pre-existing EPUB will be overwritten.')
return question_dialog(self.gui, caption, msg, det_msg) return question_dialog(self.gui, caption, msg, det_msg)
def find_a_home(self, ids): def find_a_home(self, ids):
''' '''
Find the ID of the first EPUB-Free duplicate available Find the ID of the first EPUB-Free duplicate available
:param ids: List of calibre IDs that might serve as a home. :param ids: List of calibre IDs that might serve as a home.
''' '''
for id in ids: for id in ids:
@ -373,7 +374,7 @@ class InterfacePluginAction(InterfaceAction):
zin = zipfile.ZipFile(book.filename, 'r') zin = zipfile.ZipFile(book.filename, 'r')
#print ('Kobo library filename: {0}'.format(book.filename)) #print ('Kobo library filename: {0}'.format(book.filename))
for userkey in self.userkeys: for userkey in self.userkeys:
print (_('Trying key: '), userkey.encode('hex_codec')) print (_('Trying key: '), codecs.encode(userkey, 'hex'))
check = True check = True
try: try:
fileout = PersistentTemporaryFile('.epub', dir=self.tdir) fileout = PersistentTemporaryFile('.epub', dir=self.tdir)
@ -455,7 +456,7 @@ class InterfacePluginAction(InterfaceAction):
if cancelled_count > 0: if cancelled_count > 0:
log += _('<p><b>Book imports cancelled by user:</b> {}</p>\n').format(cancelled_count) log += _('<p><b>Book imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
return (msg, log) return (msg, log)
log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds)) log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds))
if self.successful_format_adds: if self.successful_format_adds:
log += '<ul>\n' log += '<ul>\n'
for id, mi in self.successful_format_adds: for id, mi in self.successful_format_adds:
@ -474,7 +475,7 @@ class InterfacePluginAction(InterfaceAction):
log += _('<p><b>Format imports cancelled by user:</b> {}</p>\n').format(cancelled_count) log += _('<p><b>Format imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
return (msg, log) return (msg, log)
else: else:
# Single book ... don't get fancy. # Single book ... don't get fancy.
if self.ids_of_new_books: if self.ids_of_new_books:
title = self.ids_of_new_books[0][1].title title = self.ids_of_new_books[0][1].title
@ -494,4 +495,4 @@ class InterfacePluginAction(InterfaceAction):
reason = _('of unknown reasons. Gosh I\'m embarrassed!') reason = _('of unknown reasons. Gosh I\'m embarrassed!')
msg = _('<p>{0} not added because {1}').format(title, reason) msg = _('<p>{0} not added because {1}').format(title, reason)
return (msg, log) return (msg, log)

@ -427,7 +427,7 @@ class KeyValueComboBox(QComboBox):
def selected_key(self): def selected_key(self):
for key, value in self.values.iteritems(): for key, value in self.values.iteritems():
if value == unicode(self.currentText()).strip(): if value == self.currentText().strip():
return key return key
@ -450,7 +450,7 @@ class KeyComboBox(QComboBox):
def selected_key(self): def selected_key(self):
for key, value in self.values.iteritems(): for key, value in self.values.iteritems():
if key == unicode(self.currentText()).strip(): if key == self.currentText().strip():
return key return key

@ -75,7 +75,7 @@ class ConfigWidget(QWidget):
def save_settings(self): def save_settings(self):
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText()) plugin_prefs['finding_homes_for_formats'] = self.find_homes.currentText()
plugin_prefs['kobo_serials'] = self.tmpserials plugin_prefs['kobo_serials'] = self.tmpserials
plugin_prefs['kobo_directory'] = self.kobodirectory plugin_prefs['kobo_directory'] = self.kobodirectory
@ -165,7 +165,7 @@ class ManageKeysDialog(QDialog):
def delete_key(self): def delete_key(self):
if not self.listy.currentItem(): if not self.listy.currentItem():
return return
keyname = unicode(self.listy.currentItem().text()) keyname = self.listy.currentItem().text()
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
return return
self.plugin_keys.remove(keyname) self.plugin_keys.remove(keyname)
@ -202,11 +202,11 @@ class AddSerialDialog(QDialog):
@property @property
def key_name(self): def key_name(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
@property @property
def key_value(self): def key_value(self):
return unicode(self.key_ledit.text()).strip() return self.key_ledit.text().strip()
def accept(self): def accept(self):
if len(self.key_name) == 0 or self.key_name.isspace(): if len(self.key_name) == 0 or self.key_name.isspace():

@ -1,6 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Version 4.0.0 September 2020
# Python 3.0
#
# Version 3.2.5 December 2016 # Version 3.2.5 December 2016
# Improve detection of good text decryption. # Improve detection of good text decryption.
# #
@ -152,8 +155,8 @@
"""Manage all Kobo books, either encrypted or DRM-free.""" """Manage all Kobo books, either encrypted or DRM-free."""
from __future__ import print_function from __future__ import print_function
__version__ = '3.2.4' __version__ = '4.0.0'
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__) __about__ = u"Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
import sys import sys
import os import os
@ -231,7 +234,7 @@ def _load_crypto_libcrypto():
raise ENCRYPTIONError(_('Failed to initialize AES key')) raise ENCRYPTIONError(_('Failed to initialize AES key'))
def decrypt(self, data): def decrypt(self, data):
clear = '' clear = b''
for i in range(0, len(data), 16): for i in range(0, len(data), 16):
out = create_string_buffer(16) out = create_string_buffer(16)
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0) rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
@ -276,7 +279,7 @@ class SafeUnbuffered:
if self.encoding == None: if self.encoding == None:
self.encoding = "utf-8" self.encoding = "utf-8"
def write(self, data): def write(self, data):
if isinstance(data,unicode): if isinstance(data,bytes):
data = data.encode(self.encoding,"replace") data = data.encode(self.encoding,"replace")
self.stream.write(data) self.stream.write(data)
self.stream.flush() self.stream.flush()
@ -381,7 +384,7 @@ class KoboLibrary(object):
print(self.newdb.name) print(self.newdb.name)
olddb = open(kobodb, 'rb') olddb = open(kobodb, 'rb')
self.newdb.write(olddb.read(18)) self.newdb.write(olddb.read(18))
self.newdb.write('\x01\x01') self.newdb.write(b'\x01\x01')
olddb.read(2) olddb.read(2)
self.newdb.write(olddb.read()) self.newdb.write(olddb.read())
olddb.close() olddb.close()
@ -488,14 +491,14 @@ class KoboLibrary(object):
pass pass
row = cursor.fetchone() row = cursor.fetchone()
return userids return userids
def __getuserkeys (self, macaddr): def __getuserkeys (self, macaddr):
userids = self.__getuserids() userids = self.__getuserids()
userkeys = [] userkeys = []
for hash in KOBO_HASH_KEYS: for hash in KOBO_HASH_KEYS:
deviceid = hashlib.sha256(hash + macaddr).hexdigest() deviceid = hashlib.sha256((hash + macaddr).encode('ascii')).hexdigest()
for userid in userids: for userid in userids:
userkey = hashlib.sha256(deviceid + userid).hexdigest() userkey = hashlib.sha256((deviceid + userid).encode('ascii')).hexdigest()
userkeys.append(binascii.a2b_hex(userkey[32:])) userkeys.append(binascii.a2b_hex(userkey[32:]))
return userkeys return userkeys
@ -556,7 +559,7 @@ class KoboBook(object):
# Convert relative URIs # Convert relative URIs
href = item.attrib['href'] href = item.attrib['href']
if not c.match(href): if not c.match(href):
href = string.join((basedir, href), '') href = ''.join((basedir, href))
# Update books we've found from the DB. # Update books we've found from the DB.
if href in self._encryptedfiles: if href in self._encryptedfiles:
@ -606,57 +609,57 @@ class KoboFile(object):
stride = 1 stride = 1
print(u"Checking text:{0}:".format(contents[:10])) print(u"Checking text:{0}:".format(contents[:10]))
# check for byte order mark # check for byte order mark
if contents[:3]=="\xef\xbb\xbf": if contents[:3]==b"\xef\xbb\xbf":
# seems to be utf-8 with BOM # seems to be utf-8 with BOM
print(u"Could be utf-8 with BOM") print(u"Could be utf-8 with BOM")
textoffset = 3 textoffset = 3
elif contents[:2]=="\xfe\xff": elif contents[:2]==b"\xfe\xff":
# seems to be utf-16BE # seems to be utf-16BE
print(u"Could be utf-16BE") print(u"Could be utf-16BE")
textoffset = 3 textoffset = 3
stride = 2 stride = 2
elif contents[:2]=="\xff\xfe": elif contents[:2]==b"\xff\xfe":
# seems to be utf-16LE # seems to be utf-16LE
print(u"Could be utf-16LE") print(u"Could be utf-16LE")
textoffset = 2 textoffset = 2
stride = 2 stride = 2
else: else:
print(u"Perhaps utf-8 without BOM") print(u"Perhaps utf-8 without BOM")
# now check that the first few characters are in the ASCII range # now check that the first few characters are in the ASCII range
for i in xrange(textoffset,textoffset+5*stride,stride): for i in range(textoffset,textoffset+5*stride,stride):
if ord(contents[i])<32 or ord(contents[i])>127: if contents[i]<32 or contents[i]>127:
# Non-ascii, so decryption probably failed # Non-ascii, so decryption probably failed
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i]))) print(u"Bad character at {0}, value {1}".format(i,contents[i]))
raise ValueError raise ValueError
print(u"Seems to be good text") print(u"Seems to be good text")
return True return True
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml": if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml":
# utf-8 # utf-8
return True return True
elif contents[:14]=="\xfe\xff\x00<\x00?\x00x\x00m\x00l": elif contents[:14]==b"\xfe\xff\x00<\x00?\x00x\x00m\x00l":
# utf-16BE # utf-16BE
return True return True
elif contents[:14]=="\xff\xfe<\x00?\x00x\x00m\x00l\x00": elif contents[:14]==b"\xff\xfe<\x00?\x00x\x00m\x00l\x00":
# utf-16LE # utf-16LE
return True return True
elif contents[:9]=="<!DOCTYPE" or contents[:12]=="\xef\xbb\xbf<!DOCTYPE": elif contents[:9]==b"<!DOCTYPE" or contents[:12]==b"\xef\xbb\xbf<!DOCTYPE":
# utf-8 of weird <!DOCTYPE start # utf-8 of weird <!DOCTYPE start
return True return True
elif contents[:22]=="\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E": elif contents[:22]==b"\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
# utf-16BE of weird <!DOCTYPE start # utf-16BE of weird <!DOCTYPE start
return True return True
elif contents[:22]=="\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00": elif contents[:22]==b"\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
# utf-16LE of weird <!DOCTYPE start # utf-16LE of weird <!DOCTYPE start
return True return True
else: else:
print(u"Bad XML: {0}".format(contents[:8])) print(u"Bad XML: {0}".format(contents[:8]))
raise ValueError raise ValueError
elif self.mimetype == 'image/jpeg': elif self.mimetype == 'image/jpeg':
if contents[:3] == '\xff\xd8\xff': if contents[:3] == b'\xff\xd8\xff':
return True return True
else: else:
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex'))) print(u"Bad JPEG: {0}".format(contents[:3].hex()))
raise ValueError() raise ValueError()
return False return False
@ -690,7 +693,7 @@ def decrypt_book(book, lib):
return 0 return 0
result = 1 result = 1
for userkey in lib.userkeys: for userkey in lib.userkeys:
print(u"Trying key: {0}".format(userkey.encode('hex_codec'))) print(u"Trying key: {0}".format(userkey.hex()))
try: try:
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED) zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
for filename in zin.namelist(): for filename in zin.namelist():
@ -735,7 +738,7 @@ def cli_main():
print(u"{0}: {1}".format(i + 1, book.title)) print(u"{0}: {1}".format(i + 1, book.title))
print(u"Or 'all'") print(u"Or 'all'")
choice = raw_input(u"Convert book number... ") choice = input(u"Convert book number... ")
if choice == u'all': if choice == u'all':
books = list(lib.books) books = list(lib.books)
else: else:

@ -7,21 +7,24 @@ __docformat__ = 'restructuredtext en'
import os, struct, time import os, struct, time
from StringIO import StringIO try:
from StringIO import StringIO
except ImportError:
from io import StringIO
from traceback import print_exc from traceback import print_exc
try: try:
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem) from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
except ImportError: except ImportError:
from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem) from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
from calibre.constants import iswindows, DEBUG from calibre.constants import iswindows, DEBUG
from calibre import prints from calibre import prints
from calibre.gui2 import (error_dialog, gprefs) from calibre.gui2 import (error_dialog, gprefs)
from calibre.gui2.actions import menu_action_unique_name from calibre.gui2.actions import menu_action_unique_name
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION) PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
plugin_ID = None plugin_ID = None
@ -39,7 +42,7 @@ else:
def convert_qvariant(x): def convert_qvariant(x):
vt = x.type() vt = x.type()
if vt == x.String: if vt == x.String:
return unicode(x.toString()) return x.toString()
if vt == x.List: if vt == x.List:
return [convert_qvariant(i) for i in x.toList()] return [convert_qvariant(i) for i in x.toList()]
return x.toPyObject() return x.toPyObject()
@ -62,7 +65,7 @@ except NameError:
def format_plural(number, possessive=False): def format_plural(number, possessive=False):
''' '''
Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations
:param: number: variable that represents the count/len of something :param: number: variable that represents the count/len of something
''' '''
if not possessive: if not possessive:
@ -141,7 +144,7 @@ def showErrorDlg(errmsg, parent, trcbk=False):
''' '''
if trcbk: if trcbk:
error= '' error= ''
f=StringIO() f=StringIO()
print_exc(file=f) print_exc(file=f)
error_mess = f.getvalue().splitlines() error_mess = f.getvalue().splitlines()
for line in error_mess: for line in error_mess:

@ -4,7 +4,7 @@
from __future__ import with_statement from __future__ import with_statement
# ignoblekey.py # ignoblekey.py
# Copyright © 2015 Apprentice Alf and Apprentice Harper # Copyright © 2015-2020 Apprentice Harper et al.
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf # Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
@ -14,13 +14,14 @@ from __future__ import with_statement
# Revision history: # Revision history:
# 1.0 - Initial release # 1.0 - Initial release
# 1.1 - remove duplicates and return last key as single key # 1.1 - remove duplicates and return last key as single key
# 2.0 - Python 3
""" """
Get Barnes & Noble EPUB user key from nook Studio log file Get Barnes & Noble EPUB user key from nook Studio log file
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "1.1" __version__ = "2.0"
import sys import sys
import os import os
@ -318,7 +319,7 @@ def gui_main():
keyfileout.write(key) keyfileout.write(key)
success = True success = True
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile)) tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
except DrmException, e: except DrmException as e:
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e))) tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
except Exception: except Exception:
root.wm_state('normal') root.wm_state('normal')

@ -13,10 +13,10 @@ import os
import shutil import shutil
DEDRM_SRC_DIR = 'DeDRM_Plugin' DEDRM_SRC_DIR = 'DeDRM_plugin'
DEDRM_README= 'DeDRM_Plugin_ReadMe.txt' DEDRM_README= 'DeDRM_plugin_ReadMe.txt'
OBOK_SRC_DIR = 'Obok_plugin' OBOK_SRC_DIR = 'Obok_plugin'
OBOK_README = 'Obok_plugin_ReadMe.txt' OBOK_README = 'obok_plugin_ReadMe.txt'
RELEASE_DIR = 'release' RELEASE_DIR = 'release'
def make_calibre_plugin(): def make_calibre_plugin():

Loading…
Cancel
Save