diff --git a/DeDRM_plugin/__init__.py b/DeDRM_plugin/__init__.py index d2cbc2b..00640d1 100644 --- a/DeDRM_plugin/__init__.py +++ b/DeDRM_plugin/__init__.py @@ -7,7 +7,7 @@ from __future__ import with_statement # Copyright © 2008-2020 Apprentice Harper et al. __license__ = 'GPL v3' -__version__ = '6.8.0' +__version__ = '7.0.0' __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.7.0 - Handle new library in calibre. # 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. """ 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]) # Include an html helpfile in the plugin's zipfile with the following name. RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' +import codecs import sys, os, re import time import zipfile @@ -107,7 +109,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") try: self.stream.write(data) @@ -165,7 +167,7 @@ class DeDRM(FileTypePlugin): else: names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"] 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(): file_path = os.path.join(self.alfdir, entry) @@ -177,7 +179,7 @@ class DeDRM(FileTypePlugin): try: open(file_path,'wb').write(data) 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() pass @@ -187,7 +189,7 @@ class DeDRM(FileTypePlugin): # mark that this version has been initialized os.mkdir(self.verdir) - except Exception, e: + except Exception as e: traceback.print_exc() raise @@ -198,11 +200,11 @@ class DeDRM(FileTypePlugin): inf = self.temporary_file(u".epub") 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.fix() - except Exception, e: - print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]) + except Exception as e: + print(u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])) raise Exception(e) # import the decryption keys @@ -215,19 +217,19 @@ class DeDRM(FileTypePlugin): #check the book 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). for keyname, userkey in dedrmprefs['bandnkeys'].items(): 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") # Give the user key, ebook and TemporaryPersistent file to the decryption function. try: result = ignobleepub.decryptBook(userkey, inf.name, of.name) 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() result = 1 @@ -238,10 +240,10 @@ class DeDRM(FileTypePlugin): # Return the modified PersistentTemporary file to calibre. 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 - 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 defaultkeys = [] @@ -258,7 +260,7 @@ class DeDRM(FileTypePlugin): defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix']) 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() newkeys = [] @@ -269,7 +271,7 @@ class DeDRM(FileTypePlugin): if len(newkeys) > 0: try: 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") @@ -277,7 +279,7 @@ class DeDRM(FileTypePlugin): try: result = ignobleepub.decryptBook(userkey, inf.name, of.name) 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() result = 1 @@ -286,59 +288,59 @@ class DeDRM(FileTypePlugin): if result == 0: # Decryption was a success # 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: dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue) 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: - 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() # Return the modified PersistentTemporary file to calibre. 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) - except Exception, e: + 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 as e: 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)) # import the Adobe Adept ePub handler import calibre_plugins.dedrm.ineptepub as ineptepub 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). for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): - userkey = userkeyhex.decode('hex') - print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + userkey = codecs.decode(userkeyhex, 'hex') + print(u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)) of = self.temporary_file(u".epub") # Give the user key, ebook and TemporaryPersistent file to the decryption function. try: result = ineptepub.decryptBook(userkey, inf.name, of.name) 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() result = 1 try: of.close() 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: # Decryption was successful. # 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 - 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 - 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 defaultkeys = [] @@ -356,7 +358,7 @@ class DeDRM(FileTypePlugin): self.default_key = defaultkeys[0] 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() self.default_key = u"" @@ -368,14 +370,14 @@ class DeDRM(FileTypePlugin): if len(newkeys) > 0: try: 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") # Give the user key, ebook and TemporaryPersistent file to the decryption function. try: result = ineptepub.decryptBook(userkey, inf.name, of.name) 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() result = 1 @@ -384,31 +386,31 @@ class DeDRM(FileTypePlugin): if result == 0: # Decryption was a success # 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: dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) 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: - 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() - 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 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) - except Exception, 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}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) + 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)) traceback.print_exc() pass # 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)) # Not a Barnes & Noble nor an Adobe Adept # 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)) def PDFDecrypt(self,path_to_ebook): @@ -417,17 +419,17 @@ class DeDRM(FileTypePlugin): dedrmprefs = prefs.DeDRM_Prefs() # 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(): - userkey = userkeyhex.decode('hex') - print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname) + userkey = codecs.decode(userkeyhex, 'hex') + print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)) of = self.temporary_file(u".pdf") # Give the user key, ebook and TemporaryPersistent file to the decryption function. try: result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) 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() result = 1 @@ -438,10 +440,10 @@ class DeDRM(FileTypePlugin): # Return the modified PersistentTemporary file to calibre. 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 - 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 defaultkeys = [] @@ -459,7 +461,7 @@ class DeDRM(FileTypePlugin): self.default_key = defaultkeys[0] 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() self.default_key = u"" @@ -471,14 +473,14 @@ class DeDRM(FileTypePlugin): if len(newkeys) > 0: try: 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") # Give the user key, ebook and TemporaryPersistent file to the decryption function. try: result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name) 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() result = 1 @@ -487,23 +489,23 @@ class DeDRM(FileTypePlugin): if result == 0: # Decryption was a success # 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: dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex')) 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: - 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() # Return the modified PersistentTemporary file to calibre. 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) - except Exception, e: + 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 as e: pass # 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)) @@ -530,12 +532,12 @@ class DeDRM(FileTypePlugin): try: book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime) - except Exception, e: + except Exception as e: decoded = False # perhaps we need to get a new default Kindle for Mac/PC key defaultkeys = [] - print u"{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}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])) + print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) try: if iswindows or isosx: @@ -548,7 +550,7 @@ class DeDRM(FileTypePlugin): scriptpath = os.path.join(self.alfdir,u"kindlekey.py") defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix']) 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() pass @@ -558,20 +560,20 @@ class DeDRM(FileTypePlugin): if keyvalue not in dedrmprefs['kindlekeys'].values(): newkeys[keyname] = keyvalue 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: book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime) decoded = True # 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(): dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue) dedrmprefs.writeprefs() - except Exception, e: + except Exception as e: pass if not decoded: #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)) of = self.temporary_file(book.getBookExtension()) @@ -590,7 +592,7 @@ class DeDRM(FileTypePlugin): # Attempt to decrypt epub with each encryption key (generated or provided). for keyname, userkey in dedrmprefs['ereaderkeys'].items(): 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") # 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 # file to Calibre's import process. 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 - 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)) @@ -616,7 +618,7 @@ class DeDRM(FileTypePlugin): sys.stdout=SafeUnbuffered(sys.stdout) 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() booktype = os.path.splitext(path_to_ebook)[1].lower()[1:] @@ -635,9 +637,9 @@ class DeDRM(FileTypePlugin): # Adobe Adept or B&N ePub decrypted_ebook = self.ePubDecrypt(path_to_ebook) 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 - 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 def is_customizable(self): diff --git a/DeDRM_plugin/adobekey.py b/DeDRM_plugin/adobekey.py index 2ede7c2..fb5dcca 100644 --- a/DeDRM_plugin/adobekey.py +++ b/DeDRM_plugin/adobekey.py @@ -48,14 +48,16 @@ from __future__ import with_statement # 5.8 - Added getkey interface for Windows DeDRM application # 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 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. """ -from __future__ import print_function __license__ = 'GPL v3' -__version__ = '6.0' +__version__ = '7.0' import sys, os, struct, getopt @@ -118,7 +120,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return arg class ADEPTError(Exception): pass @@ -497,7 +499,7 @@ def cli_main(): try: 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])) usage(progname) sys.exit(2) @@ -586,7 +588,7 @@ def gui_main(): keyfileout.write(key) success = True 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))) except Exception: root.wm_state('normal') diff --git a/DeDRM_plugin/aescbc.py b/DeDRM_plugin/aescbc.py index 5667511..28e1843 100644 --- a/DeDRM_plugin/aescbc.py +++ b/DeDRM_plugin/aescbc.py @@ -13,6 +13,8 @@ CryptoPy Artisitic License Version 1.0 See the wonderful pure python package cryptopy-1.2.5 and read its LICENSE.txt for complete license details. + + Adjusted for Python 3 compatibility, September 2020 """ class CryptoError(Exception): @@ -101,7 +103,7 @@ class BlockCipher: numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) if more == None: # no more calls to decrypt, should have all the data 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 if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : @@ -143,7 +145,7 @@ class padWithPadLen(Pad): def removePad(self, paddedBinaryString, blockSize): """ Remove padding from a binary string """ if not(0> 8)) ^ byteXorVal) & 0xFF if decryption: keyXorVal = curByte * 257; - for j in xrange(8): + for j in range(8): wkey[j] ^= keyXorVal; dst+=chr(curByte) return dst diff --git a/DeDRM_plugin/androidkindlekey.py b/DeDRM_plugin/androidkindlekey.py index a264c7d..f05c468 100644 --- a/DeDRM_plugin/androidkindlekey.py +++ b/DeDRM_plugin/androidkindlekey.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import with_statement +from __future__ import print_function # androidkindlekey.py # 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.4 - Fix some problems 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. """ -from __future__ import print_function __license__ = 'GPL v3' -__version__ = '1.5' +__version__ = '2.0' import os import sys @@ -34,7 +35,10 @@ import tempfile import zlib import tarfile 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 # Routines common to Mac and PC @@ -49,7 +53,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -90,7 +94,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"kindlekey.py"] @@ -98,7 +102,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv class DrmException(Exception): pass @@ -248,7 +252,7 @@ def get_serials2(path=STORAGE2): traceback.print_exc() pass tokens = list(set(tokens)) - + serials = [] for x in dsns: serials.append(x) @@ -313,7 +317,7 @@ __all__ = [ 'get_serials', 'getkey'] def getkey(outfile, inpath): keys = get_serials(inpath) if len(keys) > 0: - with file(outfile, 'w') as keyfileout: + with open(outfile, 'w') as keyfileout: for key in keys: keyfileout.write(key) keyfileout.write("\n") @@ -340,7 +344,7 @@ def cli_main(): try: opts, args = getopt.getopt(argv[1:], "hb:") - except getopt.GetoptError, err: + except getopt.GetoptError as err: usage(progname) print(u"\nError in options or arguments: {0}".format(err.args[0])) return 2 @@ -444,11 +448,11 @@ def gui_main(): if not os.path.exists(outfile): break - with file(outfile, 'w') as keyfileout: + with open(outfile, 'w') as keyfileout: keyfileout.write(key) success = True 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]) return self.status['text'] = u"Select backup.ab file" diff --git a/DeDRM_plugin/argv_utils.py b/DeDRM_plugin/argv_utils.py index 85ffaa4..71f0794 100644 --- a/DeDRM_plugin/argv_utils.py +++ b/DeDRM_plugin/argv_utils.py @@ -42,7 +42,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return arg def add_cp65001_codec(): diff --git a/DeDRM_plugin/askfolder_ed.py b/DeDRM_plugin/askfolder_ed.py index a4a2ae0..1a85513 100644 --- a/DeDRM_plugin/askfolder_ed.py +++ b/DeDRM_plugin/askfolder_ed.py @@ -29,6 +29,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +# Adjusted for Python 3, September 2020 + """ AskFolder(...) -- Ask the user to select a folder Windows specific """ @@ -164,15 +166,15 @@ def AskFolder( def BrowseCallback(hwnd, uMsg, lParam, lpData): if uMsg == BFFM_INITIALIZED: if actionButtonLabel: - label = unicode(actionButtonLabel, errors='replace') + label = actionButtonLabel.decode('utf-8', 'replace') user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label) if cancelButtonLabel: - label = unicode(cancelButtonLabel, errors='replace') + label = cancelButtonLabel.decode('utf-8', 'replace') cancelButton = user32.GetDlgItem(hwnd, IDCANCEL) if cancelButton: user32.SetWindowTextW(cancelButton, label) if windowTitle: - title = unicode(windowTitle, erros='replace') + title = windowTitle.decode('utf-8', 'replace') user32.SetWindowTextW(hwnd, title) if defaultLocation: user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\')) diff --git a/DeDRM_plugin/config.py b/DeDRM_plugin/config.py index 9bfae68..2ef3223 100644 --- a/DeDRM_plugin/config.py +++ b/DeDRM_plugin/config.py @@ -6,6 +6,8 @@ from __future__ import print_function __license__ = 'GPL v3' +# Added Python 3 compatibility, September 2020 + # Standard Python modules. import os, traceback, json @@ -22,7 +24,7 @@ try: from PyQt5 import Qt as QtGui except ImportError: from PyQt4 import QtGui - + from zipfile import ZipFile # calibre modules and constants. @@ -123,7 +125,7 @@ class ConfigWidget(QWidget): def kindle_serials(self): d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog) d.exec_() - + def kindle_android(self): d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a') d.exec_() @@ -289,7 +291,7 @@ class ManageKeysDialog(QDialog): def getwineprefix(self): if self.wineprefix is not None: - return unicode(self.wp_lineedit.text()).strip() + return self.wp_lineedit.text().strip() return u"" def populate_list(self): @@ -338,7 +340,7 @@ class ManageKeysDialog(QDialog): if d.result() != d.Accepted: # rename cancelled or moot. 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 {0} to {1}?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False): return self.plugin_keys[d.key_name] = self.plugin_keys[keyname] @@ -350,7 +352,7 @@ class ManageKeysDialog(QDialog): def delete_key(self): if not self.listy.currentItem(): 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} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): return if type(self.plugin_keys) == dict: @@ -388,7 +390,7 @@ class ManageKeysDialog(QDialog): with open(fpath,'rb') as keyfile: new_key_value = keyfile.read() if self.binary_file: - new_key_value = new_key_value.encode('hex') + new_key_value = new_key_value.hex() elif self.json_file: new_key_value = json.loads(new_key_value) elif self.android_file: @@ -412,7 +414,7 @@ class ManageKeysDialog(QDialog): else: counter += 1 self.plugin_keys[new_key_name] = new_key_value - + msg = u"" if counter+skipped > 1: if counter > 0: @@ -434,7 +436,7 @@ class ManageKeysDialog(QDialog): r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False) 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 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)])] @@ -485,7 +487,7 @@ class RenameKeyDialog(QDialog): self.resize(self.sizeHint()) 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!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False) @@ -506,7 +508,7 @@ class RenameKeyDialog(QDialog): @property 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 def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): - return unicode(self.key_display.text()).strip() + return self.key_display.text().strip() @property def user_name(self): - return unicode(self.name_ledit.text()).strip().lower().replace(' ','') + return self.name_ledit.text().strip().lower().replace(' ','') @property def cc_number(self): - return unicode(self.cc_ledit.text()).strip() + return self.cc_ledit.text().strip() def retrieve_key(self): from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key @@ -675,7 +677,7 @@ class AddEReaderDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): @@ -684,11 +686,11 @@ class AddEReaderDialog(QDialog): @property def user_name(self): - return unicode(self.name_ledit.text()).strip().lower().replace(' ','') + return self.name_ledit.text().strip().lower().replace(' ','') @property def cc_number(self): - return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','') + return self.cc_ledit.text().strip().replace(' ', '').replace('-','') def accept(self): @@ -758,7 +760,7 @@ class AddAdeptDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property 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.setAlignment(Qt.AlignHCenter) layout.addWidget(default_key_error) - + # if no default, both buttons do the same self.button_box.accepted.connect(self.reject) @@ -830,7 +832,7 @@ class AddKindleDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): @@ -876,11 +878,11 @@ class AddSerialDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): - return unicode(self.key_ledit.text()).replace(' ', '') + return self.key_ledit.text().replace(' ', '') def accept(self): 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.setAlignment(Qt.AlignHCenter) file_group.addWidget(self.selected_file_name) - + key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) @@ -926,7 +928,7 @@ class AddAndroidDialog(QDialog): #key_label = QLabel(_(''), self) #key_label.setAlignment(Qt.AlignHCenter) #data_group_box_layout.addWidget(key_label) - + self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) @@ -934,16 +936,16 @@ class AddAndroidDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def file_name(self): - return unicode(self.selected_file_name.text()).strip() + return self.selected_file_name.text().strip() @property def key_value(self): return self.serials_from_file - + def get_android_file(self): 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" @@ -961,7 +963,7 @@ class AddAndroidDialog(QDialog): file_name = os.path.basename(self.filename) self.serials_from_file.extend(file_serials) self.selected_file_name.setText(file_name) - + def accept(self): if len(self.file_name) == 0 or len(self.key_value) == 0: @@ -1004,11 +1006,11 @@ class AddPIDDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): diff --git a/DeDRM_plugin/convert2xml.py b/DeDRM_plugin/convert2xml.py index 3b65c54..aa794b1 100644 --- a/DeDRM_plugin/convert2xml.py +++ b/DeDRM_plugin/convert2xml.py @@ -1,6 +1,7 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # For use with Topaz Scripts Version 2.6 +# Added Python 3 compatibility, September 2020 from __future__ import print_function class Unbuffered: @@ -107,7 +108,7 @@ def readString(file): def convert(i): result = '' val = encodeNumber(i) - for j in xrange(len(val)): + for j in range(len(val)): c = ord(val[j:j+1]) result += '%02x' % c return result @@ -121,10 +122,10 @@ class Dictionary(object): def __init__(self, dictFile): self.filename = dictFile self.size = 0 - self.fo = file(dictFile,'rb') + self.fo = open(dictFile,'rb') self.stable = [] 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.pos = 0 @@ -151,7 +152,7 @@ class Dictionary(object): return self.pos 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])) return @@ -161,7 +162,7 @@ class Dictionary(object): class PageParser(object): 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.dict = dict self.debug = debug @@ -392,7 +393,7 @@ class PageParser(object): 'startID' : (0, 'number', 1, 1), 'startID.page' : (1, 'number', 0, 0), 'startID.id' : (1, 'number', 0, 0), - + 'median_d' : (1, 'number', 0, 0), 'median_h' : (1, 'number', 0, 0), 'median_firsty' : (1, 'number', 0, 0), @@ -420,7 +421,7 @@ class PageParser(object): def get_tagpath(self, i): cnt = len(self.tagpath) 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] return result @@ -472,7 +473,7 @@ class PageParser(object): if self.debug : print('Processing: ', self.get_tagpath(0)) cnt = self.tagpath_len() - for j in xrange(cnt): + for j in range(cnt): tkn = self.get_tagpath(j) if tkn in self.token_tags : num_args = self.token_tags[tkn][0] @@ -497,7 +498,7 @@ class PageParser(object): if (subtags == 1): ntags = readEncodedNumber(self.fo) if self.debug : print('subtags: ' + token + ' has ' + str(ntags)) - for j in xrange(ntags): + for j in range(ntags): val = readEncodedNumber(self.fo) subtagres.append(self.procToken(self.dict.lookup(val))) @@ -511,7 +512,7 @@ class PageParser(object): argres = self.decodeCMD(arg,argtype) else : # num_arg scalar arguments - for i in xrange(num_args): + for i in range(num_args): argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) # 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 += 'end of each snippet. \n' print(result) - for i in xrange(cnt): + for i in range(cnt): if self.debug: print('Snippet:',str(i)) snippet = [] snippet.append(i) @@ -565,12 +566,12 @@ class PageParser(object): adj = readEncodedNumber(self.fo) mode = mode >> 1 x = [] - for i in xrange(cnt): + for i in range(cnt): x.append(readEncodedNumber(self.fo) - adj) - for i in xrange(mode): - for j in xrange(1, cnt): + for i in range(mode): + for j in range(1, cnt): x[j] = x[j] + x[j - 1] - for i in xrange(cnt): + for i in range(cnt): result.append(self.formatArg(x[i],argtype)) return result @@ -844,7 +845,7 @@ def main(argv): try: opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) - except getopt.GetoptError, err: + except getopt.GetoptError as err: # print help information and exit: print(str(err)) # will print something like "option -a not recognized" diff --git a/DeDRM_plugin/epubtest.py b/DeDRM_plugin/epubtest.py index 2b00fe7..1a08e61 100644 --- a/DeDRM_plugin/epubtest.py +++ b/DeDRM_plugin/epubtest.py @@ -10,6 +10,7 @@ # Changelog epubtest # 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf # 1.01 - Added routine for use by Windows DeDRM +# 2.00 - Added Python 3 compatibility, September 2020 # # Written in 2011 by Paul Durrant # Released with unlicense. See http://unlicense.org/ @@ -47,7 +48,7 @@ from __future__ import with_statement from __future__ import print_function -__version__ = '1.01' +__version__ = '2.0' import sys, struct, os, traceback import zlib @@ -116,7 +117,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return arg _FILENAME_LEN_OFFSET = 26 _EXTRA_LEN_OFFSET = 28 diff --git a/DeDRM_plugin/erdr2pml.py b/DeDRM_plugin/erdr2pml.py index 1dfef42..b02a494 100644 --- a/DeDRM_plugin/erdr2pml.py +++ b/DeDRM_plugin/erdr2pml.py @@ -67,8 +67,9 @@ # - Ignore sidebars for dictionaries (different format?) # 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 +# 1.00 - Added Python 3 compatibility for calibre 5.0 -__version__='0.23' +__version__='1.00' import sys, re import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback @@ -88,7 +89,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -126,7 +127,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"mobidedrm.py"] @@ -134,7 +135,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv Des = None if iswindows: @@ -200,7 +201,7 @@ class Sectionizer(object): bkType = "Book" def __init__(self, filename, ident): - self.contents = file(filename, 'rb').read() + self.contents = open(filename, 'rb').read() self.header = self.contents[0:72] self.num_sections, = struct.unpack('>H', self.contents[76:78]) # Dictionary or normal content (TODO: Not hard-coded) @@ -210,7 +211,7 @@ class Sectionizer(object): else: raise ValueError('Invalid file format') 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]) flags, val = a1, a2<<16|a3<<8|a4 self.sections.append( (offset, flags, val) ) @@ -233,7 +234,7 @@ def sanitizeFileName(name): # delete control characters name = u"".join(char for char in name if ord(char)>=32) # 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 while len(name)>0 and name[0] == u".": name = name[1:] @@ -250,7 +251,7 @@ def fixKey(key): def deXOR(text, sp, table): r='' j = sp - for i in xrange(len(text)): + for i in range(len(text)): r += chr(ord(table[j]) ^ ord(text[i])) j = j + 1 if j == len(table): @@ -276,7 +277,7 @@ class EreaderProcessor(object): def unshuff(data, shuf): r = [''] * len(data) j = 0 - for i in xrange(len(data)): + for i in range(len(data)): j = (j + shuf) % len(data) r[j] = data[i] assert len("".join(r)) == len(data) @@ -330,7 +331,7 @@ class EreaderProcessor(object): self.flags = struct.unpack('>L', r[4:8])[0] reqd_flags = (1<<9) | (1<<7) | (1<<10) if (self.flags & reqd_flags) != reqd_flags: - print "Flags: 0x%X" % self.flags + print("Flags: 0x%X" % self.flags) raise ValueError('incompatible eReader file') des = Des(fixKey(user_key)) if version == 259: @@ -361,7 +362,7 @@ class EreaderProcessor(object): sect = self.section_reader(self.first_image_page + i) name = sect[4:4+32].strip('\0') data = sect[62:] - return sanitizeFileName(unicode(name,'windows-1252')), data + return sanitizeFileName(name.decode('windows-1252')), data # def getChapterNamePMLOffsetData(self): @@ -410,7 +411,7 @@ class EreaderProcessor(object): def getText(self): des = Des(fixKey(self.content_key)) r = '' - for i in xrange(self.num_text_pages): + for i in range(self.num_text_pages): logging.debug('get page %d', 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) # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated 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) id_len = ord(fnote_ids[2]) id = fnote_ids[3:3+id_len] @@ -446,7 +447,7 @@ class EreaderProcessor(object): 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 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 = sbar_ids[3:3+id_len] smarker = '\n' % id @@ -460,7 +461,7 @@ class EreaderProcessor(object): def cleanPML(pml): # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255) pml2 = pml - for k in xrange(128,256): + for k in range(128,256): badChar = chr(k) pml2 = pml2.replace(badChar, '\\a%03d' % k) return pml2 @@ -480,26 +481,26 @@ def decryptBook(infile, outpath, make_pmlz, user_key): try: if not os.path.exists(outdir): os.makedirs(outdir) - print u"Decoding File" + print(u"Decoding File") sect = Sectionizer(infile, 'PNRdPPrs') er = EreaderProcessor(sect, user_key) if er.getNumImages() > 0: - print u"Extracting images" + print(u"Extracting images") if not os.path.exists(imagedirpath): os.makedirs(imagedirpath) - for i in xrange(er.getNumImages()): + for i in range(er.getNumImages()): 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() 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: import zipfile 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) list = os.listdir(outdir) for filename in list: @@ -518,33 +519,33 @@ def decryptBook(infile, outpath, make_pmlz, user_key): myZipFile.close() # remove temporary directory shutil.rmtree(outdir, True) - print u"Output is {0}".format(pmlzname) + print(u"Output is {0}".format(pmlzname)) else : - print u"Output is in {0}".format(outdir) - print "done" - except ValueError, e: - print u"Error: {0}".format(e) + print(u"Output is in {0}".format(outdir)) + print("done") + except ValueError as e: + print(u"Error: {0}".format(e)) traceback.print_exc() return 1 return 0 def usage(): - print u"Converts DRMed eReader books to PML Source" - print u"Usage:" - print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number" - print u" " - print u"Options: " - print u" -h prints this message" - print u" -p create PMLZ instead of source folder" - print u" --make-pmlz create PMLZ instead of source folder" - print u" " - print u"Note:" - 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 source folder created, images are in infile_img 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"Converts DRMed eReader books to PML Source") + print(u"Usage:") + print(u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number") + print(u" ") + print(u"Options: ") + print(u" -h prints this message") + print(u" -p create PMLZ instead of source folder") + print(u" --make-pmlz create PMLZ instead of source folder") + print(u" ") + print(u"Note:") + 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 source folder created, images are in infile_img 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") return 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) def cli_main(): - print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__) + print(u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)) argv=unicode_argv() try: opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"]) - except getopt.GetoptError, err: - print err.args[0] + except getopt.GetoptError as err: + print(err.args[0]) usage() return 1 make_pmlz = False @@ -585,7 +586,7 @@ def cli_main(): elif len(args)==4: 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)) diff --git a/DeDRM_plugin/genbook.py b/DeDRM_plugin/genbook.py index a7512af..6667e49 100644 --- a/DeDRM_plugin/genbook.py +++ b/DeDRM_plugin/genbook.py @@ -1,5 +1,6 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +# Added Python 3 compatibility for calibre 5.0 from __future__ import print_function from .convert2xml import encodeNumber @@ -87,9 +88,9 @@ def readString(file): def getMetaArray(metaFile): # parse the meta file result = {} - fo = file(metaFile,'rb') + fo = open(metaFile,'rb') size = readEncodedNumber(fo) - for i in xrange(size): + for i in range(size): tag = readString(fo) value = readString(fo) result[tag] = value @@ -103,10 +104,10 @@ class Dictionary(object): def __init__(self, dictFile): self.filename = dictFile self.size = 0 - self.fo = file(dictFile,'rb') + self.fo = open(dictFile,'rb') self.stable = [] 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.pos = 0 def escapestr(self, str): @@ -142,7 +143,7 @@ class PageDimParser(object): else: end = min(cnt,end) foundat = -1 - for j in xrange(pos, end): + for j in range(pos, end): item = docList[j] if item.find('=') >= 0: (name, argres) = item.split('=') @@ -195,7 +196,7 @@ class GParser(object): def getData(self, path): result = None cnt = len(self.flatdoc) - for j in xrange(cnt): + for j in range(cnt): item = self.flatdoc[j] if item.find('=') >= 0: (name, argt) = item.split('=') @@ -207,7 +208,7 @@ class GParser(object): result = argres break if (len(argres) > 0) : - for j in xrange(0,len(argres)): + for j in range(0,len(argres)): argres[j] = int(argres[j]) return result def getGlyphDim(self, gly): @@ -223,7 +224,7 @@ class GParser(object): tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]] ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]] 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): zx = tx[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') sfile = os.path.join(spath,filename) dfile = os.path.join(dpath,imgname) - imgdata = file(sfile,'rb').read() - file(dfile,'wb').write(imgdata) + imgdata = open(sfile,'rb').read() + open(dfile,'wb').write(imgdata) print("Creating cover.jpg") isCover = False cpath = os.path.join(bookDir,'img') cpath = os.path.join(cpath,'img0000.jpg') if os.path.isfile(cpath): - cover = file(cpath, 'rb').read() + cover = open(cpath, 'rb').read() cpath = os.path.join(bookDir,'cover.jpg') - file(cpath, 'wb').write(cover) + open(cpath, 'wb').write(cover) isCover = True @@ -361,7 +362,7 @@ def generateBook(bookDir, raw, fixedimage): mlst.append('\n') metastr = "".join(mlst) mlst = None - file(xname, 'wb').write(metastr) + open(xname, 'wb').write(metastr) print('Processing StyleSheet') @@ -424,10 +425,10 @@ def generateBook(bookDir, raw, fixedimage): # now get the css info cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) - file(xname, 'wb').write(cssstr) + open(xname, 'wb').write(cssstr) if buildXML: 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') gd = GlyphDict() @@ -449,10 +450,10 @@ def generateBook(bookDir, raw, fixedimage): if buildXML: 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) - for i in xrange(0, gp.count): + for i in range(0, gp.count): path = gp.getPath(i) maxh, maxw = gp.getGlyphDim(i) fullpath = '\n' % (counter * 256 + i, path, maxw, maxh) @@ -507,7 +508,7 @@ def generateBook(bookDir, raw, fixedimage): if buildXML: 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 pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) @@ -518,7 +519,7 @@ def generateBook(bookDir, raw, fixedimage): hlst.append('\n\n') htmlstr = "".join(hlst) hlst = None - file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) + open(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) print(" ") print('Extracting Table of Contents from Amazon OCR') @@ -564,7 +565,7 @@ def generateBook(bookDir, raw, fixedimage): tlst.append('\n') tlst.append('\n') 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 @@ -619,7 +620,7 @@ def generateBook(bookDir, raw, fixedimage): slst.append('\n\n') svgindex = "".join(slst) 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(" ") @@ -668,7 +669,7 @@ def generateBook(bookDir, raw, fixedimage): olst.append('\n') opfstr = "".join(olst) olst = None - file(opfname, 'wb').write(opfstr) + open(opfname, 'wb').write(opfstr) print('Processing Complete') @@ -694,7 +695,7 @@ def main(argv): try: opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"]) - except getopt.GetoptError, err: + except getopt.GetoptError as err: print(str(err)) usage() return 1 diff --git a/DeDRM_plugin/ignobleepub.py b/DeDRM_plugin/ignobleepub.py index c16d88b..9ec9459 100644 --- a/DeDRM_plugin/ignobleepub.py +++ b/DeDRM_plugin/ignobleepub.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import with_statement +from __future__ import print_function # ignobleepub.pyw, version 4.1 # 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 # 4.0 - Work if TkInter is missing # 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. """ -from __future__ import print_function __license__ = 'GPL v3' -__version__ = "4.1" +__version__ = "5.0" import sys import os @@ -65,7 +66,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -106,13 +107,13 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) return [argv[i] for i in - xrange(start, argc.value)] + range(start, argc.value)] return [u"ineptepub.py"] else: argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv class IGNOBLEError(Exception): @@ -434,7 +435,7 @@ def gui_main(): self.status['text'] = u"Decrypting..." try: decrypt_status = decryptBook(userkey, inpath, outpath) - except Exception, e: + except Exception as e: self.status['text'] = u"Error: {0}".format(e.args[0]) return if decrypt_status == 0: diff --git a/DeDRM_plugin/ignoblekey.py b/DeDRM_plugin/ignoblekey.py index fb7c6a8..0b3e60f 100644 --- a/DeDRM_plugin/ignoblekey.py +++ b/DeDRM_plugin/ignoblekey.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- from __future__ import with_statement +from __future__ import print_function # 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 @@ -14,11 +15,11 @@ from __future__ import with_statement # Revision history: # 1.0 - Initial release # 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 """ -from __future__ import print_function __license__ = 'GPL v3' __version__ = "1.1" @@ -39,7 +40,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -80,7 +81,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"ignoblekey.py"] @@ -88,7 +89,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv class DrmException(Exception): pass @@ -210,7 +211,7 @@ def getkey(outpath, files=[]): if len(keys) > 0: if not os.path.isdir(outpath): outfile = outpath - with file(outfile, 'w') as keyfileout: + with open(outfile, 'w') as keyfileout: keyfileout.write(keys[-1]) print(u"Saved a key to {0}".format(outfile)) else: @@ -221,7 +222,7 @@ def getkey(outpath, files=[]): outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount)) if not os.path.exists(outfile): break - with file(outfile, 'w') as keyfileout: + with open(outfile, 'w') as keyfileout: keyfileout.write(key) print(u"Saved a key to {0}".format(outfile)) return True @@ -244,7 +245,7 @@ def cli_main(): try: 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])) usage(progname) sys.exit(2) @@ -315,11 +316,11 @@ def gui_main(): if not os.path.exists(outfile): break - with file(outfile, 'w') as keyfileout: + with open(outfile, 'w') as keyfileout: keyfileout.write(key) success = True 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))) except Exception: root.wm_state('normal') diff --git a/DeDRM_plugin/ignoblekeyfetch.py b/DeDRM_plugin/ignoblekeyfetch.py index dd714fc..ffaf153 100644 --- a/DeDRM_plugin/ignoblekeyfetch.py +++ b/DeDRM_plugin/ignoblekeyfetch.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- from __future__ import with_statement +from __future__ import print_function -# ignoblekeyfetch.pyw, version 1.1 -# Copyright © 2015 Apprentice Harper +# ignoblekeyfetch.pyw, version 2.0 +# Copyright © 2015-2020 Apprentice Harper et al. # Released under the terms of the GNU General Public Licence, version 3 # @@ -24,11 +25,11 @@ from __future__ import with_statement # Revision history: # 1.0 - Initial version # 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 """ -from __future__ import print_function __license__ = 'GPL v3' __version__ = "1.1" @@ -46,7 +47,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -87,7 +88,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"ignoblekeyfetch.py"] @@ -95,7 +96,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv class IGNOBLEError(Exception): @@ -103,9 +104,9 @@ class IGNOBLEError(Exception): def fetch_key(email, password): # change email and password to utf-8 if unicode - if type(email)==unicode: + if type(email)==bytes: email = email.encode('utf-8') - if type(password)==unicode: + if type(password)==bytes: password = password.encode('utf-8') import random @@ -236,7 +237,7 @@ def gui_main(): self.status['text'] = u"Fetching..." try: userkey = fetch_key(email, password) - except Exception, e: + except Exception as e: self.status['text'] = u"Error: {0}".format(e.args[0]) return if len(userkey) == 28: diff --git a/DeDRM_plugin/ignoblekeygen.py b/DeDRM_plugin/ignoblekeygen.py index 5ddcd80..3582f24 100644 --- a/DeDRM_plugin/ignoblekeygen.py +++ b/DeDRM_plugin/ignoblekeygen.py @@ -2,15 +2,14 @@ # -*- coding: utf-8 -*- from __future__ import with_statement +from __future__ import print_function -# ignoblekeygen.pyw, version 2.5 -# Copyright © 2009-2010 i♥cabbages +# ignoblekeygen.pyw +# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al. # Released under the terms of the GNU General Public Licence, version 3 # -# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf - # Windows users: Before running this program, you must first install Python. # We recommend ActiveState Python 2.7.X for Windows (x86) from # 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.7 - Work if TkInter is missing # 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. """ -from __future__ import print_function __license__ = 'GPL v3' -__version__ = "2.8" +__version__ = "3.0" import sys import os @@ -57,7 +56,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -98,7 +97,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"ignoblekeygen.py"] @@ -106,7 +105,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv class IGNOBLEError(Exception): @@ -199,9 +198,9 @@ def normalize_name(name): def generate_key(name, ccn): # remove spaces and case from name and CC numbers. - if type(name)==unicode: + if type(name)==bytes: name = name.encode('utf-8') - if type(ccn)==unicode: + if type(ccn)==bytes: ccn = ccn.encode('utf-8') name = normalize_name(name) + '\x00' @@ -306,7 +305,7 @@ def gui_main(): self.status['text'] = u"Generating..." try: userkey = generate_key(name, ccn) - except Exception, e: + except Exception as e: self.status['text'] = u"Error: (0}".format(e.args[0]) return open(keypath,'wb').write(userkey) diff --git a/DeDRM_plugin/ineptepub.py b/DeDRM_plugin/ineptepub.py index cfbd084..c0e4c39 100644 --- a/DeDRM_plugin/ineptepub.py +++ b/DeDRM_plugin/ineptepub.py @@ -2,18 +2,13 @@ # -*- coding: utf-8 -*- from __future__ import with_statement -from __future__ import absolute_import -from __future__ import print_function -# ineptepub.pyw, version 6.6 -# Copyright © 2009-2020 by Apprentice Harper et al. +# ineptepub.pyw +# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al. # Released under the terms of the GNU General Public Licence, version 3 # -# Original script by i♥cabbages -# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf -# Modified 2015–2020 by Apprentice Harper et al. # Revision history: # 1 - Initial release @@ -36,17 +31,16 @@ from __future__ import print_function # 6.4 - 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.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. """ __license__ = 'GPL v3' -__version__ = "6.7" +__version__ = "7.0" -import six -from six.moves import range +import codecs import sys import os import traceback @@ -55,7 +49,6 @@ import zipfile from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED from contextlib import closing import xml.etree.ElementTree as etree -import base64 # Wrap a stream so that output gets flushed immediately # and also make sure that any unicode strings get @@ -67,7 +60,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,six.text_type): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -114,7 +107,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: 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): @@ -418,9 +411,7 @@ def decryptBook(userkey, inpath, outpath): if len(bookkey) != 172: print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))) return 1 - bookkey = bookkey.encode('ascii') - bookkey = base64.b64decode(bookkey) - bookkey = rsa.decrypt(bookkey) + bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64')) # Padded as per RSAES-PKCS1-v1_5 if bookkey[-17] != '\x00' and bookkey[-17] != 0: print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))) @@ -486,79 +477,79 @@ def cli_main(): def gui_main(): try: - import six.moves.tkinter - import six.moves.tkinter_constants - import six.moves.tkinter_filedialog - import six.moves.tkinter_messagebox + import Tkinter + import Tkconstants + import tkFileDialog + import tkMessageBox import traceback except: return cli_main() - class DecryptionDialog(six.moves.tkinter.Frame): + class DecryptionDialog(Tkinter.Frame): def __init__(self, root): - six.moves.tkinter.Frame.__init__(self, root, border=5) - self.status = six.moves.tkinter.Label(self, text=u"Select files for decryption") - self.status.pack(fill=six.moves.tkinter_constants.X, expand=1) - body = six.moves.tkinter.Frame(self) - body.pack(fill=six.moves.tkinter_constants.X, expand=1) - sticky = six.moves.tkinter_constants.E + six.moves.tkinter_constants.W + Tkinter.Frame.__init__(self, root, border=5) + self.status = Tkinter.Label(self, text=u"Select files for decryption") + self.status.pack(fill=Tkconstants.X, expand=1) + body = Tkinter.Frame(self) + body.pack(fill=Tkconstants.X, expand=1) + sticky = Tkconstants.E + Tkconstants.W body.grid_columnconfigure(1, weight=2) - six.moves.tkinter.Label(body, text=u"Key file").grid(row=0) - self.keypath = six.moves.tkinter.Entry(body, width=30) + Tkinter.Label(body, text=u"Key file").grid(row=0) + self.keypath = Tkinter.Entry(body, width=30) self.keypath.grid(row=0, column=1, sticky=sticky) if os.path.exists(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) - six.moves.tkinter.Label(body, text=u"Input file").grid(row=1) - self.inpath = six.moves.tkinter.Entry(body, width=30) + Tkinter.Label(body, text=u"Input file").grid(row=1) + self.inpath = Tkinter.Entry(body, width=30) 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) - six.moves.tkinter.Label(body, text=u"Output file").grid(row=2) - self.outpath = six.moves.tkinter.Entry(body, width=30) + Tkinter.Label(body, text=u"Output file").grid(row=2) + self.outpath = Tkinter.Entry(body, width=30) 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) - buttons = six.moves.tkinter.Frame(self) + buttons = Tkinter.Frame(self) buttons.pack() - botton = six.moves.tkinter.Button( + botton = Tkinter.Button( buttons, text=u"Decrypt", width=10, command=self.decrypt) - botton.pack(side=six.moves.tkinter_constants.LEFT) - six.moves.tkinter.Frame(buttons, width=10).pack(side=six.moves.tkinter_constants.LEFT) - button = six.moves.tkinter.Button( + botton.pack(side=Tkconstants.LEFT) + Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) + button = Tkinter.Button( 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): - keypath = six.moves.tkinter_filedialog.askopenfilename( + keypath = tkFileDialog.askopenfilename( parent=None, title=u"Select Adobe Adept \'.der\' key file", defaultextension=u".der", filetypes=[('Adobe Adept DER-encoded files', '.der'), ('All Files', '.*')]) if 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) return def get_inpath(self): - inpath = six.moves.tkinter_filedialog.askopenfilename( + inpath = tkFileDialog.askopenfilename( parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt", defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) if 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) return def get_outpath(self): - outpath = six.moves.tkinter_filedialog.asksaveasfilename( + outpath = tkFileDialog.asksaveasfilename( parent=None, title=u"Select unencrypted ePub file to produce", defaultextension=u".epub", filetypes=[('ePub files', '.epub')]) if 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) return @@ -590,11 +581,11 @@ def gui_main(): else: 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.resizable(True, False) 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() return 0 diff --git a/DeDRM_plugin/ineptpdf.py b/DeDRM_plugin/ineptpdf.py index 17db7e9..c29a536 100644 --- a/DeDRM_plugin/ineptpdf.py +++ b/DeDRM_plugin/ineptpdf.py @@ -4,13 +4,11 @@ from __future__ import with_statement # 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 # -# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf -# Modified 2015-2017 by Apprentice Harper et al. # Revision history: # 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.5 - Do not process DRM-free documents # 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. """ __license__ = 'GPL v3' -__version__ = "8.0.6" +__version__ = "9.0.0" import sys import os @@ -64,8 +62,8 @@ import re import zlib import struct import hashlib -from decimal import * -from itertools import chain, islice +from decimal import Decimal +import itertools import xml.etree.ElementTree as etree # Wrap a stream so that output gets flushed immediately @@ -75,10 +73,10 @@ class SafeUnbuffered: def __init__(self, stream): self.stream = stream self.encoding = stream.encoding - if self.encoding == None: + if self.encoding is None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -116,13 +114,13 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) return [argv[i] for i in - xrange(start, argc.value)] + range(start, argc.value)] return [u"ineptpdf.py"] else: argvencoding = sys.stdin.encoding - if argvencoding == None: + if argvencoding is None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return sys.argv class ADEPTError(Exception): @@ -378,12 +376,12 @@ def _load_crypto_pycrypto(): class RSA(object): def __init__(self, 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] self._rsa = _RSA.construct(key) def bytesToNumber(self, bytes): - total = 0L + total = 0 for byte in bytes: total = (total << 8) + byte return total @@ -411,7 +409,10 @@ ARC4, RSA, AES = _load_crypto() try: from cStringIO import StringIO 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? @@ -444,11 +445,11 @@ def nunpack(s, default=0): if not l: return default elif l == 1: - return ord(s) + return s elif l == 2: return struct.unpack('>H', s)[0] elif l == 3: - return struct.unpack('>L', '\x00'+s)[0] + return struct.unpack('>L', b'\x00'+s)[0] elif l == 4: return struct.unpack('>L', s)[0] else: @@ -487,9 +488,9 @@ class PSLiteral(PSObject): name = [] for char in self.name: if not char.isalnum(): - char = '#%02x' % ord(char) + char = b'#%02x' % char name.append(char) - return '/%s' % ''.join(name) + return b'/%s' % ''.join(name) # PSKeyword class PSKeyword(PSObject): @@ -716,7 +717,7 @@ class PSBaseParser(object): except ValueError: pass return (self.parse_main, j) - + def parse_decimal(self, s, i): m = END_NUMBER.search(s, i) if not m: @@ -949,7 +950,7 @@ class PSStackParser(PSBaseParser): try: (pos, objs) = self.end_type('d') if len(objs) % 2 != 0: - print "Incomplete dictionary construct" + print("Incomplete dictionary construct") objs.append("") # this isn't necessary. # temporary fix. is this due to rental books? # raise PSSyntaxError( @@ -1106,18 +1107,18 @@ def stream_value(x): # ascii85decode(data) def ascii85decode(data): n = b = 0 - out = '' + out = b'' for c in data: - if '!' <= c and c <= 'u': + if b'!' <= c and c <= b'u': n += 1 - b = b*85+(ord(c)-33) + b = b*85+(c-33) if n == 5: out += struct.pack('>L',b) n = b = 0 - elif c == 'z': + elif c == b'z': assert n == 0 - out += '\0\0\0\0' - elif c == '~': + out += b'\0\0\0\0' + elif c == b'~': if n: for _ in range(5-n): b = b*85+84 @@ -1138,7 +1139,7 @@ class PDFStream(PDFObject): cutdiv = len(rawdata) // 16 rawdata = rawdata[:16*cutdiv] else: - if eol in ('\r', '\n', '\r\n'): + if eol in (b'\r', b'\n', b'\r\n'): rawdata = rawdata[:length] self.dic = dic @@ -1206,14 +1207,14 @@ class PDFStream(PDFObject): raise PDFValueError( 'Columns undefined for predictor=12') columns = int_value(params['Columns']) - buf = '' - ent0 = '\x00' * columns - for i in xrange(0, len(data), columns+1): + buf = b'' + ent0 = b'\x00' * columns + for i in range(0, len(data), columns+1): pred = data[i] ent1 = data[i+1:i+1+columns] - if pred == '\x02': - ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \ - for (a,b) in zip(ent0,ent1)) + if pred == b'\x02': + ent1 = ''.join(bytes([(a+b) & 255]) \ + for (a,b) in zip(ent0,ent1)) buf += ent1 ent0 = ent1 data = buf @@ -1290,7 +1291,7 @@ class PDFXRef(object): (start, nobjs) = map(int, f) except ValueError: raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line)) - for objid in xrange(start, start+nobjs): + for objid in range(start, start+nobjs): try: (_, line) = parser.nextline() except PSEOF: @@ -1342,7 +1343,7 @@ class PDFXRefStream(object): def objids(self): for first, size in self.index: - for objid in xrange(first, first + size): + for objid in range(first, first + size): yield objid def load(self, parser, debug=0): @@ -1355,8 +1356,8 @@ class PDFXRefStream(object): raise PDFNoValidXRef('Invalid PDF stream spec.') size = stream.dic['Size'] index = stream.dic.get('Index', (0,size)) - self.index = zip(islice(index, 0, None, 2), - islice(index, 1, None, 2)) + self.index = zip(itertools.islice(index, 0, None, 2), + itertools.islice(index, 1, None, 2)) (self.fl1, self.fl2, self.fl3) = stream.dic['W'] self.data = stream.get_data() self.entlen = self.fl1+self.fl2+self.fl3 @@ -1506,10 +1507,10 @@ class PDFDocument(object): if plaintext[-16:] != 16 * chr(16): raise ADEPTError('Offlinekey cannot be decrypted, aborting ...') 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 ...') else: - cutter = -1 * ord(pdrlpol[-1]) + cutter = -1 * pdrlpol[-1] pdrlpol = pdrlpol[:cutter] return plaintext[:16] @@ -1551,7 +1552,7 @@ class PDFDocument(object): hash.update('ffffffff'.decode('hex')) if 5 <= R: # 8 - for _ in xrange(50): + for _ in range(50): hash = hashlib.md5(hash.digest()[:length/8]) key = hash.digest()[:length/8] if R == 2: @@ -1562,8 +1563,8 @@ class PDFDocument(object): hash = hashlib.md5(self.PASSWORD_PADDING) # 2 hash.update(docid[0]) # 3 x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4 - for i in xrange(1,19+1): - k = ''.join( chr(ord(c) ^ i) for c in key ) + for i in range(1,19+1): + k = ''.join(bytes([c ^ i]) for c in key ) x = ARC4.new(k).decrypt(x) u1 = x+x # 32bytes total if R == 2: @@ -1585,9 +1586,9 @@ class PDFDocument(object): if V != 4: self.decipher = self.decipher_rc4 # XXX may be AES # aes - elif V == 4 and Length == 128: - elf.decipher = self.decipher_aes - elif V == 4 and Length == 256: + elif V == 4 and length == 128: + self.decipher = self.decipher_aes + elif V == 4 and length == 256: raise PDFNotImplementedError('AES256 encryption is currently unsupported') self.ready = True return @@ -1616,18 +1617,18 @@ class PDFDocument(object): else: V = 2 elif len(bookkey) == length + 1: - V = ord(bookkey[0]) + V = bookkey[0] bookkey = bookkey[1:] else: - 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 "bookkey[0] is %d" % ord(bookkey[0]) + 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("bookkey[0] is %d" % bookkey[0]) raise ADEPTError('error decrypting book session key - mismatched length') else: # proper length unknown try with whatever you have - 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 "bookkey[0] is %d" % ord(bookkey[0]) + 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("bookkey[0] is %d" % bookkey[0]) if ebx_V == 3: V = 3 else: @@ -1671,7 +1672,7 @@ class PDFDocument(object): data = data[16:] plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data) # remove pkcs#5 aes padding - cutter = -1 * ord(plaintext[-1]) + cutter = -1 * plaintext[-1] #print cutter plaintext = plaintext[:cutter] return plaintext @@ -1682,7 +1683,7 @@ class PDFDocument(object): data = data[16:] plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data) # remove pkcs#5 aes padding - cutter = -1 * ord(plaintext[-1]) + cutter = -1 * plaintext[-1] #print cutter plaintext = plaintext[:cutter] return plaintext @@ -2026,7 +2027,7 @@ class PDFSerializer(object): if not gen_xref_stm: self.write('xref\n') 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: # force the genno to be 0 self.write("%010d 00000 n \n" % xrefs[objid][0]) @@ -2135,7 +2136,7 @@ class PDFSerializer(object): if self.last.isalnum(): self.write(' ') self.write(str(obj).lower()) - elif isinstance(obj, (int, long)): + elif isinstance(obj, int): if self.last.isalnum(): self.write(' ') self.write(str(obj)) @@ -2189,8 +2190,8 @@ def decryptBook(userkey, inpath, outpath): # help construct to make sure the method runs to the end try: serializer.dump(outf) - except Exception, e: - print u"error writing pdf: {0}".format(e.args[0]) + except Exception as e: + print(u"error writing pdf: {0}".format(e.args[0])) return 2 return 0 @@ -2201,13 +2202,13 @@ def cli_main(): argv=unicode_argv() progname = os.path.basename(argv[0]) if len(argv) != 4: - print u"usage: {0} ".format(progname) + print(u"usage: {0} ".format(progname)) return 1 keypath, inpath, outpath = argv[1:] userkey = open(keypath,'rb').read() result = decryptBook(userkey, inpath, outpath) 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 @@ -2309,7 +2310,7 @@ def gui_main(): self.status['text'] = u"Decrypting..." try: decrypt_status = decryptBook(userkey, inpath, outpath) - except Exception, e: + except Exception as e: self.status['text'] = u"Error; {0}".format(e.args[0]) return if decrypt_status == 0: diff --git a/DeDRM_plugin/ion.py b/DeDRM_plugin/ion.py index 0ea3859..f385cc3 100644 --- a/DeDRM_plugin/ion.py +++ b/DeDRM_plugin/ion.py @@ -7,7 +7,7 @@ from __future__ import with_statement # Copyright © 2013-2020 Apprentice Harper et al. __license__ = 'GPL v3' -__version__ = '2.0' +__version__ = '3.0' # Revision history: # Pascal implementation by lulzkabulz. @@ -17,7 +17,7 @@ __version__ = '2.0' # 1.2 - Added pylzma import fallback # 1.3 - Fixed lzma support for calibre 4.6+ # 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya. - +# 3.0 - Added Python 3 compatibility for calibre 5.0 """ Decrypt Kindle KFX files. @@ -33,7 +33,10 @@ import struct try: from cStringIO import StringIO except ImportError: - from StringIO import StringIO + try: + from StringIO import StringIO + except ImportError: + from io import StringIO from Crypto.Cipher import AES from Crypto.Util.py3compat import bchr, bord diff --git a/DeDRM_plugin/k4mobidedrm.py b/DeDRM_plugin/k4mobidedrm.py index 3ac753e..eaaa43b 100644 --- a/DeDRM_plugin/k4mobidedrm.py +++ b/DeDRM_plugin/k4mobidedrm.py @@ -4,10 +4,10 @@ from __future__ import with_statement # k4mobidedrm.py -# Copyright © 2008-2019 by Apprentice Harper et al. +# Copyright © 2008-2020 by Apprentice Harper et al. __license__ = 'GPL v3' -__version__ = '5.7' +__version__ = '6.0' # Engine to remove drm from Kindle and Mobipocket 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.6 - Invoke KFXZipBook to handle zipped KFX files # 5.7 - Revamp cleanup_name +# 6.0 - Added Python 3 compatibility for calibre 5.0 + import sys, os, re import csv @@ -69,7 +71,7 @@ import getopt import re import traceback import time -import htmlentitydefs +import html.entities import json class DrmException(Exception): @@ -103,7 +105,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -141,7 +143,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"mobidedrm.py"] @@ -149,7 +151,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv # cleanup unicode filenames # borrowed from calibre from calibre/src/calibre/__init__.py @@ -161,7 +163,7 @@ def cleanup_name(name): # 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"") # 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 name = u"".join(char for char in name if ord(char)>=32) # delete non-ascii characters @@ -184,19 +186,19 @@ def unescape(text): # character reference try: if text[:3] == u"&#x": - return unichr(int(text[3:-1], 16)) + return chr(int(text[3:-1], 16)) else: - return unichr(int(text[2:-1])) + return chr(int(text[2:-1])) except ValueError: pass else: # named entity try: - text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) + text = chr(htmlentitydefs.name2codepoint[text[1:-1]]) except KeyError: pass 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()): # handle the obvious cases at the beginning @@ -220,7 +222,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime mb = topazextract.TopazBook(infile) 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 totalpids = list(pids) @@ -232,7 +234,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases)) # remove any duplicates 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 try: @@ -241,7 +243,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime mb.cleanup 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 @@ -255,16 +257,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids): with open(dbfile, 'r') as keyfilein: kindleDatabase = json.loads(keyfilein.read()) kDatabases.append([dbfile,kindleDatabase]) - except Exception, e: - print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e) + except Exception as e: + print(u"Error getting database from file {0:s}: {1:s}".format(dbfile,e)) traceback.print_exc() try: book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime) - except Exception, e: - print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) + except Exception as e: + print(u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)) traceback.print_exc() return 1 @@ -287,12 +289,12 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids): outfile = os.path.join(outdir, outfilename + book.getBookExtension()) 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": zipname = os.path.join(outdir, outfilename + u"_SVG.zip") 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 book.cleanup() @@ -300,9 +302,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids): def usage(progname): - print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks" - print u"Usage:" - print u" {0} [-k ] [-p ] [-s ] [ -a ] ".format(progname) + print(u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks") + print(u"Usage:") + print(u" {0} [-k ] [-p ] [-s ] [ -a ] ".format(progname)) # # Main @@ -310,12 +312,12 @@ def usage(progname): def cli_main(): argv=unicode_argv() 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: opts, args = getopt.getopt(argv[1:], "k:p:s:a:") - except getopt.GetoptError, err: - print u"Error in options or arguments: {0}".format(err.args[0]) + except getopt.GetoptError as err: + print(u"Error in options or arguments: {0}".format(err.args[0])) usage(progname) sys.exit(2) if len(args)<2: diff --git a/DeDRM_plugin/kfxdedrm.py b/DeDRM_plugin/kfxdedrm.py index 1520f79..d3c0f7e 100644 --- a/DeDRM_plugin/kfxdedrm.py +++ b/DeDRM_plugin/kfxdedrm.py @@ -6,6 +6,9 @@ from __future__ import print_function # Engine to remove drm from Kindle KFX ebooks +# 2.0 - Added Python 3 compatibility for calibre 5.0 + + import os import shutil import zipfile @@ -13,7 +16,10 @@ import zipfile try: from cStringIO import StringIO except ImportError: - from StringIO import StringIO + try: + from StringIO import StringIO + except ImportError: + from io import StringIO try: from calibre_plugins.dedrm import ion @@ -22,7 +28,7 @@ except ImportError: __license__ = 'GPL v3' -__version__ = '1.0' +__version__ = '2.0' class KFXZipBook: diff --git a/DeDRM_plugin/kgenpids.py b/DeDRM_plugin/kgenpids.py index 529d13d..a3efee0 100644 --- a/DeDRM_plugin/kgenpids.py +++ b/DeDRM_plugin/kgenpids.py @@ -5,15 +5,17 @@ from __future__ import with_statement from __future__ import print_function # kgenpids.py -# Copyright © 2008-2017 Apprentice Harper et al. +# Copyright © 2008-2020 Apprentice Harper et al. __license__ = 'GPL v3' -__version__ = '2.1' +__version__ = '3.0' # Revision history: # 2.0 - 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 os, csv @@ -31,9 +33,9 @@ global charMap3 global charMap4 -charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' -charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' +charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M' +charMap3 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +charMap4 = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' # crypto digestroutines import hashlib @@ -84,7 +86,7 @@ def decode(data,map): def getTwoBitsFromBitField(bitField,offset): byteNumber = 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 def getSixBitsFromBitField(bitField,offset): @@ -95,9 +97,9 @@ def getSixBitsFromBitField(bitField,offset): # 8 bits to six bits encoding from hash to generate PID string def encodePID(hash): global charMap3 - PID = '' + PID = b'' for position in range (0,8): - PID += charMap3[getSixBitsFromBitField(hash,position)] + PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]]) return PID # Encryption table used to generate the device PID @@ -126,7 +128,7 @@ def generatePidSeed(table,dsn) : def generateDevicePID(table,dsn,nbRoll): global charMap4 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] index = 0 for counter in range (0,nbRoll): @@ -134,7 +136,7 @@ def generateDevicePID(table,dsn,nbRoll): index = (index+1) %8 for counter in range (0,8): index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7) - pidAscii += charMap4[index] + pidAscii += bytes([charMap4[index]]) return pidAscii def crc32(s): @@ -150,7 +152,7 @@ def checksumPid(s): for i in (0,1): b = crc & 0xff pos = (b // l) ^ (b % l) - res += charMap4[pos%l] + res += bytes([charMap4[pos%l]]) crc >>= 8 return res @@ -160,15 +162,15 @@ def pidFromSerial(s, l): global charMap4 crc = crc32(s) arr1 = [0]*l - for i in xrange(len(s)): - arr1[i%l] ^= ord(s[i]) + for i in range(len(s)): + arr1[i%l] ^= s[i] 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] - pid = "" - for i in xrange(l): + pid = b"" + for i in range(l): 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 @@ -179,7 +181,7 @@ def getKindlePids(rec209, token, serialnum): pids=[] - if isinstance(serialnum,unicode): + if isinstance(serialnum,str): serialnum = serialnum.encode('utf-8') # Compute book PID @@ -189,7 +191,7 @@ def getKindlePids(rec209, token, serialnum): pids.append(bookPID) # 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) pids.append(kindlePID) @@ -206,7 +208,7 @@ def getK4Pids(rec209, token, kindleDatabase): try: # 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: kindleAccountToken="" @@ -214,30 +216,30 @@ def getK4Pids(rec209, token, kindleDatabase): try: # 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])) except KeyError: # See if we have the info to generate the DSN try: # 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]) try: # 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])) except KeyError: # Get the IDString we added - IDString = (kindleDatabase[1])['IDString'].decode('hex') + IDString = bytearray.fromhex((kindleDatabase[1])['IDString']).decode() try: # 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])) except KeyError: # Get the UserName we added - UserName = (kindleDatabase[1])['UserName'].decode('hex') + UserName = bytearray.fromhex((kindleDatabase[1])['UserName']).decode() # encode it encodedUsername = encodeHash(UserName,charMap1) #print u"encodedUsername",encodedUsername.encode('hex') @@ -267,19 +269,19 @@ def getK4Pids(rec209, token, kindleDatabase): # Compute book PIDs # book pid - pidHash = SHA1(DSN+kindleAccountToken+rec209+token) + pidHash = SHA1(DSN.encode()+kindleAccountToken.encode()+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) pids.append(bookPID) # variant 1 - pidHash = SHA1(kindleAccountToken+rec209+token) + pidHash = SHA1(kindleAccountToken.encode()+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) pids.append(bookPID) # variant 2 - pidHash = SHA1(DSN+rec209+token) + pidHash = SHA1(DSN.encode()+rec209+token) bookPID = encodePID(pidHash) bookPID = checksumPid(bookPID) pids.append(bookPID) @@ -297,14 +299,14 @@ def getPidList(md1, md2, serials=[], kDatabases=[]): for kDatabase in kDatabases: try: 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])) traceback.print_exc() for serialnum in serials: try: 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])) traceback.print_exc() diff --git a/DeDRM_plugin/kindlekey.py b/DeDRM_plugin/kindlekey.py index 90862cb..5e9f5c8 100644 --- a/DeDRM_plugin/kindlekey.py +++ b/DeDRM_plugin/kindlekey.py @@ -7,7 +7,7 @@ from __future__ import with_statement # Copyright © 2008-2020 Apprentice Harper et al. __license__ = 'GPL v3' -__version__ = '2.7' +__version__ = '3.0' # Revision history: # 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.6 - Start adding support for Kindle 1.25+ .kinf2018 file # 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 -from struct import pack, unpack +from struct import pack, unpack, unpack_from import json import getopt @@ -108,7 +109,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return arg class DrmException(Exception): pass @@ -299,7 +300,7 @@ if iswindows: numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) if more == None: # no more calls to decrypt, should have all the data 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 if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : @@ -341,7 +342,7 @@ if iswindows: def removePad(self, paddedBinaryString, blockSize): """ Remove padding from a binary string """ if not(06: # 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['UserName'] = GetUserName() else: - print u"Couldn't decrypt file." + print(u"Couldn't decrypt file.") DB = {} return DB else: @@ -1683,7 +1683,7 @@ def getkey(outpath, files=[]): outfile = outpath with file(outfile, 'w') as keyfileout: 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: keycount = 0 for key in keys: @@ -1694,16 +1694,16 @@ def getkey(outpath, files=[]): break with file(outfile, 'w') as keyfileout: 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 False def usage(progname): - 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"If a file name is passed instead of a directory, only the first key is saved, in that file." - print u"Usage:" - print u" {0:s} [-h] [-k ] []".format(progname) + 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"If a file name is passed instead of a directory, only the first key is saved, in that file.") + print(u"Usage:") + print(u" {0:s} [-h] [-k ] []".format(progname)) def cli_main(): @@ -1711,12 +1711,12 @@ def cli_main(): sys.stderr=SafeUnbuffered(sys.stderr) argv=unicode_argv() 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: opts, args = getopt.getopt(argv[1:], "hk:") - except getopt.GetoptError, err: - print u"Error in options or arguments: {0}".format(err.args[0]) + except getopt.GetoptError as err: + print(u"Error in options or arguments: {0}".format(err.args[0])) usage(progname) sys.exit(2) @@ -1745,7 +1745,7 @@ def cli_main(): outpath = os.path.realpath(os.path.normpath(outpath)) 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 @@ -1789,7 +1789,7 @@ def gui_main(): keyfileout.write(json.dumps(key)) success = True 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))) except Exception: root.wm_state('normal') diff --git a/DeDRM_plugin/kindlepid.py b/DeDRM_plugin/kindlepid.py index 726554f..069fdc0 100644 --- a/DeDRM_plugin/kindlepid.py +++ b/DeDRM_plugin/kindlepid.py @@ -10,6 +10,7 @@ # 0.3 updated for unicode # 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 +# 1.0 Added Python 3 compatibility for calibre 5.0 from __future__ import print_function import sys @@ -25,7 +26,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -63,7 +64,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"kindlepid.py"] @@ -71,11 +72,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = "utf-8" - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] - -if sys.hexversion >= 0x3000000: - print('This script is incompatible with Python 3.x. Please install Python 2.7.x.') - sys.exit(2) + return sys.argv letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789' @@ -83,7 +80,7 @@ def crc32(s): return (~binascii.crc32(s,-1))&0xFFFFFFFF def checksumPid(s): - crc = crc32(s) + crc = crc32(s.encode('ascii')) crc = crc ^ (crc >> 16) res = s l = len(letters) @@ -99,15 +96,15 @@ def pidFromSerial(s, l): crc = crc32(s) arr1 = [0]*l - for i in xrange(len(s)): - arr1[i%l] ^= ord(s[i]) + for i in range(len(s)): + arr1[i%l] ^= s[i] 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] pid = '' - for i in xrange(l): + for i in range(l): b = arr1[i] & 0xff pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))] @@ -140,6 +137,6 @@ def cli_main(): if __name__ == "__main__": - sys.stdout=SafeUnbuffered(sys.stdout) - sys.stderr=SafeUnbuffered(sys.stderr) + #sys.stdout=SafeUnbuffered(sys.stdout) + #sys.stderr=SafeUnbuffered(sys.stderr) sys.exit(cli_main()) diff --git a/DeDRM_plugin/mobidedrm.py b/DeDRM_plugin/mobidedrm.py index e8400e4..cfd8a81 100644 --- a/DeDRM_plugin/mobidedrm.py +++ b/DeDRM_plugin/mobidedrm.py @@ -3,11 +3,11 @@ # mobidedrm.py # Copyright © 2008 The Dark Reverser -# Portions © 2008–2017 Apprentice Harper et al. +# Portions © 2008–2020 Apprentice Harper et al. from __future__ import print_function __license__ = 'GPL v3' -__version__ = u"0.42" +__version__ = u"1.00" # This is a python script. You need a Python interpreter to run it. # 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.41 - Fixed potential unicode problem in command line calls # 0.42 - Added GPL v3 licence. updated/removed some print statements +# 3.00 - Added Python 3 compatibility for calibre 5.0 import sys import os @@ -93,7 +94,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -131,7 +132,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"mobidedrm.py"] @@ -139,7 +140,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = 'utf-8' - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return sys.argv class DrmException(Exception): @@ -153,12 +154,12 @@ class DrmException(Exception): # Implementation of Pukall Cipher 1 def PC1(key, src, decryption=True): # if we can get it from alfcrypto, use that - try: - return Pukall_Cipher().PC1(key,src,decryption) - except NameError: - pass - except TypeError: - pass + #try: + # return Pukall_Cipher().PC1(key,src,decryption) + #except NameError: + # pass + #except TypeError: + # pass # use slow python version, since Pukall_Cipher didn't load sum1 = 0; @@ -167,28 +168,28 @@ def PC1(key, src, decryption=True): if len(key)!=16: DrmException (u"PC1: Bad key length") wkey = [] - for i in xrange(8): - wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) - dst = "" - for i in xrange(len(src)): + for i in range(8): + wkey.append(key[i*2]<<8 | key[i*2+1]) + dst = b'' + for i in range(len(src)): temp1 = 0; byteXorVal = 0; - for j in xrange(8): + for j in range(8): temp1 ^= wkey[j] sum2 = (sum2+j)*20021 + sum1 sum1 = (temp1*346)&0xFFFF sum2 = (sum2+sum1)&0xFFFF temp1 = (temp1*20021+1)&0xFFFF byteXorVal ^= temp1 ^ sum2 - curByte = ord(src[i]) + curByte = src[i] if not decryption: keyXorVal = curByte * 257; curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF if decryption: keyXorVal = curByte * 257; - for j in xrange(8): + for j in range(8): wkey[j] ^= keyXorVal; - dst+=chr(curByte) + dst+=bytes([curByte]) return dst def checksumPid(s): @@ -200,7 +201,7 @@ def checksumPid(s): for i in (0,1): b = crc & 0xff pos = (b // l) ^ (b % l) - res += letters[pos%l] + res += letters[pos%l].encode('ascii') crc >>= 8 return res @@ -210,7 +211,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): if size <= 0: return result while True: - v = ord(ptr[size-1]) + v = ptr[size-1] result |= (v & 0x7F) << bitpos bitpos += 7 size -= 1 @@ -226,7 +227,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags): # if multibyte data is included in the encryped data, we'll # have already cleared this flag. if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 + num += (ptr[size - num - 1] & 0x3) + 1 return num @@ -253,10 +254,10 @@ class MobiBook: print(u"AlfCrypto not found. Using python PC1 implementation.") # initial sanity check on file - self.data_file = file(infile, 'rb').read() + self.data_file = open(infile, 'rb').read() self.mobi_data = '' 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") self.magic = self.header[0x3C:0x3C+8] self.crypto_type = -1 @@ -264,7 +265,7 @@ class MobiBook: # build up section offset and flag info self.num_sections, = struct.unpack('>H', self.header[76:78]) 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]) flags, val = a1, a2<<16|a3<<8|a4 self.sections.append( (offset, flags, val) ) @@ -304,24 +305,24 @@ class MobiBook: exth = '' if exth_flag & 0x40: 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]) pos = 12 - for i in xrange(nitems): + for i in range(nitems): type, size = struct.unpack('>II', exth[pos: pos + 8]) content = exth[pos + 8: pos + size] self.meta_array[type] = content # reset the text to speech flag and clipping limit, if present if type == 401 and size == 9: # 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: # 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') pos += size - except: - pass + except Exception as e: + print(u"Cannot set meta_array: Error: {:s}".format(e.args[0])) def getBookTitle(self): codec_map = { @@ -341,19 +342,19 @@ class MobiBook: codec = codec_map[self.mobi_codepage] if title == '': title = self.header[:32] - title = title.split('\0')[0] - return unicode(title, codec) + title = title.split(b'\0')[0] + return title.decode(codec) def getPIDMetaInfo(self): - rec209 = '' - token = '' + rec209 = b'' + token = b'' if 209 in self.meta_array: rec209 = self.meta_array[209] data = rec209 # 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 # 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]) sval = self.meta_array.get(val,'') token += sval @@ -373,13 +374,14 @@ class MobiBook: def parseDRM(self, data, count, pidlist): 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: - bigpid = pid.ljust(16,'\0') + bigpid = pid.ljust(16,b'\0') + bigpid = bigpid 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 - for i in xrange(count): + for i in range(count): verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) if cksum == temp_key_sum: cookie = PC1(temp_key, cookie) @@ -393,8 +395,8 @@ class MobiBook: # Then try the default encoding that doesn't require a PID pid = '00000000' temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): + temp_key_sum = sum(temp_key) & 0xff + for i in range(count): verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) if cksum == temp_key_sum: cookie = PC1(temp_key, cookie) @@ -405,7 +407,7 @@ class MobiBook: return [found_key,pid] def getFile(self, outpath): - file(outpath,'wb').write(self.mobi_data) + open(outpath,'wb').write(self.mobi_data) def getBookType(self): if self.print_replica: @@ -442,6 +444,7 @@ class MobiBook: raise DrmException(u"Cannot decode library or rented ebooks.") goodpids = [] + # print("DEBUG ==== pidlist = ", pidlist) for pid in pidlist: if len(pid)==10: if checksumPid(pid[0:-2]) != pid: @@ -452,6 +455,8 @@ class MobiBook: else: print(u"Warning: PID {0} has wrong number of digits".format(pid)) + # print(u"======= DEBUG good pids = ", goodpids) + if self.crypto_type == 1: t1_keyvec = 'QDCVEPMU675RUBSZ' if self.magic == 'TEXtREAd': @@ -471,9 +476,9 @@ class MobiBook: if not found_key: raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids))) # 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 - self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8) + self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8) if pid=='00000000': 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))) # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) + self.patchSection(0, b'\0' * 2, 0xC) # decrypt sections print(u"Decrypting. Please wait . . .", end=' ') mobidataList = [] 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) extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) if i%100 == 0: @@ -501,7 +506,7 @@ class MobiBook: mobidataList.append(data[-extra_size:]) if self.num_sections > self.records+1: 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") return @@ -531,8 +536,8 @@ def cli_main(): pidlist = [] try: stripped_file = getUnencryptedBook(infile, pidlist) - file(outfile, 'wb').write(stripped_file) - except DrmException, e: + open(outfile, 'wb').write(stripped_file) + except DrmException as e: print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])) return 1 return 0 diff --git a/DeDRM_plugin/prefs.py b/DeDRM_plugin/prefs.py index c1bfcb9..cdbb189 100644 --- a/DeDRM_plugin/prefs.py +++ b/DeDRM_plugin/prefs.py @@ -101,9 +101,9 @@ def convertprefs(always = False): keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:]) keyvalue = generate_key(name, ccn) userkeys.append([keyname,keyvalue]) - except Exception, e: + except Exception as e: traceback.print_exc() - print e.args[0] + print(e.args[0]) pass return userkeys @@ -118,9 +118,9 @@ def convertprefs(always = False): keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:]) keyvalue = getuser_key(name,cc).encode('hex') userkeys.append([keyname,keyvalue]) - except Exception, e: + except Exception as e: traceback.print_exc() - print e.args[0] + print(e.args[0]) pass return userkeys @@ -161,7 +161,7 @@ def convertprefs(always = False): 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" EREADERPLUGINNAME = "eReader PDB 2 PML" @@ -177,7 +177,7 @@ def convertprefs(always = False): sc = config['plugin_customization'] val = sc.pop(IGNOBLEPLUGINNAME, 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']) userkeys = parseIgnobleString(str(val)) for keypair in userkeys: @@ -185,7 +185,7 @@ def convertprefs(always = False): value = keypair[1] dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) 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 dedrmprefs.writeprefs(False) @@ -193,7 +193,7 @@ def convertprefs(always = False): # old string to stored keys... get that personal data out of plain sight. val = sc.pop(EREADERPLUGINNAME, 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']) userkeys = parseeReaderString(str(val)) for keypair in userkeys: @@ -201,14 +201,14 @@ def convertprefs(always = False): value = keypair[1] dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value) 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 dedrmprefs.writeprefs(False) # get old Kindle plugin configuration string val = sc.pop(OLDKINDLEPLUGINNAME, 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']) priorserialcount = len(dedrmprefs['serials']) pids, serials = parseKindleString(val) @@ -218,7 +218,7 @@ def convertprefs(always = False): dedrmprefs.addvaluetoprefs('serials',serial) addedpidcount = len(dedrmprefs['pids']) - priorpidcount 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 dedrmprefs.writeprefs(False) @@ -234,7 +234,7 @@ def convertprefs(always = False): dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value) addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount 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 dedrmprefs.writeprefs(False) @@ -247,7 +247,7 @@ def convertprefs(always = False): dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value) addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount 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 dedrmprefs.writeprefs(False) @@ -260,7 +260,7 @@ def convertprefs(always = False): addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount # no need to delete old prefs, since they contain no recoverable private data 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 dedrmprefs.writeprefs(False) @@ -277,19 +277,19 @@ def convertprefs(always = False): dedrmprefs.addvaluetoprefs('serials',serial) addedpidcount = len(dedrmprefs['pids']) - priorpidcount 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 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: if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "": dedrmprefs.set('adobewineprefix',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: traceback.print_exc() # Make the json write all the prefs to disk 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)) diff --git a/DeDRM_plugin/scriptinterface.py b/DeDRM_plugin/scriptinterface.py index bcdf0a7..25f23ad 100644 --- a/DeDRM_plugin/scriptinterface.py +++ b/DeDRM_plugin/scriptinterface.py @@ -6,13 +6,13 @@ from __future__ import print_function import sys import os import re -import ineptepub -import ignobleepub -import epubtest -import zipfix -import ineptpdf -import erdr2pml -import k4mobidedrm +import calibre_plugins.dedrm.ineptepub +import calibre_plugins.dedrm.ignobleepub +import calibre_plugins.dedrm.epubtest +import calibre_plugins.dedrm.zipfix +import calibre_plugins.dedrm.ineptpdf +import calibre_plugins.dedrm.erdr2pml +import calibre_plugins.dedrm.k4mobidedrm import traceback def decryptepub(infile, outdir, rscpath): @@ -46,7 +46,7 @@ def decryptepub(infile, outdir, rscpath): if rv == 0: print("Decrypted Adobe ePub with key file {0}".format(filename)) break - except Exception, e: + except Exception as e: errlog += traceback.format_exc() errlog += str(e) rv = 1 @@ -66,7 +66,7 @@ def decryptepub(infile, outdir, rscpath): if rv == 0: print("Decrypted B&N ePub with key file {0}".format(filename)) break - except Exception, e: + except Exception as e: errlog += traceback.format_exc() errlog += str(e) rv = 1 @@ -104,7 +104,7 @@ def decryptpdf(infile, outdir, rscpath): rv = ineptpdf.decryptBook(userkey, infile, outfile) if rv == 0: break - except Exception, e: + except Exception as e: errlog += traceback.format_exc() errlog += str(e) rv = 1 @@ -132,7 +132,7 @@ def decryptpdb(infile, outdir, rscpath): return 1 try: rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8)) - except Exception, e: + except Exception as e: errlog += traceback.format_exc() errlog += str(e) rv = 1 @@ -193,7 +193,7 @@ def decryptk4mobi(infile, outdir, rscpath): androidFiles.append(dpath) try: rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums) - except Exception, e: + except Exception as e: errlog += traceback.format_exc() errlog += str(e) rv = 1 diff --git a/DeDRM_plugin/simpleprefs.py b/DeDRM_plugin/simpleprefs.py index 0809944..5524663 100644 --- a/DeDRM_plugin/simpleprefs.py +++ b/DeDRM_plugin/simpleprefs.py @@ -46,7 +46,7 @@ class SimplePrefs(object): try : data = file(filepath,'rb').read() self.prefs[key] = data - except Exception, e: + except Exception as e: pass def getPreferences(self): @@ -71,7 +71,7 @@ class SimplePrefs(object): else: try: file(filepath,'wb').write(data) - except Exception, e: + except Exception as e: pass self.prefs = newprefs return diff --git a/DeDRM_plugin/topazextract.py b/DeDRM_plugin/topazextract.py index 7d91f2c..ca0101d 100644 --- a/DeDRM_plugin/topazextract.py +++ b/DeDRM_plugin/topazextract.py @@ -7,9 +7,10 @@ # Changelog # 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility # 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 -__version__ = '5.0' +__version__ = '6.0' import sys import os, csv, getopt @@ -17,7 +18,7 @@ import zlib, zipfile, tempfile, shutil import traceback from struct import pack from struct import unpack -from alfcrypto import Topaz_Cipher +from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher class SafeUnbuffered: def __init__(self, stream): @@ -26,7 +27,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -64,7 +65,7 @@ def unicode_argv(): # Remove Python executable and commands if present start = argc.value - len(sys.argv) 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 # this should never happen return [u"mobidedrm.py"] @@ -72,7 +73,7 @@ def unicode_argv(): argvencoding = sys.stdin.encoding if argvencoding == None: argvencoding = 'utf-8' - return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv] + return argv #global switch debug = False @@ -196,7 +197,7 @@ def decryptDkeyRecords(data,PID): class TopazBook: def __init__(self, filename): - self.fo = file(filename, 'rb') + self.fo = open(filename, 'rb') self.outdir = tempfile.mkdtemp() # self.outdir = 'rawdat' self.bookPayloadOffset = 0 @@ -319,7 +320,7 @@ class TopazBook: fixedimage=True try: 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"attempting to extrct files without a book key") self.createBookDirectory() @@ -345,7 +346,7 @@ class TopazBook: data = keydata try: bookKeys+=decryptDkeyRecords(data,pid) - except DrmException, e: + except DrmException as e: pass else: bookKey = bookKeys[0] @@ -357,7 +358,7 @@ class TopazBook: self.setBookKey(bookKey) self.createBookDirectory() - self.extractFiles() + self.extractFiles() print(u"Successfully Extracted Topaz contents") if inCalibre: from calibre_plugins.dedrm import genbook @@ -411,7 +412,7 @@ class TopazBook: print(u".", end=' ') record = self.getBookPayloadRecord(name,index) if record != '': - file(outputFile, 'wb').write(record) + open(outputFile, 'wb').write(record) print(u" ") def getFile(self, zipname): @@ -454,7 +455,7 @@ def cli_main(): try: 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])) usage(progname) return 1 @@ -513,7 +514,7 @@ def cli_main(): # removing internal temporary directory of pieces tb.cleanup() - except DrmException, e: + except DrmException as e: print(u"Decryption failed\n{0}".format(traceback.format_exc())) try: @@ -522,8 +523,8 @@ def cli_main(): pass return 1 - except Exception, e: - print(u"Decryption failed\m{0}".format(traceback.format_exc())) + except Exception as e: + print(u"Decryption failed\n{0}".format(traceback.format_exc())) try: tb.cleanup() except: diff --git a/DeDRM_plugin/utilities.py b/DeDRM_plugin/utilities.py index c62b043..56c64fd 100644 --- a/DeDRM_plugin/utilities.py +++ b/DeDRM_plugin/utilities.py @@ -3,7 +3,7 @@ from __future__ import with_statement -from ignoblekeygen import generate_key +from calibre_plugins.dedrm.ignoblekeygen import generate_key __license__ = 'GPL v3' @@ -21,8 +21,8 @@ DETAILED_MESSAGE = \ def uStrCmp (s1, s2, caseless=False): import unicodedata as ud - str1 = s1 if isinstance(s1, unicode) else unicode(s1) - str2 = s2 if isinstance(s2, unicode) else unicode(s2) + str1 = s1 if isinstance(s1, unicode) else s1.decode('utf-8') + str2 = s2 if isinstance(s2, unicode) else s2.decode('utf-8') if caseless: return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower()) else: diff --git a/DeDRM_plugin/wineutils.py b/DeDRM_plugin/wineutils.py index 65435a2..c5d4dee 100644 --- a/DeDRM_plugin/wineutils.py +++ b/DeDRM_plugin/wineutils.py @@ -40,7 +40,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""): cmdline = cmdline.encode(sys.getfilesystemencoding()) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) 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])) if wineprefix != "" and os.path.exists(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()) p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False) 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])) # try finding winekeys anyway, even if above code errored diff --git a/DeDRM_plugin/zipfilerugged.py b/DeDRM_plugin/zipfilerugged.py index 4a55a69..d20dab6 100644 --- a/DeDRM_plugin/zipfilerugged.py +++ b/DeDRM_plugin/zipfilerugged.py @@ -2,10 +2,12 @@ Read and write ZIP files. """ import struct, os, time, sys, shutil -import binascii, cStringIO, stat +import binascii, stat import io import re +from io import BytesIO + try: import zlib # We may need its compression method crc32 = zlib.crc32 @@ -45,8 +47,8 @@ ZIP_DEFLATED = 8 # The "end of central directory" structure, magic number, size, and indices # (section V.I in the format document) -structEndArchive = "<4s4H2LH" -stringEndArchive = "PK\005\006" +structEndArchive = b"<4s4H2LH" +stringEndArchive = b"PK\005\006" sizeEndCentDir = struct.calcsize(structEndArchive) _ECD_SIGNATURE = 0 @@ -64,8 +66,8 @@ _ECD_LOCATION = 9 # The "central directory" structure, magic number, size, and indices # of entries in the structure (section V.F in the format document) -structCentralDir = "<4s4B4HL2L5H2L" -stringCentralDir = "PK\001\002" +structCentralDir = b"<4s4B4HL2L5H2L" +stringCentralDir = b"PK\001\002" sizeCentralDir = struct.calcsize(structCentralDir) # 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 # (section V.A in the format document) -structFileHeader = "<4s2B4HL2L2H" -stringFileHeader = "PK\003\004" +structFileHeader = b"<4s2B4HL2L2H" +stringFileHeader = b"PK\003\004" sizeFileHeader = struct.calcsize(structFileHeader) _FH_SIGNATURE = 0 @@ -109,14 +111,14 @@ _FH_FILENAME_LENGTH = 10 _FH_EXTRA_FIELD_LENGTH = 11 # The "Zip64 end of central directory locator" structure, magic number, and size -structEndArchive64Locator = "<4sLQL" -stringEndArchive64Locator = "PK\x06\x07" +structEndArchive64Locator = b"<4sLQL" +stringEndArchive64Locator = b"PK\x06\x07" sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator) # The "Zip64 end of central directory" record, magic number, size, and indices # (section V.G in the format document) -structEndArchive64 = "<4sQ2H2L4Q" -stringEndArchive64 = "PK\x06\x06" +structEndArchive64 = b"<4sQ2H2L4Q" +stringEndArchive64 = b"PK\x06\x06" sizeEndCentDir64 = struct.calcsize(structEndArchive64) _CD64_SIGNATURE = 0 @@ -275,7 +277,7 @@ class ZipInfo (object): # Terminate the file name at the first null byte. Null bytes in file # 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: filename = filename[0:null_byte] # 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 # Standard values: self.compress_type = ZIP_STORED # Type of compression for the file - self.comment = "" # Comment for each file - self.extra = "" # ZIP extra data + self.comment = b"" # Comment for each file + self.extra = b"" # ZIP extra data if sys.platform == 'win32': self.create_system = 0 # System which created ZIP archive else: @@ -343,23 +345,13 @@ class ZipInfo (object): return header + filename + extra def _encodeFilenameFlags(self): - if isinstance(self.filename, unicode): + if isinstance(self.filename, bytes): + return self.filename, self.flag_bits + else: try: return self.filename.encode('ascii'), self.flag_bits except UnicodeEncodeError: 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): # Try to decode the extra field. @@ -377,20 +369,20 @@ class ZipInfo (object): elif ln == 0: counts = () else: - raise RuntimeError, "Corrupt extra field %s"%(ln,) + raise RuntimeError("Corrupt extra field %s"%(ln,)) idx = 0 # 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] idx += 1 - if self.compress_size == 0xFFFFFFFFL: + if self.compress_size == 0xFFFFFFFF: self.compress_size = counts[idx] idx += 1 - if self.header_offset == 0xffffffffL: + if self.header_offset == 0xffffffff: old = self.header_offset self.header_offset = counts[idx] idx+=1 @@ -481,9 +473,9 @@ class ZipExtFile(io.BufferedIOBase): if self._compress_type == ZIP_DEFLATED: self._decompressor = zlib.decompressobj(-15) - self._unconsumed = '' + self._unconsumed = b'' - self._readbuffer = '' + self._readbuffer = b'' self._offset = 0 self._universal = 'U' in mode @@ -514,10 +506,10 @@ class ZipExtFile(io.BufferedIOBase): if not self._universal: return io.BufferedIOBase.readline(self, limit) - line = '' + line = b'' while limit < 0 or len(line) < limit: readahead = self.peek(2) - if readahead == '': + if readahead == b'': 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.. """ - buf = '' + buf = b'' while n < 0 or n is None or n > len(buf): data = self.read1(n) if len(data) == 0: @@ -594,7 +586,7 @@ class ZipExtFile(io.BufferedIOBase): self._compress_left -= len(data) 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: self._readbuffer = self._readbuffer[self._offset:] + data @@ -651,10 +643,10 @@ class ZipFile: pass elif compression == ZIP_DEFLATED: if not zlib: - raise RuntimeError,\ - "Compression requires the (missing) zlib module" + raise RuntimeError( + "Compression requires the (missing) zlib module") else: - raise RuntimeError, "That compression method is not supported" + raise RuntimeError("That compression method is not supported") self._allowZip64 = allowZip64 self._didModify = False @@ -664,10 +656,10 @@ class ZipFile: self.compression = compression # Method of compression self.mode = key = mode.replace('b', '')[0] self.pwd = None - self.comment = '' + self.comment = b'' # Check if we were passed a file-like object - if isinstance(file, basestring): + if isinstance(file, str): self._filePassed = 0 self.filename = file modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'} @@ -699,7 +691,7 @@ class ZipFile: if not self._filePassed: self.fp.close() self.fp = None - raise RuntimeError, 'Mode must be "r", "w" or "a"' + raise RuntimeError('Mode must be "r", "w" or "a"') def __enter__(self): return self @@ -723,9 +715,9 @@ class ZipFile: fp = self.fp endrec = _EndRecData(fp) if not endrec: - raise BadZipfile, "File is not a zip file" + raise BadZipfile("File is not a zip file") if self.debug > 1: - print endrec + print(endrec) size_cd = endrec[_ECD_SIZE] # bytes in central directory offset_cd = endrec[_ECD_OFFSET] # offset of central directory self.comment = endrec[_ECD_COMMENT] # archive comment @@ -738,20 +730,20 @@ class ZipFile: if self.debug > 2: 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 = offset_cd + concat fp.seek(self.start_dir, 0) data = fp.read(size_cd) - fp = cStringIO.StringIO(data) + fp = BytesIO(data) total = 0 while total < size_cd: centdir = fp.read(sizeCentralDir) 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) if self.debug > 2: - print centdir + print(centdir) filename = fp.read(centdir[_CD_FILENAME_LENGTH]) # Create ZipInfo instance to store file information x = ZipInfo(filename) @@ -769,7 +761,6 @@ class ZipFile: x._decodeExtra() x.header_offset = x.header_offset + concat - x.filename = x._decodeFilename() self.filelist.append(x) self.NameToInfo[x.filename] = x @@ -779,7 +770,7 @@ class ZipFile: + centdir[_CD_COMMENT_LENGTH]) if self.debug > 2: - print "total", total + print("total", total) def namelist(self): @@ -796,10 +787,10 @@ class ZipFile: def printdir(self): """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: 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): """Read all the files and check the CRC.""" @@ -834,10 +825,10 @@ class ZipFile: def open(self, name, mode="r", pwd=None): """Return file-like object for 'name'.""" 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: - raise RuntimeError, \ - "Attempt to read ZIP archive that was already closed" + raise RuntimeError( + "Attempt to read ZIP archive that was already closed") # Only open a new file for instances where we were not # given a file object in the constructor @@ -859,7 +850,7 @@ class ZipFile: # Skip the file header: fheader = zef_file.read(sizeFileHeader) 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) fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) @@ -867,9 +858,9 @@ class ZipFile: zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) if fname != zinfo.orig_filename: - raise BadZipfile, \ + raise BadZipfile( 'File name in directory "%s" and header "%s" differ.' % ( - zinfo.orig_filename, fname) + zinfo.orig_filename, fname)) # check for encrypted flag & handle password is_encrypted = zinfo.flag_bits & 0x1 @@ -878,8 +869,8 @@ class ZipFile: if not pwd: pwd = self.pwd if not pwd: - raise RuntimeError, "File %s is encrypted, " \ - "password required for extraction" % name + raise RuntimeError("File %s is encrypted, " \ + "password required for extraction" % name) zd = _ZipDecrypter(pwd) # The first 12 bytes in the cypher stream is an encryption header @@ -956,7 +947,7 @@ class ZipFile: return targetpath source = self.open(member, pwd=pwd) - target = file(targetpath, "wb") + target = open(targetpath, "wb") shutil.copyfileobj(source, target) source.close() target.close() @@ -967,18 +958,18 @@ class ZipFile: """Check for errors before writing a file to the archive.""" if zinfo.filename in self.NameToInfo: if self.debug: # Warning for duplicate names - print "Duplicate name:", zinfo.filename + print("Duplicate name:", zinfo.filename) 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: - raise RuntimeError, \ - "Attempt to write ZIP archive that was already closed" + raise RuntimeError( + "Attempt to write ZIP archive that was already closed") if zinfo.compress_type == ZIP_DEFLATED and not zlib: - raise RuntimeError, \ - "Compression requires the (missing) zlib module" + raise RuntimeError( + "Compression requires the (missing) zlib module") if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): - raise RuntimeError, \ - "That compression method is not supported" + raise RuntimeError( + "That compression method is not supported") if zinfo.file_size > ZIP64_LIMIT: if not self._allowZip64: raise LargeZipFile("Filesize would require ZIP64 extensions") @@ -1006,7 +997,7 @@ class ZipFile: if isdir: arcname += '/' 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: zinfo.compress_type = self.compression else: @@ -1076,7 +1067,7 @@ class ZipFile: date_time=time.localtime(time.time())[:6]) zinfo.compress_type = self.compression - zinfo.external_attr = 0600 << 16 + zinfo.external_attr = 0x0600 << 16 else: zinfo = zinfo_or_arcname @@ -1141,7 +1132,7 @@ class ZipFile: if zinfo.header_offset > ZIP64_LIMIT: extra.append(zinfo.header_offset) - header_offset = 0xffffffffL + header_offset = 0xffffffff else: header_offset = zinfo.header_offset @@ -1169,14 +1160,14 @@ class ZipFile: 0, zinfo.internal_attr, zinfo.external_attr, header_offset) except DeprecationWarning: - print >>sys.stderr, (structCentralDir, + print(structCentralDir, stringCentralDir, create_version, zinfo.create_system, extract_version, zinfo.reserved, zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, zinfo.CRC, compress_size, file_size, len(zinfo.filename), len(extra_data), len(zinfo.comment), 0, zinfo.internal_attr, zinfo.external_attr, - header_offset) + header_offset, sys.stderr) raise self.fp.write(centdir) self.fp.write(filename) @@ -1250,10 +1241,10 @@ class PyZipFile(ZipFile): else: basename = name 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) if self.debug: - print "Adding", arcname + print("Adding", arcname) self.write(fname, arcname) dirlist = os.listdir(pathname) dirlist.remove("__init__.py") @@ -1269,12 +1260,12 @@ class PyZipFile(ZipFile): fname, arcname = self._get_codename(path[0:-3], basename) if self.debug: - print "Adding", arcname + print("Adding", arcname) self.write(fname, arcname) else: # This is NOT a package directory, add its files at top level if self.debug: - print "Adding files from directory", pathname + print("Adding files from directory", pathname) for filename in os.listdir(pathname): path = os.path.join(pathname, filename) root, ext = os.path.splitext(filename) @@ -1282,15 +1273,15 @@ class PyZipFile(ZipFile): fname, arcname = self._get_codename(path[0:-3], basename) if self.debug: - print "Adding", arcname + print("Adding", arcname) self.write(fname, arcname) else: if pathname[-3:] != ".py": - raise RuntimeError, \ - 'Files added with writepy() must end with ".py"' + raise RuntimeError( + 'Files added with writepy() must end with ".py"') fname, arcname = self._get_codename(pathname[0:-3], basename) if self.debug: - print "Adding file", arcname + print("Adding file", arcname) self.write(fname, arcname) 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: import py_compile if self.debug: - print "Compiling", file_py + print("Compiling", file_py) try: py_compile.compile(file_py, file_pyc, None, True) - except py_compile.PyCompileError,err: - print err.msg + except py_compile.PyCompileError as err: + print(err.msg) fname = file_pyc else: fname = file_pyc @@ -1337,12 +1328,12 @@ def main(args = None): args = sys.argv[1:] if not args or args[0] not in ('-l', '-c', '-e', '-t'): - print USAGE + print(USAGE) sys.exit(1) if args[0] == '-l': if len(args) != 2: - print USAGE + print(USAGE) sys.exit(1) zf = ZipFile(args[1], 'r') zf.printdir() @@ -1350,15 +1341,15 @@ def main(args = None): elif args[0] == '-t': if len(args) != 2: - print USAGE + print(USAGE) sys.exit(1) zf = ZipFile(args[1], 'r') zf.testzip() - print "Done testing" + print("Done testing") elif args[0] == '-e': if len(args) != 3: - print USAGE + print(USAGE) sys.exit(1) zf = ZipFile(args[1], 'r') @@ -1378,7 +1369,7 @@ def main(args = None): elif args[0] == '-c': if len(args) < 3: - print USAGE + print(USAGE) sys.exit(1) def addToZip(zf, path, zippath): diff --git a/DeDRM_plugin/zipfix.py b/DeDRM_plugin/zipfix.py index d3371f2..190cf44 100644 --- a/DeDRM_plugin/zipfix.py +++ b/DeDRM_plugin/zipfix.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# zipfix.py, version 1.1 -# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf +# zipfix.py +# Copyright © 2010-2020 by some_updates, DiapDealer and Apprentice Alf # Released under the terms of the GNU General Public Licence, version 3 # @@ -10,6 +10,7 @@ # Revision history: # 1.0 - Initial release # 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). @@ -21,7 +22,7 @@ __version__ = "1.1" import sys import zlib -import zipfilerugged +import calibre_plugins.dedrm.zipfilerugged as zipfilerugged import os import os.path import getopt @@ -49,7 +50,7 @@ class fixZip: self.inzip = zipfilerugged.ZipFile(zinput,'r') self.outzip = zipfilerugged.ZipFile(zoutput,'w') # 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): local_header_offset = zi.header_offset @@ -115,7 +116,7 @@ class fixZip: # if epub write mimetype file first, with no compression if self.ztype == 'epub': # 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 try: # if the mimetype is present, get its info, including time-stamp @@ -129,7 +130,7 @@ class fixZip: mimeinfo.create_system = oldmimeinfo.create_system except: pass - self.outzip.writestr(mimeinfo, _MIMETYPE) + self.outzip.writestr(mimeinfo, _MIMETYPE.encode('ascii')) # write the rest of the files for zinfo in self.inzip.infolist(): @@ -171,7 +172,7 @@ def repairBook(infile, outfile): fr = fixZip(infile, outfile) fr.fix() return 0 - except Exception, e: + except Exception as e: print("Error Occurred ", e) return 2 diff --git a/Obok_plugin/action.py b/Obok_plugin/action.py index a0b63a6..802a833 100644 --- a/Obok_plugin/action.py +++ b/Obok_plugin/action.py @@ -6,13 +6,14 @@ __license__ = 'GPL v3' __docformat__ = 'restructuredtext en' +import codecs import os, traceback, zipfile try: from PyQt5.Qt import QToolButton, QUrl except ImportError: from PyQt4.Qt import QToolButton, QUrl - + from calibre.gui2 import open_url, question_dialog from calibre.gui2.actions import InterfaceAction 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, AddEpubFormatsProgressDialog, ResultsSummaryDialog) 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) from calibre_plugins.obok_dedrm.utilities import ( get_icon, set_plugin_icon_resources, showErrorDlg, format_plural, @@ -53,7 +54,7 @@ class InterfacePluginAction(InterfaceAction): def genesis(self): icon_resources = self.load_resources(PLUGIN_ICONS) set_plugin_icon_resources(PLUGIN_NAME, icon_resources) - + self.qaction.setIcon(get_icon(PLUGIN_ICONS[0])) self.qaction.triggered.connect(self.launchObok) self.gui.keyboard.finalize() @@ -106,10 +107,10 @@ class InterfacePluginAction(InterfaceAction): # Get a list of Kobo titles books = self.build_book_list() if len(books) < 1: - msg = _('

No books found in Kobo Library\nAre you sure it\'s installed\configured\synchronized?') + msg = _('

No books found in Kobo Library\nAre you sure it\'s installed/configured/synchronized?') showErrorDlg(msg, None) return - + # Check to see if a key can be retrieved using the legacy obok method. legacy_key = legacy_obok().get_legacy_cookie_id if legacy_key is not None: @@ -154,7 +155,7 @@ class InterfacePluginAction(InterfaceAction): # Close Kobo Library object 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. if len(self.books_to_add): 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): ''' 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. ''' 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 (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) ''' 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): ''' 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 mi: calibre metadata object :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)) else: 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. if self.formats_to_add: 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.exec_() return - + 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. ''' ''' Terisa: Improve the message @@ -327,13 +328,13 @@ class InterfacePluginAction(InterfaceAction): msg = _('

{0} -- not added because of {1} in your library.

').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?

') msg += _('NOTE: no pre-existing EPUB will be overwritten.') - + return question_dialog(self.gui, caption, msg, det_msg) def find_a_home(self, ids): ''' Find the ID of the first EPUB-Free duplicate available - + :param ids: List of calibre IDs that might serve as a home. ''' for id in ids: @@ -373,7 +374,7 @@ class InterfacePluginAction(InterfaceAction): zin = zipfile.ZipFile(book.filename, 'r') #print ('Kobo library filename: {0}'.format(book.filename)) for userkey in self.userkeys: - print (_('Trying key: '), userkey.encode('hex_codec')) + print (_('Trying key: '), codecs.encode(userkey, 'hex')) check = True try: fileout = PersistentTemporaryFile('.epub', dir=self.tdir) @@ -455,7 +456,7 @@ class InterfacePluginAction(InterfaceAction): if cancelled_count > 0: log += _('

Book imports cancelled by user: {}

\n').format(cancelled_count) return (msg, log) - log += _('

New EPUB formats inserted in existing calibre books: {0}

\n').format(len(self.successful_format_adds)) + log += _('

New EPUB formats inserted in existing calibre books: {0}

\n').format(len(self.successful_format_adds)) if self.successful_format_adds: log += '
    \n' for id, mi in self.successful_format_adds: @@ -474,7 +475,7 @@ class InterfacePluginAction(InterfaceAction): log += _('

    Format imports cancelled by user: {}

    \n').format(cancelled_count) return (msg, log) else: - + # Single book ... don't get fancy. if self.ids_of_new_books: title = self.ids_of_new_books[0][1].title @@ -494,4 +495,4 @@ class InterfacePluginAction(InterfaceAction): reason = _('of unknown reasons. Gosh I\'m embarrassed!') msg = _('

    {0} not added because {1}').format(title, reason) return (msg, log) - + diff --git a/Obok_plugin/common_utils.py b/Obok_plugin/common_utils.py index 0f2164a..babad1c 100644 --- a/Obok_plugin/common_utils.py +++ b/Obok_plugin/common_utils.py @@ -427,7 +427,7 @@ class KeyValueComboBox(QComboBox): def selected_key(self): for key, value in self.values.iteritems(): - if value == unicode(self.currentText()).strip(): + if value == self.currentText().strip(): return key @@ -450,7 +450,7 @@ class KeyComboBox(QComboBox): def selected_key(self): for key, value in self.values.iteritems(): - if key == unicode(self.currentText()).strip(): + if key == self.currentText().strip(): return key diff --git a/Obok_plugin/config.py b/Obok_plugin/config.py index 8244b91..522b86a 100644 --- a/Obok_plugin/config.py +++ b/Obok_plugin/config.py @@ -75,7 +75,7 @@ class ConfigWidget(QWidget): 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_directory'] = self.kobodirectory @@ -165,7 +165,7 @@ class ManageKeysDialog(QDialog): def delete_key(self): if not self.listy.currentItem(): 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} {0}?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False): return self.plugin_keys.remove(keyname) @@ -202,11 +202,11 @@ class AddSerialDialog(QDialog): @property def key_name(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() @property def key_value(self): - return unicode(self.key_ledit.text()).strip() + return self.key_ledit.text().strip() def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): diff --git a/Obok_plugin/obok/obok.py b/Obok_plugin/obok/obok.py index 21ef14a..80bc058 100644 --- a/Obok_plugin/obok/obok.py +++ b/Obok_plugin/obok/obok.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Version 4.0.0 September 2020 +# Python 3.0 +# # Version 3.2.5 December 2016 # Improve detection of good text decryption. # @@ -152,8 +155,8 @@ """Manage all Kobo books, either encrypted or DRM-free.""" from __future__ import print_function -__version__ = '3.2.4' -__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__) +__version__ = '4.0.0' +__about__ = u"Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__) import sys import os @@ -231,7 +234,7 @@ def _load_crypto_libcrypto(): raise ENCRYPTIONError(_('Failed to initialize AES key')) def decrypt(self, data): - clear = '' + clear = b'' for i in range(0, len(data), 16): out = create_string_buffer(16) rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0) @@ -276,7 +279,7 @@ class SafeUnbuffered: if self.encoding == None: self.encoding = "utf-8" def write(self, data): - if isinstance(data,unicode): + if isinstance(data,bytes): data = data.encode(self.encoding,"replace") self.stream.write(data) self.stream.flush() @@ -381,7 +384,7 @@ class KoboLibrary(object): print(self.newdb.name) olddb = open(kobodb, 'rb') self.newdb.write(olddb.read(18)) - self.newdb.write('\x01\x01') + self.newdb.write(b'\x01\x01') olddb.read(2) self.newdb.write(olddb.read()) olddb.close() @@ -488,14 +491,14 @@ class KoboLibrary(object): pass row = cursor.fetchone() return userids - + def __getuserkeys (self, macaddr): userids = self.__getuserids() userkeys = [] for hash in KOBO_HASH_KEYS: - deviceid = hashlib.sha256(hash + macaddr).hexdigest() + deviceid = hashlib.sha256((hash + macaddr).encode('ascii')).hexdigest() 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:])) return userkeys @@ -556,7 +559,7 @@ class KoboBook(object): # Convert relative URIs href = item.attrib['href'] if not c.match(href): - href = string.join((basedir, href), '') + href = ''.join((basedir, href)) # Update books we've found from the DB. if href in self._encryptedfiles: @@ -606,57 +609,57 @@ class KoboFile(object): stride = 1 print(u"Checking text:{0}:".format(contents[:10])) # 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 print(u"Could be utf-8 with BOM") textoffset = 3 - elif contents[:2]=="\xfe\xff": + elif contents[:2]==b"\xfe\xff": # seems to be utf-16BE print(u"Could be utf-16BE") textoffset = 3 stride = 2 - elif contents[:2]=="\xff\xfe": + elif contents[:2]==b"\xff\xfe": # seems to be utf-16LE print(u"Could be utf-16LE") textoffset = 2 stride = 2 else: print(u"Perhaps utf-8 without BOM") - + # now check that the first few characters are in the ASCII range - for i in xrange(textoffset,textoffset+5*stride,stride): - if ord(contents[i])<32 or ord(contents[i])>127: + for i in range(textoffset,textoffset+5*stride,stride): + if contents[i]<32 or contents[i]>127: # 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 print(u"Seems to be good text") return True - if contents[:5]=="