Remove redundant source files in macOS app
parent
30eeeea618
commit
0055386f7b
@ -1,647 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# __init__.py for DeDRM_plugin
|
|
||||||
# Copyright © 2008-2018 Apprentice Harper et al.
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
|
||||||
# <http://www.gnu.org/licenses/>
|
|
||||||
#
|
|
||||||
# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts.
|
|
||||||
# We had the much easier job of converting them to a calibre plugin.
|
|
||||||
#
|
|
||||||
# This plugin is meant to decrypt eReader PDBs, Adobe Adept ePubs, Barnes & Noble ePubs,
|
|
||||||
# Adobe Adept PDFs, Amazon Kindle and Mobipocket files without having to
|
|
||||||
# install any dependencies... other than having calibre installed, of course.
|
|
||||||
#
|
|
||||||
# Configuration:
|
|
||||||
# Check out the plugin's configuration settings by clicking the "Customize plugin"
|
|
||||||
# button when you have the "DeDRM" plugin highlighted (under Preferences->
|
|
||||||
# Plugins->File type plugins). Once you have the configuration dialog open, you'll
|
|
||||||
# see a Help link on the top right-hand side.
|
|
||||||
#
|
|
||||||
# Revision history:
|
|
||||||
# 6.0.0 - Initial release
|
|
||||||
# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions
|
|
||||||
# 6.0.2 - Restored call to Wine to get Kindle for PC keys, added for ADE
|
|
||||||
# 6.0.3 - Fixes for Kindle for Mac and Windows non-ascii user names
|
|
||||||
# 6.0.4 - Fixes for stand-alone scripts and applications
|
|
||||||
# and pdb files in plugin and initial conversion of prefs.
|
|
||||||
# 6.0.5 - Fix a key issue
|
|
||||||
# 6.0.6 - Fix up an incorrect function call
|
|
||||||
# 6.0.7 - Error handling for incomplete PDF metadata
|
|
||||||
# 6.0.8 - Fixes a Wine key issue and topaz support
|
|
||||||
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
|
|
||||||
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
|
||||||
# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
|
|
||||||
# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
|
|
||||||
# 6.2.1 - Fix for non-ascii Windows user names
|
|
||||||
# 6.2.2 - Added URL method for B&N/nook books
|
|
||||||
# 6.3.0 - Added in Kindle for Android serial number solution
|
|
||||||
# 6.3.1 - Version number bump for clarity
|
|
||||||
# 6.3.2 - Fixed Kindle for Android help file
|
|
||||||
# 6.3.3 - Bug fix for Kindle for PC support
|
|
||||||
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
|
||||||
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
|
||||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
|
||||||
# 6.4.0 - Updated for new Kindle for PC encryption
|
|
||||||
# 6.4.1 - Fix for some new tags in Topaz ebooks.
|
|
||||||
# 6.4.2 - Fix for more new tags in Topaz ebooks and very small Topaz ebooks
|
|
||||||
# 6.4.3 - Fix for error that only appears when not in debug mode
|
|
||||||
# Also includes fix for Macs with bonded ethernet ports
|
|
||||||
# 6.5.0 - Big update to Macintosh app
|
|
||||||
# Fix for some more 'new' tags in Topaz ebooks.
|
|
||||||
# Fix an error in wineutils.py
|
|
||||||
# 6.5.1 - Updated version number, added PDF check for DRM-free documents
|
|
||||||
# 6.5.2 - Another Topaz fix
|
|
||||||
# 6.5.3 - Warn about KFX files explicitly
|
|
||||||
# 6.5.4 - Mac App Fix, improve PDF decryption, handle latest tcl changes in ActivePython
|
|
||||||
# 6.5.5 - Finally a fix for the Windows non-ASCII user names.
|
|
||||||
# 6.6.0 - Add kfx and kfx-zip as supported file types (also invoke this plugin if the original
|
|
||||||
# imported format was azw8 since that may be converted to kfx)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Decrypt DRMed ebooks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
PLUGIN_NAME = u"DeDRM"
|
|
||||||
PLUGIN_VERSION_TUPLE = (6, 6, 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 sys, os, re
|
|
||||||
import time
|
|
||||||
import zipfile
|
|
||||||
import traceback
|
|
||||||
from zipfile import ZipFile
|
|
||||||
|
|
||||||
class DeDRMError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
from calibre.customize import FileTypePlugin
|
|
||||||
from calibre.constants import iswindows, isosx
|
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
from calibre.utils.config import config_dir
|
|
||||||
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get safely
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
try:
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
except:
|
|
||||||
# We can do nothing if a write fails
|
|
||||||
pass
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
class DeDRM(FileTypePlugin):
|
|
||||||
name = PLUGIN_NAME
|
|
||||||
description = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
|
||||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
|
||||||
version = PLUGIN_VERSION_TUPLE
|
|
||||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
|
||||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
|
||||||
on_import = True
|
|
||||||
priority = 600
|
|
||||||
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
"""
|
|
||||||
Dynamic modules can't be imported/loaded from a zipfile.
|
|
||||||
So this routine will extract the appropriate
|
|
||||||
library for the target OS and copy it to the 'alfcrypto' subdirectory of
|
|
||||||
calibre's configuration directory. That 'alfcrypto' directory is then
|
|
||||||
inserted into the syspath (as the very first entry) in the run function
|
|
||||||
so the CDLL stuff will work in the alfcrypto.py script.
|
|
||||||
|
|
||||||
The extraction only happens once per version of the plugin
|
|
||||||
Also perform upgrade of preferences once per version
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
|
||||||
if not os.path.exists(self.pluginsdir):
|
|
||||||
os.mkdir(self.pluginsdir)
|
|
||||||
self.maindir = os.path.join(self.pluginsdir,u"DeDRM")
|
|
||||||
if not os.path.exists(self.maindir):
|
|
||||||
os.mkdir(self.maindir)
|
|
||||||
self.helpdir = os.path.join(self.maindir,u"help")
|
|
||||||
if not os.path.exists(self.helpdir):
|
|
||||||
os.mkdir(self.helpdir)
|
|
||||||
self.alfdir = os.path.join(self.maindir,u"libraryfiles")
|
|
||||||
if not os.path.exists(self.alfdir):
|
|
||||||
os.mkdir(self.alfdir)
|
|
||||||
# only continue if we've never run this version of the plugin before
|
|
||||||
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
|
||||||
if not os.path.exists(self.verdir):
|
|
||||||
if iswindows:
|
|
||||||
names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
|
|
||||||
elif isosx:
|
|
||||||
names = [u"libalfcrypto.dylib"]
|
|
||||||
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)
|
|
||||||
|
|
||||||
for entry, data in lib_dict.items():
|
|
||||||
file_path = os.path.join(self.alfdir, entry)
|
|
||||||
try:
|
|
||||||
os.remove(file_path)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
open(file_path,'wb').write(data)
|
|
||||||
except:
|
|
||||||
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
# convert old preferences, if necessary.
|
|
||||||
from calibre_plugins.dedrm.prefs import convertprefs
|
|
||||||
convertprefs()
|
|
||||||
|
|
||||||
# mark that this version has been initialized
|
|
||||||
os.mkdir(self.verdir)
|
|
||||||
except Exception, e:
|
|
||||||
traceback.print_exc()
|
|
||||||
raise
|
|
||||||
|
|
||||||
def ePubDecrypt(self,path_to_ebook):
|
|
||||||
# Create a TemporaryPersistent file to work with.
|
|
||||||
# Check original epub archive for zip errors.
|
|
||||||
import calibre_plugins.dedrm.zipfix
|
|
||||||
|
|
||||||
inf = self.temporary_file(u".epub")
|
|
||||||
try:
|
|
||||||
print u"{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])
|
|
||||||
raise Exception(e)
|
|
||||||
|
|
||||||
# import the decryption keys
|
|
||||||
import calibre_plugins.dedrm.prefs as prefs
|
|
||||||
dedrmprefs = prefs.DeDRM_Prefs()
|
|
||||||
|
|
||||||
# import the Barnes & Noble ePub handler
|
|
||||||
import calibre_plugins.dedrm.ignobleepub as ignobleepub
|
|
||||||
|
|
||||||
|
|
||||||
#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))
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
result = 1
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
if result == 0:
|
|
||||||
# Decryption was successful.
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# get the default NOOK Study keys
|
|
||||||
defaultkeys = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
if iswindows or isosx:
|
|
||||||
from calibre_plugins.dedrm.ignoblekey import nookkeys
|
|
||||||
|
|
||||||
defaultkeys = nookkeys()
|
|
||||||
else: # linux
|
|
||||||
from wineutils import WineGetKeys
|
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,u"ignoblekey.py")
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
newkeys = []
|
|
||||||
for keyvalue in defaultkeys:
|
|
||||||
if keyvalue not in dedrmprefs['bandnkeys'].values():
|
|
||||||
newkeys.append(keyvalue)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
result = 1
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# get the default Adobe keys
|
|
||||||
defaultkeys = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
if iswindows or isosx:
|
|
||||||
from calibre_plugins.dedrm.adobekey import adeptkeys
|
|
||||||
|
|
||||||
defaultkeys = adeptkeys()
|
|
||||||
else: # linux
|
|
||||||
from wineutils import WineGetKeys
|
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
|
||||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
|
||||||
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
self.default_key = u""
|
|
||||||
|
|
||||||
newkeys = []
|
|
||||||
for keyvalue in defaultkeys:
|
|
||||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
|
||||||
newkeys.append(keyvalue)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
result = 1
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
# 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)
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
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):
|
|
||||||
import calibre_plugins.dedrm.prefs as prefs
|
|
||||||
import calibre_plugins.dedrm.ineptpdf
|
|
||||||
|
|
||||||
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))
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
result = 1
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
if result == 0:
|
|
||||||
# Decryption was successful.
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# get the default Adobe keys
|
|
||||||
defaultkeys = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
if iswindows or isosx:
|
|
||||||
from calibre_plugins.dedrm.adobekey import adeptkeys
|
|
||||||
|
|
||||||
defaultkeys = adeptkeys()
|
|
||||||
else: # linux
|
|
||||||
from wineutils import WineGetKeys
|
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
|
||||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
|
||||||
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
self.default_key = u""
|
|
||||||
|
|
||||||
newkeys = []
|
|
||||||
for keyvalue in defaultkeys:
|
|
||||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
|
||||||
newkeys.append(keyvalue)
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
result = 1
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
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)
|
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
def KindleMobiDecrypt(self,path_to_ebook):
|
|
||||||
|
|
||||||
# add the alfcrypto directory to sys.path so alfcrypto.py
|
|
||||||
# will be able to locate the custom lib(s) for CDLL import.
|
|
||||||
sys.path.insert(0, self.alfdir)
|
|
||||||
# Had to move this import here so the custom libs can be
|
|
||||||
# extracted to the appropriate places beforehand these routines
|
|
||||||
# look for them.
|
|
||||||
import calibre_plugins.dedrm.prefs as prefs
|
|
||||||
import calibre_plugins.dedrm.k4mobidedrm
|
|
||||||
|
|
||||||
dedrmprefs = prefs.DeDRM_Prefs()
|
|
||||||
pids = dedrmprefs['pids']
|
|
||||||
serials = dedrmprefs['serials']
|
|
||||||
for android_serials_list in dedrmprefs['androidkeys'].values():
|
|
||||||
#print android_serials_list
|
|
||||||
serials.extend(android_serials_list)
|
|
||||||
#print serials
|
|
||||||
androidFiles = []
|
|
||||||
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
|
||||||
|
|
||||||
try:
|
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
|
|
||||||
except Exception, 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)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if iswindows or isosx:
|
|
||||||
from calibre_plugins.dedrm.kindlekey import kindlekeys
|
|
||||||
|
|
||||||
defaultkeys = kindlekeys()
|
|
||||||
else: # linux
|
|
||||||
from wineutils import WineGetKeys
|
|
||||||
|
|
||||||
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)
|
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
|
|
||||||
newkeys = {}
|
|
||||||
for i,keyvalue in enumerate(defaultkeys):
|
|
||||||
keyname = u"default_key_{0:d}".format(i+1)
|
|
||||||
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")
|
|
||||||
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")
|
|
||||||
for keyvalue in newkeys.values():
|
|
||||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
|
||||||
dedrmprefs.writeprefs()
|
|
||||||
except Exception, 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)
|
|
||||||
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())
|
|
||||||
book.getFile(of.name)
|
|
||||||
of.close()
|
|
||||||
book.cleanup()
|
|
||||||
return of.name
|
|
||||||
|
|
||||||
|
|
||||||
def eReaderDecrypt(self,path_to_ebook):
|
|
||||||
|
|
||||||
import calibre_plugins.dedrm.prefs as prefs
|
|
||||||
import calibre_plugins.dedrm.erdr2pml
|
|
||||||
|
|
||||||
dedrmprefs = prefs.DeDRM_Prefs()
|
|
||||||
# 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)
|
|
||||||
of = self.temporary_file(u".pmlz")
|
|
||||||
|
|
||||||
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
|
||||||
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex'))
|
|
||||||
|
|
||||||
of.close()
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
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 u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
|
||||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
|
||||||
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
|
||||||
|
|
||||||
# make sure any unicode output gets converted safely with 'replace'
|
|
||||||
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))
|
|
||||||
self.starttime = time.time()
|
|
||||||
|
|
||||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
|
||||||
if booktype in ['prc','mobi','pobi','azw','azw1','azw3','azw4','tpz','kfx-zip']:
|
|
||||||
# Kindle/Mobipocket
|
|
||||||
decrypted_ebook = self.KindleMobiDecrypt(path_to_ebook)
|
|
||||||
elif booktype == 'pdb':
|
|
||||||
# eReader
|
|
||||||
decrypted_ebook = self.eReaderDecrypt(path_to_ebook)
|
|
||||||
pass
|
|
||||||
elif booktype == 'pdf':
|
|
||||||
# Adobe Adept PDF (hopefully)
|
|
||||||
decrypted_ebook = self.PDFDecrypt(path_to_ebook)
|
|
||||||
pass
|
|
||||||
elif booktype == 'epub':
|
|
||||||
# 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)
|
|
||||||
return path_to_ebook
|
|
||||||
print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
|
||||||
return decrypted_ebook
|
|
||||||
|
|
||||||
def is_customizable(self):
|
|
||||||
# return true to allow customization via the Plugin->Preferences.
|
|
||||||
return True
|
|
||||||
|
|
||||||
def config_widget(self):
|
|
||||||
import calibre_plugins.dedrm.config as config
|
|
||||||
return config.ConfigWidget(self.plugin_path, self.alfdir)
|
|
||||||
|
|
||||||
def save_settings(self, config_widget):
|
|
||||||
config_widget.save_settings()
|
|
@ -1,75 +0,0 @@
|
|||||||
import sys
|
|
||||||
import Tkinter
|
|
||||||
import Tkconstants
|
|
||||||
|
|
||||||
class ActivityBar(Tkinter.Frame):
|
|
||||||
|
|
||||||
def __init__(self, master, length=300, height=20, barwidth=15, interval=50, bg='white', fillcolor='orchid1',\
|
|
||||||
bd=2, relief=Tkconstants.GROOVE, *args, **kw):
|
|
||||||
Tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
|
|
||||||
self._master = master
|
|
||||||
self._interval = interval
|
|
||||||
self._maximum = length
|
|
||||||
self._startx = 0
|
|
||||||
self._barwidth = barwidth
|
|
||||||
self._bardiv = length / barwidth
|
|
||||||
if self._bardiv < 10:
|
|
||||||
self._bardiv = 10
|
|
||||||
stopx = self._startx + self._barwidth
|
|
||||||
if stopx > self._maximum:
|
|
||||||
stopx = self._maximum
|
|
||||||
# self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
|
||||||
# highlightthickness=0, relief='flat', bd=0)
|
|
||||||
self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
|
||||||
highlightthickness=0, relief=relief, bd=bd)
|
|
||||||
self._canv.pack(fill='both', expand=1)
|
|
||||||
self._rect = self._canv.create_rectangle(0, 0, self._canv.winfo_reqwidth(), self._canv.winfo_reqheight(), fill=fillcolor, width=0)
|
|
||||||
|
|
||||||
self._set()
|
|
||||||
self.bind('<Configure>', self._update_coords)
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
def _update_coords(self, event):
|
|
||||||
'''Updates the position of the rectangle inside the canvas when the size of
|
|
||||||
the widget gets changed.'''
|
|
||||||
# looks like we have to call update_idletasks() twice to make sure
|
|
||||||
# to get the results we expect
|
|
||||||
self._canv.update_idletasks()
|
|
||||||
self._maximum = self._canv.winfo_width()
|
|
||||||
self._startx = 0
|
|
||||||
self._barwidth = self._maximum / self._bardiv
|
|
||||||
if self._barwidth < 2:
|
|
||||||
self._barwidth = 2
|
|
||||||
stopx = self._startx + self._barwidth
|
|
||||||
if stopx > self._maximum:
|
|
||||||
stopx = self._maximum
|
|
||||||
self._canv.coords(self._rect, 0, 0, stopx, self._canv.winfo_height())
|
|
||||||
self._canv.update_idletasks()
|
|
||||||
|
|
||||||
def _set(self):
|
|
||||||
if self._startx < 0:
|
|
||||||
self._startx = 0
|
|
||||||
if self._startx > self._maximum:
|
|
||||||
self._startx = self._startx % self._maximum
|
|
||||||
stopx = self._startx + self._barwidth
|
|
||||||
if stopx > self._maximum:
|
|
||||||
stopx = self._maximum
|
|
||||||
self._canv.coords(self._rect, self._startx, 0, stopx, self._canv.winfo_height())
|
|
||||||
self._canv.update_idletasks()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self._running = True
|
|
||||||
self.after(self._interval, self._step)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._running = False
|
|
||||||
self._set()
|
|
||||||
|
|
||||||
def _step(self):
|
|
||||||
if self._running:
|
|
||||||
stepsize = self._barwidth / 4
|
|
||||||
if stepsize < 2:
|
|
||||||
stepsize = 2
|
|
||||||
self._startx += stepsize
|
|
||||||
self._set()
|
|
||||||
self.after(self._interval, self._step)
|
|
@ -1,568 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Routines for doing AES CBC in one file
|
|
||||||
|
|
||||||
Modified by some_updates to extract
|
|
||||||
and combine only those parts needed for AES CBC
|
|
||||||
into one simple to add python file
|
|
||||||
|
|
||||||
Original Version
|
|
||||||
Copyright (c) 2002 by Paul A. Lambert
|
|
||||||
Under:
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class CryptoError(Exception):
|
|
||||||
""" Base class for crypto exceptions """
|
|
||||||
def __init__(self,errorMessage='Error!'):
|
|
||||||
self.message = errorMessage
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
class InitCryptoError(CryptoError):
|
|
||||||
""" Crypto errors during algorithm initialization """
|
|
||||||
class BadKeySizeError(InitCryptoError):
|
|
||||||
""" Bad key size error """
|
|
||||||
class EncryptError(CryptoError):
|
|
||||||
""" Error in encryption processing """
|
|
||||||
class DecryptError(CryptoError):
|
|
||||||
""" Error in decryption processing """
|
|
||||||
class DecryptNotBlockAlignedError(DecryptError):
|
|
||||||
""" Error in decryption processing """
|
|
||||||
|
|
||||||
def xorS(a,b):
|
|
||||||
""" XOR two strings """
|
|
||||||
assert len(a)==len(b)
|
|
||||||
x = []
|
|
||||||
for i in range(len(a)):
|
|
||||||
x.append( chr(ord(a[i])^ord(b[i])))
|
|
||||||
return ''.join(x)
|
|
||||||
|
|
||||||
def xor(a,b):
|
|
||||||
""" XOR two strings """
|
|
||||||
x = []
|
|
||||||
for i in range(min(len(a),len(b))):
|
|
||||||
x.append( chr(ord(a[i])^ord(b[i])))
|
|
||||||
return ''.join(x)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Base 'BlockCipher' and Pad classes for cipher instances.
|
|
||||||
BlockCipher supports automatic padding and type conversion. The BlockCipher
|
|
||||||
class was written to make the actual algorithm code more readable and
|
|
||||||
not for performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class BlockCipher:
|
|
||||||
""" Block ciphers """
|
|
||||||
def __init__(self):
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.resetEncrypt()
|
|
||||||
self.resetDecrypt()
|
|
||||||
def resetEncrypt(self):
|
|
||||||
self.encryptBlockCount = 0
|
|
||||||
self.bytesToEncrypt = ''
|
|
||||||
def resetDecrypt(self):
|
|
||||||
self.decryptBlockCount = 0
|
|
||||||
self.bytesToDecrypt = ''
|
|
||||||
|
|
||||||
def encrypt(self, plainText, more = None):
|
|
||||||
""" Encrypt a string and return a binary string """
|
|
||||||
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
|
|
||||||
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
|
|
||||||
cipherText = ''
|
|
||||||
for i in range(numBlocks):
|
|
||||||
bStart = i*self.blockSize
|
|
||||||
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
|
|
||||||
self.encryptBlockCount += 1
|
|
||||||
cipherText += ctBlock
|
|
||||||
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
||||||
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
||||||
else:
|
|
||||||
self.bytesToEncrypt = ''
|
|
||||||
|
|
||||||
if more == None: # no more data expected from caller
|
|
||||||
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
|
|
||||||
if len(finalBytes) > 0:
|
|
||||||
ctBlock = self.encryptBlock(finalBytes)
|
|
||||||
self.encryptBlockCount += 1
|
|
||||||
cipherText += ctBlock
|
|
||||||
self.resetEncrypt()
|
|
||||||
return cipherText
|
|
||||||
|
|
||||||
def decrypt(self, cipherText, more = None):
|
|
||||||
""" Decrypt a string and return a string """
|
|
||||||
self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
# hold back some bytes in case last decrypt has zero len
|
|
||||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
|
||||||
numBlocks -= 1
|
|
||||||
numExtraBytes = self.blockSize
|
|
||||||
|
|
||||||
plainText = ''
|
|
||||||
for i in range(numBlocks):
|
|
||||||
bStart = i*self.blockSize
|
|
||||||
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
|
||||||
self.decryptBlockCount += 1
|
|
||||||
plainText += ptBlock
|
|
||||||
|
|
||||||
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
||||||
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
||||||
else:
|
|
||||||
self.bytesToEncrypt = ''
|
|
||||||
|
|
||||||
if more == None: # last decrypt remove padding
|
|
||||||
plainText = self.padding.removePad(plainText, self.blockSize)
|
|
||||||
self.resetDecrypt()
|
|
||||||
return plainText
|
|
||||||
|
|
||||||
|
|
||||||
class Pad:
|
|
||||||
def __init__(self):
|
|
||||||
pass # eventually could put in calculation of min and max size extension
|
|
||||||
|
|
||||||
class padWithPadLen(Pad):
|
|
||||||
""" Pad a binary string with the length of the padding """
|
|
||||||
|
|
||||||
def addPad(self, extraBytes, blockSize):
|
|
||||||
""" Add padding to a binary string to make it an even multiple
|
|
||||||
of the block size """
|
|
||||||
blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
|
|
||||||
padLength = blockSize - numExtraBytes
|
|
||||||
return extraBytes + padLength*chr(padLength)
|
|
||||||
|
|
||||||
def removePad(self, paddedBinaryString, blockSize):
|
|
||||||
""" Remove padding from a binary string """
|
|
||||||
if not(0<len(paddedBinaryString)):
|
|
||||||
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
|
||||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
|
||||||
|
|
||||||
class noPadding(Pad):
|
|
||||||
""" No padding. Use this to get ECB behavior from encrypt/decrypt """
|
|
||||||
|
|
||||||
def addPad(self, extraBytes, blockSize):
|
|
||||||
""" Add no padding """
|
|
||||||
return extraBytes
|
|
||||||
|
|
||||||
def removePad(self, paddedBinaryString, blockSize):
|
|
||||||
""" Remove no padding """
|
|
||||||
return paddedBinaryString
|
|
||||||
|
|
||||||
"""
|
|
||||||
Rijndael encryption algorithm
|
|
||||||
This byte oriented implementation is intended to closely
|
|
||||||
match FIPS specification for readability. It is not implemented
|
|
||||||
for performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Rijndael(BlockCipher):
|
|
||||||
""" Rijndael encryption algorithm """
|
|
||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16, blockSize=16 ):
|
|
||||||
self.name = 'RIJNDAEL'
|
|
||||||
self.keySize = keySize
|
|
||||||
self.strength = keySize*8
|
|
||||||
self.blockSize = blockSize # blockSize is in bytes
|
|
||||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
|
||||||
|
|
||||||
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
|
||||||
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
|
||||||
|
|
||||||
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
|
||||||
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
|
||||||
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
|
||||||
# the block (Nb) and key (Nk) sizes.
|
|
||||||
if key != None:
|
|
||||||
self.setKey(key)
|
|
||||||
|
|
||||||
def setKey(self, key):
|
|
||||||
""" Set a key and generate the expanded key """
|
|
||||||
assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
|
|
||||||
self.__expandedKey = keyExpansion(self, key)
|
|
||||||
self.reset() # BlockCipher.reset()
|
|
||||||
|
|
||||||
def encryptBlock(self, plainTextBlock):
|
|
||||||
""" Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
|
|
||||||
self.state = self._toBlock(plainTextBlock)
|
|
||||||
AddRoundKey(self, self.__expandedKey[0:self.Nb])
|
|
||||||
for round in range(1,self.Nr): #for round = 1 step 1 to Nr
|
|
||||||
SubBytes(self)
|
|
||||||
ShiftRows(self)
|
|
||||||
MixColumns(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
|
|
||||||
SubBytes(self)
|
|
||||||
ShiftRows(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
|
|
||||||
return self._toBString(self.state)
|
|
||||||
|
|
||||||
|
|
||||||
def decryptBlock(self, encryptedBlock):
|
|
||||||
""" decrypt a block (array of bytes) """
|
|
||||||
self.state = self._toBlock(encryptedBlock)
|
|
||||||
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
|
|
||||||
for round in range(self.Nr-1,0,-1):
|
|
||||||
InvShiftRows(self)
|
|
||||||
InvSubBytes(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
|
|
||||||
InvMixColumns(self)
|
|
||||||
InvShiftRows(self)
|
|
||||||
InvSubBytes(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[0:self.Nb])
|
|
||||||
return self._toBString(self.state)
|
|
||||||
|
|
||||||
def _toBlock(self, bs):
|
|
||||||
""" Convert binary string to array of bytes, state[col][row]"""
|
|
||||||
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
|
||||||
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
|
|
||||||
|
|
||||||
def _toBString(self, block):
|
|
||||||
""" Convert block (array of bytes) to binary string """
|
|
||||||
l = []
|
|
||||||
for col in block:
|
|
||||||
for rowElement in col:
|
|
||||||
l.append(chr(rowElement))
|
|
||||||
return ''.join(l)
|
|
||||||
#-------------------------------------
|
|
||||||
""" Number of rounds Nr = NrTable[Nb][Nk]
|
|
||||||
|
|
||||||
Nb Nk=4 Nk=5 Nk=6 Nk=7 Nk=8
|
|
||||||
------------------------------------- """
|
|
||||||
NrTable = {4: {4:10, 5:11, 6:12, 7:13, 8:14},
|
|
||||||
5: {4:11, 5:11, 6:12, 7:13, 8:14},
|
|
||||||
6: {4:12, 5:12, 6:12, 7:13, 8:14},
|
|
||||||
7: {4:13, 5:13, 6:13, 7:13, 8:14},
|
|
||||||
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
|
|
||||||
#-------------------------------------
|
|
||||||
def keyExpansion(algInstance, keyString):
|
|
||||||
""" Expand a string of size keySize into a larger array """
|
|
||||||
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
|
||||||
key = [ord(byte) for byte in keyString] # convert string to list
|
|
||||||
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
|
|
||||||
for i in range(Nk,Nb*(Nr+1)):
|
|
||||||
temp = w[i-1] # a four byte column
|
|
||||||
if (i%Nk) == 0 :
|
|
||||||
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
|
||||||
temp = [ Sbox[byte] for byte in temp ]
|
|
||||||
temp[0] ^= Rcon[i/Nk]
|
|
||||||
elif Nk > 6 and i%Nk == 4 :
|
|
||||||
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
|
||||||
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
|
||||||
return w
|
|
||||||
|
|
||||||
Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
|
|
||||||
0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
|
|
||||||
0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
def AddRoundKey(algInstance, keyBlock):
|
|
||||||
""" XOR the algorithm state with a block of key material """
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] ^= keyBlock[column][row]
|
|
||||||
#-------------------------------------
|
|
||||||
|
|
||||||
def SubBytes(algInstance):
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
|
|
||||||
|
|
||||||
def InvSubBytes(algInstance):
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
|
|
||||||
|
|
||||||
Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
|
|
||||||
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
|
||||||
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
|
|
||||||
0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
|
||||||
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
|
|
||||||
0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
|
||||||
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
|
|
||||||
0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
|
||||||
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
|
|
||||||
0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
|
||||||
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
|
|
||||||
0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
|
||||||
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
|
|
||||||
0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
|
||||||
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
|
|
||||||
0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
|
||||||
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
|
|
||||||
0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
|
||||||
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
|
|
||||||
0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
|
||||||
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
|
|
||||||
0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
|
||||||
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
|
|
||||||
0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
|
||||||
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
|
|
||||||
0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
|
||||||
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
|
|
||||||
0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
|
||||||
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
|
|
||||||
0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
|
||||||
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
|
|
||||||
0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
|
|
||||||
|
|
||||||
InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
|
|
||||||
0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
|
|
||||||
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
|
|
||||||
0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
|
|
||||||
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
|
|
||||||
0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
|
|
||||||
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
|
|
||||||
0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
|
|
||||||
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
|
|
||||||
0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
|
|
||||||
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
|
|
||||||
0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
|
|
||||||
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
|
|
||||||
0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
|
|
||||||
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
|
|
||||||
0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
|
|
||||||
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
|
|
||||||
0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
|
|
||||||
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
|
|
||||||
0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
|
|
||||||
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
|
|
||||||
0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
|
|
||||||
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
|
|
||||||
0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
|
|
||||||
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
|
|
||||||
0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
|
|
||||||
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
|
|
||||||
0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
|
|
||||||
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
|
|
||||||
0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
|
|
||||||
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
|
|
||||||
0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
""" For each block size (Nb), the ShiftRow operation shifts row i
|
|
||||||
by the amount Ci. Note that row 0 is not shifted.
|
|
||||||
Nb C1 C2 C3
|
|
||||||
------------------- """
|
|
||||||
shiftOffset = { 4 : ( 0, 1, 2, 3),
|
|
||||||
5 : ( 0, 1, 2, 3),
|
|
||||||
6 : ( 0, 1, 2, 3),
|
|
||||||
7 : ( 0, 1, 2, 4),
|
|
||||||
8 : ( 0, 1, 3, 4) }
|
|
||||||
def ShiftRows(algInstance):
|
|
||||||
tmp = [0]*algInstance.Nb # list of size Nb
|
|
||||||
for r in range(1,4): # row 0 reamains unchanged and can be skipped
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
algInstance.state[c][r] = tmp[c]
|
|
||||||
def InvShiftRows(algInstance):
|
|
||||||
tmp = [0]*algInstance.Nb # list of size Nb
|
|
||||||
for r in range(1,4): # row 0 reamains unchanged and can be skipped
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
algInstance.state[c][r] = tmp[c]
|
|
||||||
#-------------------------------------
|
|
||||||
def MixColumns(a):
|
|
||||||
Sprime = [0,0,0,0]
|
|
||||||
for j in range(a.Nb): # for each column
|
|
||||||
Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
|
|
||||||
Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
|
|
||||||
Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
|
|
||||||
Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
|
|
||||||
for i in range(4):
|
|
||||||
a.state[j][i] = Sprime[i]
|
|
||||||
|
|
||||||
def InvMixColumns(a):
|
|
||||||
""" Mix the four bytes of every column in a linear way
|
|
||||||
This is the opposite operation of Mixcolumn """
|
|
||||||
Sprime = [0,0,0,0]
|
|
||||||
for j in range(a.Nb): # for each column
|
|
||||||
Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
|
|
||||||
Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
|
|
||||||
Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
|
|
||||||
Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
|
|
||||||
for i in range(4):
|
|
||||||
a.state[j][i] = Sprime[i]
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
def mul(a, b):
|
|
||||||
""" Multiply two elements of GF(2^m)
|
|
||||||
needed for MixColumn and InvMixColumn """
|
|
||||||
if (a !=0 and b!=0):
|
|
||||||
return Alogtable[(Logtable[a] + Logtable[b])%255]
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
|
|
||||||
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
|
|
||||||
125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
|
|
||||||
101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
|
|
||||||
150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
|
|
||||||
102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
|
|
||||||
126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
|
|
||||||
43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
|
|
||||||
175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
|
|
||||||
44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
|
|
||||||
127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
|
|
||||||
204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
|
|
||||||
151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
|
|
||||||
83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
|
|
||||||
68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
|
|
||||||
103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
|
|
||||||
|
|
||||||
Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
|
|
||||||
95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
|
|
||||||
229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
|
|
||||||
83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
|
|
||||||
76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
|
|
||||||
131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
|
|
||||||
181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
|
|
||||||
254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
|
|
||||||
251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
|
|
||||||
195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
|
|
||||||
159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
|
|
||||||
155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
|
|
||||||
252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
|
|
||||||
69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
|
|
||||||
18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
|
|
||||||
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
AES Encryption Algorithm
|
|
||||||
The AES algorithm is just Rijndael algorithm restricted to the default
|
|
||||||
blockSize of 128 bits.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class AES(Rijndael):
|
|
||||||
""" The AES algorithm is the Rijndael block cipher restricted to block
|
|
||||||
sizes of 128 bits and key sizes of 128, 192 or 256 bits
|
|
||||||
"""
|
|
||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
|
||||||
""" Initialize AES, keySize is in bytes """
|
|
||||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
|
||||||
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
|
||||||
|
|
||||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
|
||||||
|
|
||||||
self.name = 'AES'
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
CBC mode of encryption for block ciphers.
|
|
||||||
This algorithm mode wraps any BlockCipher to make a
|
|
||||||
Cipher Block Chaining mode.
|
|
||||||
"""
|
|
||||||
from random import Random # should change to crypto.random!!!
|
|
||||||
|
|
||||||
|
|
||||||
class CBC(BlockCipher):
|
|
||||||
""" The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
|
|
||||||
algorithms. The initialization (IV) is automatic if set to None. Padding
|
|
||||||
is also automatic based on the Pad class used to initialize the algorithm
|
|
||||||
"""
|
|
||||||
def __init__(self, blockCipherInstance, padding = padWithPadLen()):
|
|
||||||
""" CBC algorithms are created by initializing with a BlockCipher instance """
|
|
||||||
self.baseCipher = blockCipherInstance
|
|
||||||
self.name = self.baseCipher.name + '_CBC'
|
|
||||||
self.blockSize = self.baseCipher.blockSize
|
|
||||||
self.keySize = self.baseCipher.keySize
|
|
||||||
self.padding = padding
|
|
||||||
self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
|
|
||||||
self.r = Random() # for IV generation, currently uses
|
|
||||||
# mediocre standard distro version <----------------
|
|
||||||
import time
|
|
||||||
newSeed = time.ctime()+str(self.r) # seed with instance location
|
|
||||||
self.r.seed(newSeed) # to make unique
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def setKey(self, key):
|
|
||||||
self.baseCipher.setKey(key)
|
|
||||||
|
|
||||||
# Overload to reset both CBC state and the wrapped baseCipher
|
|
||||||
def resetEncrypt(self):
|
|
||||||
BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
|
|
||||||
self.baseCipher.resetEncrypt() # reset base cipher encrypt state
|
|
||||||
|
|
||||||
def resetDecrypt(self):
|
|
||||||
BlockCipher.resetDecrypt(self) # reset CBC state (super class)
|
|
||||||
self.baseCipher.resetDecrypt() # reset base cipher decrypt state
|
|
||||||
|
|
||||||
def encrypt(self, plainText, iv=None, more=None):
|
|
||||||
""" CBC encryption - overloads baseCipher to allow optional explicit IV
|
|
||||||
when iv=None, iv is auto generated!
|
|
||||||
"""
|
|
||||||
if self.encryptBlockCount == 0:
|
|
||||||
self.iv = iv
|
|
||||||
else:
|
|
||||||
assert(iv==None), 'IV used only on first call to encrypt'
|
|
||||||
|
|
||||||
return BlockCipher.encrypt(self,plainText, more=more)
|
|
||||||
|
|
||||||
def decrypt(self, cipherText, iv=None, more=None):
|
|
||||||
""" CBC decryption - overloads baseCipher to allow optional explicit IV
|
|
||||||
when iv=None, iv is auto generated!
|
|
||||||
"""
|
|
||||||
if self.decryptBlockCount == 0:
|
|
||||||
self.iv = iv
|
|
||||||
else:
|
|
||||||
assert(iv==None), 'IV used only on first call to decrypt'
|
|
||||||
|
|
||||||
return BlockCipher.decrypt(self, cipherText, more=more)
|
|
||||||
|
|
||||||
def encryptBlock(self, plainTextBlock):
|
|
||||||
""" CBC block encryption, IV is set with 'encrypt' """
|
|
||||||
auto_IV = ''
|
|
||||||
if self.encryptBlockCount == 0:
|
|
||||||
if self.iv == None:
|
|
||||||
# generate IV and use
|
|
||||||
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
|
|
||||||
self.prior_encr_CT_block = self.iv
|
|
||||||
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
|
|
||||||
else: # application provided IV
|
|
||||||
assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
|
|
||||||
self.prior_encr_CT_block = self.iv
|
|
||||||
""" encrypt the prior CT XORed with the PT """
|
|
||||||
ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
|
|
||||||
self.prior_encr_CT_block = ct
|
|
||||||
return auto_IV+ct
|
|
||||||
|
|
||||||
def decryptBlock(self, encryptedBlock):
|
|
||||||
""" Decrypt a single block """
|
|
||||||
|
|
||||||
if self.decryptBlockCount == 0: # first call, process IV
|
|
||||||
if self.iv == None: # auto decrypt IV?
|
|
||||||
self.prior_CT_block = encryptedBlock
|
|
||||||
return ''
|
|
||||||
else:
|
|
||||||
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
|
||||||
self.prior_CT_block = self.iv
|
|
||||||
|
|
||||||
dct = self.baseCipher.decryptBlock(encryptedBlock)
|
|
||||||
""" XOR the prior decrypted CT with the prior CT """
|
|
||||||
dct_XOR_priorCT = xor( self.prior_CT_block, dct )
|
|
||||||
|
|
||||||
self.prior_CT_block = encryptedBlock
|
|
||||||
|
|
||||||
return dct_XOR_priorCT
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
AES_CBC Encryption Algorithm
|
|
||||||
"""
|
|
||||||
|
|
||||||
class AES_CBC(CBC):
|
|
||||||
""" AES encryption in CBC feedback mode """
|
|
||||||
def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
|
|
||||||
CBC.__init__( self, AES(key, noPadding(), keySize), padding)
|
|
||||||
self.name = 'AES_CBC'
|
|
Binary file not shown.
@ -1,301 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# crypto library mainly by some_updates
|
|
||||||
|
|
||||||
# pbkdf2.py pbkdf2 code taken from pbkdf2.py
|
|
||||||
# pbkdf2.py Copyright © 2004 Matt Johnston <matt @ ucc asn au>
|
|
||||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
|
||||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
import hmac
|
|
||||||
from struct import pack
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
# interface to needed routines libalfcrypto
|
|
||||||
def _load_libalfcrypto():
|
|
||||||
import ctypes
|
|
||||||
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
|
|
||||||
|
|
||||||
pointer_size = ctypes.sizeof(ctypes.c_voidp)
|
|
||||||
name_of_lib = None
|
|
||||||
if sys.platform.startswith('darwin'):
|
|
||||||
name_of_lib = 'libalfcrypto.dylib'
|
|
||||||
elif sys.platform.startswith('win'):
|
|
||||||
if pointer_size == 4:
|
|
||||||
name_of_lib = 'alfcrypto.dll'
|
|
||||||
else:
|
|
||||||
name_of_lib = 'alfcrypto64.dll'
|
|
||||||
else:
|
|
||||||
if pointer_size == 4:
|
|
||||||
name_of_lib = 'libalfcrypto32.so'
|
|
||||||
else:
|
|
||||||
name_of_lib = 'libalfcrypto64.so'
|
|
||||||
|
|
||||||
# hard code to local location for libalfcrypto
|
|
||||||
libalfcrypto = os.path.join(sys.path[0],name_of_lib)
|
|
||||||
if not os.path.isfile(libalfcrypto):
|
|
||||||
libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
|
|
||||||
if not os.path.isfile(libalfcrypto):
|
|
||||||
libalfcrypto = os.path.join('.',name_of_lib)
|
|
||||||
if not os.path.isfile(libalfcrypto):
|
|
||||||
raise Exception('libalfcrypto not found at %s' % libalfcrypto)
|
|
||||||
|
|
||||||
libalfcrypto = CDLL(libalfcrypto)
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libalfcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
# aes cbc decryption
|
|
||||||
#
|
|
||||||
# struct aes_key_st {
|
|
||||||
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
|
|
||||||
# int rounds;
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# typedef struct aes_key_st AES_KEY;
|
|
||||||
#
|
|
||||||
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
|
|
||||||
# const unsigned long length, const AES_KEY *key,
|
|
||||||
# unsigned char *ivec, const int enc);
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
|
||||||
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Pukall 1 Cipher
|
|
||||||
# unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
|
|
||||||
# unsigned char *dest, unsigned int len, int decryption);
|
|
||||||
|
|
||||||
PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
|
|
||||||
|
|
||||||
# Topaz Encryption
|
|
||||||
# typedef struct _TpzCtx {
|
|
||||||
# unsigned int v[2];
|
|
||||||
# } TpzCtx;
|
|
||||||
#
|
|
||||||
# void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
|
|
||||||
# void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
|
|
||||||
|
|
||||||
class TPZ_CTX(Structure):
|
|
||||||
_fields_ = [('v', c_long * 2)]
|
|
||||||
|
|
||||||
TPZ_CTX_p = POINTER(TPZ_CTX)
|
|
||||||
topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
|
|
||||||
topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
|
|
||||||
|
|
||||||
|
|
||||||
class AES_CBC(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._blocksize = 0
|
|
||||||
self._keyctx = None
|
|
||||||
self._iv = 0
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise Exception('AES CBC improper key used')
|
|
||||||
return
|
|
||||||
keyctx = self._keyctx = AES_KEY()
|
|
||||||
self._iv = iv
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
|
||||||
if rv < 0:
|
|
||||||
raise Exception('Failed to initialize AES CBC key')
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
mutable_iv = create_string_buffer(self._iv, len(self._iv))
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise Exception('AES CBC decryption failed')
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
class Pukall_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.key = None
|
|
||||||
|
|
||||||
def PC1(self, key, src, decryption=True):
|
|
||||||
self.key = key
|
|
||||||
out = create_string_buffer(len(src))
|
|
||||||
de = 0
|
|
||||||
if decryption:
|
|
||||||
de = 1
|
|
||||||
rv = PC1(key, len(key), src, out, len(src), de)
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
class Topaz_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._ctx = None
|
|
||||||
|
|
||||||
def ctx_init(self, key):
|
|
||||||
tpz_ctx = self._ctx = TPZ_CTX()
|
|
||||||
topazCryptoInit(tpz_ctx, key, len(key))
|
|
||||||
return tpz_ctx
|
|
||||||
|
|
||||||
def decrypt(self, data, ctx=None):
|
|
||||||
if ctx == None:
|
|
||||||
ctx = self._ctx
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
topazCryptoDecrypt(ctx, data, out, len(data))
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
print u"Using Library AlfCrypto DLL/DYLIB/SO"
|
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_python_alfcrypto():
|
|
||||||
|
|
||||||
import aescbc
|
|
||||||
|
|
||||||
class Pukall_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.key = None
|
|
||||||
|
|
||||||
def PC1(self, key, src, decryption=True):
|
|
||||||
sum1 = 0;
|
|
||||||
sum2 = 0;
|
|
||||||
keyXorVal = 0;
|
|
||||||
if len(key)!=16:
|
|
||||||
raise Exception('Pukall_Cipher: 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)):
|
|
||||||
temp1 = 0;
|
|
||||||
byteXorVal = 0;
|
|
||||||
for j in xrange(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])
|
|
||||||
if not decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
|
||||||
if decryption:
|
|
||||||
keyXorVal = curByte * 257;
|
|
||||||
for j in xrange(8):
|
|
||||||
wkey[j] ^= keyXorVal;
|
|
||||||
dst+=chr(curByte)
|
|
||||||
return dst
|
|
||||||
|
|
||||||
class Topaz_Cipher(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._ctx = None
|
|
||||||
|
|
||||||
def ctx_init(self, key):
|
|
||||||
ctx1 = 0x0CAFFE19E
|
|
||||||
for keyChar in key:
|
|
||||||
keyByte = ord(keyChar)
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
|
|
||||||
self._ctx = [ctx1, ctx2]
|
|
||||||
return [ctx1,ctx2]
|
|
||||||
|
|
||||||
def decrypt(self, data, ctx=None):
|
|
||||||
if ctx == None:
|
|
||||||
ctx = self._ctx
|
|
||||||
ctx1 = ctx[0]
|
|
||||||
ctx2 = ctx[1]
|
|
||||||
plainText = ""
|
|
||||||
for dataChar in data:
|
|
||||||
dataByte = ord(dataChar)
|
|
||||||
m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
|
|
||||||
ctx2 = ctx1
|
|
||||||
ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
|
|
||||||
plainText += chr(m)
|
|
||||||
return plainText
|
|
||||||
|
|
||||||
class AES_CBC(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._key = None
|
|
||||||
self._iv = None
|
|
||||||
self.aes = None
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._key = userkey
|
|
||||||
self._iv = iv
|
|
||||||
self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
iv = self._iv
|
|
||||||
cleartext = self.aes.decrypt(iv + data)
|
|
||||||
return cleartext
|
|
||||||
|
|
||||||
print u"Using Library AlfCrypto Python"
|
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES_CBC = Pukall_Cipher = Topaz_Cipher = None
|
|
||||||
cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, Exception):
|
|
||||||
pass
|
|
||||||
return AES_CBC, Pukall_Cipher, Topaz_Cipher
|
|
||||||
|
|
||||||
AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
|
|
||||||
|
|
||||||
|
|
||||||
class KeyIVGen(object):
|
|
||||||
# this only exists in openssl so we will use pure python implementation instead
|
|
||||||
# PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
|
||||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
|
||||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
|
||||||
|
|
||||||
def xorstr( a, b ):
|
|
||||||
if len(a) != len(b):
|
|
||||||
raise Exception("xorstr(): lengths differ")
|
|
||||||
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
|
||||||
|
|
||||||
def prf( h, data ):
|
|
||||||
hm = h.copy()
|
|
||||||
hm.update( data )
|
|
||||||
return hm.digest()
|
|
||||||
|
|
||||||
def pbkdf2_F( h, salt, itercount, blocknum ):
|
|
||||||
U = prf( h, salt + pack('>i',blocknum ) )
|
|
||||||
T = U
|
|
||||||
for i in range(2, itercount+1):
|
|
||||||
U = prf( h, U )
|
|
||||||
T = xorstr( T, U )
|
|
||||||
return T
|
|
||||||
|
|
||||||
sha = hashlib.sha1
|
|
||||||
digest_size = sha().digest_size
|
|
||||||
# l - number of output blocks to produce
|
|
||||||
l = keylen / digest_size
|
|
||||||
if keylen % digest_size != 0:
|
|
||||||
l += 1
|
|
||||||
h = hmac.new( passwd, None, sha )
|
|
||||||
T = ""
|
|
||||||
for i in range(1, l+1):
|
|
||||||
T += pbkdf2_F( h, salt, iter, i )
|
|
||||||
return T[0: keylen]
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
@ -1,468 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# androidkindlekey.py
|
|
||||||
# Copyright © 2013-15 by Thom and Apprentice Harper
|
|
||||||
# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
|
|
||||||
#
|
|
||||||
|
|
||||||
# Revision history:
|
|
||||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
|
||||||
# 1.1 - map_data_storage.db decryption to serial number
|
|
||||||
# 1.2 - Changed to be callable from AppleScript by returning only serial number
|
|
||||||
# - and changed name to androidkindlekey.py
|
|
||||||
# - and added in unicode command line support
|
|
||||||
# 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
|
|
||||||
|
|
||||||
"""
|
|
||||||
Retrieve Kindle for Android Serial Number.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = '1.5'
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import getopt
|
|
||||||
import tempfile
|
|
||||||
import zlib
|
|
||||||
import tarfile
|
|
||||||
from hashlib import md5
|
|
||||||
from cStringIO import StringIO
|
|
||||||
from binascii import a2b_hex, b2a_hex
|
|
||||||
|
|
||||||
# Routines common to Mac and PC
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre.constants import iswindows, isosx
|
|
||||||
except:
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
|
||||||
# as a list of Unicode strings and encode them as utf-8
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(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"]
|
|
||||||
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]
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
STORAGE = u"backup.ab"
|
|
||||||
STORAGE1 = u"AmazonSecureStorage.xml"
|
|
||||||
STORAGE2 = u"map_data_storage.db"
|
|
||||||
|
|
||||||
class AndroidObfuscation(object):
|
|
||||||
'''AndroidObfuscation
|
|
||||||
For the key, it's written in java, and run in android dalvikvm
|
|
||||||
'''
|
|
||||||
|
|
||||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
|
||||||
cipher = self._get_cipher()
|
|
||||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
|
||||||
plaintext += chr(padding) * padding
|
|
||||||
return b2a_hex(cipher.encrypt(plaintext))
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
|
||||||
cipher = self._get_cipher()
|
|
||||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
|
||||||
return plaintext[:-ord(plaintext[-1])]
|
|
||||||
|
|
||||||
def _get_cipher(self):
|
|
||||||
try:
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
return AES.new(self.key)
|
|
||||||
except ImportError:
|
|
||||||
from aescbc import AES, noPadding
|
|
||||||
return AES(self.key, padding=noPadding())
|
|
||||||
|
|
||||||
class AndroidObfuscationV2(AndroidObfuscation):
|
|
||||||
'''AndroidObfuscationV2
|
|
||||||
'''
|
|
||||||
|
|
||||||
count = 503
|
|
||||||
password = 'Thomsun was here!'
|
|
||||||
|
|
||||||
def __init__(self, salt):
|
|
||||||
key = self.password + salt
|
|
||||||
for _ in range(self.count):
|
|
||||||
key = md5(key).digest()
|
|
||||||
self.key = key[:8]
|
|
||||||
self.iv = key[8:16]
|
|
||||||
|
|
||||||
def _get_cipher(self):
|
|
||||||
try :
|
|
||||||
from Crypto.Cipher import DES
|
|
||||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
|
||||||
except ImportError:
|
|
||||||
from python_des import Des, CBC
|
|
||||||
return Des(self.key, CBC, self.iv)
|
|
||||||
|
|
||||||
def parse_preference(path):
|
|
||||||
''' parse android's shared preference xml '''
|
|
||||||
storage = {}
|
|
||||||
read = open(path)
|
|
||||||
for line in read:
|
|
||||||
line = line.strip()
|
|
||||||
# <string name="key">value</string>
|
|
||||||
if line.startswith('<string name="'):
|
|
||||||
index = line.find('"', 14)
|
|
||||||
key = line[14:index]
|
|
||||||
value = line[index+2:-9]
|
|
||||||
storage[key] = value
|
|
||||||
read.close()
|
|
||||||
return storage
|
|
||||||
|
|
||||||
def get_serials1(path=STORAGE1):
|
|
||||||
''' get serials from android's shared preference xml '''
|
|
||||||
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return []
|
|
||||||
|
|
||||||
storage = parse_preference(path)
|
|
||||||
salt = storage.get('AmazonSaltKey')
|
|
||||||
if salt and len(salt) == 16:
|
|
||||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
|
||||||
else:
|
|
||||||
obfuscation = AndroidObfuscation()
|
|
||||||
|
|
||||||
def get_value(key):
|
|
||||||
encrypted_key = obfuscation.encrypt(key)
|
|
||||||
encrypted_value = storage.get(encrypted_key)
|
|
||||||
if encrypted_value:
|
|
||||||
return obfuscation.decrypt(encrypted_value)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
# also see getK4Pids in kgenpids.py
|
|
||||||
try:
|
|
||||||
dsnid = get_value('DsnId')
|
|
||||||
except:
|
|
||||||
sys.stderr.write('cannot get DsnId\n')
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
|
||||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
serials = []
|
|
||||||
if dsnid:
|
|
||||||
serials.append(dsnid)
|
|
||||||
for token in tokens:
|
|
||||||
if token:
|
|
||||||
serials.append('%s%s' % (dsnid, token))
|
|
||||||
serials.append(token)
|
|
||||||
return serials
|
|
||||||
|
|
||||||
def get_serials2(path=STORAGE2):
|
|
||||||
''' get serials from android's sql database '''
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return []
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
connection = sqlite3.connect(path)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
|
|
||||||
userdata_keys = cursor.fetchall()
|
|
||||||
dsns = []
|
|
||||||
for userdata_row in userdata_keys:
|
|
||||||
try:
|
|
||||||
if userdata_row and userdata_row[0]:
|
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
|
||||||
if len(userdata_utf8) > 0:
|
|
||||||
dsns.append(userdata_utf8)
|
|
||||||
except:
|
|
||||||
print "Error getting one of the device serial name keys"
|
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
dsns = list(set(dsns))
|
|
||||||
|
|
||||||
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%kindle.account.tokens%' ''')
|
|
||||||
userdata_keys = cursor.fetchall()
|
|
||||||
tokens = []
|
|
||||||
for userdata_row in userdata_keys:
|
|
||||||
try:
|
|
||||||
if userdata_row and userdata_row[0]:
|
|
||||||
userdata_utf8 = userdata_row[0].encode('utf8')
|
|
||||||
if len(userdata_utf8) > 0:
|
|
||||||
tokens.append(userdata_utf8)
|
|
||||||
except:
|
|
||||||
print "Error getting one of the account token keys"
|
|
||||||
traceback.print_exc()
|
|
||||||
pass
|
|
||||||
tokens = list(set(tokens))
|
|
||||||
|
|
||||||
serials = []
|
|
||||||
for x in dsns:
|
|
||||||
serials.append(x)
|
|
||||||
for y in tokens:
|
|
||||||
serials.append('%s%s' % (x, y))
|
|
||||||
for y in tokens:
|
|
||||||
serials.append(y)
|
|
||||||
return serials
|
|
||||||
|
|
||||||
def get_serials(path=STORAGE):
|
|
||||||
'''get serials from files in from android backup.ab
|
|
||||||
backup.ab can be get using adb command:
|
|
||||||
shell> adb backup com.amazon.kindle
|
|
||||||
or from individual files if they're passed.
|
|
||||||
'''
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return []
|
|
||||||
|
|
||||||
basename = os.path.basename(path)
|
|
||||||
if basename == STORAGE1:
|
|
||||||
return get_serials1(path)
|
|
||||||
elif basename == STORAGE2:
|
|
||||||
return get_serials2(path)
|
|
||||||
|
|
||||||
output = None
|
|
||||||
try :
|
|
||||||
read = open(path, 'rb')
|
|
||||||
head = read.read(24)
|
|
||||||
if head[:14] == 'ANDROID BACKUP':
|
|
||||||
output = StringIO(zlib.decompress(read.read()))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
read.close()
|
|
||||||
|
|
||||||
if not output:
|
|
||||||
return []
|
|
||||||
|
|
||||||
serials = []
|
|
||||||
tar = tarfile.open(fileobj=output)
|
|
||||||
for member in tar.getmembers():
|
|
||||||
if member.name.strip().endswith(STORAGE1):
|
|
||||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
|
||||||
write.write(tar.extractfile(member).read())
|
|
||||||
write.close()
|
|
||||||
write_path = os.path.abspath(write.name)
|
|
||||||
serials.extend(get_serials1(write_path))
|
|
||||||
os.remove(write_path)
|
|
||||||
elif member.name.strip().endswith(STORAGE2):
|
|
||||||
write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
|
||||||
write.write(tar.extractfile(member).read())
|
|
||||||
write.close()
|
|
||||||
write_path = os.path.abspath(write.name)
|
|
||||||
serials.extend(get_serials2(write_path))
|
|
||||||
os.remove(write_path)
|
|
||||||
return list(set(serials))
|
|
||||||
|
|
||||||
__all__ = [ 'get_serials', 'getkey']
|
|
||||||
|
|
||||||
# procedure for CLI and GUI interfaces
|
|
||||||
# returns single or multiple keys (one per line) in the specified file
|
|
||||||
def getkey(outfile, inpath):
|
|
||||||
keys = get_serials(inpath)
|
|
||||||
if len(keys) > 0:
|
|
||||||
with file(outfile, 'w') as keyfileout:
|
|
||||||
for key in keys:
|
|
||||||
keyfileout.write(key)
|
|
||||||
keyfileout.write("\n")
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def usage(progname):
|
|
||||||
print u"Decrypts the serial number(s) of Kindle For Android from Android backup or file"
|
|
||||||
print u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+."
|
|
||||||
print u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml"
|
|
||||||
print u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db"
|
|
||||||
print u""
|
|
||||||
print u"Usage:"
|
|
||||||
print u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname)
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
argv=unicode_argv()
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
usage(progname)
|
|
||||||
print u"\nError in options or arguments: {0}".format(err.args[0])
|
|
||||||
return 2
|
|
||||||
|
|
||||||
inpath = ""
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "-h":
|
|
||||||
usage(progname)
|
|
||||||
return 0
|
|
||||||
if o == "-b":
|
|
||||||
inpath = a
|
|
||||||
|
|
||||||
if len(args) > 1:
|
|
||||||
usage(progname)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if len(args) == 1:
|
|
||||||
# save to the specified file or directory
|
|
||||||
outfile = args[0]
|
|
||||||
if not os.path.isabs(outfile):
|
|
||||||
outfile = os.path.join(os.path.dirname(argv[0]),outfile)
|
|
||||||
outfile = os.path.abspath(outfile)
|
|
||||||
if os.path.isdir(outfile):
|
|
||||||
outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
|
|
||||||
else:
|
|
||||||
# save to the same directory as the script
|
|
||||||
outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
|
|
||||||
|
|
||||||
# make sure the outpath is OK
|
|
||||||
outfile = os.path.realpath(os.path.normpath(outfile))
|
|
||||||
|
|
||||||
if not os.path.isfile(inpath):
|
|
||||||
usage(progname)
|
|
||||||
print u"\n{0:s} file not found".format(inpath)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if getkey(outfile, inpath):
|
|
||||||
print u"\nSaved Kindle for Android key to {0}".format(outfile)
|
|
||||||
else:
|
|
||||||
print u"\nCould not retrieve Kindle for Android key."
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
|
||||||
try:
|
|
||||||
import Tkinter
|
|
||||||
import Tkconstants
|
|
||||||
import tkMessageBox
|
|
||||||
import tkFileDialog
|
|
||||||
except:
|
|
||||||
print "Tkinter not installed"
|
|
||||||
return cli_main()
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
|
||||||
def __init__(self, root):
|
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
|
||||||
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
|
||||||
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)
|
|
||||||
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
|
||||||
self.keypath = Tkinter.Entry(body, width=40)
|
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
|
||||||
self.keypath.insert(2, u"backup.ab")
|
|
||||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
|
||||||
button.grid(row=0, column=2)
|
|
||||||
buttons = Tkinter.Frame(self)
|
|
||||||
buttons.pack()
|
|
||||||
button2 = Tkinter.Button(
|
|
||||||
buttons, text=u"Extract", width=10, command=self.generate)
|
|
||||||
button2.pack(side=Tkconstants.LEFT)
|
|
||||||
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
|
||||||
button3 = Tkinter.Button(
|
|
||||||
buttons, text=u"Quit", width=10, command=self.quit)
|
|
||||||
button3.pack(side=Tkconstants.RIGHT)
|
|
||||||
|
|
||||||
def get_keypath(self):
|
|
||||||
keypath = tkFileDialog.askopenfilename(
|
|
||||||
parent=None, title=u"Select backup.ab file",
|
|
||||||
defaultextension=u".ab",
|
|
||||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
|
||||||
('All Files', '.*')])
|
|
||||||
if keypath:
|
|
||||||
keypath = os.path.normpath(keypath)
|
|
||||||
self.keypath.delete(0, Tkconstants.END)
|
|
||||||
self.keypath.insert(0, keypath)
|
|
||||||
return
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
inpath = self.keypath.get()
|
|
||||||
self.status['text'] = u"Getting key..."
|
|
||||||
try:
|
|
||||||
keys = get_serials(inpath)
|
|
||||||
keycount = 0
|
|
||||||
for key in keys:
|
|
||||||
while True:
|
|
||||||
keycount += 1
|
|
||||||
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
|
||||||
if not os.path.exists(outfile):
|
|
||||||
break
|
|
||||||
|
|
||||||
with file(outfile, 'w') as keyfileout:
|
|
||||||
keyfileout.write(key)
|
|
||||||
success = True
|
|
||||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
|
||||||
except Exception, e:
|
|
||||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
|
||||||
return
|
|
||||||
self.status['text'] = u"Select backup.ab file"
|
|
||||||
|
|
||||||
argv=unicode_argv()
|
|
||||||
progpath, progname = os.path.split(argv[0])
|
|
||||||
root = Tkinter.Tk()
|
|
||||||
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
|
||||||
root.resizable(True, False)
|
|
||||||
root.minsize(300, 0)
|
|
||||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
|
||||||
root.mainloop()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
sys.exit(cli_main())
|
|
||||||
sys.exit(gui_main())
|
|
@ -1,88 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
import locale
|
|
||||||
import codecs
|
|
||||||
|
|
||||||
# get sys.argv arguments and encode them into utf-8
|
|
||||||
def unicode_argv():
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'.
|
|
||||||
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(start, argc.value)]
|
|
||||||
# if we don't have any arguments at all, just pass back script name
|
|
||||||
# this should never happen
|
|
||||||
return [u"DeDRM.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]
|
|
||||||
|
|
||||||
|
|
||||||
def add_cp65001_codec():
|
|
||||||
try:
|
|
||||||
codecs.lookup('cp65001')
|
|
||||||
except LookupError:
|
|
||||||
codecs.register(
|
|
||||||
lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def set_utf8_default_encoding():
|
|
||||||
if sys.getdefaultencoding() == 'utf-8':
|
|
||||||
return
|
|
||||||
|
|
||||||
# Regenerate setdefaultencoding.
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf-8')
|
|
||||||
|
|
||||||
for attr in dir(locale):
|
|
||||||
if attr[0:3] != 'LC_':
|
|
||||||
continue
|
|
||||||
aref = getattr(locale, attr)
|
|
||||||
try:
|
|
||||||
locale.setlocale(aref, '')
|
|
||||||
except locale.Error:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
lang = locale.getlocale(aref)[0]
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
continue
|
|
||||||
if lang:
|
|
||||||
try:
|
|
||||||
locale.setlocale(aref, (lang, 'UTF-8'))
|
|
||||||
except locale.Error:
|
|
||||||
os.environ[attr] = lang + '.UTF-8'
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
|
||||||
except locale.Error:
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
# to work around tk_chooseDirectory not properly returning unicode paths on Windows
|
|
||||||
# need to use a dialog that can be hacked up to actually return full unicode paths
|
|
||||||
# originally based on AskFolder from EasyDialogs for Windows but modified to fix it
|
|
||||||
# to actually use unicode for path
|
|
||||||
|
|
||||||
# The original license for EasyDialogs is as follows
|
|
||||||
#
|
|
||||||
# Copyright (c) 2003-2005 Jimmy Retzlaff
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
# copy of this software and associated documentation files (the "Software"),
|
|
||||||
# to deal in the Software without restriction, including without limitation
|
|
||||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
# and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
# Software is furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in
|
|
||||||
# all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
# DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
"""
|
|
||||||
AskFolder(...) -- Ask the user to select a folder Windows specific
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
import ctypes.wintypes as wintypes
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AskFolder']
|
|
||||||
|
|
||||||
# Load required Windows DLLs
|
|
||||||
ole32 = ctypes.windll.ole32
|
|
||||||
shell32 = ctypes.windll.shell32
|
|
||||||
user32 = ctypes.windll.user32
|
|
||||||
|
|
||||||
|
|
||||||
# Windows Constants
|
|
||||||
BFFM_INITIALIZED = 1
|
|
||||||
BFFM_SETOKTEXT = 1129
|
|
||||||
BFFM_SETSELECTIONA = 1126
|
|
||||||
BFFM_SETSELECTIONW = 1127
|
|
||||||
BIF_EDITBOX = 16
|
|
||||||
BS_DEFPUSHBUTTON = 1
|
|
||||||
CB_ADDSTRING = 323
|
|
||||||
CB_GETCURSEL = 327
|
|
||||||
CB_SETCURSEL = 334
|
|
||||||
CDM_SETCONTROLTEXT = 1128
|
|
||||||
EM_GETLINECOUNT = 186
|
|
||||||
EM_GETMARGINS = 212
|
|
||||||
EM_POSFROMCHAR = 214
|
|
||||||
EM_SETSEL = 177
|
|
||||||
GWL_STYLE = -16
|
|
||||||
IDC_STATIC = -1
|
|
||||||
IDCANCEL = 2
|
|
||||||
IDNO = 7
|
|
||||||
IDOK = 1
|
|
||||||
IDYES = 6
|
|
||||||
MAX_PATH = 260
|
|
||||||
OFN_ALLOWMULTISELECT = 512
|
|
||||||
OFN_ENABLEHOOK = 32
|
|
||||||
OFN_ENABLESIZING = 8388608
|
|
||||||
OFN_ENABLETEMPLATEHANDLE = 128
|
|
||||||
OFN_EXPLORER = 524288
|
|
||||||
OFN_OVERWRITEPROMPT = 2
|
|
||||||
OPENFILENAME_SIZE_VERSION_400 = 76
|
|
||||||
PBM_GETPOS = 1032
|
|
||||||
PBM_SETMARQUEE = 1034
|
|
||||||
PBM_SETPOS = 1026
|
|
||||||
PBM_SETRANGE = 1025
|
|
||||||
PBM_SETRANGE32 = 1030
|
|
||||||
PBS_MARQUEE = 8
|
|
||||||
PM_REMOVE = 1
|
|
||||||
SW_HIDE = 0
|
|
||||||
SW_SHOW = 5
|
|
||||||
SW_SHOWNORMAL = 1
|
|
||||||
SWP_NOACTIVATE = 16
|
|
||||||
SWP_NOMOVE = 2
|
|
||||||
SWP_NOSIZE = 1
|
|
||||||
SWP_NOZORDER = 4
|
|
||||||
VER_PLATFORM_WIN32_NT = 2
|
|
||||||
WM_COMMAND = 273
|
|
||||||
WM_GETTEXT = 13
|
|
||||||
WM_GETTEXTLENGTH = 14
|
|
||||||
WM_INITDIALOG = 272
|
|
||||||
WM_NOTIFY = 78
|
|
||||||
|
|
||||||
# Windows function prototypes
|
|
||||||
BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM)
|
|
||||||
|
|
||||||
# Windows types
|
|
||||||
LPCTSTR = ctypes.c_char_p
|
|
||||||
LPTSTR = ctypes.c_char_p
|
|
||||||
LPVOID = ctypes.c_voidp
|
|
||||||
TCHAR = ctypes.c_char
|
|
||||||
|
|
||||||
class BROWSEINFO(ctypes.Structure):
|
|
||||||
_fields_ = [
|
|
||||||
("hwndOwner", wintypes.HWND),
|
|
||||||
("pidlRoot", LPVOID),
|
|
||||||
("pszDisplayName", LPTSTR),
|
|
||||||
("lpszTitle", LPCTSTR),
|
|
||||||
("ulFlags", ctypes.c_uint),
|
|
||||||
("lpfn", BrowseCallbackProc),
|
|
||||||
("lParam", wintypes.LPARAM),
|
|
||||||
("iImage", ctypes.c_int)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Utilities
|
|
||||||
def CenterWindow(hwnd):
|
|
||||||
desktopRect = GetWindowRect(user32.GetDesktopWindow())
|
|
||||||
myRect = GetWindowRect(hwnd)
|
|
||||||
x = width(desktopRect) // 2 - width(myRect) // 2
|
|
||||||
y = height(desktopRect) // 2 - height(myRect) // 2
|
|
||||||
user32.SetWindowPos(hwnd, 0,
|
|
||||||
desktopRect.left + x,
|
|
||||||
desktopRect.top + y,
|
|
||||||
0, 0,
|
|
||||||
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def GetWindowRect(hwnd):
|
|
||||||
rect = wintypes.RECT()
|
|
||||||
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
||||||
return rect
|
|
||||||
|
|
||||||
def width(rect):
|
|
||||||
return rect.right-rect.left
|
|
||||||
|
|
||||||
def height(rect):
|
|
||||||
return rect.bottom-rect.top
|
|
||||||
|
|
||||||
|
|
||||||
def AskFolder(
|
|
||||||
message=None,
|
|
||||||
version=None,
|
|
||||||
defaultLocation=None,
|
|
||||||
location=None,
|
|
||||||
windowTitle=None,
|
|
||||||
actionButtonLabel=None,
|
|
||||||
cancelButtonLabel=None,
|
|
||||||
multiple=None):
|
|
||||||
"""Display a dialog asking the user for select a folder.
|
|
||||||
modified to use unicode strings as much as possible
|
|
||||||
returns unicode path
|
|
||||||
"""
|
|
||||||
|
|
||||||
def BrowseCallback(hwnd, uMsg, lParam, lpData):
|
|
||||||
if uMsg == BFFM_INITIALIZED:
|
|
||||||
if actionButtonLabel:
|
|
||||||
label = unicode(actionButtonLabel, errors='replace')
|
|
||||||
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
|
|
||||||
if cancelButtonLabel:
|
|
||||||
label = unicode(cancelButtonLabel, errors='replace')
|
|
||||||
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
|
|
||||||
if cancelButton:
|
|
||||||
user32.SetWindowTextW(cancelButton, label)
|
|
||||||
if windowTitle:
|
|
||||||
title = unicode(windowTitle, erros='replace')
|
|
||||||
user32.SetWindowTextW(hwnd, title)
|
|
||||||
if defaultLocation:
|
|
||||||
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
|
|
||||||
if location:
|
|
||||||
x, y = location
|
|
||||||
desktopRect = wintypes.RECT()
|
|
||||||
user32.GetWindowRect(0, ctypes.byref(desktopRect))
|
|
||||||
user32.SetWindowPos(hwnd, 0,
|
|
||||||
desktopRect.left + x,
|
|
||||||
desktopRect.top + y, 0, 0,
|
|
||||||
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER)
|
|
||||||
else:
|
|
||||||
CenterWindow(hwnd)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# This next line is needed to prevent gc of the callback
|
|
||||||
callback = BrowseCallbackProc(BrowseCallback)
|
|
||||||
|
|
||||||
browseInfo = BROWSEINFO()
|
|
||||||
browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1))
|
|
||||||
browseInfo.lpszTitle = message
|
|
||||||
browseInfo.lpfn = callback
|
|
||||||
|
|
||||||
pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo))
|
|
||||||
if not pidl:
|
|
||||||
result = None
|
|
||||||
else:
|
|
||||||
path = LPCWSTR(u" " * (MAX_PATH+1))
|
|
||||||
shell32.SHGetPathFromIDListW(pidl, path)
|
|
||||||
ole32.CoTaskMemFree(pidl)
|
|
||||||
result = path.value
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,884 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
# For use with Topaz Scripts Version 2.6
|
|
||||||
|
|
||||||
class Unbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
def write(self, data):
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
class TpzDRMError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Get a 7 bit encoded number from string. The most
|
|
||||||
# significant byte comes first and has the high bit (8th) set
|
|
||||||
|
|
||||||
def readEncodedNumber(file):
|
|
||||||
flag = False
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
|
|
||||||
if data == 0xFF:
|
|
||||||
flag = True
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
|
|
||||||
if data >= 0x80:
|
|
||||||
datax = (data & 0x7F)
|
|
||||||
while data >= 0x80 :
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
datax = (datax <<7) + (data & 0x7F)
|
|
||||||
data = datax
|
|
||||||
|
|
||||||
if flag:
|
|
||||||
data = -data
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
# returns a binary string that encodes a number into 7 bits
|
|
||||||
# most significant byte first which has the high bit set
|
|
||||||
|
|
||||||
def encodeNumber(number):
|
|
||||||
result = ""
|
|
||||||
negative = False
|
|
||||||
flag = 0
|
|
||||||
|
|
||||||
if number < 0 :
|
|
||||||
number = -number + 1
|
|
||||||
negative = True
|
|
||||||
|
|
||||||
while True:
|
|
||||||
byte = number & 0x7F
|
|
||||||
number = number >> 7
|
|
||||||
byte += flag
|
|
||||||
result += chr(byte)
|
|
||||||
flag = 0x80
|
|
||||||
if number == 0 :
|
|
||||||
if (byte == 0xFF and negative == False) :
|
|
||||||
result += chr(0x80)
|
|
||||||
break
|
|
||||||
|
|
||||||
if negative:
|
|
||||||
result += chr(0xFF)
|
|
||||||
|
|
||||||
return result[::-1]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# create / read a length prefixed string from the file
|
|
||||||
|
|
||||||
def lengthPrefixString(data):
|
|
||||||
return encodeNumber(len(data))+data
|
|
||||||
|
|
||||||
def readString(file):
|
|
||||||
stringLength = readEncodedNumber(file)
|
|
||||||
if (stringLength == None):
|
|
||||||
return ""
|
|
||||||
sv = file.read(stringLength)
|
|
||||||
if (len(sv) != stringLength):
|
|
||||||
return ""
|
|
||||||
return unpack(str(stringLength)+"s",sv)[0]
|
|
||||||
|
|
||||||
|
|
||||||
# convert a binary string generated by encodeNumber (7 bit encoded number)
|
|
||||||
# to the value you would find inside the page*.dat files to be processed
|
|
||||||
|
|
||||||
def convert(i):
|
|
||||||
result = ''
|
|
||||||
val = encodeNumber(i)
|
|
||||||
for j in xrange(len(val)):
|
|
||||||
c = ord(val[j:j+1])
|
|
||||||
result += '%02x' % c
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# the complete string table used to store all book text content
|
|
||||||
# as well as the xml tokens and values that make sense out of it
|
|
||||||
|
|
||||||
class Dictionary(object):
|
|
||||||
def __init__(self, dictFile):
|
|
||||||
self.filename = dictFile
|
|
||||||
self.size = 0
|
|
||||||
self.fo = file(dictFile,'rb')
|
|
||||||
self.stable = []
|
|
||||||
self.size = readEncodedNumber(self.fo)
|
|
||||||
for i in xrange(self.size):
|
|
||||||
self.stable.append(self.escapestr(readString(self.fo)))
|
|
||||||
self.pos = 0
|
|
||||||
|
|
||||||
def escapestr(self, str):
|
|
||||||
str = str.replace('&','&')
|
|
||||||
str = str.replace('<','<')
|
|
||||||
str = str.replace('>','>')
|
|
||||||
str = str.replace('=','=')
|
|
||||||
return str
|
|
||||||
|
|
||||||
def lookup(self,val):
|
|
||||||
if ((val >= 0) and (val < self.size)) :
|
|
||||||
self.pos = val
|
|
||||||
return self.stable[self.pos]
|
|
||||||
else:
|
|
||||||
print "Error - %d outside of string table limits" % val
|
|
||||||
raise TpzDRMError('outside of string table limits')
|
|
||||||
# sys.exit(-1)
|
|
||||||
|
|
||||||
def getSize(self):
|
|
||||||
return self.size
|
|
||||||
|
|
||||||
def getPos(self):
|
|
||||||
return self.pos
|
|
||||||
|
|
||||||
def dumpDict(self):
|
|
||||||
for i in xrange(self.size):
|
|
||||||
print "%d %s %s" % (i, convert(i), self.stable[i])
|
|
||||||
return
|
|
||||||
|
|
||||||
# parses the xml snippets that are represented by each page*.dat file.
|
|
||||||
# also parses the other0.dat file - the main stylesheet
|
|
||||||
# and information used to inject the xml snippets into page*.dat files
|
|
||||||
|
|
||||||
class PageParser(object):
|
|
||||||
def __init__(self, filename, dict, debug, flat_xml):
|
|
||||||
self.fo = file(filename,'rb')
|
|
||||||
self.id = os.path.basename(filename).replace('.dat','')
|
|
||||||
self.dict = dict
|
|
||||||
self.debug = debug
|
|
||||||
self.first_unknown = True
|
|
||||||
self.flat_xml = flat_xml
|
|
||||||
self.tagpath = []
|
|
||||||
self.doc = []
|
|
||||||
self.snippetList = []
|
|
||||||
|
|
||||||
|
|
||||||
# hash table used to enable the decoding process
|
|
||||||
# This has all been developed by trial and error so it may still have omissions or
|
|
||||||
# contain errors
|
|
||||||
# Format:
|
|
||||||
# tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
|
|
||||||
|
|
||||||
token_tags = {
|
|
||||||
'x' : (1, 'scalar_number', 0, 0),
|
|
||||||
'y' : (1, 'scalar_number', 0, 0),
|
|
||||||
'h' : (1, 'scalar_number', 0, 0),
|
|
||||||
'w' : (1, 'scalar_number', 0, 0),
|
|
||||||
'firstWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'rootID' : (1, 'scalar_number', 0, 0),
|
|
||||||
'stemID' : (1, 'scalar_number', 0, 0),
|
|
||||||
'type' : (1, 'scalar_text', 0, 0),
|
|
||||||
|
|
||||||
'info' : (0, 'number', 1, 0),
|
|
||||||
|
|
||||||
'info.word' : (0, 'number', 1, 1),
|
|
||||||
'info.word.ocrText' : (1, 'text', 0, 0),
|
|
||||||
'info.word.firstGlyph' : (1, 'raw', 0, 0),
|
|
||||||
'info.word.lastGlyph' : (1, 'raw', 0, 0),
|
|
||||||
'info.word.bl' : (1, 'raw', 0, 0),
|
|
||||||
'info.word.link_id' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'glyph' : (0, 'number', 1, 1),
|
|
||||||
'glyph.x' : (1, 'number', 0, 0),
|
|
||||||
'glyph.y' : (1, 'number', 0, 0),
|
|
||||||
'glyph.glyphID' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'dehyphen' : (0, 'number', 1, 1),
|
|
||||||
'dehyphen.rootID' : (1, 'number', 0, 0),
|
|
||||||
'dehyphen.stemID' : (1, 'number', 0, 0),
|
|
||||||
'dehyphen.stemPage' : (1, 'number', 0, 0),
|
|
||||||
'dehyphen.sh' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'links' : (0, 'number', 1, 1),
|
|
||||||
'links.page' : (1, 'number', 0, 0),
|
|
||||||
'links.rel' : (1, 'number', 0, 0),
|
|
||||||
'links.row' : (1, 'number', 0, 0),
|
|
||||||
'links.title' : (1, 'text', 0, 0),
|
|
||||||
'links.href' : (1, 'text', 0, 0),
|
|
||||||
'links.type' : (1, 'text', 0, 0),
|
|
||||||
'links.id' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'paraCont' : (0, 'number', 1, 1),
|
|
||||||
'paraCont.rootID' : (1, 'number', 0, 0),
|
|
||||||
'paraCont.stemID' : (1, 'number', 0, 0),
|
|
||||||
'paraCont.stemPage' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'paraStems' : (0, 'number', 1, 1),
|
|
||||||
'paraStems.stemID' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'wordStems' : (0, 'number', 1, 1),
|
|
||||||
'wordStems.stemID' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'empty' : (1, 'snippets', 1, 0),
|
|
||||||
|
|
||||||
'page' : (1, 'snippets', 1, 0),
|
|
||||||
'page.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'page.pageid' : (1, 'scalar_text', 0, 0),
|
|
||||||
'page.pagelabel' : (1, 'scalar_text', 0, 0),
|
|
||||||
'page.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'page.h' : (1, 'scalar_number', 0, 0),
|
|
||||||
'page.w' : (1, 'scalar_number', 0, 0),
|
|
||||||
'page.startID' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'group' : (1, 'snippets', 1, 0),
|
|
||||||
'group.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'group.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'group._tag' : (1, 'scalar_text', 0, 0),
|
|
||||||
'group.orientation': (1, 'scalar_text', 0, 0),
|
|
||||||
|
|
||||||
'region' : (1, 'snippets', 1, 0),
|
|
||||||
'region.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'region.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'region.x' : (1, 'scalar_number', 0, 0),
|
|
||||||
'region.y' : (1, 'scalar_number', 0, 0),
|
|
||||||
'region.h' : (1, 'scalar_number', 0, 0),
|
|
||||||
'region.w' : (1, 'scalar_number', 0, 0),
|
|
||||||
'region.orientation' : (1, 'scalar_text', 0, 0),
|
|
||||||
|
|
||||||
'empty_text_region' : (1, 'snippets', 1, 0),
|
|
||||||
|
|
||||||
'img' : (1, 'snippets', 1, 0),
|
|
||||||
'img.x' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.y' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.h' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.w' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.src' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.color_src' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'img.image_type' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'paragraph' : (1, 'snippets', 1, 0),
|
|
||||||
'paragraph.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
|
|
||||||
'word_semantic' : (1, 'snippets', 1, 1),
|
|
||||||
'word_semantic.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'word_semantic.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'word' : (1, 'snippets', 1, 0),
|
|
||||||
'word.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'word.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'word.firstGlyph' : (1, 'scalar_number', 0, 0),
|
|
||||||
'word.lastGlyph' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'_span' : (1, 'snippets', 1, 0),
|
|
||||||
'_span.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'_span.firstWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.gridSize' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'span' : (1, 'snippets', 1, 0),
|
|
||||||
'span.firstWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.lastWord' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.gridSize' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'extratokens' : (1, 'snippets', 1, 0),
|
|
||||||
'extratokens.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'extratokens.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
|
||||||
|
|
||||||
'glyph.h' : (1, 'number', 0, 0),
|
|
||||||
'glyph.w' : (1, 'number', 0, 0),
|
|
||||||
'glyph.use' : (1, 'number', 0, 0),
|
|
||||||
'glyph.vtx' : (1, 'number', 0, 1),
|
|
||||||
'glyph.len' : (1, 'number', 0, 1),
|
|
||||||
'glyph.dpi' : (1, 'number', 0, 0),
|
|
||||||
'vtx' : (0, 'number', 1, 1),
|
|
||||||
'vtx.x' : (1, 'number', 0, 0),
|
|
||||||
'vtx.y' : (1, 'number', 0, 0),
|
|
||||||
'len' : (0, 'number', 1, 1),
|
|
||||||
'len.n' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'book' : (1, 'snippets', 1, 0),
|
|
||||||
'version' : (1, 'snippets', 1, 0),
|
|
||||||
'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.Schema_id' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.Schema_version' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.Topaz_version' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.chapterheaders' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.creation_date' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.header_footer' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.letter_insertion' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.findlists' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.page_num' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.page_type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.bad_text' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.margins' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.staggered_lines' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
|
|
||||||
'version.toc' : (1, 'scalar_text', 0, 0),
|
|
||||||
|
|
||||||
'stylesheet' : (1, 'snippets', 1, 0),
|
|
||||||
'style' : (1, 'snippets', 1, 0),
|
|
||||||
'style._tag' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style.type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style._after_type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style._parent_type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style._after_parent_type' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style.class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'style._after_class' : (1, 'scalar_text', 0, 0),
|
|
||||||
'rule' : (1, 'snippets', 1, 0),
|
|
||||||
'rule.attr' : (1, 'scalar_text', 0, 0),
|
|
||||||
'rule.value' : (1, 'scalar_text', 0, 0),
|
|
||||||
|
|
||||||
'original' : (0, 'number', 1, 1),
|
|
||||||
'original.pnum' : (1, 'number', 0, 0),
|
|
||||||
'original.pid' : (1, 'text', 0, 0),
|
|
||||||
'pages' : (0, 'number', 1, 1),
|
|
||||||
'pages.ref' : (1, 'number', 0, 0),
|
|
||||||
'pages.id' : (1, 'number', 0, 0),
|
|
||||||
'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),
|
|
||||||
'median_lasty' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'num_footers_maybe' : (1, 'number', 0, 0),
|
|
||||||
'num_footers_yes' : (1, 'number', 0, 0),
|
|
||||||
'num_headers_maybe' : (1, 'number', 0, 0),
|
|
||||||
'num_headers_yes' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
'tracking' : (1, 'number', 0, 0),
|
|
||||||
'src' : (1, 'text', 0, 0),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# full tag path record keeping routines
|
|
||||||
def tag_push(self, token):
|
|
||||||
self.tagpath.append(token)
|
|
||||||
def tag_pop(self):
|
|
||||||
if len(self.tagpath) > 0 :
|
|
||||||
self.tagpath.pop()
|
|
||||||
def tagpath_len(self):
|
|
||||||
return len(self.tagpath)
|
|
||||||
def get_tagpath(self, i):
|
|
||||||
cnt = len(self.tagpath)
|
|
||||||
if i < cnt : result = self.tagpath[i]
|
|
||||||
for j in xrange(i+1, cnt) :
|
|
||||||
result += '.' + self.tagpath[j]
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# list of absolute command byte values values that indicate
|
|
||||||
# various types of loop meachanisms typically used to generate vectors
|
|
||||||
|
|
||||||
cmd_list = (0x76, 0x76)
|
|
||||||
|
|
||||||
# peek at and return 1 byte that is ahead by i bytes
|
|
||||||
def peek(self, aheadi):
|
|
||||||
c = self.fo.read(aheadi)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
self.fo.seek(-aheadi,1)
|
|
||||||
c = c[-1:]
|
|
||||||
return ord(c)
|
|
||||||
|
|
||||||
|
|
||||||
# get the next value from the file being processed
|
|
||||||
def getNext(self):
|
|
||||||
nbyte = self.peek(1);
|
|
||||||
if (nbyte == None):
|
|
||||||
return None
|
|
||||||
val = readEncodedNumber(self.fo)
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
# format an arg by argtype
|
|
||||||
def formatArg(self, arg, argtype):
|
|
||||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
|
||||||
result = self.dict.lookup(arg)
|
|
||||||
elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') :
|
|
||||||
result = arg
|
|
||||||
elif (argtype == 'snippets') :
|
|
||||||
result = arg
|
|
||||||
else :
|
|
||||||
print "Error Unknown argtype %s" % argtype
|
|
||||||
sys.exit(-2)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# process the next tag token, recursively handling subtags,
|
|
||||||
# arguments, and commands
|
|
||||||
def procToken(self, token):
|
|
||||||
|
|
||||||
known_token = False
|
|
||||||
self.tag_push(token)
|
|
||||||
|
|
||||||
if self.debug : print 'Processing: ', self.get_tagpath(0)
|
|
||||||
cnt = self.tagpath_len()
|
|
||||||
for j in xrange(cnt):
|
|
||||||
tkn = self.get_tagpath(j)
|
|
||||||
if tkn in self.token_tags :
|
|
||||||
num_args = self.token_tags[tkn][0]
|
|
||||||
argtype = self.token_tags[tkn][1]
|
|
||||||
subtags = self.token_tags[tkn][2]
|
|
||||||
splcase = self.token_tags[tkn][3]
|
|
||||||
ntags = -1
|
|
||||||
known_token = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if known_token :
|
|
||||||
|
|
||||||
# handle subtags if present
|
|
||||||
subtagres = []
|
|
||||||
if (splcase == 1):
|
|
||||||
# this type of tag uses of escape marker 0x74 indicate subtag count
|
|
||||||
if self.peek(1) == 0x74:
|
|
||||||
skip = readEncodedNumber(self.fo)
|
|
||||||
subtags = 1
|
|
||||||
num_args = 0
|
|
||||||
|
|
||||||
if (subtags == 1):
|
|
||||||
ntags = readEncodedNumber(self.fo)
|
|
||||||
if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
|
|
||||||
for j in xrange(ntags):
|
|
||||||
val = readEncodedNumber(self.fo)
|
|
||||||
subtagres.append(self.procToken(self.dict.lookup(val)))
|
|
||||||
|
|
||||||
# arguments can be scalars or vectors of text or numbers
|
|
||||||
argres = []
|
|
||||||
if num_args > 0 :
|
|
||||||
firstarg = self.peek(1)
|
|
||||||
if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'):
|
|
||||||
# single argument is a variable length vector of data
|
|
||||||
arg = readEncodedNumber(self.fo)
|
|
||||||
argres = self.decodeCMD(arg,argtype)
|
|
||||||
else :
|
|
||||||
# num_arg scalar arguments
|
|
||||||
for i in xrange(num_args):
|
|
||||||
argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
|
|
||||||
|
|
||||||
# build the return tag
|
|
||||||
result = []
|
|
||||||
tkn = self.get_tagpath(0)
|
|
||||||
result.append(tkn)
|
|
||||||
result.append(subtagres)
|
|
||||||
result.append(argtype)
|
|
||||||
result.append(argres)
|
|
||||||
self.tag_pop()
|
|
||||||
return result
|
|
||||||
|
|
||||||
# all tokens that need to be processed should be in the hash
|
|
||||||
# table if it may indicate a problem, either new token
|
|
||||||
# or an out of sync condition
|
|
||||||
else:
|
|
||||||
result = []
|
|
||||||
if (self.debug or self.first_unknown):
|
|
||||||
print 'Unknown Token:', token
|
|
||||||
self.first_unknown = False
|
|
||||||
self.tag_pop()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# special loop used to process code snippets
|
|
||||||
# it is NEVER used to format arguments.
|
|
||||||
# builds the snippetList
|
|
||||||
def doLoop72(self, argtype):
|
|
||||||
cnt = readEncodedNumber(self.fo)
|
|
||||||
if self.debug :
|
|
||||||
result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n'
|
|
||||||
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):
|
|
||||||
if self.debug: print 'Snippet:',str(i)
|
|
||||||
snippet = []
|
|
||||||
snippet.append(i)
|
|
||||||
val = readEncodedNumber(self.fo)
|
|
||||||
snippet.append(self.procToken(self.dict.lookup(val)))
|
|
||||||
self.snippetList.append(snippet)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# general loop code gracisouly submitted by "skindle" - thank you!
|
|
||||||
def doLoop76Mode(self, argtype, cnt, mode):
|
|
||||||
result = []
|
|
||||||
adj = 0
|
|
||||||
if mode & 1:
|
|
||||||
adj = readEncodedNumber(self.fo)
|
|
||||||
mode = mode >> 1
|
|
||||||
x = []
|
|
||||||
for i in xrange(cnt):
|
|
||||||
x.append(readEncodedNumber(self.fo) - adj)
|
|
||||||
for i in xrange(mode):
|
|
||||||
for j in xrange(1, cnt):
|
|
||||||
x[j] = x[j] + x[j - 1]
|
|
||||||
for i in xrange(cnt):
|
|
||||||
result.append(self.formatArg(x[i],argtype))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# dispatches loop commands bytes with various modes
|
|
||||||
# The 0x76 style loops are used to build vectors
|
|
||||||
|
|
||||||
# This was all derived by trial and error and
|
|
||||||
# new loop types may exist that are not handled here
|
|
||||||
# since they did not appear in the test cases
|
|
||||||
|
|
||||||
def decodeCMD(self, cmd, argtype):
|
|
||||||
if (cmd == 0x76):
|
|
||||||
|
|
||||||
# loop with cnt, and mode to control loop styles
|
|
||||||
cnt = readEncodedNumber(self.fo)
|
|
||||||
mode = readEncodedNumber(self.fo)
|
|
||||||
|
|
||||||
if self.debug : print 'Loop for', cnt, 'with mode', mode, ': '
|
|
||||||
return self.doLoop76Mode(argtype, cnt, mode)
|
|
||||||
|
|
||||||
if self.dbug: print "Unknown command", cmd
|
|
||||||
result = []
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# add full tag path to injected snippets
|
|
||||||
def updateName(self, tag, prefix):
|
|
||||||
name = tag[0]
|
|
||||||
subtagList = tag[1]
|
|
||||||
argtype = tag[2]
|
|
||||||
argList = tag[3]
|
|
||||||
nname = prefix + '.' + name
|
|
||||||
nsubtaglist = []
|
|
||||||
for j in subtagList:
|
|
||||||
nsubtaglist.append(self.updateName(j,prefix))
|
|
||||||
ntag = []
|
|
||||||
ntag.append(nname)
|
|
||||||
ntag.append(nsubtaglist)
|
|
||||||
ntag.append(argtype)
|
|
||||||
ntag.append(argList)
|
|
||||||
return ntag
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# perform depth first injection of specified snippets into this one
|
|
||||||
def injectSnippets(self, snippet):
|
|
||||||
snipno, tag = snippet
|
|
||||||
name = tag[0]
|
|
||||||
subtagList = tag[1]
|
|
||||||
argtype = tag[2]
|
|
||||||
argList = tag[3]
|
|
||||||
nsubtagList = []
|
|
||||||
if len(argList) > 0 :
|
|
||||||
for j in argList:
|
|
||||||
asnip = self.snippetList[j]
|
|
||||||
aso, atag = self.injectSnippets(asnip)
|
|
||||||
atag = self.updateName(atag, name)
|
|
||||||
nsubtagList.append(atag)
|
|
||||||
argtype='number'
|
|
||||||
argList=[]
|
|
||||||
if len(nsubtagList) > 0 :
|
|
||||||
subtagList.extend(nsubtagList)
|
|
||||||
tag = []
|
|
||||||
tag.append(name)
|
|
||||||
tag.append(subtagList)
|
|
||||||
tag.append(argtype)
|
|
||||||
tag.append(argList)
|
|
||||||
snippet = []
|
|
||||||
snippet.append(snipno)
|
|
||||||
snippet.append(tag)
|
|
||||||
return snippet
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# format the tag for output
|
|
||||||
def formatTag(self, node):
|
|
||||||
name = node[0]
|
|
||||||
subtagList = node[1]
|
|
||||||
argtype = node[2]
|
|
||||||
argList = node[3]
|
|
||||||
fullpathname = name.split('.')
|
|
||||||
nodename = fullpathname.pop()
|
|
||||||
ilvl = len(fullpathname)
|
|
||||||
indent = ' ' * (3 * ilvl)
|
|
||||||
rlst = []
|
|
||||||
rlst.append(indent + '<' + nodename + '>')
|
|
||||||
if len(argList) > 0:
|
|
||||||
alst = []
|
|
||||||
for j in argList:
|
|
||||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
|
||||||
alst.append(j + '|')
|
|
||||||
else :
|
|
||||||
alst.append(str(j) + ',')
|
|
||||||
argres = "".join(alst)
|
|
||||||
argres = argres[0:-1]
|
|
||||||
if argtype == 'snippets' :
|
|
||||||
rlst.append('snippets:' + argres)
|
|
||||||
else :
|
|
||||||
rlst.append(argres)
|
|
||||||
if len(subtagList) > 0 :
|
|
||||||
rlst.append('\n')
|
|
||||||
for j in subtagList:
|
|
||||||
if len(j) > 0 :
|
|
||||||
rlst.append(self.formatTag(j))
|
|
||||||
rlst.append(indent + '</' + nodename + '>\n')
|
|
||||||
else:
|
|
||||||
rlst.append('</' + nodename + '>\n')
|
|
||||||
return "".join(rlst)
|
|
||||||
|
|
||||||
|
|
||||||
# flatten tag
|
|
||||||
def flattenTag(self, node):
|
|
||||||
name = node[0]
|
|
||||||
subtagList = node[1]
|
|
||||||
argtype = node[2]
|
|
||||||
argList = node[3]
|
|
||||||
rlst = []
|
|
||||||
rlst.append(name)
|
|
||||||
if (len(argList) > 0):
|
|
||||||
alst = []
|
|
||||||
for j in argList:
|
|
||||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
|
||||||
alst.append(j + '|')
|
|
||||||
else :
|
|
||||||
alst.append(str(j) + '|')
|
|
||||||
argres = "".join(alst)
|
|
||||||
argres = argres[0:-1]
|
|
||||||
if argtype == 'snippets' :
|
|
||||||
rlst.append('.snippets=' + argres)
|
|
||||||
else :
|
|
||||||
rlst.append('=' + argres)
|
|
||||||
rlst.append('\n')
|
|
||||||
for j in subtagList:
|
|
||||||
if len(j) > 0 :
|
|
||||||
rlst.append(self.flattenTag(j))
|
|
||||||
return "".join(rlst)
|
|
||||||
|
|
||||||
|
|
||||||
# reduce create xml output
|
|
||||||
def formatDoc(self, flat_xml):
|
|
||||||
rlst = []
|
|
||||||
for j in self.doc :
|
|
||||||
if len(j) > 0:
|
|
||||||
if flat_xml:
|
|
||||||
rlst.append(self.flattenTag(j))
|
|
||||||
else:
|
|
||||||
rlst.append(self.formatTag(j))
|
|
||||||
result = "".join(rlst)
|
|
||||||
if self.debug : print result
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# main loop - parse the page.dat files
|
|
||||||
# to create structured document and snippets
|
|
||||||
|
|
||||||
# FIXME: value at end of magic appears to be a subtags count
|
|
||||||
# but for what? For now, inject an 'info" tag as it is in
|
|
||||||
# every dictionary and seems close to what is meant
|
|
||||||
# The alternative is to special case the last _ "0x5f" to mean something
|
|
||||||
|
|
||||||
def process(self):
|
|
||||||
|
|
||||||
# peek at the first bytes to see what type of file it is
|
|
||||||
magic = self.fo.read(9)
|
|
||||||
if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
|
|
||||||
first_token = 'info'
|
|
||||||
elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
|
|
||||||
skip = self.fo.read(2)
|
|
||||||
first_token = 'info'
|
|
||||||
elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
|
|
||||||
first_token = 'info'
|
|
||||||
elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
|
|
||||||
skip = self.fo.read(3)
|
|
||||||
first_token = 'info'
|
|
||||||
else :
|
|
||||||
# other0.dat file
|
|
||||||
first_token = None
|
|
||||||
self.fo.seek(-9,1)
|
|
||||||
|
|
||||||
|
|
||||||
# main loop to read and build the document tree
|
|
||||||
while True:
|
|
||||||
|
|
||||||
if first_token != None :
|
|
||||||
# use "inserted" first token 'info' for page and glyph files
|
|
||||||
tag = self.procToken(first_token)
|
|
||||||
if len(tag) > 0 :
|
|
||||||
self.doc.append(tag)
|
|
||||||
first_token = None
|
|
||||||
|
|
||||||
v = self.getNext()
|
|
||||||
if (v == None):
|
|
||||||
break
|
|
||||||
|
|
||||||
if (v == 0x72):
|
|
||||||
self.doLoop72('number')
|
|
||||||
elif (v > 0) and (v < self.dict.getSize()) :
|
|
||||||
tag = self.procToken(self.dict.lookup(v))
|
|
||||||
if len(tag) > 0 :
|
|
||||||
self.doc.append(tag)
|
|
||||||
else:
|
|
||||||
if self.debug:
|
|
||||||
print "Main Loop: Unknown value: %x" % v
|
|
||||||
if (v == 0):
|
|
||||||
if (self.peek(1) == 0x5f):
|
|
||||||
skip = self.fo.read(1)
|
|
||||||
first_token = 'info'
|
|
||||||
|
|
||||||
# now do snippet injection
|
|
||||||
if len(self.snippetList) > 0 :
|
|
||||||
if self.debug : print 'Injecting Snippets:'
|
|
||||||
snippet = self.injectSnippets(self.snippetList[0])
|
|
||||||
snipno = snippet[0]
|
|
||||||
tag_add = snippet[1]
|
|
||||||
if self.debug : print self.formatTag(tag_add)
|
|
||||||
if len(tag_add) > 0:
|
|
||||||
self.doc.append(tag_add)
|
|
||||||
|
|
||||||
# handle generation of xml output
|
|
||||||
xmlpage = self.formatDoc(self.flat_xml)
|
|
||||||
|
|
||||||
return xmlpage
|
|
||||||
|
|
||||||
|
|
||||||
def fromData(dict, fname):
|
|
||||||
flat_xml = True
|
|
||||||
debug = False
|
|
||||||
pp = PageParser(fname, dict, debug, flat_xml)
|
|
||||||
xmlpage = pp.process()
|
|
||||||
return xmlpage
|
|
||||||
|
|
||||||
def getXML(dict, fname):
|
|
||||||
flat_xml = False
|
|
||||||
debug = False
|
|
||||||
pp = PageParser(fname, dict, debug, flat_xml)
|
|
||||||
xmlpage = pp.process()
|
|
||||||
return xmlpage
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print 'Usage: '
|
|
||||||
print ' convert2xml.py dict0000.dat infile.dat '
|
|
||||||
print ' '
|
|
||||||
print ' Options:'
|
|
||||||
print ' -h print this usage help message '
|
|
||||||
print ' -d turn on debug output to check for potential errors '
|
|
||||||
print ' --flat-xml output the flattened xml page description only '
|
|
||||||
print ' '
|
|
||||||
print ' This program will attempt to convert a page*.dat file or '
|
|
||||||
print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. '
|
|
||||||
print ' '
|
|
||||||
print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump '
|
|
||||||
print ' the *.dat files from a Topaz format e-book.'
|
|
||||||
|
|
||||||
#
|
|
||||||
# Main
|
|
||||||
#
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
dictFile = ""
|
|
||||||
pageFile = ""
|
|
||||||
debug = False
|
|
||||||
flat_xml = False
|
|
||||||
printOutput = False
|
|
||||||
if len(argv) == 0:
|
|
||||||
printOutput = True
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
|
|
||||||
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
|
|
||||||
# print help information and exit:
|
|
||||||
print str(err) # will print something like "option -a not recognized"
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if len(opts) == 0 and len(args) == 0 :
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
for o, a in opts:
|
|
||||||
if o =="-d":
|
|
||||||
debug=True
|
|
||||||
if o =="-h":
|
|
||||||
usage()
|
|
||||||
sys.exit(0)
|
|
||||||
if o =="--flat-xml":
|
|
||||||
flat_xml = True
|
|
||||||
|
|
||||||
dictFile, pageFile = args[0], args[1]
|
|
||||||
|
|
||||||
# read in the string table dictionary
|
|
||||||
dict = Dictionary(dictFile)
|
|
||||||
# dict.dumpDict()
|
|
||||||
|
|
||||||
# create a page parser
|
|
||||||
pp = PageParser(pageFile, dict, debug, flat_xml)
|
|
||||||
|
|
||||||
xmlpage = pp.process()
|
|
||||||
|
|
||||||
if printOutput:
|
|
||||||
print xmlpage
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return xmlpage
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(''))
|
|
@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# base64.py, version 1.0
|
|
||||||
# Copyright © 2010 Apprentice Alf
|
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
|
||||||
# later. <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
# Revision history:
|
|
||||||
# 1 - Initial release. To allow Applescript to do base64 encoding
|
|
||||||
|
|
||||||
"""
|
|
||||||
Provide base64 encoding.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import base64
|
|
||||||
|
|
||||||
def usage(progname):
|
|
||||||
print "Applies base64 encoding to the supplied file, sending to standard output"
|
|
||||||
print "Usage:"
|
|
||||||
print " %s <infile>" % progname
|
|
||||||
|
|
||||||
def cli_main(argv=sys.argv):
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
|
|
||||||
if len(argv)<2:
|
|
||||||
usage(progname)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
keypath = argv[1]
|
|
||||||
with open(keypath, 'rb') as f:
|
|
||||||
keyder = f.read()
|
|
||||||
print keyder.encode('base64')
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(cli_main())
|
|
@ -1,208 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# This is a python script. You need a Python interpreter to run it.
|
|
||||||
# For example, ActiveState Python, which exists for windows.
|
|
||||||
#
|
|
||||||
# Changelog drmcheck
|
|
||||||
# 1.00 - Initial version, with code from various other scripts
|
|
||||||
# 1.01 - Moved authorship announcement to usage section.
|
|
||||||
#
|
|
||||||
# Changelog epubtest
|
|
||||||
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
|
|
||||||
# 1.01 - Added routine for use by Windows DeDRM
|
|
||||||
#
|
|
||||||
# Written in 2011 by Paul Durrant
|
|
||||||
# Released with unlicense. See http://unlicense.org/
|
|
||||||
#
|
|
||||||
#############################################################################
|
|
||||||
#
|
|
||||||
# This is free and unencumbered software released into the public domain.
|
|
||||||
#
|
|
||||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
||||||
# distribute this software, either in source code form or as a compiled
|
|
||||||
# binary, for any purpose, commercial or non-commercial, and by any
|
|
||||||
# means.
|
|
||||||
#
|
|
||||||
# In jurisdictions that recognize copyright laws, the author or authors
|
|
||||||
# of this software dedicate any and all copyright interest in the
|
|
||||||
# software to the public domain. We make this dedication for the benefit
|
|
||||||
# of the public at large and to the detriment of our heirs and
|
|
||||||
# successors. We intend this dedication to be an overt act of
|
|
||||||
# relinquishment in perpetuity of all present and future rights to this
|
|
||||||
# software under copyright law.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
||||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
# OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
#############################################################################
|
|
||||||
#
|
|
||||||
# It's still polite to give attribution if you do reuse this code.
|
|
||||||
#
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__version__ = '1.01'
|
|
||||||
|
|
||||||
import sys, struct, os
|
|
||||||
import zlib
|
|
||||||
import zipfile
|
|
||||||
import xml.etree.ElementTree as etree
|
|
||||||
|
|
||||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
|
||||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre.constants import iswindows, isosx
|
|
||||||
except:
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
|
||||||
# as a list of Unicode strings and encode them as utf-8
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(start, argc.value)]
|
|
||||||
# if we don't have any arguments at all, just pass back script name
|
|
||||||
# this should never happen
|
|
||||||
return [u"epubtest.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]
|
|
||||||
|
|
||||||
_FILENAME_LEN_OFFSET = 26
|
|
||||||
_EXTRA_LEN_OFFSET = 28
|
|
||||||
_FILENAME_OFFSET = 30
|
|
||||||
_MAX_SIZE = 64 * 1024
|
|
||||||
|
|
||||||
|
|
||||||
def uncompress(cmpdata):
|
|
||||||
dc = zlib.decompressobj(-15)
|
|
||||||
data = ''
|
|
||||||
while len(cmpdata) > 0:
|
|
||||||
if len(cmpdata) > _MAX_SIZE :
|
|
||||||
newdata = cmpdata[0:_MAX_SIZE]
|
|
||||||
cmpdata = cmpdata[_MAX_SIZE:]
|
|
||||||
else:
|
|
||||||
newdata = cmpdata
|
|
||||||
cmpdata = ''
|
|
||||||
newdata = dc.decompress(newdata)
|
|
||||||
unprocessed = dc.unconsumed_tail
|
|
||||||
if len(unprocessed) == 0:
|
|
||||||
newdata += dc.flush()
|
|
||||||
data += newdata
|
|
||||||
cmpdata += unprocessed
|
|
||||||
unprocessed = ''
|
|
||||||
return data
|
|
||||||
|
|
||||||
def getfiledata(file, zi):
|
|
||||||
# get file name length and exta data length to find start of file data
|
|
||||||
local_header_offset = zi.header_offset
|
|
||||||
|
|
||||||
file.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
|
||||||
leninfo = file.read(2)
|
|
||||||
local_name_length, = struct.unpack('<H', leninfo)
|
|
||||||
|
|
||||||
file.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
|
||||||
exinfo = file.read(2)
|
|
||||||
extra_field_length, = struct.unpack('<H', exinfo)
|
|
||||||
|
|
||||||
file.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
|
||||||
data = None
|
|
||||||
|
|
||||||
# if not compressed we are good to go
|
|
||||||
if zi.compress_type == zipfile.ZIP_STORED:
|
|
||||||
data = file.read(zi.file_size)
|
|
||||||
|
|
||||||
# if compressed we must decompress it using zlib
|
|
||||||
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
|
||||||
cmpdata = file.read(zi.compress_size)
|
|
||||||
data = uncompress(cmpdata)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def encryption(infile):
|
|
||||||
# returns encryption: one of Unencrypted, Adobe, B&N and Unknown
|
|
||||||
encryption = "Unknown"
|
|
||||||
try:
|
|
||||||
with open(infile,'rb') as infileobject:
|
|
||||||
bookdata = infileobject.read(58)
|
|
||||||
# Check for Zip
|
|
||||||
if bookdata[0:0+2] == "PK":
|
|
||||||
foundrights = False
|
|
||||||
foundencryption = False
|
|
||||||
inzip = zipfile.ZipFile(infile,'r')
|
|
||||||
namelist = set(inzip.namelist())
|
|
||||||
if 'META-INF/rights.xml' not in namelist or 'META-INF/encryption.xml' not in namelist:
|
|
||||||
encryption = "Unencrypted"
|
|
||||||
else:
|
|
||||||
rights = etree.fromstring(inzip.read('META-INF/rights.xml'))
|
|
||||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
|
||||||
expr = './/%s' % (adept('encryptedKey'),)
|
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
|
||||||
if len(bookkey) == 172:
|
|
||||||
encryption = "Adobe"
|
|
||||||
elif len(bookkey) == 64:
|
|
||||||
encryption = "B&N"
|
|
||||||
else:
|
|
||||||
encryption = "Unknown"
|
|
||||||
except:
|
|
||||||
traceback.print_exc()
|
|
||||||
return encryption
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argv=unicode_argv()
|
|
||||||
print encryption(argv[1])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
sys.exit(main())
|
|
@ -1,801 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
# For use with Topaz Scripts Version 2.6
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import math
|
|
||||||
import getopt
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
|
|
||||||
class DocParser(object):
|
|
||||||
def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
|
|
||||||
self.id = os.path.basename(fileid).replace('.dat','')
|
|
||||||
self.svgcount = 0
|
|
||||||
self.docList = flatxml.split('\n')
|
|
||||||
self.docSize = len(self.docList)
|
|
||||||
self.classList = {}
|
|
||||||
self.bookDir = bookDir
|
|
||||||
self.gdict = gdict
|
|
||||||
tmpList = classlst.split('\n')
|
|
||||||
for pclass in tmpList:
|
|
||||||
if pclass != '':
|
|
||||||
# remove the leading period from the css name
|
|
||||||
cname = pclass[1:]
|
|
||||||
self.classList[cname] = True
|
|
||||||
self.fixedimage = fixedimage
|
|
||||||
self.ocrtext = []
|
|
||||||
self.link_id = []
|
|
||||||
self.link_title = []
|
|
||||||
self.link_page = []
|
|
||||||
self.link_href = []
|
|
||||||
self.link_type = []
|
|
||||||
self.dehyphen_rootid = []
|
|
||||||
self.paracont_stemid = []
|
|
||||||
self.parastems_stemid = []
|
|
||||||
|
|
||||||
|
|
||||||
def getGlyph(self, gid):
|
|
||||||
result = ''
|
|
||||||
id='id="gl%d"' % gid
|
|
||||||
return self.gdict.lookup(id)
|
|
||||||
|
|
||||||
def glyphs_to_image(self, glyphList):
|
|
||||||
|
|
||||||
def extract(path, key):
|
|
||||||
b = path.find(key) + len(key)
|
|
||||||
e = path.find(' ',b)
|
|
||||||
return int(path[b:e])
|
|
||||||
|
|
||||||
svgDir = os.path.join(self.bookDir,'svg')
|
|
||||||
|
|
||||||
imgDir = os.path.join(self.bookDir,'img')
|
|
||||||
imgname = self.id + '_%04d.svg' % self.svgcount
|
|
||||||
imgfile = os.path.join(imgDir,imgname)
|
|
||||||
|
|
||||||
# get glyph information
|
|
||||||
gxList = self.getData('info.glyph.x',0,-1)
|
|
||||||
gyList = self.getData('info.glyph.y',0,-1)
|
|
||||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
|
||||||
|
|
||||||
gids = []
|
|
||||||
maxws = []
|
|
||||||
maxhs = []
|
|
||||||
xs = []
|
|
||||||
ys = []
|
|
||||||
gdefs = []
|
|
||||||
|
|
||||||
# get path defintions, positions, dimensions for each glyph
|
|
||||||
# that makes up the image, and find min x and min y to reposition origin
|
|
||||||
minx = -1
|
|
||||||
miny = -1
|
|
||||||
for j in glyphList:
|
|
||||||
gid = gidList[j]
|
|
||||||
gids.append(gid)
|
|
||||||
|
|
||||||
xs.append(gxList[j])
|
|
||||||
if minx == -1: minx = gxList[j]
|
|
||||||
else : minx = min(minx, gxList[j])
|
|
||||||
|
|
||||||
ys.append(gyList[j])
|
|
||||||
if miny == -1: miny = gyList[j]
|
|
||||||
else : miny = min(miny, gyList[j])
|
|
||||||
|
|
||||||
path = self.getGlyph(gid)
|
|
||||||
gdefs.append(path)
|
|
||||||
|
|
||||||
maxws.append(extract(path,'width='))
|
|
||||||
maxhs.append(extract(path,'height='))
|
|
||||||
|
|
||||||
|
|
||||||
# change the origin to minx, miny and calc max height and width
|
|
||||||
maxw = maxws[0] + xs[0] - minx
|
|
||||||
maxh = maxhs[0] + ys[0] - miny
|
|
||||||
for j in xrange(0, len(xs)):
|
|
||||||
xs[j] = xs[j] - minx
|
|
||||||
ys[j] = ys[j] - miny
|
|
||||||
maxw = max( maxw, (maxws[j] + xs[j]) )
|
|
||||||
maxh = max( maxh, (maxhs[j] + ys[j]) )
|
|
||||||
|
|
||||||
# open the image file for output
|
|
||||||
ifile = open(imgfile,'w')
|
|
||||||
ifile.write('<?xml version="1.0" standalone="no"?>\n')
|
|
||||||
ifile.write('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
|
||||||
ifile.write('<svg width="%dpx" height="%dpx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
|
|
||||||
ifile.write('<defs>\n')
|
|
||||||
for j in xrange(0,len(gdefs)):
|
|
||||||
ifile.write(gdefs[j])
|
|
||||||
ifile.write('</defs>\n')
|
|
||||||
for j in xrange(0,len(gids)):
|
|
||||||
ifile.write('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (gids[j], xs[j], ys[j]))
|
|
||||||
ifile.write('</svg>')
|
|
||||||
ifile.close()
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# return tag at line pos in document
|
|
||||||
def lineinDoc(self, pos) :
|
|
||||||
if (pos >= 0) and (pos < self.docSize) :
|
|
||||||
item = self.docList[pos]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=',1)
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
return name, argres
|
|
||||||
|
|
||||||
|
|
||||||
# find tag in doc if within pos to end inclusive
|
|
||||||
def findinDoc(self, tagpath, pos, end) :
|
|
||||||
result = None
|
|
||||||
if end == -1 :
|
|
||||||
end = self.docSize
|
|
||||||
else:
|
|
||||||
end = min(self.docSize, end)
|
|
||||||
foundat = -1
|
|
||||||
for j in xrange(pos, end):
|
|
||||||
item = self.docList[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=',1)
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
if name.endswith(tagpath) :
|
|
||||||
result = argres
|
|
||||||
foundat = j
|
|
||||||
break
|
|
||||||
return foundat, result
|
|
||||||
|
|
||||||
|
|
||||||
# return list of start positions for the tagpath
|
|
||||||
def posinDoc(self, tagpath):
|
|
||||||
startpos = []
|
|
||||||
pos = 0
|
|
||||||
res = ""
|
|
||||||
while res != None :
|
|
||||||
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
|
||||||
if res != None :
|
|
||||||
startpos.append(foundpos)
|
|
||||||
pos = foundpos + 1
|
|
||||||
return startpos
|
|
||||||
|
|
||||||
|
|
||||||
# returns a vector of integers for the tagpath
|
|
||||||
def getData(self, tagpath, pos, end):
|
|
||||||
argres=[]
|
|
||||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
|
||||||
if (argt != None) and (len(argt) > 0) :
|
|
||||||
argList = argt.split('|')
|
|
||||||
argres = [ int(strval) for strval in argList]
|
|
||||||
return argres
|
|
||||||
|
|
||||||
|
|
||||||
# get the class
|
|
||||||
def getClass(self, pclass):
|
|
||||||
nclass = pclass
|
|
||||||
|
|
||||||
# class names are an issue given topaz may start them with numerals (not allowed),
|
|
||||||
# use a mix of cases (which cause some browsers problems), and actually
|
|
||||||
# attach numbers after "_reclustered*" to the end to deal classeses that inherit
|
|
||||||
# from a base class (but then not actually provide all of these _reclustereed
|
|
||||||
# classes in the stylesheet!
|
|
||||||
|
|
||||||
# so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass
|
|
||||||
# that exists in the stylesheet first, and then adding this specific class
|
|
||||||
# after
|
|
||||||
|
|
||||||
# also some class names have spaces in them so need to convert to dashes
|
|
||||||
if nclass != None :
|
|
||||||
nclass = nclass.replace(' ','-')
|
|
||||||
classres = ''
|
|
||||||
nclass = nclass.lower()
|
|
||||||
nclass = 'cl-' + nclass
|
|
||||||
baseclass = ''
|
|
||||||
# graphic is the base class for captions
|
|
||||||
if nclass.find('cl-cap-') >=0 :
|
|
||||||
classres = 'graphic' + ' '
|
|
||||||
else :
|
|
||||||
# strip to find baseclass
|
|
||||||
p = nclass.find('_')
|
|
||||||
if p > 0 :
|
|
||||||
baseclass = nclass[0:p]
|
|
||||||
if baseclass in self.classList:
|
|
||||||
classres += baseclass + ' '
|
|
||||||
classres += nclass
|
|
||||||
nclass = classres
|
|
||||||
return nclass
|
|
||||||
|
|
||||||
|
|
||||||
# develop a sorted description of the starting positions of
|
|
||||||
# groups and regions on the page, as well as the page type
|
|
||||||
def PageDescription(self):
|
|
||||||
|
|
||||||
def compare(x, y):
|
|
||||||
(xtype, xval) = x
|
|
||||||
(ytype, yval) = y
|
|
||||||
if xval > yval:
|
|
||||||
return 1
|
|
||||||
if xval == yval:
|
|
||||||
return 0
|
|
||||||
return -1
|
|
||||||
|
|
||||||
result = []
|
|
||||||
(pos, pagetype) = self.findinDoc('page.type',0,-1)
|
|
||||||
|
|
||||||
groupList = self.posinDoc('page.group')
|
|
||||||
groupregionList = self.posinDoc('page.group.region')
|
|
||||||
pageregionList = self.posinDoc('page.region')
|
|
||||||
# integrate into one list
|
|
||||||
for j in groupList:
|
|
||||||
result.append(('grpbeg',j))
|
|
||||||
for j in groupregionList:
|
|
||||||
result.append(('gregion',j))
|
|
||||||
for j in pageregionList:
|
|
||||||
result.append(('pregion',j))
|
|
||||||
result.sort(compare)
|
|
||||||
|
|
||||||
# insert group end and page end indicators
|
|
||||||
inGroup = False
|
|
||||||
j = 0
|
|
||||||
while True:
|
|
||||||
if j == len(result): break
|
|
||||||
rtype = result[j][0]
|
|
||||||
rval = result[j][1]
|
|
||||||
if not inGroup and (rtype == 'grpbeg') :
|
|
||||||
inGroup = True
|
|
||||||
j = j + 1
|
|
||||||
elif inGroup and (rtype in ('grpbeg', 'pregion')):
|
|
||||||
result.insert(j,('grpend',rval))
|
|
||||||
inGroup = False
|
|
||||||
else:
|
|
||||||
j = j + 1
|
|
||||||
if inGroup:
|
|
||||||
result.append(('grpend',-1))
|
|
||||||
result.append(('pageend', -1))
|
|
||||||
return pagetype, result
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# build a description of the paragraph
|
|
||||||
def getParaDescription(self, start, end, regtype):
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
# paragraph
|
|
||||||
(pos, pclass) = self.findinDoc('paragraph.class',start,end)
|
|
||||||
|
|
||||||
pclass = self.getClass(pclass)
|
|
||||||
|
|
||||||
# if paragraph uses extratokens (extra glyphs) then make it fixed
|
|
||||||
(pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end)
|
|
||||||
|
|
||||||
# build up a description of the paragraph in result and return it
|
|
||||||
# first check for the basic - all words paragraph
|
|
||||||
(pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end)
|
|
||||||
(pos, slast) = self.findinDoc('paragraph.lastWord',start,end)
|
|
||||||
if (sfirst != None) and (slast != None) :
|
|
||||||
first = int(sfirst)
|
|
||||||
last = int(slast)
|
|
||||||
|
|
||||||
makeImage = (regtype == 'vertical') or (regtype == 'table')
|
|
||||||
makeImage = makeImage or (extraglyphs != None)
|
|
||||||
if self.fixedimage:
|
|
||||||
makeImage = makeImage or (regtype == 'fixed')
|
|
||||||
|
|
||||||
if (pclass != None):
|
|
||||||
makeImage = makeImage or (pclass.find('.inverted') >= 0)
|
|
||||||
if self.fixedimage :
|
|
||||||
makeImage = makeImage or (pclass.find('cl-f-') >= 0)
|
|
||||||
|
|
||||||
# before creating an image make sure glyph info exists
|
|
||||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
|
||||||
|
|
||||||
makeImage = makeImage & (len(gidList) > 0)
|
|
||||||
|
|
||||||
if not makeImage :
|
|
||||||
# standard all word paragraph
|
|
||||||
for wordnum in xrange(first, last):
|
|
||||||
result.append(('ocr', wordnum))
|
|
||||||
return pclass, result
|
|
||||||
|
|
||||||
# convert paragraph to svg image
|
|
||||||
# translate first and last word into first and last glyphs
|
|
||||||
# and generate inline image and include it
|
|
||||||
glyphList = []
|
|
||||||
firstglyphList = self.getData('word.firstGlyph',0,-1)
|
|
||||||
gidList = self.getData('info.glyph.glyphID',0,-1)
|
|
||||||
firstGlyph = firstglyphList[first]
|
|
||||||
if last < len(firstglyphList):
|
|
||||||
lastGlyph = firstglyphList[last]
|
|
||||||
else :
|
|
||||||
lastGlyph = len(gidList)
|
|
||||||
|
|
||||||
# handle case of white sapce paragraphs with no actual glyphs in them
|
|
||||||
# by reverting to text based paragraph
|
|
||||||
if firstGlyph >= lastGlyph:
|
|
||||||
# revert to standard text based paragraph
|
|
||||||
for wordnum in xrange(first, last):
|
|
||||||
result.append(('ocr', wordnum))
|
|
||||||
return pclass, result
|
|
||||||
|
|
||||||
for glyphnum in xrange(firstGlyph, lastGlyph):
|
|
||||||
glyphList.append(glyphnum)
|
|
||||||
# include any extratokens if they exist
|
|
||||||
(pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end)
|
|
||||||
(pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end)
|
|
||||||
if (sfg != None) and (slg != None):
|
|
||||||
for glyphnum in xrange(int(sfg), int(slg)):
|
|
||||||
glyphList.append(glyphnum)
|
|
||||||
num = self.svgcount
|
|
||||||
self.glyphs_to_image(glyphList)
|
|
||||||
self.svgcount += 1
|
|
||||||
result.append(('svg', num))
|
|
||||||
return pclass, result
|
|
||||||
|
|
||||||
# this type of paragraph may be made up of multiple spans, inline
|
|
||||||
# word monograms (images), and words with semantic meaning,
|
|
||||||
# plus glyphs used to form starting letter of first word
|
|
||||||
|
|
||||||
# need to parse this type line by line
|
|
||||||
line = start + 1
|
|
||||||
word_class = ''
|
|
||||||
|
|
||||||
# if end is -1 then we must search to end of document
|
|
||||||
if end == -1 :
|
|
||||||
end = self.docSize
|
|
||||||
|
|
||||||
# seems some xml has last* coming before first* so we have to
|
|
||||||
# handle any order
|
|
||||||
sp_first = -1
|
|
||||||
sp_last = -1
|
|
||||||
|
|
||||||
gl_first = -1
|
|
||||||
gl_last = -1
|
|
||||||
|
|
||||||
ws_first = -1
|
|
||||||
ws_last = -1
|
|
||||||
|
|
||||||
word_class = ''
|
|
||||||
|
|
||||||
word_semantic_type = ''
|
|
||||||
|
|
||||||
while (line < end) :
|
|
||||||
|
|
||||||
(name, argres) = self.lineinDoc(line)
|
|
||||||
|
|
||||||
if name.endswith('span.firstWord') :
|
|
||||||
sp_first = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('span.lastWord') :
|
|
||||||
sp_last = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('word.firstGlyph') :
|
|
||||||
gl_first = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('word.lastGlyph') :
|
|
||||||
gl_last = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('word_semantic.firstWord'):
|
|
||||||
ws_first = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('word_semantic.lastWord'):
|
|
||||||
ws_last = int(argres)
|
|
||||||
|
|
||||||
elif name.endswith('word.class'):
|
|
||||||
# we only handle spaceafter word class
|
|
||||||
try:
|
|
||||||
(cname, space) = argres.split('-',1)
|
|
||||||
if space == '' : space = '0'
|
|
||||||
if (cname == 'spaceafter') and (int(space) > 0) :
|
|
||||||
word_class = 'sa'
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif name.endswith('word.img.src'):
|
|
||||||
result.append(('img' + word_class, int(argres)))
|
|
||||||
word_class = ''
|
|
||||||
|
|
||||||
elif name.endswith('region.img.src'):
|
|
||||||
result.append(('img' + word_class, int(argres)))
|
|
||||||
|
|
||||||
if (sp_first != -1) and (sp_last != -1):
|
|
||||||
for wordnum in xrange(sp_first, sp_last):
|
|
||||||
result.append(('ocr', wordnum))
|
|
||||||
sp_first = -1
|
|
||||||
sp_last = -1
|
|
||||||
|
|
||||||
if (gl_first != -1) and (gl_last != -1):
|
|
||||||
glyphList = []
|
|
||||||
for glyphnum in xrange(gl_first, gl_last):
|
|
||||||
glyphList.append(glyphnum)
|
|
||||||
num = self.svgcount
|
|
||||||
self.glyphs_to_image(glyphList)
|
|
||||||
self.svgcount += 1
|
|
||||||
result.append(('svg', num))
|
|
||||||
gl_first = -1
|
|
||||||
gl_last = -1
|
|
||||||
|
|
||||||
if (ws_first != -1) and (ws_last != -1):
|
|
||||||
for wordnum in xrange(ws_first, ws_last):
|
|
||||||
result.append(('ocr', wordnum))
|
|
||||||
ws_first = -1
|
|
||||||
ws_last = -1
|
|
||||||
|
|
||||||
line += 1
|
|
||||||
|
|
||||||
return pclass, result
|
|
||||||
|
|
||||||
|
|
||||||
def buildParagraph(self, pclass, pdesc, type, regtype) :
|
|
||||||
parares = ''
|
|
||||||
sep =''
|
|
||||||
|
|
||||||
classres = ''
|
|
||||||
if pclass :
|
|
||||||
classres = ' class="' + pclass + '"'
|
|
||||||
|
|
||||||
br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
|
|
||||||
|
|
||||||
handle_links = len(self.link_id) > 0
|
|
||||||
|
|
||||||
if (type == 'full') or (type == 'begin') :
|
|
||||||
parares += '<p' + classres + '>'
|
|
||||||
|
|
||||||
if (type == 'end'):
|
|
||||||
parares += ' '
|
|
||||||
|
|
||||||
lstart = len(parares)
|
|
||||||
|
|
||||||
cnt = len(pdesc)
|
|
||||||
|
|
||||||
for j in xrange( 0, cnt) :
|
|
||||||
|
|
||||||
(wtype, num) = pdesc[j]
|
|
||||||
|
|
||||||
if wtype == 'ocr' :
|
|
||||||
try:
|
|
||||||
word = self.ocrtext[num]
|
|
||||||
except:
|
|
||||||
word = ""
|
|
||||||
|
|
||||||
sep = ' '
|
|
||||||
|
|
||||||
if handle_links:
|
|
||||||
link = self.link_id[num]
|
|
||||||
if (link > 0):
|
|
||||||
linktype = self.link_type[link-1]
|
|
||||||
title = self.link_title[link-1]
|
|
||||||
if (title == "") or (parares.rfind(title) < 0):
|
|
||||||
title=parares[lstart:]
|
|
||||||
if linktype == 'external' :
|
|
||||||
linkhref = self.link_href[link-1]
|
|
||||||
linkhtml = '<a href="%s">' % linkhref
|
|
||||||
else :
|
|
||||||
if len(self.link_page) >= link :
|
|
||||||
ptarget = self.link_page[link-1] - 1
|
|
||||||
linkhtml = '<a href="#page%04d">' % ptarget
|
|
||||||
else :
|
|
||||||
# just link to the current page
|
|
||||||
linkhtml = '<a href="#' + self.id + '">'
|
|
||||||
linkhtml += title + '</a>'
|
|
||||||
pos = parares.rfind(title)
|
|
||||||
if pos >= 0:
|
|
||||||
parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
|
|
||||||
else :
|
|
||||||
parares += linkhtml
|
|
||||||
lstart = len(parares)
|
|
||||||
if word == '_link_' : word = ''
|
|
||||||
elif (link < 0) :
|
|
||||||
if word == '_link_' : word = ''
|
|
||||||
|
|
||||||
if word == '_lb_':
|
|
||||||
if ((num-1) in self.dehyphen_rootid ) or handle_links:
|
|
||||||
word = ''
|
|
||||||
sep = ''
|
|
||||||
elif br_lb :
|
|
||||||
word = '<br />\n'
|
|
||||||
sep = ''
|
|
||||||
else :
|
|
||||||
word = '\n'
|
|
||||||
sep = ''
|
|
||||||
|
|
||||||
if num in self.dehyphen_rootid :
|
|
||||||
word = word[0:-1]
|
|
||||||
sep = ''
|
|
||||||
|
|
||||||
parares += word + sep
|
|
||||||
|
|
||||||
elif wtype == 'img' :
|
|
||||||
sep = ''
|
|
||||||
parares += '<img src="img/img%04d.jpg" alt="" />' % num
|
|
||||||
parares += sep
|
|
||||||
|
|
||||||
elif wtype == 'imgsa' :
|
|
||||||
sep = ' '
|
|
||||||
parares += '<img src="img/img%04d.jpg" alt="" />' % num
|
|
||||||
parares += sep
|
|
||||||
|
|
||||||
elif wtype == 'svg' :
|
|
||||||
sep = ''
|
|
||||||
parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
|
|
||||||
parares += sep
|
|
||||||
|
|
||||||
if len(sep) > 0 : parares = parares[0:-1]
|
|
||||||
if (type == 'full') or (type == 'end') :
|
|
||||||
parares += '</p>'
|
|
||||||
return parares
|
|
||||||
|
|
||||||
|
|
||||||
def buildTOCEntry(self, pdesc) :
|
|
||||||
parares = ''
|
|
||||||
sep =''
|
|
||||||
tocentry = ''
|
|
||||||
handle_links = len(self.link_id) > 0
|
|
||||||
|
|
||||||
lstart = 0
|
|
||||||
|
|
||||||
cnt = len(pdesc)
|
|
||||||
for j in xrange( 0, cnt) :
|
|
||||||
|
|
||||||
(wtype, num) = pdesc[j]
|
|
||||||
|
|
||||||
if wtype == 'ocr' :
|
|
||||||
word = self.ocrtext[num]
|
|
||||||
sep = ' '
|
|
||||||
|
|
||||||
if handle_links:
|
|
||||||
link = self.link_id[num]
|
|
||||||
if (link > 0):
|
|
||||||
linktype = self.link_type[link-1]
|
|
||||||
title = self.link_title[link-1]
|
|
||||||
title = title.rstrip('. ')
|
|
||||||
alt_title = parares[lstart:]
|
|
||||||
alt_title = alt_title.strip()
|
|
||||||
# now strip off the actual printed page number
|
|
||||||
alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.')
|
|
||||||
alt_title = alt_title.rstrip('. ')
|
|
||||||
# skip over any external links - can't have them in a books toc
|
|
||||||
if linktype == 'external' :
|
|
||||||
title = ''
|
|
||||||
alt_title = ''
|
|
||||||
linkpage = ''
|
|
||||||
else :
|
|
||||||
if len(self.link_page) >= link :
|
|
||||||
ptarget = self.link_page[link-1] - 1
|
|
||||||
linkpage = '%04d' % ptarget
|
|
||||||
else :
|
|
||||||
# just link to the current page
|
|
||||||
linkpage = self.id[4:]
|
|
||||||
if len(alt_title) >= len(title):
|
|
||||||
title = alt_title
|
|
||||||
if title != '' and linkpage != '':
|
|
||||||
tocentry += title + '|' + linkpage + '\n'
|
|
||||||
lstart = len(parares)
|
|
||||||
if word == '_link_' : word = ''
|
|
||||||
elif (link < 0) :
|
|
||||||
if word == '_link_' : word = ''
|
|
||||||
|
|
||||||
if word == '_lb_':
|
|
||||||
word = ''
|
|
||||||
sep = ''
|
|
||||||
|
|
||||||
if num in self.dehyphen_rootid :
|
|
||||||
word = word[0:-1]
|
|
||||||
sep = ''
|
|
||||||
|
|
||||||
parares += word + sep
|
|
||||||
|
|
||||||
else :
|
|
||||||
continue
|
|
||||||
|
|
||||||
return tocentry
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# walk the document tree collecting the information needed
|
|
||||||
# to build an html page using the ocrText
|
|
||||||
|
|
||||||
def process(self):
|
|
||||||
|
|
||||||
tocinfo = ''
|
|
||||||
hlst = []
|
|
||||||
|
|
||||||
# get the ocr text
|
|
||||||
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
|
|
||||||
if argres : self.ocrtext = argres.split('|')
|
|
||||||
|
|
||||||
# get information to dehyphenate the text
|
|
||||||
self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1)
|
|
||||||
|
|
||||||
# determine if first paragraph is continued from previous page
|
|
||||||
(pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1)
|
|
||||||
first_para_continued = (self.parastems_stemid != None)
|
|
||||||
|
|
||||||
# determine if last paragraph is continued onto the next page
|
|
||||||
(pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1)
|
|
||||||
last_para_continued = (self.paracont_stemid != None)
|
|
||||||
|
|
||||||
# collect link ids
|
|
||||||
self.link_id = self.getData('info.word.link_id',0,-1)
|
|
||||||
|
|
||||||
# collect link destination page numbers
|
|
||||||
self.link_page = self.getData('info.links.page',0,-1)
|
|
||||||
|
|
||||||
# collect link types (container versus external)
|
|
||||||
(pos, argres) = self.findinDoc('info.links.type',0,-1)
|
|
||||||
if argres : self.link_type = argres.split('|')
|
|
||||||
|
|
||||||
# collect link destinations
|
|
||||||
(pos, argres) = self.findinDoc('info.links.href',0,-1)
|
|
||||||
if argres : self.link_href = argres.split('|')
|
|
||||||
|
|
||||||
# collect link titles
|
|
||||||
(pos, argres) = self.findinDoc('info.links.title',0,-1)
|
|
||||||
if argres :
|
|
||||||
self.link_title = argres.split('|')
|
|
||||||
else:
|
|
||||||
self.link_title.append('')
|
|
||||||
|
|
||||||
# get a descriptions of the starting points of the regions
|
|
||||||
# and groups on the page
|
|
||||||
(pagetype, pageDesc) = self.PageDescription()
|
|
||||||
regcnt = len(pageDesc) - 1
|
|
||||||
|
|
||||||
anchorSet = False
|
|
||||||
breakSet = False
|
|
||||||
inGroup = False
|
|
||||||
|
|
||||||
# process each region on the page and convert what you can to html
|
|
||||||
|
|
||||||
for j in xrange(regcnt):
|
|
||||||
|
|
||||||
(etype, start) = pageDesc[j]
|
|
||||||
(ntype, end) = pageDesc[j+1]
|
|
||||||
|
|
||||||
|
|
||||||
# set anchor for link target on this page
|
|
||||||
if not anchorSet and not first_para_continued:
|
|
||||||
hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
|
|
||||||
hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
|
|
||||||
anchorSet = True
|
|
||||||
|
|
||||||
# handle groups of graphics with text captions
|
|
||||||
if (etype == 'grpbeg'):
|
|
||||||
(pos, grptype) = self.findinDoc('group.type', start, end)
|
|
||||||
if grptype != None:
|
|
||||||
if grptype == 'graphic':
|
|
||||||
gcstr = ' class="' + grptype + '"'
|
|
||||||
hlst.append('<div' + gcstr + '>')
|
|
||||||
inGroup = True
|
|
||||||
|
|
||||||
elif (etype == 'grpend'):
|
|
||||||
if inGroup:
|
|
||||||
hlst.append('</div>\n')
|
|
||||||
inGroup = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
(pos, regtype) = self.findinDoc('region.type',start,end)
|
|
||||||
|
|
||||||
if regtype == 'graphic' :
|
|
||||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
|
||||||
if simgsrc:
|
|
||||||
if inGroup:
|
|
||||||
hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
|
|
||||||
else:
|
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
|
||||||
|
|
||||||
elif regtype == 'chapterheading' :
|
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
|
||||||
if not breakSet:
|
|
||||||
hlst.append('<div style="page-break-after: always;"> </div>\n')
|
|
||||||
breakSet = True
|
|
||||||
tag = 'h1'
|
|
||||||
if pclass and (len(pclass) >= 7):
|
|
||||||
if pclass[3:7] == 'ch1-' : tag = 'h1'
|
|
||||||
if pclass[3:7] == 'ch2-' : tag = 'h2'
|
|
||||||
if pclass[3:7] == 'ch3-' : tag = 'h3'
|
|
||||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
|
||||||
else:
|
|
||||||
hlst.append('<' + tag + '>')
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
|
||||||
hlst.append('</' + tag + '>')
|
|
||||||
|
|
||||||
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
|
|
||||||
ptype = 'full'
|
|
||||||
# check to see if this is a continution from the previous page
|
|
||||||
if first_para_continued :
|
|
||||||
ptype = 'end'
|
|
||||||
first_para_continued = False
|
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
|
||||||
if pclass and (len(pclass) >= 6) and (ptype == 'full'):
|
|
||||||
tag = 'p'
|
|
||||||
if pclass[3:6] == 'h1-' : tag = 'h4'
|
|
||||||
if pclass[3:6] == 'h2-' : tag = 'h5'
|
|
||||||
if pclass[3:6] == 'h3-' : tag = 'h6'
|
|
||||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
|
||||||
hlst.append('</' + tag + '>')
|
|
||||||
else :
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
|
||||||
|
|
||||||
elif (regtype == 'tocentry') :
|
|
||||||
ptype = 'full'
|
|
||||||
if first_para_continued :
|
|
||||||
ptype = 'end'
|
|
||||||
first_para_continued = False
|
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
|
||||||
tocinfo += self.buildTOCEntry(pdesc)
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
|
||||||
|
|
||||||
elif (regtype == 'vertical') or (regtype == 'table') :
|
|
||||||
ptype = 'full'
|
|
||||||
if inGroup:
|
|
||||||
ptype = 'middle'
|
|
||||||
if first_para_continued :
|
|
||||||
ptype = 'end'
|
|
||||||
first_para_continued = False
|
|
||||||
(pclass, pdesc) = self.getParaDescription(start, end, regtype)
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
|
||||||
|
|
||||||
|
|
||||||
elif (regtype == 'synth_fcvr.center'):
|
|
||||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
|
||||||
if simgsrc:
|
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
|
||||||
|
|
||||||
else :
|
|
||||||
print ' Making region type', regtype,
|
|
||||||
(pos, temp) = self.findinDoc('paragraph',start,end)
|
|
||||||
(pos2, temp) = self.findinDoc('span',start,end)
|
|
||||||
if pos != -1 or pos2 != -1:
|
|
||||||
print ' a "text" region'
|
|
||||||
orig_regtype = regtype
|
|
||||||
regtype = 'fixed'
|
|
||||||
ptype = 'full'
|
|
||||||
# check to see if this is a continution from the previous page
|
|
||||||
if first_para_continued :
|
|
||||||
ptype = 'end'
|
|
||||||
first_para_continued = False
|
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
|
||||||
if not pclass:
|
|
||||||
if orig_regtype.endswith('.right') : pclass = 'cl-right'
|
|
||||||
elif orig_regtype.endswith('.center') : pclass = 'cl-center'
|
|
||||||
elif orig_regtype.endswith('.left') : pclass = 'cl-left'
|
|
||||||
elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
|
|
||||||
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
|
||||||
tag = 'p'
|
|
||||||
if pclass[3:6] == 'h1-' : tag = 'h4'
|
|
||||||
if pclass[3:6] == 'h2-' : tag = 'h5'
|
|
||||||
if pclass[3:6] == 'h3-' : tag = 'h6'
|
|
||||||
hlst.append('<' + tag + ' class="' + pclass + '">')
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
|
||||||
hlst.append('</' + tag + '>')
|
|
||||||
else :
|
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
|
||||||
else :
|
|
||||||
print ' a "graphic" region'
|
|
||||||
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
|
||||||
if simgsrc:
|
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
|
||||||
|
|
||||||
|
|
||||||
htmlpage = "".join(hlst)
|
|
||||||
if last_para_continued :
|
|
||||||
if htmlpage[-4:] == '</p>':
|
|
||||||
htmlpage = htmlpage[0:-4]
|
|
||||||
last_para_continued = False
|
|
||||||
|
|
||||||
return htmlpage, tocinfo
|
|
||||||
|
|
||||||
|
|
||||||
def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage):
|
|
||||||
# create a document parser
|
|
||||||
dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage)
|
|
||||||
htmlpage, tocinfo = dp.process()
|
|
||||||
return htmlpage, tocinfo
|
|
@ -1,249 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
|
|
||||||
class PParser(object):
|
|
||||||
def __init__(self, gd, flatxml, meta_array):
|
|
||||||
self.gd = gd
|
|
||||||
self.flatdoc = flatxml.split('\n')
|
|
||||||
self.docSize = len(self.flatdoc)
|
|
||||||
self.temp = []
|
|
||||||
|
|
||||||
self.ph = -1
|
|
||||||
self.pw = -1
|
|
||||||
startpos = self.posinDoc('page.h') or self.posinDoc('book.h')
|
|
||||||
for p in startpos:
|
|
||||||
(name, argres) = self.lineinDoc(p)
|
|
||||||
self.ph = max(self.ph, int(argres))
|
|
||||||
startpos = self.posinDoc('page.w') or self.posinDoc('book.w')
|
|
||||||
for p in startpos:
|
|
||||||
(name, argres) = self.lineinDoc(p)
|
|
||||||
self.pw = max(self.pw, int(argres))
|
|
||||||
|
|
||||||
if self.ph <= 0:
|
|
||||||
self.ph = int(meta_array.get('pageHeight', '11000'))
|
|
||||||
if self.pw <= 0:
|
|
||||||
self.pw = int(meta_array.get('pageWidth', '8500'))
|
|
||||||
|
|
||||||
res = []
|
|
||||||
startpos = self.posinDoc('info.glyph.x')
|
|
||||||
for p in startpos:
|
|
||||||
argres = self.getDataatPos('info.glyph.x', p)
|
|
||||||
res.extend(argres)
|
|
||||||
self.gx = res
|
|
||||||
|
|
||||||
res = []
|
|
||||||
startpos = self.posinDoc('info.glyph.y')
|
|
||||||
for p in startpos:
|
|
||||||
argres = self.getDataatPos('info.glyph.y', p)
|
|
||||||
res.extend(argres)
|
|
||||||
self.gy = res
|
|
||||||
|
|
||||||
res = []
|
|
||||||
startpos = self.posinDoc('info.glyph.glyphID')
|
|
||||||
for p in startpos:
|
|
||||||
argres = self.getDataatPos('info.glyph.glyphID', p)
|
|
||||||
res.extend(argres)
|
|
||||||
self.gid = res
|
|
||||||
|
|
||||||
|
|
||||||
# return tag at line pos in document
|
|
||||||
def lineinDoc(self, pos) :
|
|
||||||
if (pos >= 0) and (pos < self.docSize) :
|
|
||||||
item = self.flatdoc[pos]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=',1)
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
return name, argres
|
|
||||||
|
|
||||||
# find tag in doc if within pos to end inclusive
|
|
||||||
def findinDoc(self, tagpath, pos, end) :
|
|
||||||
result = None
|
|
||||||
if end == -1 :
|
|
||||||
end = self.docSize
|
|
||||||
else:
|
|
||||||
end = min(self.docSize, end)
|
|
||||||
foundat = -1
|
|
||||||
for j in xrange(pos, end):
|
|
||||||
item = self.flatdoc[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=',1)
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
if name.endswith(tagpath) :
|
|
||||||
result = argres
|
|
||||||
foundat = j
|
|
||||||
break
|
|
||||||
return foundat, result
|
|
||||||
|
|
||||||
# return list of start positions for the tagpath
|
|
||||||
def posinDoc(self, tagpath):
|
|
||||||
startpos = []
|
|
||||||
pos = 0
|
|
||||||
res = ""
|
|
||||||
while res != None :
|
|
||||||
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
|
||||||
if res != None :
|
|
||||||
startpos.append(foundpos)
|
|
||||||
pos = foundpos + 1
|
|
||||||
return startpos
|
|
||||||
|
|
||||||
def getData(self, path):
|
|
||||||
result = None
|
|
||||||
cnt = len(self.flatdoc)
|
|
||||||
for j in xrange(cnt):
|
|
||||||
item = self.flatdoc[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argt) = item.split('=')
|
|
||||||
argres = argt.split('|')
|
|
||||||
else:
|
|
||||||
name = item
|
|
||||||
argres = []
|
|
||||||
if (name.endswith(path)):
|
|
||||||
result = argres
|
|
||||||
break
|
|
||||||
if (len(argres) > 0) :
|
|
||||||
for j in xrange(0,len(argres)):
|
|
||||||
argres[j] = int(argres[j])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getDataatPos(self, path, pos):
|
|
||||||
result = None
|
|
||||||
item = self.flatdoc[pos]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argt) = item.split('=')
|
|
||||||
argres = argt.split('|')
|
|
||||||
else:
|
|
||||||
name = item
|
|
||||||
argres = []
|
|
||||||
if (len(argres) > 0) :
|
|
||||||
for j in xrange(0,len(argres)):
|
|
||||||
argres[j] = int(argres[j])
|
|
||||||
if (name.endswith(path)):
|
|
||||||
result = argres
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getDataTemp(self, path):
|
|
||||||
result = None
|
|
||||||
cnt = len(self.temp)
|
|
||||||
for j in xrange(cnt):
|
|
||||||
item = self.temp[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argt) = item.split('=')
|
|
||||||
argres = argt.split('|')
|
|
||||||
else:
|
|
||||||
name = item
|
|
||||||
argres = []
|
|
||||||
if (name.endswith(path)):
|
|
||||||
result = argres
|
|
||||||
self.temp.pop(j)
|
|
||||||
break
|
|
||||||
if (len(argres) > 0) :
|
|
||||||
for j in xrange(0,len(argres)):
|
|
||||||
argres[j] = int(argres[j])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getImages(self):
|
|
||||||
result = []
|
|
||||||
self.temp = self.flatdoc
|
|
||||||
while (self.getDataTemp('img') != None):
|
|
||||||
h = self.getDataTemp('img.h')[0]
|
|
||||||
w = self.getDataTemp('img.w')[0]
|
|
||||||
x = self.getDataTemp('img.x')[0]
|
|
||||||
y = self.getDataTemp('img.y')[0]
|
|
||||||
src = self.getDataTemp('img.src')[0]
|
|
||||||
result.append('<image xlink:href="../img/img%04d.jpg" x="%d" y="%d" width="%d" height="%d" />\n' % (src, x, y, w, h))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getGlyphs(self):
|
|
||||||
result = []
|
|
||||||
if (self.gid != None) and (len(self.gid) > 0):
|
|
||||||
glyphs = []
|
|
||||||
for j in set(self.gid):
|
|
||||||
glyphs.append(j)
|
|
||||||
glyphs.sort()
|
|
||||||
for gid in glyphs:
|
|
||||||
id='id="gl%d"' % gid
|
|
||||||
path = self.gd.lookup(id)
|
|
||||||
if path:
|
|
||||||
result.append(id + ' ' + path)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
|
|
||||||
mlst = []
|
|
||||||
pp = PParser(gdict, flat_xml, meta_array)
|
|
||||||
mlst.append('<?xml version="1.0" standalone="no"?>\n')
|
|
||||||
if (raw):
|
|
||||||
mlst.append('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
|
||||||
mlst.append('<svg width="%fin" height="%fin" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1))
|
|
||||||
mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
|
|
||||||
else:
|
|
||||||
mlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
|
|
||||||
mlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n')
|
|
||||||
mlst.append('<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors']))
|
|
||||||
mlst.append('<script><![CDATA[\n')
|
|
||||||
mlst.append('function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n')
|
|
||||||
mlst.append('var dpi=%d;\n' % scaledpi)
|
|
||||||
if (previd) :
|
|
||||||
mlst.append('var prevpage="page%04d.xhtml";\n' % (previd))
|
|
||||||
if (nextid) :
|
|
||||||
mlst.append('var nextpage="page%04d.xhtml";\n' % (nextid))
|
|
||||||
mlst.append('var pw=%d;var ph=%d;' % (pp.pw, pp.ph))
|
|
||||||
mlst.append('function zoomin(){dpi=dpi*(0.8);setsize();}\n')
|
|
||||||
mlst.append('function zoomout(){dpi=dpi*1.25;setsize();}\n')
|
|
||||||
mlst.append('function setsize(){var svg=document.getElementById("svgimg");var prev=document.getElementById("prevsvg");var next=document.getElementById("nextsvg");var width=(pw/dpi)+"in";var height=(ph/dpi)+"in";svg.setAttribute("width",width);svg.setAttribute("height",height);prev.setAttribute("height",height);prev.setAttribute("width","50px");next.setAttribute("height",height);next.setAttribute("width","50px");}\n')
|
|
||||||
mlst.append('function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n')
|
|
||||||
mlst.append('function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n')
|
|
||||||
mlst.append('var gt=gd();if(gt>0){dpi=gt;}\n')
|
|
||||||
mlst.append('window.onload=setsize;\n')
|
|
||||||
mlst.append(']]></script>\n')
|
|
||||||
mlst.append('</head>\n')
|
|
||||||
mlst.append('<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n')
|
|
||||||
mlst.append('<div style="white-space:nowrap;">\n')
|
|
||||||
if previd == None:
|
|
||||||
mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
|
|
||||||
else:
|
|
||||||
mlst.append('<a href="javascript:ppage();"><svg id="prevsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,150,95,5,95,295" fill="#AAAAAA" /></svg></a>\n')
|
|
||||||
|
|
||||||
mlst.append('<a href="javascript:npage();"><svg id="svgimg" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="background-color:#FFF;border:1px solid black;">' % (pp.pw, pp.ph))
|
|
||||||
if (pp.gid != None):
|
|
||||||
mlst.append('<defs>\n')
|
|
||||||
gdefs = pp.getGlyphs()
|
|
||||||
for j in xrange(0,len(gdefs)):
|
|
||||||
mlst.append(gdefs[j])
|
|
||||||
mlst.append('</defs>\n')
|
|
||||||
img = pp.getImages()
|
|
||||||
if (img != None):
|
|
||||||
for j in xrange(0,len(img)):
|
|
||||||
mlst.append(img[j])
|
|
||||||
if (pp.gid != None):
|
|
||||||
for j in xrange(0,len(pp.gid)):
|
|
||||||
mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
|
|
||||||
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
|
|
||||||
xpos = "%d" % (pp.pw // 3)
|
|
||||||
ypos = "%d" % (pp.ph // 3)
|
|
||||||
mlst.append('<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n')
|
|
||||||
if (raw) :
|
|
||||||
mlst.append('</svg>')
|
|
||||||
else :
|
|
||||||
mlst.append('</svg></a>\n')
|
|
||||||
if nextid == None:
|
|
||||||
mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"></svg></a>\n')
|
|
||||||
else :
|
|
||||||
mlst.append('<a href="javascript:npage();"><svg id="nextsvg" viewBox="0 0 100 300" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#777"><polygon points="5,5,5,295,95,150" fill="#AAAAAA" /></svg></a>\n')
|
|
||||||
mlst.append('</div>\n')
|
|
||||||
mlst.append('<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n')
|
|
||||||
mlst.append('</body>\n')
|
|
||||||
mlst.append('</html>\n')
|
|
||||||
return "".join(mlst)
|
|
@ -1,721 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
class Unbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
def write(self, data):
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
sys.stdout=Unbuffered(sys.stdout)
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
class TpzDRMError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# local support routines
|
|
||||||
if 'calibre' in sys.modules:
|
|
||||||
inCalibre = True
|
|
||||||
else:
|
|
||||||
inCalibre = False
|
|
||||||
|
|
||||||
if inCalibre :
|
|
||||||
from calibre_plugins.dedrm import convert2xml
|
|
||||||
from calibre_plugins.dedrm import flatxml2html
|
|
||||||
from calibre_plugins.dedrm import flatxml2svg
|
|
||||||
from calibre_plugins.dedrm import stylexml2css
|
|
||||||
else :
|
|
||||||
import convert2xml
|
|
||||||
import flatxml2html
|
|
||||||
import flatxml2svg
|
|
||||||
import stylexml2css
|
|
||||||
|
|
||||||
# global switch
|
|
||||||
buildXML = False
|
|
||||||
|
|
||||||
# Get a 7 bit encoded number from a file
|
|
||||||
def readEncodedNumber(file):
|
|
||||||
flag = False
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
if data == 0xFF:
|
|
||||||
flag = True
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
if data >= 0x80:
|
|
||||||
datax = (data & 0x7F)
|
|
||||||
while data >= 0x80 :
|
|
||||||
c = file.read(1)
|
|
||||||
if (len(c) == 0):
|
|
||||||
return None
|
|
||||||
data = ord(c)
|
|
||||||
datax = (datax <<7) + (data & 0x7F)
|
|
||||||
data = datax
|
|
||||||
if flag:
|
|
||||||
data = -data
|
|
||||||
return data
|
|
||||||
|
|
||||||
# Get a length prefixed string from the file
|
|
||||||
def lengthPrefixString(data):
|
|
||||||
return encodeNumber(len(data))+data
|
|
||||||
|
|
||||||
def readString(file):
|
|
||||||
stringLength = readEncodedNumber(file)
|
|
||||||
if (stringLength == None):
|
|
||||||
return None
|
|
||||||
sv = file.read(stringLength)
|
|
||||||
if (len(sv) != stringLength):
|
|
||||||
return ""
|
|
||||||
return unpack(str(stringLength)+"s",sv)[0]
|
|
||||||
|
|
||||||
def getMetaArray(metaFile):
|
|
||||||
# parse the meta file
|
|
||||||
result = {}
|
|
||||||
fo = file(metaFile,'rb')
|
|
||||||
size = readEncodedNumber(fo)
|
|
||||||
for i in xrange(size):
|
|
||||||
tag = readString(fo)
|
|
||||||
value = readString(fo)
|
|
||||||
result[tag] = value
|
|
||||||
# print tag, value
|
|
||||||
fo.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
# dictionary of all text strings by index value
|
|
||||||
class Dictionary(object):
|
|
||||||
def __init__(self, dictFile):
|
|
||||||
self.filename = dictFile
|
|
||||||
self.size = 0
|
|
||||||
self.fo = file(dictFile,'rb')
|
|
||||||
self.stable = []
|
|
||||||
self.size = readEncodedNumber(self.fo)
|
|
||||||
for i in xrange(self.size):
|
|
||||||
self.stable.append(self.escapestr(readString(self.fo)))
|
|
||||||
self.pos = 0
|
|
||||||
def escapestr(self, str):
|
|
||||||
str = str.replace('&','&')
|
|
||||||
str = str.replace('<','<')
|
|
||||||
str = str.replace('>','>')
|
|
||||||
str = str.replace('=','=')
|
|
||||||
return str
|
|
||||||
def lookup(self,val):
|
|
||||||
if ((val >= 0) and (val < self.size)) :
|
|
||||||
self.pos = val
|
|
||||||
return self.stable[self.pos]
|
|
||||||
else:
|
|
||||||
print "Error: %d outside of string table limits" % val
|
|
||||||
raise TpzDRMError('outside or string table limits')
|
|
||||||
# sys.exit(-1)
|
|
||||||
def getSize(self):
|
|
||||||
return self.size
|
|
||||||
def getPos(self):
|
|
||||||
return self.pos
|
|
||||||
|
|
||||||
|
|
||||||
class PageDimParser(object):
|
|
||||||
def __init__(self, flatxml):
|
|
||||||
self.flatdoc = flatxml.split('\n')
|
|
||||||
# find tag if within pos to end inclusive
|
|
||||||
def findinDoc(self, tagpath, pos, end) :
|
|
||||||
result = None
|
|
||||||
docList = self.flatdoc
|
|
||||||
cnt = len(docList)
|
|
||||||
if end == -1 :
|
|
||||||
end = cnt
|
|
||||||
else:
|
|
||||||
end = min(cnt,end)
|
|
||||||
foundat = -1
|
|
||||||
for j in xrange(pos, end):
|
|
||||||
item = docList[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=')
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
if name.endswith(tagpath) :
|
|
||||||
result = argres
|
|
||||||
foundat = j
|
|
||||||
break
|
|
||||||
return foundat, result
|
|
||||||
def process(self):
|
|
||||||
(pos, sph) = self.findinDoc('page.h',0,-1)
|
|
||||||
(pos, spw) = self.findinDoc('page.w',0,-1)
|
|
||||||
if (sph == None): sph = '-1'
|
|
||||||
if (spw == None): spw = '-1'
|
|
||||||
return sph, spw
|
|
||||||
|
|
||||||
def getPageDim(flatxml):
|
|
||||||
# create a document parser
|
|
||||||
dp = PageDimParser(flatxml)
|
|
||||||
(ph, pw) = dp.process()
|
|
||||||
return ph, pw
|
|
||||||
|
|
||||||
class GParser(object):
|
|
||||||
def __init__(self, flatxml):
|
|
||||||
self.flatdoc = flatxml.split('\n')
|
|
||||||
self.dpi = 1440
|
|
||||||
self.gh = self.getData('info.glyph.h')
|
|
||||||
self.gw = self.getData('info.glyph.w')
|
|
||||||
self.guse = self.getData('info.glyph.use')
|
|
||||||
if self.guse :
|
|
||||||
self.count = len(self.guse)
|
|
||||||
else :
|
|
||||||
self.count = 0
|
|
||||||
self.gvtx = self.getData('info.glyph.vtx')
|
|
||||||
self.glen = self.getData('info.glyph.len')
|
|
||||||
self.gdpi = self.getData('info.glyph.dpi')
|
|
||||||
self.vx = self.getData('info.vtx.x')
|
|
||||||
self.vy = self.getData('info.vtx.y')
|
|
||||||
self.vlen = self.getData('info.len.n')
|
|
||||||
if self.vlen :
|
|
||||||
self.glen.append(len(self.vlen))
|
|
||||||
elif self.glen:
|
|
||||||
self.glen.append(0)
|
|
||||||
if self.vx :
|
|
||||||
self.gvtx.append(len(self.vx))
|
|
||||||
elif self.gvtx :
|
|
||||||
self.gvtx.append(0)
|
|
||||||
def getData(self, path):
|
|
||||||
result = None
|
|
||||||
cnt = len(self.flatdoc)
|
|
||||||
for j in xrange(cnt):
|
|
||||||
item = self.flatdoc[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argt) = item.split('=')
|
|
||||||
argres = argt.split('|')
|
|
||||||
else:
|
|
||||||
name = item
|
|
||||||
argres = []
|
|
||||||
if (name == path):
|
|
||||||
result = argres
|
|
||||||
break
|
|
||||||
if (len(argres) > 0) :
|
|
||||||
for j in xrange(0,len(argres)):
|
|
||||||
argres[j] = int(argres[j])
|
|
||||||
return result
|
|
||||||
def getGlyphDim(self, gly):
|
|
||||||
if self.gdpi[gly] == 0:
|
|
||||||
return 0, 0
|
|
||||||
maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
|
|
||||||
maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
|
|
||||||
return maxh, maxw
|
|
||||||
def getPath(self, gly):
|
|
||||||
path = ''
|
|
||||||
if (gly < 0) or (gly >= self.count):
|
|
||||||
return path
|
|
||||||
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]):
|
|
||||||
if (p == 0):
|
|
||||||
zx = tx[0:self.vlen[k]+1]
|
|
||||||
zy = ty[0:self.vlen[k]+1]
|
|
||||||
else:
|
|
||||||
zx = tx[self.vlen[k-1]+1:self.vlen[k]+1]
|
|
||||||
zy = ty[self.vlen[k-1]+1:self.vlen[k]+1]
|
|
||||||
p += 1
|
|
||||||
j = 0
|
|
||||||
while ( j < len(zx) ):
|
|
||||||
if (j == 0):
|
|
||||||
# Start Position.
|
|
||||||
path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly])
|
|
||||||
elif (j <= len(zx)-3):
|
|
||||||
# Cubic Bezier Curve
|
|
||||||
path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly])
|
|
||||||
j += 2
|
|
||||||
elif (j == len(zx)-2):
|
|
||||||
# Cubic Bezier Curve to Start Position
|
|
||||||
path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
|
|
||||||
j += 1
|
|
||||||
elif (j == len(zx)-1):
|
|
||||||
# Quadratic Bezier Curve to Start Position
|
|
||||||
path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
|
|
||||||
|
|
||||||
j += 1
|
|
||||||
path += 'z'
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# dictionary of all text strings by index value
|
|
||||||
class GlyphDict(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.gdict = {}
|
|
||||||
def lookup(self, id):
|
|
||||||
# id='id="gl%d"' % val
|
|
||||||
if id in self.gdict:
|
|
||||||
return self.gdict[id]
|
|
||||||
return None
|
|
||||||
def addGlyph(self, val, path):
|
|
||||||
id='id="gl%d"' % val
|
|
||||||
self.gdict[id] = path
|
|
||||||
|
|
||||||
|
|
||||||
def generateBook(bookDir, raw, fixedimage):
|
|
||||||
# sanity check Topaz file extraction
|
|
||||||
if not os.path.exists(bookDir) :
|
|
||||||
print "Can not find directory with unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
dictFile = os.path.join(bookDir,'dict0000.dat')
|
|
||||||
if not os.path.exists(dictFile) :
|
|
||||||
print "Can not find dict0000.dat file"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
pageDir = os.path.join(bookDir,'page')
|
|
||||||
if not os.path.exists(pageDir) :
|
|
||||||
print "Can not find page directory in unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
imgDir = os.path.join(bookDir,'img')
|
|
||||||
if not os.path.exists(imgDir) :
|
|
||||||
print "Can not find image directory in unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
glyphsDir = os.path.join(bookDir,'glyphs')
|
|
||||||
if not os.path.exists(glyphsDir) :
|
|
||||||
print "Can not find glyphs directory in unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
metaFile = os.path.join(bookDir,'metadata0000.dat')
|
|
||||||
if not os.path.exists(metaFile) :
|
|
||||||
print "Can not find metadata0000.dat in unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
svgDir = os.path.join(bookDir,'svg')
|
|
||||||
if not os.path.exists(svgDir) :
|
|
||||||
os.makedirs(svgDir)
|
|
||||||
|
|
||||||
if buildXML:
|
|
||||||
xmlDir = os.path.join(bookDir,'xml')
|
|
||||||
if not os.path.exists(xmlDir) :
|
|
||||||
os.makedirs(xmlDir)
|
|
||||||
|
|
||||||
otherFile = os.path.join(bookDir,'other0000.dat')
|
|
||||||
if not os.path.exists(otherFile) :
|
|
||||||
print "Can not find other0000.dat in unencrypted book"
|
|
||||||
return 1
|
|
||||||
|
|
||||||
print "Updating to color images if available"
|
|
||||||
spath = os.path.join(bookDir,'color_img')
|
|
||||||
dpath = os.path.join(bookDir,'img')
|
|
||||||
filenames = os.listdir(spath)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
for filename in filenames:
|
|
||||||
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)
|
|
||||||
|
|
||||||
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()
|
|
||||||
cpath = os.path.join(bookDir,'cover.jpg')
|
|
||||||
file(cpath, 'wb').write(cover)
|
|
||||||
isCover = True
|
|
||||||
|
|
||||||
|
|
||||||
print 'Processing Dictionary'
|
|
||||||
dict = Dictionary(dictFile)
|
|
||||||
|
|
||||||
print 'Processing Meta Data and creating OPF'
|
|
||||||
meta_array = getMetaArray(metaFile)
|
|
||||||
|
|
||||||
# replace special chars in title and authors like & < >
|
|
||||||
title = meta_array.get('Title','No Title Provided')
|
|
||||||
title = title.replace('&','&')
|
|
||||||
title = title.replace('<','<')
|
|
||||||
title = title.replace('>','>')
|
|
||||||
meta_array['Title'] = title
|
|
||||||
authors = meta_array.get('Authors','No Authors Provided')
|
|
||||||
authors = authors.replace('&','&')
|
|
||||||
authors = authors.replace('<','<')
|
|
||||||
authors = authors.replace('>','>')
|
|
||||||
meta_array['Authors'] = authors
|
|
||||||
|
|
||||||
if buildXML:
|
|
||||||
xname = os.path.join(xmlDir, 'metadata.xml')
|
|
||||||
mlst = []
|
|
||||||
for key in meta_array:
|
|
||||||
mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
|
|
||||||
metastr = "".join(mlst)
|
|
||||||
mlst = None
|
|
||||||
file(xname, 'wb').write(metastr)
|
|
||||||
|
|
||||||
print 'Processing StyleSheet'
|
|
||||||
|
|
||||||
# get some scaling info from metadata to use while processing styles
|
|
||||||
# and first page info
|
|
||||||
|
|
||||||
fontsize = '135'
|
|
||||||
if 'fontSize' in meta_array:
|
|
||||||
fontsize = meta_array['fontSize']
|
|
||||||
|
|
||||||
# also get the size of a normal text page
|
|
||||||
# get the total number of pages unpacked as a safety check
|
|
||||||
filenames = os.listdir(pageDir)
|
|
||||||
numfiles = len(filenames)
|
|
||||||
|
|
||||||
spage = '1'
|
|
||||||
if 'firstTextPage' in meta_array:
|
|
||||||
spage = meta_array['firstTextPage']
|
|
||||||
pnum = int(spage)
|
|
||||||
if pnum >= numfiles or pnum < 0:
|
|
||||||
# metadata is wrong so just select a page near the front
|
|
||||||
# 10% of the book to get a normal text page
|
|
||||||
pnum = int(0.10 * numfiles)
|
|
||||||
# print "first normal text page is", spage
|
|
||||||
|
|
||||||
# get page height and width from first text page for use in stylesheet scaling
|
|
||||||
pname = 'page%04d.dat' % (pnum - 1)
|
|
||||||
fname = os.path.join(pageDir,pname)
|
|
||||||
flat_xml = convert2xml.fromData(dict, fname)
|
|
||||||
|
|
||||||
(ph, pw) = getPageDim(flat_xml)
|
|
||||||
if (ph == '-1') or (ph == '0') : ph = '11000'
|
|
||||||
if (pw == '-1') or (pw == '0') : pw = '8500'
|
|
||||||
meta_array['pageHeight'] = ph
|
|
||||||
meta_array['pageWidth'] = pw
|
|
||||||
if 'fontSize' not in meta_array.keys():
|
|
||||||
meta_array['fontSize'] = fontsize
|
|
||||||
|
|
||||||
# process other.dat for css info and for map of page files to svg images
|
|
||||||
# this map is needed because some pages actually are made up of multiple
|
|
||||||
# pageXXXX.xml files
|
|
||||||
xname = os.path.join(bookDir, 'style.css')
|
|
||||||
flat_xml = convert2xml.fromData(dict, otherFile)
|
|
||||||
|
|
||||||
# extract info.original.pid to get original page information
|
|
||||||
pageIDMap = {}
|
|
||||||
pageidnums = stylexml2css.getpageIDMap(flat_xml)
|
|
||||||
if len(pageidnums) == 0:
|
|
||||||
filenames = os.listdir(pageDir)
|
|
||||||
numfiles = len(filenames)
|
|
||||||
for k in range(numfiles):
|
|
||||||
pageidnums.append(k)
|
|
||||||
# create a map from page ids to list of page file nums to process for that page
|
|
||||||
for i in range(len(pageidnums)):
|
|
||||||
id = pageidnums[i]
|
|
||||||
if id in pageIDMap.keys():
|
|
||||||
pageIDMap[id].append(i)
|
|
||||||
else:
|
|
||||||
pageIDMap[id] = [i]
|
|
||||||
|
|
||||||
# now get the css info
|
|
||||||
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
|
|
||||||
file(xname, 'wb').write(cssstr)
|
|
||||||
if buildXML:
|
|
||||||
xname = os.path.join(xmlDir, 'other0000.xml')
|
|
||||||
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
|
||||||
|
|
||||||
print 'Processing Glyphs'
|
|
||||||
gd = GlyphDict()
|
|
||||||
filenames = os.listdir(glyphsDir)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
glyfname = os.path.join(svgDir,'glyphs.svg')
|
|
||||||
glyfile = open(glyfname, 'w')
|
|
||||||
glyfile.write('<?xml version="1.0" standalone="no"?>\n')
|
|
||||||
glyfile.write('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
|
||||||
glyfile.write('<svg width="512" height="512" viewBox="0 0 511 511" xmlns="http://www.w3.org/2000/svg" version="1.1">\n')
|
|
||||||
glyfile.write('<title>Glyphs for %s</title>\n' % meta_array['Title'])
|
|
||||||
glyfile.write('<defs>\n')
|
|
||||||
counter = 0
|
|
||||||
for filename in filenames:
|
|
||||||
# print ' ', filename
|
|
||||||
print '.',
|
|
||||||
fname = os.path.join(glyphsDir,filename)
|
|
||||||
flat_xml = convert2xml.fromData(dict, fname)
|
|
||||||
|
|
||||||
if buildXML:
|
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
|
||||||
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
|
||||||
|
|
||||||
gp = GParser(flat_xml)
|
|
||||||
for i in xrange(0, gp.count):
|
|
||||||
path = gp.getPath(i)
|
|
||||||
maxh, maxw = gp.getGlyphDim(i)
|
|
||||||
fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh)
|
|
||||||
glyfile.write(fullpath)
|
|
||||||
gd.addGlyph(counter * 256 + i, fullpath)
|
|
||||||
counter += 1
|
|
||||||
glyfile.write('</defs>\n')
|
|
||||||
glyfile.write('</svg>\n')
|
|
||||||
glyfile.close()
|
|
||||||
print " "
|
|
||||||
|
|
||||||
|
|
||||||
# start up the html
|
|
||||||
# also build up tocentries while processing html
|
|
||||||
htmlFileName = "book.html"
|
|
||||||
hlst = []
|
|
||||||
hlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
||||||
hlst.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n')
|
|
||||||
hlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n')
|
|
||||||
hlst.append('<head>\n')
|
|
||||||
hlst.append('<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n')
|
|
||||||
hlst.append('<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n')
|
|
||||||
hlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
|
|
||||||
hlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
|
|
||||||
if 'ASIN' in meta_array:
|
|
||||||
hlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
|
|
||||||
if 'GUID' in meta_array:
|
|
||||||
hlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
|
|
||||||
hlst.append('<link href="style.css" rel="stylesheet" type="text/css" />\n')
|
|
||||||
hlst.append('</head>\n<body>\n')
|
|
||||||
|
|
||||||
print 'Processing Pages'
|
|
||||||
# Books are at 1440 DPI. This is rendering at twice that size for
|
|
||||||
# readability when rendering to the screen.
|
|
||||||
scaledpi = 1440.0
|
|
||||||
|
|
||||||
filenames = os.listdir(pageDir)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
numfiles = len(filenames)
|
|
||||||
|
|
||||||
xmllst = []
|
|
||||||
elst = []
|
|
||||||
|
|
||||||
for filename in filenames:
|
|
||||||
# print ' ', filename
|
|
||||||
print ".",
|
|
||||||
fname = os.path.join(pageDir,filename)
|
|
||||||
flat_xml = convert2xml.fromData(dict, fname)
|
|
||||||
|
|
||||||
# keep flat_xml for later svg processing
|
|
||||||
xmllst.append(flat_xml)
|
|
||||||
|
|
||||||
if buildXML:
|
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
|
||||||
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
|
||||||
|
|
||||||
# first get the html
|
|
||||||
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
|
|
||||||
elst.append(tocinfo)
|
|
||||||
hlst.append(pagehtml)
|
|
||||||
|
|
||||||
# finish up the html string and output it
|
|
||||||
hlst.append('</body>\n</html>\n')
|
|
||||||
htmlstr = "".join(hlst)
|
|
||||||
hlst = None
|
|
||||||
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
|
|
||||||
|
|
||||||
print " "
|
|
||||||
print 'Extracting Table of Contents from Amazon OCR'
|
|
||||||
|
|
||||||
# first create a table of contents file for the svg images
|
|
||||||
tlst = []
|
|
||||||
tlst.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
||||||
tlst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
|
|
||||||
tlst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
|
|
||||||
tlst.append('<head>\n')
|
|
||||||
tlst.append('<title>' + meta_array['Title'] + '</title>\n')
|
|
||||||
tlst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
|
|
||||||
tlst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
|
|
||||||
if 'ASIN' in meta_array:
|
|
||||||
tlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
|
|
||||||
if 'GUID' in meta_array:
|
|
||||||
tlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
|
|
||||||
tlst.append('</head>\n')
|
|
||||||
tlst.append('<body>\n')
|
|
||||||
|
|
||||||
tlst.append('<h2>Table of Contents</h2>\n')
|
|
||||||
start = pageidnums[0]
|
|
||||||
if (raw):
|
|
||||||
startname = 'page%04d.svg' % start
|
|
||||||
else:
|
|
||||||
startname = 'page%04d.xhtml' % start
|
|
||||||
|
|
||||||
tlst.append('<h3><a href="' + startname + '">Start of Book</a></h3>\n')
|
|
||||||
# build up a table of contents for the svg xhtml output
|
|
||||||
tocentries = "".join(elst)
|
|
||||||
elst = None
|
|
||||||
toclst = tocentries.split('\n')
|
|
||||||
toclst.pop()
|
|
||||||
for entry in toclst:
|
|
||||||
print entry
|
|
||||||
title, pagenum = entry.split('|')
|
|
||||||
id = pageidnums[int(pagenum)]
|
|
||||||
if (raw):
|
|
||||||
fname = 'page%04d.svg' % id
|
|
||||||
else:
|
|
||||||
fname = 'page%04d.xhtml' % id
|
|
||||||
tlst.append('<h3><a href="'+ fname + '">' + title + '</a></h3>\n')
|
|
||||||
tlst.append('</body>\n')
|
|
||||||
tlst.append('</html>\n')
|
|
||||||
tochtml = "".join(tlst)
|
|
||||||
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
|
|
||||||
|
|
||||||
|
|
||||||
# now create index_svg.xhtml that points to all required files
|
|
||||||
slst = []
|
|
||||||
slst.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
||||||
slst.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
|
|
||||||
slst.append('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >')
|
|
||||||
slst.append('<head>\n')
|
|
||||||
slst.append('<title>' + meta_array['Title'] + '</title>\n')
|
|
||||||
slst.append('<meta name="Author" content="' + meta_array['Authors'] + '" />\n')
|
|
||||||
slst.append('<meta name="Title" content="' + meta_array['Title'] + '" />\n')
|
|
||||||
if 'ASIN' in meta_array:
|
|
||||||
slst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
|
|
||||||
if 'GUID' in meta_array:
|
|
||||||
slst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
|
|
||||||
slst.append('</head>\n')
|
|
||||||
slst.append('<body>\n')
|
|
||||||
|
|
||||||
print "Building svg images of each book page"
|
|
||||||
slst.append('<h2>List of Pages</h2>\n')
|
|
||||||
slst.append('<div>\n')
|
|
||||||
idlst = sorted(pageIDMap.keys())
|
|
||||||
numids = len(idlst)
|
|
||||||
cnt = len(idlst)
|
|
||||||
previd = None
|
|
||||||
for j in range(cnt):
|
|
||||||
pageid = idlst[j]
|
|
||||||
if j < cnt - 1:
|
|
||||||
nextid = idlst[j+1]
|
|
||||||
else:
|
|
||||||
nextid = None
|
|
||||||
print '.',
|
|
||||||
pagelst = pageIDMap[pageid]
|
|
||||||
flst = []
|
|
||||||
for page in pagelst:
|
|
||||||
flst.append(xmllst[page])
|
|
||||||
flat_svg = "".join(flst)
|
|
||||||
flst=None
|
|
||||||
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
|
|
||||||
if (raw) :
|
|
||||||
pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w')
|
|
||||||
slst.append('<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid))
|
|
||||||
else :
|
|
||||||
pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w')
|
|
||||||
slst.append('<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid))
|
|
||||||
previd = pageid
|
|
||||||
pfile.write(svgxml)
|
|
||||||
pfile.close()
|
|
||||||
counter += 1
|
|
||||||
slst.append('</div>\n')
|
|
||||||
slst.append('<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n')
|
|
||||||
slst.append('</body>\n</html>\n')
|
|
||||||
svgindex = "".join(slst)
|
|
||||||
slst = None
|
|
||||||
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
|
|
||||||
|
|
||||||
print " "
|
|
||||||
|
|
||||||
# build the opf file
|
|
||||||
opfname = os.path.join(bookDir, 'book.opf')
|
|
||||||
olst = []
|
|
||||||
olst.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
||||||
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
|
|
||||||
# adding metadata
|
|
||||||
olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
|
|
||||||
if 'GUID' in meta_array:
|
|
||||||
olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
|
|
||||||
if 'ASIN' in meta_array:
|
|
||||||
olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
|
|
||||||
if 'oASIN' in meta_array:
|
|
||||||
olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n')
|
|
||||||
olst.append(' <dc:title>' + meta_array['Title'] + '</dc:title>\n')
|
|
||||||
olst.append(' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n')
|
|
||||||
olst.append(' <dc:language>en</dc:language>\n')
|
|
||||||
olst.append(' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n')
|
|
||||||
if isCover:
|
|
||||||
olst.append(' <meta name="cover" content="bookcover"/>\n')
|
|
||||||
olst.append(' </metadata>\n')
|
|
||||||
olst.append('<manifest>\n')
|
|
||||||
olst.append(' <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n')
|
|
||||||
olst.append(' <item id="stylesheet" href="style.css" media-type="text/css"/>\n')
|
|
||||||
# adding image files to manifest
|
|
||||||
filenames = os.listdir(imgDir)
|
|
||||||
filenames = sorted(filenames)
|
|
||||||
for filename in filenames:
|
|
||||||
imgname, imgext = os.path.splitext(filename)
|
|
||||||
if imgext == '.jpg':
|
|
||||||
imgext = 'jpeg'
|
|
||||||
if imgext == '.svg':
|
|
||||||
imgext = 'svg+xml'
|
|
||||||
olst.append(' <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n')
|
|
||||||
if isCover:
|
|
||||||
olst.append(' <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n')
|
|
||||||
olst.append('</manifest>\n')
|
|
||||||
# adding spine
|
|
||||||
olst.append('<spine>\n <itemref idref="book" />\n</spine>\n')
|
|
||||||
if isCover:
|
|
||||||
olst.append(' <guide>\n')
|
|
||||||
olst.append(' <reference href="cover.jpg" type="cover" title="Cover"/>\n')
|
|
||||||
olst.append(' </guide>\n')
|
|
||||||
olst.append('</package>\n')
|
|
||||||
opfstr = "".join(olst)
|
|
||||||
olst = None
|
|
||||||
file(opfname, 'wb').write(opfstr)
|
|
||||||
|
|
||||||
print 'Processing Complete'
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print "genbook.py generates a book from the extract Topaz Files"
|
|
||||||
print "Usage:"
|
|
||||||
print " genbook.py [-r] [-h [--fixed-image] <bookDir> "
|
|
||||||
print " "
|
|
||||||
print "Options:"
|
|
||||||
print " -h : help - print this usage message"
|
|
||||||
print " -r : generate raw svg files (not wrapped in xhtml)"
|
|
||||||
print " --fixed-image : genearate any Fixed Area as an svg image in the html"
|
|
||||||
print " "
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
bookDir = ''
|
|
||||||
if len(argv) == 0:
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
|
||||||
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
print str(err)
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if len(opts) == 0 and len(args) == 0 :
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
raw = 0
|
|
||||||
fixedimage = True
|
|
||||||
for o, a in opts:
|
|
||||||
if o =="-h":
|
|
||||||
usage()
|
|
||||||
return 0
|
|
||||||
if o =="-r":
|
|
||||||
raw = 1
|
|
||||||
if o =="--fixed-image":
|
|
||||||
fixedimage = True
|
|
||||||
|
|
||||||
bookDir = args[0]
|
|
||||||
|
|
||||||
rv = generateBook(bookDir, raw, fixedimage)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(''))
|
|
@ -1,336 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# ignoblekey.py
|
|
||||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
|
||||||
|
|
||||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
|
||||||
# <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
# Revision history:
|
|
||||||
# 1.0 - Initial release
|
|
||||||
# 1.1 - remove duplicates and return last key as single key
|
|
||||||
|
|
||||||
"""
|
|
||||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
|
||||||
"""
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = "1.1"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import hashlib
|
|
||||||
import getopt
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre.constants import iswindows, isosx
|
|
||||||
except:
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
|
||||||
# as a list of Unicode strings and encode them as utf-8
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(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"]
|
|
||||||
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]
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Locate all of the nookStudy/nook for PC/Mac log file and return as list
|
|
||||||
def getNookLogFiles():
|
|
||||||
logFiles = []
|
|
||||||
found = False
|
|
||||||
if iswindows:
|
|
||||||
import _winreg as winreg
|
|
||||||
|
|
||||||
# some 64 bit machines do not have the proper registry key for some reason
|
|
||||||
# or the python interface to the 32 vs 64 bit registry is broken
|
|
||||||
paths = set()
|
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
|
||||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
if 'USERPROFILE' in os.environ.keys():
|
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
|
||||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
# User Shell Folders show take precedent over Shell Folders if present
|
|
||||||
try:
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
except WindowsError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'AppData')[0]
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
except WindowsError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
except WindowsError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'AppData')[0]
|
|
||||||
if os.path.isdir(path):
|
|
||||||
paths.add(path)
|
|
||||||
except WindowsError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for path in paths:
|
|
||||||
# look for nookStudy log file
|
|
||||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
|
||||||
if os.path.isfile(logpath):
|
|
||||||
found = True
|
|
||||||
print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
|
|
||||||
logFiles.append(logpath)
|
|
||||||
else:
|
|
||||||
home = os.getenv('HOME')
|
|
||||||
# check for BNClientLog.txt in various locations
|
|
||||||
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
|
|
||||||
if os.path.isfile(testpath):
|
|
||||||
logFiles.append(testpath)
|
|
||||||
print('Found nookStudy log file: ' + testpath)
|
|
||||||
found = True
|
|
||||||
testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
|
|
||||||
if os.path.isfile(testpath):
|
|
||||||
logFiles.append(testpath)
|
|
||||||
print('Found nookStudy log file: ' + testpath)
|
|
||||||
found = True
|
|
||||||
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
|
|
||||||
if os.path.isfile(testpath):
|
|
||||||
logFiles.append(testpath)
|
|
||||||
print('Found nookStudy log file: ' + testpath)
|
|
||||||
found = True
|
|
||||||
testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
|
|
||||||
if os.path.isfile(testpath):
|
|
||||||
logFiles.append(testpath)
|
|
||||||
print('Found nookStudy log file: ' + testpath)
|
|
||||||
found = True
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
print('No nook Study log files have been found.')
|
|
||||||
return logFiles
|
|
||||||
|
|
||||||
|
|
||||||
# Extract CCHash key(s) from log file
|
|
||||||
def getKeysFromLog(kLogFile):
|
|
||||||
keys = []
|
|
||||||
regex = re.compile("ccHash: \"(.{28})\"");
|
|
||||||
for line in open(kLogFile):
|
|
||||||
for m in regex.findall(line):
|
|
||||||
keys.append(m)
|
|
||||||
return keys
|
|
||||||
|
|
||||||
# interface for calibre plugin
|
|
||||||
def nookkeys(files = []):
|
|
||||||
keys = []
|
|
||||||
if files == []:
|
|
||||||
files = getNookLogFiles()
|
|
||||||
for file in files:
|
|
||||||
fileKeys = getKeysFromLog(file)
|
|
||||||
if fileKeys:
|
|
||||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
|
||||||
keys.extend(fileKeys)
|
|
||||||
return list(set(keys))
|
|
||||||
|
|
||||||
# interface for Python DeDRM
|
|
||||||
# returns single key or multiple keys, depending on path or file passed in
|
|
||||||
def getkey(outpath, files=[]):
|
|
||||||
keys = nookkeys(files)
|
|
||||||
if len(keys) > 0:
|
|
||||||
if not os.path.isdir(outpath):
|
|
||||||
outfile = outpath
|
|
||||||
with file(outfile, 'w') as keyfileout:
|
|
||||||
keyfileout.write(keys[-1])
|
|
||||||
print u"Saved a key to {0}".format(outfile)
|
|
||||||
else:
|
|
||||||
keycount = 0
|
|
||||||
for key in keys:
|
|
||||||
while True:
|
|
||||||
keycount += 1
|
|
||||||
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:
|
|
||||||
keyfileout.write(key)
|
|
||||||
print u"Saved a key to {0}".format(outfile)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def usage(progname):
|
|
||||||
print u"Finds the nook Study 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 <logFile>] [<outpath>]".format(progname)
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
argv=unicode_argv()
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
print u"{0} v{1}\nCopyright © 2015 Apprentice Alf".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])
|
|
||||||
usage(progname)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
files = []
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "-h":
|
|
||||||
usage(progname)
|
|
||||||
sys.exit(0)
|
|
||||||
if o == "-k":
|
|
||||||
files = [a]
|
|
||||||
|
|
||||||
if len(args) > 1:
|
|
||||||
usage(progname)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if len(args) == 1:
|
|
||||||
# save to the specified file or directory
|
|
||||||
outpath = args[0]
|
|
||||||
if not os.path.isabs(outpath):
|
|
||||||
outpath = os.path.abspath(outpath)
|
|
||||||
else:
|
|
||||||
# save to the same directory as the script
|
|
||||||
outpath = os.path.dirname(argv[0])
|
|
||||||
|
|
||||||
# make sure the outpath is the
|
|
||||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
|
||||||
|
|
||||||
if not getkey(outpath, files):
|
|
||||||
print u"Could not retrieve nook Study key."
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
|
||||||
try:
|
|
||||||
import Tkinter
|
|
||||||
import Tkconstants
|
|
||||||
import tkMessageBox
|
|
||||||
import traceback
|
|
||||||
except:
|
|
||||||
return cli_main()
|
|
||||||
|
|
||||||
class ExceptionDialog(Tkinter.Frame):
|
|
||||||
def __init__(self, root, text):
|
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
|
||||||
label = Tkinter.Label(self, text=u"Unexpected error:",
|
|
||||||
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
|
||||||
label.pack(fill=Tkconstants.X, expand=0)
|
|
||||||
self.text = Tkinter.Text(self)
|
|
||||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
|
||||||
|
|
||||||
self.text.insert(Tkconstants.END, text)
|
|
||||||
|
|
||||||
|
|
||||||
argv=unicode_argv()
|
|
||||||
root = Tkinter.Tk()
|
|
||||||
root.withdraw()
|
|
||||||
progpath, progname = os.path.split(argv[0])
|
|
||||||
success = False
|
|
||||||
try:
|
|
||||||
keys = nookkeys()
|
|
||||||
keycount = 0
|
|
||||||
for key in keys:
|
|
||||||
print key
|
|
||||||
while True:
|
|
||||||
keycount += 1
|
|
||||||
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
|
||||||
if not os.path.exists(outfile):
|
|
||||||
break
|
|
||||||
|
|
||||||
with file(outfile, 'w') as keyfileout:
|
|
||||||
keyfileout.write(key)
|
|
||||||
success = True
|
|
||||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
|
||||||
except DrmException, e:
|
|
||||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
|
||||||
except Exception:
|
|
||||||
root.wm_state('normal')
|
|
||||||
root.title(progname)
|
|
||||||
text = traceback.format_exc()
|
|
||||||
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
|
||||||
root.mainloop()
|
|
||||||
if not success:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
sys.exit(cli_main())
|
|
||||||
sys.exit(gui_main())
|
|
@ -1,258 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# ignoblekeyfetch.pyw, version 1.1
|
|
||||||
# Copyright © 2015 Apprentice Harper
|
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
|
||||||
# <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
# Based on discoveries by "Nobody You Know"
|
|
||||||
# Code partly based on ignoblekeygen.py by several people.
|
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python.
|
|
||||||
# We recommend ActiveState Python 2.7.X for Windows from
|
|
||||||
# http://www.activestate.com/activepython/downloads.
|
|
||||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
|
||||||
#
|
|
||||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
|
||||||
# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
|
|
||||||
# it when it has been associated with PythonLauncher.
|
|
||||||
|
|
||||||
# Revision history:
|
|
||||||
# 1.0 - Initial version
|
|
||||||
# 1.1 - Try second URL if first one fails
|
|
||||||
|
|
||||||
"""
|
|
||||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
|
||||||
"""
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = "1.1"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre.constants import iswindows, isosx
|
|
||||||
except:
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
|
||||||
# as a list of Unicode strings and encode them as utf-8
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(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"]
|
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def fetch_key(email, password):
|
|
||||||
# change email and password to utf-8 if unicode
|
|
||||||
if type(email)==unicode:
|
|
||||||
email = email.encode('utf-8')
|
|
||||||
if type(password)==unicode:
|
|
||||||
password = password.encode('utf-8')
|
|
||||||
|
|
||||||
import random
|
|
||||||
random = "%030x" % random.randrange(16**30)
|
|
||||||
|
|
||||||
import urllib, urllib2, re
|
|
||||||
|
|
||||||
# try the URL from nook for PC
|
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
|
||||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
|
||||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
|
||||||
#print fetch_url
|
|
||||||
|
|
||||||
found = ''
|
|
||||||
try:
|
|
||||||
req = urllib2.Request(fetch_url)
|
|
||||||
response = urllib2.urlopen(req)
|
|
||||||
the_page = response.read()
|
|
||||||
#print the_page
|
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
|
||||||
except:
|
|
||||||
found = ''
|
|
||||||
if len(found)!=28:
|
|
||||||
# try the URL from android devices
|
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
|
||||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
|
||||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
|
||||||
#print fetch_url
|
|
||||||
|
|
||||||
found = ''
|
|
||||||
try:
|
|
||||||
req = urllib2.Request(fetch_url)
|
|
||||||
response = urllib2.urlopen(req)
|
|
||||||
the_page = response.read()
|
|
||||||
#print the_page
|
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
|
||||||
except:
|
|
||||||
found = ''
|
|
||||||
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
argv=unicode_argv()
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
if len(argv) != 4:
|
|
||||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
|
||||||
return 1
|
|
||||||
email, password, keypath = argv[1:]
|
|
||||||
userkey = fetch_key(email, password)
|
|
||||||
if len(userkey) == 28:
|
|
||||||
open(keypath,'wb').write(userkey)
|
|
||||||
return 0
|
|
||||||
print u"Failed to fetch key."
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
|
||||||
try:
|
|
||||||
import Tkinter
|
|
||||||
import tkFileDialog
|
|
||||||
import Tkconstants
|
|
||||||
import tkMessageBox
|
|
||||||
import traceback
|
|
||||||
except:
|
|
||||||
return cli_main()
|
|
||||||
|
|
||||||
class DecryptionDialog(Tkinter.Frame):
|
|
||||||
def __init__(self, root):
|
|
||||||
Tkinter.Frame.__init__(self, root, border=5)
|
|
||||||
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
|
||||||
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)
|
|
||||||
Tkinter.Label(body, text=u"Account email address").grid(row=0)
|
|
||||||
self.name = Tkinter.Entry(body, width=40)
|
|
||||||
self.name.grid(row=0, column=1, sticky=sticky)
|
|
||||||
Tkinter.Label(body, text=u"Account password").grid(row=1)
|
|
||||||
self.ccn = Tkinter.Entry(body, width=40)
|
|
||||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
|
||||||
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
|
||||||
self.keypath = Tkinter.Entry(body, width=40)
|
|
||||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
|
||||||
self.keypath.insert(2, u"bnepubkey.b64")
|
|
||||||
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
|
||||||
button.grid(row=2, column=2)
|
|
||||||
buttons = Tkinter.Frame(self)
|
|
||||||
buttons.pack()
|
|
||||||
botton = Tkinter.Button(
|
|
||||||
buttons, text=u"Fetch", width=10, command=self.generate)
|
|
||||||
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=Tkconstants.RIGHT)
|
|
||||||
|
|
||||||
def get_keypath(self):
|
|
||||||
keypath = tkFileDialog.asksaveasfilename(
|
|
||||||
parent=None, title=u"Select B&N ePub key file to produce",
|
|
||||||
defaultextension=u".b64",
|
|
||||||
filetypes=[('base64-encoded files', '.b64'),
|
|
||||||
('All Files', '.*')])
|
|
||||||
if keypath:
|
|
||||||
keypath = os.path.normpath(keypath)
|
|
||||||
self.keypath.delete(0, Tkconstants.END)
|
|
||||||
self.keypath.insert(0, keypath)
|
|
||||||
return
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
email = self.name.get()
|
|
||||||
password = self.ccn.get()
|
|
||||||
keypath = self.keypath.get()
|
|
||||||
if not email:
|
|
||||||
self.status['text'] = u"Email address not given"
|
|
||||||
return
|
|
||||||
if not password:
|
|
||||||
self.status['text'] = u"Account password not given"
|
|
||||||
return
|
|
||||||
if not keypath:
|
|
||||||
self.status['text'] = u"Output keyfile path not set"
|
|
||||||
return
|
|
||||||
self.status['text'] = u"Fetching..."
|
|
||||||
try:
|
|
||||||
userkey = fetch_key(email, password)
|
|
||||||
except Exception, e:
|
|
||||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
|
||||||
return
|
|
||||||
if len(userkey) == 28:
|
|
||||||
open(keypath,'wb').write(userkey)
|
|
||||||
self.status['text'] = u"Keyfile fetched successfully"
|
|
||||||
else:
|
|
||||||
self.status['text'] = u"Keyfile fetch failed."
|
|
||||||
|
|
||||||
root = Tkinter.Tk()
|
|
||||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
|
||||||
root.resizable(True, False)
|
|
||||||
root.minsize(300, 0)
|
|
||||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
|
||||||
root.mainloop()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
sys.exit(cli_main())
|
|
||||||
sys.exit(gui_main())
|
|
File diff suppressed because it is too large
Load Diff
@ -1,985 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
|
|
||||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import struct
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Util.py3compat import bchr, bord
|
|
||||||
|
|
||||||
try:
|
|
||||||
# lzma library from calibre 2.35.0 or later
|
|
||||||
import lzma.lzma1 as calibre_lzma
|
|
||||||
except ImportError:
|
|
||||||
calibre_lzma = None
|
|
||||||
try:
|
|
||||||
import lzma
|
|
||||||
except ImportError:
|
|
||||||
# Need pip backports.lzma on Python <3.3
|
|
||||||
try:
|
|
||||||
from backports import lzma
|
|
||||||
except ImportError:
|
|
||||||
# Windows-friendly choice: pylzma wheels
|
|
||||||
import pylzma as lzma
|
|
||||||
|
|
||||||
|
|
||||||
TID_NULL = 0
|
|
||||||
TID_BOOLEAN = 1
|
|
||||||
TID_POSINT = 2
|
|
||||||
TID_NEGINT = 3
|
|
||||||
TID_FLOAT = 4
|
|
||||||
TID_DECIMAL = 5
|
|
||||||
TID_TIMESTAMP = 6
|
|
||||||
TID_SYMBOL = 7
|
|
||||||
TID_STRING = 8
|
|
||||||
TID_CLOB = 9
|
|
||||||
TID_BLOB = 0xA
|
|
||||||
TID_LIST = 0xB
|
|
||||||
TID_SEXP = 0xC
|
|
||||||
TID_STRUCT = 0xD
|
|
||||||
TID_TYPEDECL = 0xE
|
|
||||||
TID_UNUSED = 0xF
|
|
||||||
|
|
||||||
|
|
||||||
SID_UNKNOWN = -1
|
|
||||||
SID_ION = 1
|
|
||||||
SID_ION_1_0 = 2
|
|
||||||
SID_ION_SYMBOL_TABLE = 3
|
|
||||||
SID_NAME = 4
|
|
||||||
SID_VERSION = 5
|
|
||||||
SID_IMPORTS = 6
|
|
||||||
SID_SYMBOLS = 7
|
|
||||||
SID_MAX_ID = 8
|
|
||||||
SID_ION_SHARED_SYMBOL_TABLE = 9
|
|
||||||
SID_ION_1_0_MAX = 10
|
|
||||||
|
|
||||||
|
|
||||||
LEN_IS_VAR_LEN = 0xE
|
|
||||||
LEN_IS_NULL = 0xF
|
|
||||||
|
|
||||||
|
|
||||||
VERSION_MARKER = b"\x01\x00\xEA"
|
|
||||||
|
|
||||||
|
|
||||||
# asserts must always raise exceptions for proper functioning
|
|
||||||
def _assert(test, msg="Exception"):
|
|
||||||
if not test:
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class SystemSymbols(object):
|
|
||||||
ION = '$ion'
|
|
||||||
ION_1_0 = '$ion_1_0'
|
|
||||||
ION_SYMBOL_TABLE = '$ion_symbol_table'
|
|
||||||
NAME = 'name'
|
|
||||||
VERSION = 'version'
|
|
||||||
IMPORTS = 'imports'
|
|
||||||
SYMBOLS = 'symbols'
|
|
||||||
MAX_ID = 'max_id'
|
|
||||||
ION_SHARED_SYMBOL_TABLE = '$ion_shared_symbol_table'
|
|
||||||
|
|
||||||
|
|
||||||
class IonCatalogItem(object):
|
|
||||||
name = ""
|
|
||||||
version = 0
|
|
||||||
symnames = []
|
|
||||||
|
|
||||||
def __init__(self, name, version, symnames):
|
|
||||||
self.name = name
|
|
||||||
self.version = version
|
|
||||||
self.symnames = symnames
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolToken(object):
|
|
||||||
text = ""
|
|
||||||
sid = 0
|
|
||||||
|
|
||||||
def __init__(self, text, sid):
|
|
||||||
if text == "" and sid == 0:
|
|
||||||
raise ValueError("Symbol token must have Text or SID")
|
|
||||||
|
|
||||||
self.text = text
|
|
||||||
self.sid = sid
|
|
||||||
|
|
||||||
|
|
||||||
class SymbolTable(object):
|
|
||||||
table = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.table = [None] * SID_ION_1_0_MAX
|
|
||||||
self.table[SID_ION] = SystemSymbols.ION
|
|
||||||
self.table[SID_ION_1_0] = SystemSymbols.ION_1_0
|
|
||||||
self.table[SID_ION_SYMBOL_TABLE] = SystemSymbols.ION_SYMBOL_TABLE
|
|
||||||
self.table[SID_NAME] = SystemSymbols.NAME
|
|
||||||
self.table[SID_VERSION] = SystemSymbols.VERSION
|
|
||||||
self.table[SID_IMPORTS] = SystemSymbols.IMPORTS
|
|
||||||
self.table[SID_SYMBOLS] = SystemSymbols.SYMBOLS
|
|
||||||
self.table[SID_MAX_ID] = SystemSymbols.MAX_ID
|
|
||||||
self.table[SID_ION_SHARED_SYMBOL_TABLE] = SystemSymbols.ION_SHARED_SYMBOL_TABLE
|
|
||||||
|
|
||||||
def findbyid(self, sid):
|
|
||||||
if sid < 1:
|
|
||||||
raise ValueError("Invalid symbol id")
|
|
||||||
|
|
||||||
if sid < len(self.table):
|
|
||||||
return self.table[sid]
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def import_(self, table, maxid):
|
|
||||||
for i in range(maxid):
|
|
||||||
self.table.append(table.symnames[i])
|
|
||||||
|
|
||||||
def importunknown(self, name, maxid):
|
|
||||||
for i in range(maxid):
|
|
||||||
self.table.append("%s#%d" % (name, i + 1))
|
|
||||||
|
|
||||||
|
|
||||||
class ParserState:
|
|
||||||
Invalid,BeforeField,BeforeTID,BeforeValue,AfterValue,EOF = 1,2,3,4,5,6
|
|
||||||
|
|
||||||
ContainerRec = collections.namedtuple("ContainerRec", "nextpos, tid, remaining")
|
|
||||||
|
|
||||||
|
|
||||||
class BinaryIonParser(object):
|
|
||||||
eof = False
|
|
||||||
state = None
|
|
||||||
localremaining = 0
|
|
||||||
needhasnext = False
|
|
||||||
isinstruct = False
|
|
||||||
valuetid = 0
|
|
||||||
valuefieldid = 0
|
|
||||||
parenttid = 0
|
|
||||||
valuelen = 0
|
|
||||||
valueisnull = False
|
|
||||||
valueistrue = False
|
|
||||||
value = None
|
|
||||||
didimports = False
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.annotations = []
|
|
||||||
self.catalog = []
|
|
||||||
|
|
||||||
self.stream = stream
|
|
||||||
self.initpos = stream.tell()
|
|
||||||
self.reset()
|
|
||||||
self.symbols = SymbolTable()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.state = ParserState.BeforeTID
|
|
||||||
self.needhasnext = True
|
|
||||||
self.localremaining = -1
|
|
||||||
self.eof = False
|
|
||||||
self.isinstruct = False
|
|
||||||
self.containerstack = []
|
|
||||||
self.stream.seek(self.initpos)
|
|
||||||
|
|
||||||
def addtocatalog(self, name, version, symbols):
|
|
||||||
self.catalog.append(IonCatalogItem(name, version, symbols))
|
|
||||||
|
|
||||||
def hasnext(self):
|
|
||||||
while self.needhasnext and not self.eof:
|
|
||||||
self.hasnextraw()
|
|
||||||
if len(self.containerstack) == 0 and not self.valueisnull:
|
|
||||||
if self.valuetid == TID_SYMBOL:
|
|
||||||
if self.value == SID_ION_1_0:
|
|
||||||
self.needhasnext = True
|
|
||||||
elif self.valuetid == TID_STRUCT:
|
|
||||||
for a in self.annotations:
|
|
||||||
if a == SID_ION_SYMBOL_TABLE:
|
|
||||||
self.parsesymboltable()
|
|
||||||
self.needhasnext = True
|
|
||||||
break
|
|
||||||
return not self.eof
|
|
||||||
|
|
||||||
def hasnextraw(self):
|
|
||||||
self.clearvalue()
|
|
||||||
while self.valuetid == -1 and not self.eof:
|
|
||||||
self.needhasnext = False
|
|
||||||
if self.state == ParserState.BeforeField:
|
|
||||||
_assert(self.valuefieldid == SID_UNKNOWN)
|
|
||||||
|
|
||||||
self.valuefieldid = self.readfieldid()
|
|
||||||
if self.valuefieldid != SID_UNKNOWN:
|
|
||||||
self.state = ParserState.BeforeTID
|
|
||||||
else:
|
|
||||||
self.eof = True
|
|
||||||
|
|
||||||
elif self.state == ParserState.BeforeTID:
|
|
||||||
self.state = ParserState.BeforeValue
|
|
||||||
self.valuetid = self.readtypeid()
|
|
||||||
if self.valuetid == -1:
|
|
||||||
self.state = ParserState.EOF
|
|
||||||
self.eof = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.valuetid == TID_TYPEDECL:
|
|
||||||
if self.valuelen == 0:
|
|
||||||
self.checkversionmarker()
|
|
||||||
else:
|
|
||||||
self.loadannotations()
|
|
||||||
|
|
||||||
elif self.state == ParserState.BeforeValue:
|
|
||||||
self.skip(self.valuelen)
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
|
|
||||||
elif self.state == ParserState.AfterValue:
|
|
||||||
if self.isinstruct:
|
|
||||||
self.state = ParserState.BeforeField
|
|
||||||
else:
|
|
||||||
self.state = ParserState.BeforeTID
|
|
||||||
|
|
||||||
else:
|
|
||||||
_assert(self.state == ParserState.EOF)
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
if self.hasnext():
|
|
||||||
self.needhasnext = True
|
|
||||||
return self.valuetid
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def push(self, typeid, nextposition, nextremaining):
|
|
||||||
self.containerstack.append(ContainerRec(nextpos=nextposition, tid=typeid, remaining=nextremaining))
|
|
||||||
|
|
||||||
def stepin(self):
|
|
||||||
_assert(self.valuetid in [TID_STRUCT, TID_LIST, TID_SEXP] and not self.eof,
|
|
||||||
"valuetid=%s eof=%s" % (self.valuetid, self.eof))
|
|
||||||
_assert((not self.valueisnull or self.state == ParserState.AfterValue) and
|
|
||||||
(self.valueisnull or self.state == ParserState.BeforeValue))
|
|
||||||
|
|
||||||
nextrem = self.localremaining
|
|
||||||
if nextrem != -1:
|
|
||||||
nextrem -= self.valuelen
|
|
||||||
if nextrem < 0:
|
|
||||||
nextrem = 0
|
|
||||||
self.push(self.parenttid, self.stream.tell() + self.valuelen, nextrem)
|
|
||||||
|
|
||||||
self.isinstruct = (self.valuetid == TID_STRUCT)
|
|
||||||
if self.isinstruct:
|
|
||||||
self.state = ParserState.BeforeField
|
|
||||||
else:
|
|
||||||
self.state = ParserState.BeforeTID
|
|
||||||
|
|
||||||
self.localremaining = self.valuelen
|
|
||||||
self.parenttid = self.valuetid
|
|
||||||
self.clearvalue()
|
|
||||||
self.needhasnext = True
|
|
||||||
|
|
||||||
def stepout(self):
|
|
||||||
rec = self.containerstack.pop()
|
|
||||||
|
|
||||||
self.eof = False
|
|
||||||
self.parenttid = rec.tid
|
|
||||||
if self.parenttid == TID_STRUCT:
|
|
||||||
self.isinstruct = True
|
|
||||||
self.state = ParserState.BeforeField
|
|
||||||
else:
|
|
||||||
self.isinstruct = False
|
|
||||||
self.state = ParserState.BeforeTID
|
|
||||||
self.needhasnext = True
|
|
||||||
|
|
||||||
self.clearvalue()
|
|
||||||
curpos = self.stream.tell()
|
|
||||||
if rec.nextpos > curpos:
|
|
||||||
self.skip(rec.nextpos - curpos)
|
|
||||||
else:
|
|
||||||
_assert(rec.nextpos == curpos)
|
|
||||||
|
|
||||||
self.localremaining = rec.remaining
|
|
||||||
|
|
||||||
def read(self, count=1):
|
|
||||||
if self.localremaining != -1:
|
|
||||||
self.localremaining -= count
|
|
||||||
_assert(self.localremaining >= 0)
|
|
||||||
|
|
||||||
result = self.stream.read(count)
|
|
||||||
if len(result) == 0:
|
|
||||||
raise EOFError()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def readfieldid(self):
|
|
||||||
if self.localremaining != -1 and self.localremaining < 1:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.readvaruint()
|
|
||||||
except EOFError:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def readtypeid(self):
|
|
||||||
if self.localremaining != -1:
|
|
||||||
if self.localremaining < 1:
|
|
||||||
return -1
|
|
||||||
self.localremaining -= 1
|
|
||||||
|
|
||||||
b = self.stream.read(1)
|
|
||||||
if len(b) < 1:
|
|
||||||
return -1
|
|
||||||
b = bord(b)
|
|
||||||
result = b >> 4
|
|
||||||
ln = b & 0xF
|
|
||||||
|
|
||||||
if ln == LEN_IS_VAR_LEN:
|
|
||||||
ln = self.readvaruint()
|
|
||||||
elif ln == LEN_IS_NULL:
|
|
||||||
ln = 0
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
elif result == TID_NULL:
|
|
||||||
# Must have LEN_IS_NULL
|
|
||||||
_assert(False)
|
|
||||||
elif result == TID_BOOLEAN:
|
|
||||||
_assert(ln <= 1)
|
|
||||||
self.valueistrue = (ln == 1)
|
|
||||||
ln = 0
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
elif result == TID_STRUCT:
|
|
||||||
if ln == 1:
|
|
||||||
ln = self.readvaruint()
|
|
||||||
|
|
||||||
self.valuelen = ln
|
|
||||||
return result
|
|
||||||
|
|
||||||
def readvarint(self):
|
|
||||||
b = bord(self.read())
|
|
||||||
negative = ((b & 0x40) != 0)
|
|
||||||
result = (b & 0x3F)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while (b & 0x80) == 0 and i < 4:
|
|
||||||
b = bord(self.read())
|
|
||||||
result = (result << 7) | (b & 0x7F)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
|
||||||
|
|
||||||
if negative:
|
|
||||||
return -result
|
|
||||||
return result
|
|
||||||
|
|
||||||
def readvaruint(self):
|
|
||||||
b = bord(self.read())
|
|
||||||
result = (b & 0x7F)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while (b & 0x80) == 0 and i < 4:
|
|
||||||
b = bord(self.read())
|
|
||||||
result = (result << 7) | (b & 0x7F)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
_assert(i < 4 or (b & 0x80) != 0, "int overflow")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def readdecimal(self):
|
|
||||||
if self.valuelen == 0:
|
|
||||||
return 0.
|
|
||||||
|
|
||||||
rem = self.localremaining - self.valuelen
|
|
||||||
self.localremaining = self.valuelen
|
|
||||||
exponent = self.readvarint()
|
|
||||||
|
|
||||||
_assert(self.localremaining > 0, "Only exponent in ReadDecimal")
|
|
||||||
_assert(self.localremaining <= 8, "Decimal overflow")
|
|
||||||
|
|
||||||
signed = False
|
|
||||||
b = [bord(x) for x in self.read(self.localremaining)]
|
|
||||||
if (b[0] & 0x80) != 0:
|
|
||||||
b[0] = b[0] & 0x7F
|
|
||||||
signed = True
|
|
||||||
|
|
||||||
# Convert variably sized network order integer into 64-bit little endian
|
|
||||||
j = 0
|
|
||||||
vb = [0] * 8
|
|
||||||
for i in range(len(b), -1, -1):
|
|
||||||
vb[i] = b[j]
|
|
||||||
j += 1
|
|
||||||
|
|
||||||
v = struct.unpack("<Q", b"".join(bchr(x) for x in vb))[0]
|
|
||||||
|
|
||||||
result = v * (10 ** exponent)
|
|
||||||
if signed:
|
|
||||||
result = -result
|
|
||||||
|
|
||||||
self.localremaining = rem
|
|
||||||
return result
|
|
||||||
|
|
||||||
def skip(self, count):
|
|
||||||
if self.localremaining != -1:
|
|
||||||
self.localremaining -= count
|
|
||||||
if self.localremaining < 0:
|
|
||||||
raise EOFError()
|
|
||||||
|
|
||||||
self.stream.seek(count, os.SEEK_CUR)
|
|
||||||
|
|
||||||
def parsesymboltable(self):
|
|
||||||
self.next() # shouldn't do anything?
|
|
||||||
|
|
||||||
_assert(self.valuetid == TID_STRUCT)
|
|
||||||
|
|
||||||
if self.didimports:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stepin()
|
|
||||||
|
|
||||||
fieldtype = self.next()
|
|
||||||
while fieldtype != -1:
|
|
||||||
if not self.valueisnull:
|
|
||||||
_assert(self.valuefieldid == SID_IMPORTS, "Unsupported symbol table field id")
|
|
||||||
|
|
||||||
if fieldtype == TID_LIST:
|
|
||||||
self.gatherimports()
|
|
||||||
|
|
||||||
fieldtype = self.next()
|
|
||||||
|
|
||||||
self.stepout()
|
|
||||||
self.didimports = True
|
|
||||||
|
|
||||||
def gatherimports(self):
|
|
||||||
self.stepin()
|
|
||||||
|
|
||||||
t = self.next()
|
|
||||||
while t != -1:
|
|
||||||
if not self.valueisnull and t == TID_STRUCT:
|
|
||||||
self.readimport()
|
|
||||||
|
|
||||||
t = self.next()
|
|
||||||
|
|
||||||
self.stepout()
|
|
||||||
|
|
||||||
def readimport(self):
|
|
||||||
version = -1
|
|
||||||
maxid = -1
|
|
||||||
name = ""
|
|
||||||
|
|
||||||
self.stepin()
|
|
||||||
|
|
||||||
t = self.next()
|
|
||||||
while t != -1:
|
|
||||||
if not self.valueisnull and self.valuefieldid != SID_UNKNOWN:
|
|
||||||
if self.valuefieldid == SID_NAME:
|
|
||||||
name = self.stringvalue()
|
|
||||||
elif self.valuefieldid == SID_VERSION:
|
|
||||||
version = self.intvalue()
|
|
||||||
elif self.valuefieldid == SID_MAX_ID:
|
|
||||||
maxid = self.intvalue()
|
|
||||||
|
|
||||||
t = self.next()
|
|
||||||
|
|
||||||
self.stepout()
|
|
||||||
|
|
||||||
if name == "" or name == SystemSymbols.ION:
|
|
||||||
return
|
|
||||||
|
|
||||||
if version < 1:
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
table = self.findcatalogitem(name)
|
|
||||||
if maxid < 0:
|
|
||||||
_assert(table is not None and version == table.version, "Import %s lacks maxid" % name)
|
|
||||||
maxid = len(table.symnames)
|
|
||||||
|
|
||||||
if table is not None:
|
|
||||||
self.symbols.import_(table, min(maxid, len(table.symnames)))
|
|
||||||
else:
|
|
||||||
self.symbols.importunknown(name, maxid)
|
|
||||||
|
|
||||||
def intvalue(self):
|
|
||||||
_assert(self.valuetid in [TID_POSINT, TID_NEGINT], "Not an int")
|
|
||||||
|
|
||||||
self.preparevalue()
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def stringvalue(self):
|
|
||||||
_assert(self.valuetid == TID_STRING, "Not a string")
|
|
||||||
|
|
||||||
if self.valueisnull:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
self.preparevalue()
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def symbolvalue(self):
|
|
||||||
_assert(self.valuetid == TID_SYMBOL, "Not a symbol")
|
|
||||||
|
|
||||||
self.preparevalue()
|
|
||||||
result = self.symbols.findbyid(self.value)
|
|
||||||
if result == "":
|
|
||||||
result = "SYMBOL#%d" % self.value
|
|
||||||
return result
|
|
||||||
|
|
||||||
def lobvalue(self):
|
|
||||||
_assert(self.valuetid in [TID_CLOB, TID_BLOB], "Not a LOB type: %s" % self.getfieldname())
|
|
||||||
|
|
||||||
if self.valueisnull:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result = self.read(self.valuelen)
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
return result
|
|
||||||
|
|
||||||
def decimalvalue(self):
|
|
||||||
_assert(self.valuetid == TID_DECIMAL, "Not a decimal")
|
|
||||||
|
|
||||||
self.preparevalue()
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def preparevalue(self):
|
|
||||||
if self.value is None:
|
|
||||||
self.loadscalarvalue()
|
|
||||||
|
|
||||||
def loadscalarvalue(self):
|
|
||||||
if self.valuetid not in [TID_NULL, TID_BOOLEAN, TID_POSINT, TID_NEGINT,
|
|
||||||
TID_FLOAT, TID_DECIMAL, TID_TIMESTAMP,
|
|
||||||
TID_SYMBOL, TID_STRING]:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.valueisnull:
|
|
||||||
self.value = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.valuetid == TID_STRING:
|
|
||||||
self.value = self.read(self.valuelen).decode("UTF-8")
|
|
||||||
|
|
||||||
elif self.valuetid in (TID_POSINT, TID_NEGINT, TID_SYMBOL):
|
|
||||||
if self.valuelen == 0:
|
|
||||||
self.value = 0
|
|
||||||
else:
|
|
||||||
_assert(self.valuelen <= 4, "int too long: %d" % self.valuelen)
|
|
||||||
v = 0
|
|
||||||
for i in range(self.valuelen - 1, -1, -1):
|
|
||||||
v = (v | (bord(self.read()) << (i * 8)))
|
|
||||||
|
|
||||||
if self.valuetid == TID_NEGINT:
|
|
||||||
self.value = -v
|
|
||||||
else:
|
|
||||||
self.value = v
|
|
||||||
|
|
||||||
elif self.valuetid == TID_DECIMAL:
|
|
||||||
self.value = self.readdecimal()
|
|
||||||
|
|
||||||
#else:
|
|
||||||
# _assert(False, "Unhandled scalar type %d" % self.valuetid)
|
|
||||||
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
|
|
||||||
def clearvalue(self):
|
|
||||||
self.valuetid = -1
|
|
||||||
self.value = None
|
|
||||||
self.valueisnull = False
|
|
||||||
self.valuefieldid = SID_UNKNOWN
|
|
||||||
self.annotations = []
|
|
||||||
|
|
||||||
def loadannotations(self):
|
|
||||||
ln = self.readvaruint()
|
|
||||||
maxpos = self.stream.tell() + ln
|
|
||||||
while self.stream.tell() < maxpos:
|
|
||||||
self.annotations.append(self.readvaruint())
|
|
||||||
self.valuetid = self.readtypeid()
|
|
||||||
|
|
||||||
def checkversionmarker(self):
|
|
||||||
for i in VERSION_MARKER:
|
|
||||||
_assert(self.read() == i, "Unknown version marker")
|
|
||||||
|
|
||||||
self.valuelen = 0
|
|
||||||
self.valuetid = TID_SYMBOL
|
|
||||||
self.value = SID_ION_1_0
|
|
||||||
self.valueisnull = False
|
|
||||||
self.valuefieldid = SID_UNKNOWN
|
|
||||||
self.state = ParserState.AfterValue
|
|
||||||
|
|
||||||
def findcatalogitem(self, name):
|
|
||||||
for result in self.catalog:
|
|
||||||
if result.name == name:
|
|
||||||
return result
|
|
||||||
|
|
||||||
def forceimport(self, symbols):
|
|
||||||
item = IonCatalogItem("Forced", 1, symbols)
|
|
||||||
self.symbols.import_(item, len(symbols))
|
|
||||||
|
|
||||||
def getfieldname(self):
|
|
||||||
if self.valuefieldid == SID_UNKNOWN:
|
|
||||||
return ""
|
|
||||||
return self.symbols.findbyid(self.valuefieldid)
|
|
||||||
|
|
||||||
def getfieldnamesymbol(self):
|
|
||||||
return SymbolToken(self.getfieldname(), self.valuefieldid)
|
|
||||||
|
|
||||||
def gettypename(self):
|
|
||||||
if len(self.annotations) == 0:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return self.symbols.findbyid(self.annotations[0])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def printlob(b):
|
|
||||||
if b is None:
|
|
||||||
return "null"
|
|
||||||
|
|
||||||
result = ""
|
|
||||||
for i in b:
|
|
||||||
result += ("%02x " % bord(i))
|
|
||||||
|
|
||||||
if len(result) > 0:
|
|
||||||
result = result[:-1]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def ionwalk(self, supert, indent, lst):
|
|
||||||
while self.hasnext():
|
|
||||||
if supert == TID_STRUCT:
|
|
||||||
L = self.getfieldname() + ":"
|
|
||||||
else:
|
|
||||||
L = ""
|
|
||||||
|
|
||||||
t = self.next()
|
|
||||||
if t in [TID_STRUCT, TID_LIST]:
|
|
||||||
if L != "":
|
|
||||||
lst.append(indent + L)
|
|
||||||
L = self.gettypename()
|
|
||||||
if L != "":
|
|
||||||
lst.append(indent + L + "::")
|
|
||||||
if t == TID_STRUCT:
|
|
||||||
lst.append(indent + "{")
|
|
||||||
else:
|
|
||||||
lst.append(indent + "[")
|
|
||||||
|
|
||||||
self.stepin()
|
|
||||||
self.ionwalk(t, indent + " ", lst)
|
|
||||||
self.stepout()
|
|
||||||
|
|
||||||
if t == TID_STRUCT:
|
|
||||||
lst.append(indent + "}")
|
|
||||||
else:
|
|
||||||
lst.append(indent + "]")
|
|
||||||
|
|
||||||
else:
|
|
||||||
if t == TID_STRING:
|
|
||||||
L += ('"%s"' % self.stringvalue())
|
|
||||||
elif t in [TID_CLOB, TID_BLOB]:
|
|
||||||
L += ("{%s}" % self.printlob(self.lobvalue()))
|
|
||||||
elif t == TID_POSINT:
|
|
||||||
L += str(self.intvalue())
|
|
||||||
elif t == TID_SYMBOL:
|
|
||||||
tn = self.gettypename()
|
|
||||||
if tn != "":
|
|
||||||
tn += "::"
|
|
||||||
L += tn + self.symbolvalue()
|
|
||||||
elif t == TID_DECIMAL:
|
|
||||||
L += str(self.decimalvalue())
|
|
||||||
else:
|
|
||||||
L += ("TID %d" % t)
|
|
||||||
lst.append(indent + L)
|
|
||||||
|
|
||||||
def print_(self, lst):
|
|
||||||
self.reset()
|
|
||||||
self.ionwalk(-1, "", lst)
|
|
||||||
|
|
||||||
|
|
||||||
SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
|
|
||||||
'com.amazon.drm.EnvelopeMetadata@1.0', 'size', 'page_size',
|
|
||||||
'encryption_key', 'encryption_transformation',
|
|
||||||
'encryption_voucher', 'signing_key', 'signing_algorithm',
|
|
||||||
'signing_voucher', 'com.amazon.drm.EncryptedPage@1.0',
|
|
||||||
'cipher_text', 'cipher_iv', 'com.amazon.drm.Signature@1.0',
|
|
||||||
'data', 'com.amazon.drm.EnvelopeIndexTable@1.0', 'length',
|
|
||||||
'offset', 'algorithm', 'encoded', 'encryption_algorithm',
|
|
||||||
'hashing_algorithm', 'expires', 'format', 'id',
|
|
||||||
'lock_parameters', 'strategy', 'com.amazon.drm.Key@1.0',
|
|
||||||
'com.amazon.drm.KeySet@1.0', 'com.amazon.drm.PIDv3@1.0',
|
|
||||||
'com.amazon.drm.PlainTextPage@1.0',
|
|
||||||
'com.amazon.drm.PlainText@1.0', 'com.amazon.drm.PrivateKey@1.0',
|
|
||||||
'com.amazon.drm.PublicKey@1.0', 'com.amazon.drm.SecretKey@1.0',
|
|
||||||
'com.amazon.drm.Voucher@1.0', 'public_key', 'private_key',
|
|
||||||
'com.amazon.drm.KeyPair@1.0', 'com.amazon.drm.ProtectedData@1.0',
|
|
||||||
'doctype', 'com.amazon.drm.EnvelopeIndexTableOffset@1.0',
|
|
||||||
'enddoc', 'license_type', 'license', 'watermark', 'key', 'value',
|
|
||||||
'com.amazon.drm.License@1.0', 'category', 'metadata',
|
|
||||||
'categorized_metadata', 'com.amazon.drm.CategorizedMetadata@1.0',
|
|
||||||
'com.amazon.drm.VoucherEnvelope@1.0', 'mac', 'voucher',
|
|
||||||
'com.amazon.drm.ProtectedData@2.0',
|
|
||||||
'com.amazon.drm.Envelope@2.0',
|
|
||||||
'com.amazon.drm.EnvelopeMetadata@2.0',
|
|
||||||
'com.amazon.drm.EncryptedPage@2.0',
|
|
||||||
'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
|
|
||||||
'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
|
|
||||||
|
|
||||||
def addprottable(ion):
|
|
||||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
|
||||||
|
|
||||||
|
|
||||||
def pkcs7pad(msg, blocklen):
|
|
||||||
paddinglen = blocklen - len(msg) % blocklen
|
|
||||||
padding = bchr(paddinglen) * paddinglen
|
|
||||||
return msg + padding
|
|
||||||
|
|
||||||
|
|
||||||
def pkcs7unpad(msg, blocklen):
|
|
||||||
_assert(len(msg) % blocklen == 0)
|
|
||||||
|
|
||||||
paddinglen = bord(msg[-1])
|
|
||||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
|
||||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
|
||||||
|
|
||||||
return msg[:-paddinglen]
|
|
||||||
|
|
||||||
|
|
||||||
class DrmIonVoucher(object):
|
|
||||||
envelope = None
|
|
||||||
voucher = None
|
|
||||||
drmkey = None
|
|
||||||
license_type = "Unknown"
|
|
||||||
|
|
||||||
encalgorithm = ""
|
|
||||||
enctransformation = ""
|
|
||||||
hashalgorithm = ""
|
|
||||||
|
|
||||||
lockparams = None
|
|
||||||
|
|
||||||
ciphertext = b""
|
|
||||||
cipheriv = b""
|
|
||||||
secretkey = b""
|
|
||||||
|
|
||||||
def __init__(self, voucherenv, dsn, secret):
|
|
||||||
self.dsn,self.secret = dsn,secret
|
|
||||||
|
|
||||||
self.lockparams = []
|
|
||||||
|
|
||||||
self.envelope = BinaryIonParser(voucherenv)
|
|
||||||
addprottable(self.envelope)
|
|
||||||
|
|
||||||
def decryptvoucher(self):
|
|
||||||
shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
|
|
||||||
|
|
||||||
self.lockparams.sort()
|
|
||||||
for param in self.lockparams:
|
|
||||||
if param == "ACCOUNT_SECRET":
|
|
||||||
shared += param + self.secret
|
|
||||||
elif param == "CLIENT_ID":
|
|
||||||
shared += param + self.dsn
|
|
||||||
else:
|
|
||||||
_assert(False, "Unknown lock parameter: %s" % param)
|
|
||||||
|
|
||||||
sharedsecret = shared.encode("UTF-8")
|
|
||||||
|
|
||||||
key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
|
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
|
||||||
b = aes.decrypt(self.ciphertext)
|
|
||||||
b = pkcs7unpad(b, 16)
|
|
||||||
|
|
||||||
self.drmkey = BinaryIonParser(StringIO(b))
|
|
||||||
addprottable(self.drmkey)
|
|
||||||
|
|
||||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
|
||||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
|
||||||
|
|
||||||
self.drmkey.stepin()
|
|
||||||
while self.drmkey.hasnext():
|
|
||||||
self.drmkey.next()
|
|
||||||
if self.drmkey.gettypename() != "com.amazon.drm.SecretKey@1.0":
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.drmkey.stepin()
|
|
||||||
while self.drmkey.hasnext():
|
|
||||||
self.drmkey.next()
|
|
||||||
if self.drmkey.getfieldname() == "algorithm":
|
|
||||||
_assert(self.drmkey.stringvalue() == "AES", "Unknown cipher algorithm: %s" % self.drmkey.stringvalue())
|
|
||||||
elif self.drmkey.getfieldname() == "format":
|
|
||||||
_assert(self.drmkey.stringvalue() == "RAW", "Unknown key format: %s" % self.drmkey.stringvalue())
|
|
||||||
elif self.drmkey.getfieldname() == "encoded":
|
|
||||||
self.secretkey = self.drmkey.lobvalue()
|
|
||||||
|
|
||||||
self.drmkey.stepout()
|
|
||||||
break
|
|
||||||
|
|
||||||
self.drmkey.stepout()
|
|
||||||
|
|
||||||
def parse(self):
|
|
||||||
self.envelope.reset()
|
|
||||||
_assert(self.envelope.hasnext(), "Envelope is empty")
|
|
||||||
_assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
|
|
||||||
"Unknown type encountered in envelope, expected VoucherEnvelope")
|
|
||||||
|
|
||||||
self.envelope.stepin()
|
|
||||||
while self.envelope.hasnext():
|
|
||||||
self.envelope.next()
|
|
||||||
field = self.envelope.getfieldname()
|
|
||||||
if field == "voucher":
|
|
||||||
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
|
||||||
addprottable(self.voucher)
|
|
||||||
continue
|
|
||||||
elif field != "strategy":
|
|
||||||
continue
|
|
||||||
|
|
||||||
_assert(self.envelope.gettypename() == "com.amazon.drm.PIDv3@1.0", "Unknown strategy: %s" % self.envelope.gettypename())
|
|
||||||
|
|
||||||
self.envelope.stepin()
|
|
||||||
while self.envelope.hasnext():
|
|
||||||
self.envelope.next()
|
|
||||||
field = self.envelope.getfieldname()
|
|
||||||
if field == "encryption_algorithm":
|
|
||||||
self.encalgorithm = self.envelope.stringvalue()
|
|
||||||
elif field == "encryption_transformation":
|
|
||||||
self.enctransformation = self.envelope.stringvalue()
|
|
||||||
elif field == "hashing_algorithm":
|
|
||||||
self.hashalgorithm = self.envelope.stringvalue()
|
|
||||||
elif field == "lock_parameters":
|
|
||||||
self.envelope.stepin()
|
|
||||||
while self.envelope.hasnext():
|
|
||||||
_assert(self.envelope.next() == TID_STRING, "Expected string list for lock_parameters")
|
|
||||||
self.lockparams.append(self.envelope.stringvalue())
|
|
||||||
self.envelope.stepout()
|
|
||||||
|
|
||||||
self.envelope.stepout()
|
|
||||||
|
|
||||||
self.parsevoucher()
|
|
||||||
|
|
||||||
def parsevoucher(self):
|
|
||||||
_assert(self.voucher.hasnext(), "Voucher is empty")
|
|
||||||
_assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
|
|
||||||
"Unknown type, expected Voucher")
|
|
||||||
|
|
||||||
self.voucher.stepin()
|
|
||||||
while self.voucher.hasnext():
|
|
||||||
self.voucher.next()
|
|
||||||
|
|
||||||
if self.voucher.getfieldname() == "cipher_iv":
|
|
||||||
self.cipheriv = self.voucher.lobvalue()
|
|
||||||
elif self.voucher.getfieldname() == "cipher_text":
|
|
||||||
self.ciphertext = self.voucher.lobvalue()
|
|
||||||
elif self.voucher.getfieldname() == "license":
|
|
||||||
_assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
|
|
||||||
"Unknown license: %s" % self.voucher.gettypename())
|
|
||||||
self.voucher.stepin()
|
|
||||||
while self.voucher.hasnext():
|
|
||||||
self.voucher.next()
|
|
||||||
if self.voucher.getfieldname() == "license_type":
|
|
||||||
self.license_type = self.voucher.stringvalue()
|
|
||||||
self.voucher.stepout()
|
|
||||||
|
|
||||||
def printenvelope(self, lst):
|
|
||||||
self.envelope.print_(lst)
|
|
||||||
|
|
||||||
def printkey(self, lst):
|
|
||||||
if self.voucher is None:
|
|
||||||
self.parse()
|
|
||||||
if self.drmkey is None:
|
|
||||||
self.decryptvoucher()
|
|
||||||
|
|
||||||
self.drmkey.print_(lst)
|
|
||||||
|
|
||||||
def printvoucher(self, lst):
|
|
||||||
if self.voucher is None:
|
|
||||||
self.parse()
|
|
||||||
|
|
||||||
self.voucher.print_(lst)
|
|
||||||
|
|
||||||
def getlicensetype(self):
|
|
||||||
return self.license_type
|
|
||||||
|
|
||||||
|
|
||||||
class DrmIon(object):
|
|
||||||
ion = None
|
|
||||||
voucher = None
|
|
||||||
vouchername = ""
|
|
||||||
key = b""
|
|
||||||
onvoucherrequired = None
|
|
||||||
|
|
||||||
def __init__(self, ionstream, onvoucherrequired):
|
|
||||||
self.ion = BinaryIonParser(ionstream)
|
|
||||||
addprottable(self.ion)
|
|
||||||
self.onvoucherrequired = onvoucherrequired
|
|
||||||
|
|
||||||
def parse(self, outpages):
|
|
||||||
self.ion.reset()
|
|
||||||
|
|
||||||
_assert(self.ion.hasnext(), "DRMION envelope is empty")
|
|
||||||
_assert(self.ion.next() == TID_SYMBOL and self.ion.gettypename() == "doctype", "Expected doctype symbol")
|
|
||||||
_assert(self.ion.next() == TID_LIST and self.ion.gettypename() in ["com.amazon.drm.Envelope@1.0", "com.amazon.drm.Envelope@2.0"],
|
|
||||||
"Unknown type encountered in DRMION envelope, expected Envelope, got %s" % self.ion.gettypename())
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if self.ion.gettypename() == "enddoc":
|
|
||||||
break
|
|
||||||
|
|
||||||
self.ion.stepin()
|
|
||||||
while self.ion.hasnext():
|
|
||||||
self.ion.next()
|
|
||||||
|
|
||||||
if self.ion.gettypename() in ["com.amazon.drm.EnvelopeMetadata@1.0", "com.amazon.drm.EnvelopeMetadata@2.0"]:
|
|
||||||
self.ion.stepin()
|
|
||||||
while self.ion.hasnext():
|
|
||||||
self.ion.next()
|
|
||||||
if self.ion.getfieldname() != "encryption_voucher":
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.vouchername == "":
|
|
||||||
self.vouchername = self.ion.stringvalue()
|
|
||||||
self.voucher = self.onvoucherrequired(self.vouchername)
|
|
||||||
self.key = self.voucher.secretkey
|
|
||||||
_assert(self.key is not None, "Unable to obtain secret key from voucher")
|
|
||||||
else:
|
|
||||||
_assert(self.vouchername == self.ion.stringvalue(),
|
|
||||||
"Unexpected: Different vouchers required for same file?")
|
|
||||||
|
|
||||||
self.ion.stepout()
|
|
||||||
|
|
||||||
elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
|
|
||||||
decompress = False
|
|
||||||
ct = None
|
|
||||||
civ = None
|
|
||||||
self.ion.stepin()
|
|
||||||
while self.ion.hasnext():
|
|
||||||
self.ion.next()
|
|
||||||
if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
|
|
||||||
decompress = True
|
|
||||||
if self.ion.getfieldname() == "cipher_text":
|
|
||||||
ct = self.ion.lobvalue()
|
|
||||||
elif self.ion.getfieldname() == "cipher_iv":
|
|
||||||
civ = self.ion.lobvalue()
|
|
||||||
|
|
||||||
if ct is not None and civ is not None:
|
|
||||||
self.processpage(ct, civ, outpages, decompress)
|
|
||||||
self.ion.stepout()
|
|
||||||
|
|
||||||
self.ion.stepout()
|
|
||||||
if not self.ion.hasnext():
|
|
||||||
break
|
|
||||||
self.ion.next()
|
|
||||||
|
|
||||||
def print_(self, lst):
|
|
||||||
self.ion.print_(lst)
|
|
||||||
|
|
||||||
def processpage(self, ct, civ, outpages, decompress):
|
|
||||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
|
||||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
|
||||||
|
|
||||||
if not decompress:
|
|
||||||
outpages.write(msg)
|
|
||||||
return
|
|
||||||
|
|
||||||
_assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
|
|
||||||
|
|
||||||
if calibre_lzma is not None:
|
|
||||||
with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
|
|
||||||
f.seek(0)
|
|
||||||
outpages.write(f.read())
|
|
||||||
return
|
|
||||||
|
|
||||||
decomp = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
|
|
||||||
while not decomp.eof:
|
|
||||||
segment = decomp.decompress(msg[1:])
|
|
||||||
msg = b"" # Contents were internally buffered after the first call
|
|
||||||
outpages.write(segment)
|
|
@ -1,108 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# Engine to remove drm from Kindle KFX ebooks
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
try:
|
|
||||||
from calibre_plugins.dedrm import ion
|
|
||||||
except ImportError:
|
|
||||||
import ion
|
|
||||||
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = '1.0'
|
|
||||||
|
|
||||||
|
|
||||||
class KFXZipBook:
|
|
||||||
def __init__(self, infile):
|
|
||||||
self.infile = infile
|
|
||||||
self.voucher = None
|
|
||||||
self.decrypted = {}
|
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
def processBook(self, totalpids):
|
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
|
||||||
for filename in zf.namelist():
|
|
||||||
data = zf.read(filename)
|
|
||||||
if data.startswith('\xeaDRMION\xee'):
|
|
||||||
if self.voucher is None:
|
|
||||||
self.decrypt_voucher(totalpids)
|
|
||||||
print u'Decrypting KFX DRMION: {0}'.format(filename)
|
|
||||||
outfile = StringIO()
|
|
||||||
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
|
||||||
self.decrypted[filename] = outfile.getvalue()
|
|
||||||
|
|
||||||
if not self.decrypted:
|
|
||||||
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
|
||||||
|
|
||||||
def decrypt_voucher(self, totalpids):
|
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
|
||||||
for info in zf.infolist():
|
|
||||||
if info.file_size < 0x10000:
|
|
||||||
data = zf.read(info.filename)
|
|
||||||
if data.startswith('\xe0\x01\x00\xea') and 'ProtectedData' in data:
|
|
||||||
break # found DRM voucher
|
|
||||||
else:
|
|
||||||
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
|
||||||
|
|
||||||
print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
|
|
||||||
|
|
||||||
for pid in [''] + totalpids:
|
|
||||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
|
|
||||||
if len(pid) == dsn_len + secret_len:
|
|
||||||
break # split pid into DSN and account secret
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
|
||||||
voucher.parse()
|
|
||||||
voucher.decryptvoucher()
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
|
||||||
|
|
||||||
print u'KFX DRM voucher successfully decrypted'
|
|
||||||
|
|
||||||
license_type = voucher.getlicensetype()
|
|
||||||
if license_type != "Purchase":
|
|
||||||
raise Exception((u'This book is licensed as {0}. '
|
|
||||||
'These tools are intended for use on purchased books.').format(license_type))
|
|
||||||
|
|
||||||
self.voucher = voucher
|
|
||||||
|
|
||||||
def getBookTitle(self):
|
|
||||||
return os.path.splitext(os.path.split(self.infile)[1])[0]
|
|
||||||
|
|
||||||
def getBookExtension(self):
|
|
||||||
return '.kfx-zip'
|
|
||||||
|
|
||||||
def getBookType(self):
|
|
||||||
return 'KFX-ZIP'
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getFile(self, outpath):
|
|
||||||
if not self.decrypted:
|
|
||||||
shutil.copyfile(self.infile, outpath)
|
|
||||||
else:
|
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zif:
|
|
||||||
with zipfile.ZipFile(outpath, 'w') as zof:
|
|
||||||
for info in zif.infolist():
|
|
||||||
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
|
|
@ -1,310 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
# kgenpids.py
|
|
||||||
# Copyright © 2008-2017 Apprentice Harper et al.
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = '2.1'
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os, csv
|
|
||||||
import binascii
|
|
||||||
import zlib
|
|
||||||
import re
|
|
||||||
from struct import pack, unpack, unpack_from
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
global charMap1
|
|
||||||
global charMap3
|
|
||||||
global charMap4
|
|
||||||
|
|
||||||
|
|
||||||
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
|
||||||
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
||||||
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
|
||||||
|
|
||||||
# crypto digestroutines
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
def MD5(message):
|
|
||||||
ctx = hashlib.md5()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
def SHA1(message):
|
|
||||||
ctx = hashlib.sha1()
|
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
|
|
||||||
# Encode the bytes in data with the characters in map
|
|
||||||
def encode(data, map):
|
|
||||||
result = ''
|
|
||||||
for char in data:
|
|
||||||
value = ord(char)
|
|
||||||
Q = (value ^ 0x80) // len(map)
|
|
||||||
R = value % len(map)
|
|
||||||
result += map[Q]
|
|
||||||
result += map[R]
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
|
||||||
def encodeHash(data,map):
|
|
||||||
return encode(MD5(data),map)
|
|
||||||
|
|
||||||
# Decode the string in data with the characters in map. Returns the decoded bytes
|
|
||||||
def decode(data,map):
|
|
||||||
result = ''
|
|
||||||
for i in range (0,len(data)-1,2):
|
|
||||||
high = map.find(data[i])
|
|
||||||
low = map.find(data[i+1])
|
|
||||||
if (high == -1) or (low == -1) :
|
|
||||||
break
|
|
||||||
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
|
||||||
result += pack('B',value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
#
|
|
||||||
# PID generation routines
|
|
||||||
#
|
|
||||||
|
|
||||||
# Returns two bit at offset from a bit field
|
|
||||||
def getTwoBitsFromBitField(bitField,offset):
|
|
||||||
byteNumber = offset // 4
|
|
||||||
bitPosition = 6 - 2*(offset % 4)
|
|
||||||
return ord(bitField[byteNumber]) >> bitPosition & 3
|
|
||||||
|
|
||||||
# Returns the six bits at offset from a bit field
|
|
||||||
def getSixBitsFromBitField(bitField,offset):
|
|
||||||
offset *= 3
|
|
||||||
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
|
||||||
return value
|
|
||||||
|
|
||||||
# 8 bits to six bits encoding from hash to generate PID string
|
|
||||||
def encodePID(hash):
|
|
||||||
global charMap3
|
|
||||||
PID = ''
|
|
||||||
for position in range (0,8):
|
|
||||||
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
|
||||||
return PID
|
|
||||||
|
|
||||||
# Encryption table used to generate the device PID
|
|
||||||
def generatePidEncryptionTable() :
|
|
||||||
table = []
|
|
||||||
for counter1 in range (0,0x100):
|
|
||||||
value = counter1
|
|
||||||
for counter2 in range (0,8):
|
|
||||||
if (value & 1 == 0) :
|
|
||||||
value = value >> 1
|
|
||||||
else :
|
|
||||||
value = value >> 1
|
|
||||||
value = value ^ 0xEDB88320
|
|
||||||
table.append(value)
|
|
||||||
return table
|
|
||||||
|
|
||||||
# Seed value used to generate the device PID
|
|
||||||
def generatePidSeed(table,dsn) :
|
|
||||||
value = 0
|
|
||||||
for counter in range (0,4) :
|
|
||||||
index = (ord(dsn[counter]) ^ value) &0xFF
|
|
||||||
value = (value >> 8) ^ table[index]
|
|
||||||
return value
|
|
||||||
|
|
||||||
# Generate the device PID
|
|
||||||
def generateDevicePID(table,dsn,nbRoll):
|
|
||||||
global charMap4
|
|
||||||
seed = generatePidSeed(table,dsn)
|
|
||||||
pidAscii = ''
|
|
||||||
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):
|
|
||||||
pid[index] = pid[index] ^ ord(dsn[counter])
|
|
||||||
index = (index+1) %8
|
|
||||||
for counter in range (0,8):
|
|
||||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
|
||||||
pidAscii += charMap4[index]
|
|
||||||
return pidAscii
|
|
||||||
|
|
||||||
def crc32(s):
|
|
||||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
|
||||||
|
|
||||||
# convert from 8 digit PID to 10 digit PID with checksum
|
|
||||||
def checksumPid(s):
|
|
||||||
global charMap4
|
|
||||||
crc = crc32(s)
|
|
||||||
crc = crc ^ (crc >> 16)
|
|
||||||
res = s
|
|
||||||
l = len(charMap4)
|
|
||||||
for i in (0,1):
|
|
||||||
b = crc & 0xff
|
|
||||||
pos = (b // l) ^ (b % l)
|
|
||||||
res += charMap4[pos%l]
|
|
||||||
crc >>= 8
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
# old kindle serial number to fixed pid
|
|
||||||
def pidFromSerial(s, l):
|
|
||||||
global charMap4
|
|
||||||
crc = crc32(s)
|
|
||||||
arr1 = [0]*l
|
|
||||||
for i in xrange(len(s)):
|
|
||||||
arr1[i%l] ^= ord(s[i])
|
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
|
||||||
for i in xrange(l):
|
|
||||||
arr1[i] ^= crc_bytes[i&3]
|
|
||||||
pid = ""
|
|
||||||
for i in xrange(l):
|
|
||||||
b = arr1[i] & 0xff
|
|
||||||
pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
|
||||||
return pid
|
|
||||||
|
|
||||||
|
|
||||||
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
|
|
||||||
def getKindlePids(rec209, token, serialnum):
|
|
||||||
if rec209 is None:
|
|
||||||
return [serialnum]
|
|
||||||
|
|
||||||
pids=[]
|
|
||||||
|
|
||||||
if isinstance(serialnum,unicode):
|
|
||||||
serialnum = serialnum.encode('utf-8')
|
|
||||||
|
|
||||||
# Compute book PID
|
|
||||||
pidHash = SHA1(serialnum+rec209+token)
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
bookPID = checksumPid(bookPID)
|
|
||||||
pids.append(bookPID)
|
|
||||||
|
|
||||||
# compute fixed pid for old pre 2.5 firmware update pid as well
|
|
||||||
kindlePID = pidFromSerial(serialnum, 7) + "*"
|
|
||||||
kindlePID = checksumPid(kindlePID)
|
|
||||||
pids.append(kindlePID)
|
|
||||||
|
|
||||||
return pids
|
|
||||||
|
|
||||||
|
|
||||||
# parse the Kindleinfo file to calculate the book pid.
|
|
||||||
|
|
||||||
keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber']
|
|
||||||
|
|
||||||
def getK4Pids(rec209, token, kindleDatabase):
|
|
||||||
global charMap1
|
|
||||||
pids = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get the kindle account token, if present
|
|
||||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
kindleAccountToken=""
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get the DSN token, if present
|
|
||||||
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
|
||||||
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')
|
|
||||||
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get the SerialNumber token, if present
|
|
||||||
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
|
||||||
print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
|
|
||||||
except KeyError:
|
|
||||||
# Get the IDString we added
|
|
||||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get the UsernameHash token, if present
|
|
||||||
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
|
||||||
print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
|
|
||||||
except KeyError:
|
|
||||||
# Get the UserName we added
|
|
||||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
|
||||||
# encode it
|
|
||||||
encodedUsername = encodeHash(UserName,charMap1)
|
|
||||||
#print u"encodedUsername",encodedUsername.encode('hex')
|
|
||||||
except KeyError:
|
|
||||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
|
||||||
return pids
|
|
||||||
|
|
||||||
# Get the ID string used
|
|
||||||
encodedIDString = encodeHash(IDString,charMap1)
|
|
||||||
#print u"encodedIDString",encodedIDString.encode('hex')
|
|
||||||
|
|
||||||
# concat, hash and encode to calculate the DSN
|
|
||||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
|
||||||
#print u"DSN",DSN.encode('hex')
|
|
||||||
pass
|
|
||||||
|
|
||||||
if rec209 is None:
|
|
||||||
pids.append(DSN+kindleAccountToken)
|
|
||||||
return pids
|
|
||||||
|
|
||||||
# Compute the device PID (for which I can tell, is used for nothing).
|
|
||||||
table = generatePidEncryptionTable()
|
|
||||||
devicePID = generateDevicePID(table,DSN,4)
|
|
||||||
devicePID = checksumPid(devicePID)
|
|
||||||
pids.append(devicePID)
|
|
||||||
|
|
||||||
# Compute book PIDs
|
|
||||||
|
|
||||||
# book pid
|
|
||||||
pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
bookPID = checksumPid(bookPID)
|
|
||||||
pids.append(bookPID)
|
|
||||||
|
|
||||||
# variant 1
|
|
||||||
pidHash = SHA1(kindleAccountToken+rec209+token)
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
bookPID = checksumPid(bookPID)
|
|
||||||
pids.append(bookPID)
|
|
||||||
|
|
||||||
# variant 2
|
|
||||||
pidHash = SHA1(DSN+rec209+token)
|
|
||||||
bookPID = encodePID(pidHash)
|
|
||||||
bookPID = checksumPid(bookPID)
|
|
||||||
pids.append(bookPID)
|
|
||||||
|
|
||||||
return pids
|
|
||||||
|
|
||||||
def getPidList(md1, md2, serials=[], kDatabases=[]):
|
|
||||||
pidlst = []
|
|
||||||
|
|
||||||
if kDatabases is None:
|
|
||||||
kDatabases = []
|
|
||||||
if serials is None:
|
|
||||||
serials = []
|
|
||||||
|
|
||||||
for kDatabase in kDatabases:
|
|
||||||
try:
|
|
||||||
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
|
||||||
except Exception, 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:
|
|
||||||
print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
return pidlst
|
|
File diff suppressed because it is too large
Load Diff
@ -1,144 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Mobipocket PID calculator v0.4 for Amazon Kindle.
|
|
||||||
# Copyright (c) 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
|
|
||||||
# History:
|
|
||||||
# 0.1 Initial release
|
|
||||||
# 0.2 Added support for generating PID for iPhone (thanks to mbp)
|
|
||||||
# 0.3 changed to autoflush stdout, fixed return code usage
|
|
||||||
# 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
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'.
|
|
||||||
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(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"]
|
|
||||||
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]
|
|
||||||
|
|
||||||
if sys.hexversion >= 0x3000000:
|
|
||||||
print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
|
||||||
|
|
||||||
def crc32(s):
|
|
||||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
|
||||||
|
|
||||||
def checksumPid(s):
|
|
||||||
crc = crc32(s)
|
|
||||||
crc = crc ^ (crc >> 16)
|
|
||||||
res = s
|
|
||||||
l = len(letters)
|
|
||||||
for i in (0,1):
|
|
||||||
b = crc & 0xff
|
|
||||||
pos = (b // l) ^ (b % l)
|
|
||||||
res += letters[pos%l]
|
|
||||||
crc >>= 8
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def pidFromSerial(s, l):
|
|
||||||
crc = crc32(s)
|
|
||||||
|
|
||||||
arr1 = [0]*l
|
|
||||||
for i in xrange(len(s)):
|
|
||||||
arr1[i%l] ^= ord(s[i])
|
|
||||||
|
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
|
||||||
for i in xrange(l):
|
|
||||||
arr1[i] ^= crc_bytes[i&3]
|
|
||||||
|
|
||||||
pid = ''
|
|
||||||
for i in xrange(l):
|
|
||||||
b = arr1[i] & 0xff
|
|
||||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
|
||||||
|
|
||||||
return pid
|
|
||||||
|
|
||||||
def cli_main():
|
|
||||||
print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky"
|
|
||||||
argv=unicode_argv()
|
|
||||||
if len(argv)==2:
|
|
||||||
serial = argv[1]
|
|
||||||
else:
|
|
||||||
print u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
|
|
||||||
return 1
|
|
||||||
if len(serial)==16:
|
|
||||||
if serial.startswith("B") or serial.startswith("9"):
|
|
||||||
print u"Kindle serial number detected"
|
|
||||||
else:
|
|
||||||
print u"Warning: unrecognized serial number. Please recheck input."
|
|
||||||
return 1
|
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
|
||||||
print u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid))
|
|
||||||
return 0
|
|
||||||
elif len(serial)==40:
|
|
||||||
print u"iPhone serial number (UDID) detected"
|
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),8)
|
|
||||||
print u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid))
|
|
||||||
return 0
|
|
||||||
print u"Warning: unrecognized serial number. Please recheck input."
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
sys.exit(cli_main())
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,89 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
# implement just enough of des from openssl to make erdr2pml.py happy
|
|
||||||
|
|
||||||
def load_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
# typedef struct DES_ks
|
|
||||||
# {
|
|
||||||
# union
|
|
||||||
# {
|
|
||||||
# DES_cblock cblock;
|
|
||||||
# /* make sure things are correct size on machines with
|
|
||||||
# * 8 byte longs */
|
|
||||||
# DES_LONG deslong[2];
|
|
||||||
# } ks[16];
|
|
||||||
# } DES_key_schedule;
|
|
||||||
|
|
||||||
# just create a big enough place to hold everything
|
|
||||||
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
|
||||||
class DES_KEY_SCHEDULE(Structure):
|
|
||||||
_fields_ = [('DES_cblock1', c_char * 16),
|
|
||||||
('DES_cblock2', c_char * 16),
|
|
||||||
('DES_cblock3', c_char * 16),
|
|
||||||
('DES_cblock4', c_char * 16),
|
|
||||||
('DES_cblock5', c_char * 16),
|
|
||||||
('DES_cblock6', c_char * 16),
|
|
||||||
('DES_cblock7', c_char * 16),
|
|
||||||
('DES_cblock8', c_char * 16),
|
|
||||||
('DES_cblock9', c_char * 16),
|
|
||||||
('DES_cblock10', c_char * 16),
|
|
||||||
('DES_cblock11', c_char * 16),
|
|
||||||
('DES_cblock12', c_char * 16),
|
|
||||||
('DES_cblock13', c_char * 16),
|
|
||||||
('DES_cblock14', c_char * 16),
|
|
||||||
('DES_cblock15', c_char * 16),
|
|
||||||
('DES_cblock16', c_char * 16)]
|
|
||||||
|
|
||||||
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
|
||||||
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
|
||||||
|
|
||||||
|
|
||||||
class DES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
if len(key) != 8 :
|
|
||||||
raise Exception('DES improper key used')
|
|
||||||
return
|
|
||||||
self.key = key
|
|
||||||
self.keyschedule = DES_KEY_SCHEDULE()
|
|
||||||
DES_set_key(self.key, self.keyschedule)
|
|
||||||
def desdecrypt(self, data):
|
|
||||||
ob = create_string_buffer(len(data))
|
|
||||||
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
|
||||||
return ob.raw
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
i = 0
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = data[i:i+8]
|
|
||||||
processed_block = self.desdecrypt(block)
|
|
||||||
result.append(processed_block)
|
|
||||||
i += 8
|
|
||||||
return ''.join(result)
|
|
||||||
|
|
||||||
return DES
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
|
|
||||||
def load_pycrypto():
|
|
||||||
try :
|
|
||||||
from Crypto.Cipher import DES as _DES
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
class DES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
if len(key) != 8 :
|
|
||||||
raise Error('DES improper key used')
|
|
||||||
self.key = key
|
|
||||||
self._des = _DES.new(key,_DES.MODE_ECB)
|
|
||||||
def desdecrypt(self, data):
|
|
||||||
return self._des.decrypt(data)
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
i = 0
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = data[i:i+8]
|
|
||||||
processed_block = self.desdecrypt(block)
|
|
||||||
result.append(processed_block)
|
|
||||||
i += 8
|
|
||||||
return ''.join(result)
|
|
||||||
return DES
|
|
@ -1,220 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
import sys
|
|
||||||
|
|
||||||
ECB = 0
|
|
||||||
CBC = 1
|
|
||||||
class Des(object):
|
|
||||||
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
|
||||||
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
|
||||||
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
|
||||||
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
|
||||||
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
|
||||||
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
|
||||||
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
|
||||||
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
|
||||||
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
|
||||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
||||||
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
|
||||||
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
|
||||||
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
|
||||||
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
|
||||||
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
|
||||||
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
|
||||||
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
|
||||||
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
|
||||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
|
||||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
|
||||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
|
||||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
|
||||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
|
||||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
|
||||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
|
||||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
|
||||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
|
||||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
|
||||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
|
||||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
|
||||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
|
||||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
|
||||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
|
||||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
|
||||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
|
||||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
|
||||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
|
||||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
|
||||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
|
||||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
|
||||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
|
||||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
|
||||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
|
||||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
|
||||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
|
||||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
|
||||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
|
||||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
|
||||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
|
||||||
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
|
||||||
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
|
||||||
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
|
||||||
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
|
||||||
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
|
||||||
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
|
||||||
# Type of crypting being done
|
|
||||||
ENCRYPT = 0x00
|
|
||||||
DECRYPT = 0x01
|
|
||||||
def __init__(self, key, mode=ECB, IV=None):
|
|
||||||
if len(key) != 8:
|
|
||||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
|
||||||
self.block_size = 8
|
|
||||||
self.key_size = 8
|
|
||||||
self.__padding = ''
|
|
||||||
self.setMode(mode)
|
|
||||||
if IV:
|
|
||||||
self.setIV(IV)
|
|
||||||
self.L = []
|
|
||||||
self.R = []
|
|
||||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
|
||||||
self.final = []
|
|
||||||
self.setKey(key)
|
|
||||||
def getKey(self):
|
|
||||||
return self.__key
|
|
||||||
def setKey(self, key):
|
|
||||||
self.__key = key
|
|
||||||
self.__create_sub_keys()
|
|
||||||
def getMode(self):
|
|
||||||
return self.__mode
|
|
||||||
def setMode(self, mode):
|
|
||||||
self.__mode = mode
|
|
||||||
def getIV(self):
|
|
||||||
return self.__iv
|
|
||||||
def setIV(self, IV):
|
|
||||||
if not IV or len(IV) != self.block_size:
|
|
||||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
|
||||||
self.__iv = IV
|
|
||||||
def getPadding(self):
|
|
||||||
return self.__padding
|
|
||||||
def __String_to_BitList(self, data):
|
|
||||||
l = len(data) * 8
|
|
||||||
result = [0] * l
|
|
||||||
pos = 0
|
|
||||||
for c in data:
|
|
||||||
i = 7
|
|
||||||
ch = ord(c)
|
|
||||||
while i >= 0:
|
|
||||||
if ch & (1 << i) != 0:
|
|
||||||
result[pos] = 1
|
|
||||||
else:
|
|
||||||
result[pos] = 0
|
|
||||||
pos += 1
|
|
||||||
i -= 1
|
|
||||||
return result
|
|
||||||
def __BitList_to_String(self, data):
|
|
||||||
result = ''
|
|
||||||
pos = 0
|
|
||||||
c = 0
|
|
||||||
while pos < len(data):
|
|
||||||
c += data[pos] << (7 - (pos % 8))
|
|
||||||
if (pos % 8) == 7:
|
|
||||||
result += chr(c)
|
|
||||||
c = 0
|
|
||||||
pos += 1
|
|
||||||
return result
|
|
||||||
def __permutate(self, table, block):
|
|
||||||
return [block[x] for x in table]
|
|
||||||
def __create_sub_keys(self):
|
|
||||||
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
|
||||||
i = 0
|
|
||||||
self.L = key[:28]
|
|
||||||
self.R = key[28:]
|
|
||||||
while i < 16:
|
|
||||||
j = 0
|
|
||||||
while j < Des.__left_rotations[i]:
|
|
||||||
self.L.append(self.L[0])
|
|
||||||
del self.L[0]
|
|
||||||
self.R.append(self.R[0])
|
|
||||||
del self.R[0]
|
|
||||||
j += 1
|
|
||||||
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
|
||||||
i += 1
|
|
||||||
def __des_crypt(self, block, crypt_type):
|
|
||||||
block = self.__permutate(Des.__ip, block)
|
|
||||||
self.L = block[:32]
|
|
||||||
self.R = block[32:]
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
iteration = 0
|
|
||||||
iteration_adjustment = 1
|
|
||||||
else:
|
|
||||||
iteration = 15
|
|
||||||
iteration_adjustment = -1
|
|
||||||
i = 0
|
|
||||||
while i < 16:
|
|
||||||
tempR = self.R[:]
|
|
||||||
self.R = self.__permutate(Des.__expansion_table, self.R)
|
|
||||||
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
|
||||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
|
||||||
j = 0
|
|
||||||
Bn = [0] * 32
|
|
||||||
pos = 0
|
|
||||||
while j < 8:
|
|
||||||
m = (B[j][0] << 1) + B[j][5]
|
|
||||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
|
||||||
v = Des.__sbox[j][(m << 4) + n]
|
|
||||||
Bn[pos] = (v & 8) >> 3
|
|
||||||
Bn[pos + 1] = (v & 4) >> 2
|
|
||||||
Bn[pos + 2] = (v & 2) >> 1
|
|
||||||
Bn[pos + 3] = v & 1
|
|
||||||
pos += 4
|
|
||||||
j += 1
|
|
||||||
self.R = self.__permutate(Des.__p, Bn)
|
|
||||||
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
|
||||||
self.L = tempR
|
|
||||||
i += 1
|
|
||||||
iteration += iteration_adjustment
|
|
||||||
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
|
||||||
return self.final
|
|
||||||
def crypt(self, data, crypt_type):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
if len(data) % self.block_size != 0:
|
|
||||||
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
|
||||||
if not self.getPadding():
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
|
||||||
else:
|
|
||||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if self.getIV():
|
|
||||||
iv = self.__String_to_BitList(self.getIV())
|
|
||||||
else:
|
|
||||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
|
||||||
i = 0
|
|
||||||
dict = {}
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = self.__String_to_BitList(data[i:i+8])
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
block = [x ^ y for x, y in zip(block, iv)]
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
if crypt_type == Des.DECRYPT:
|
|
||||||
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
|
||||||
iv = block
|
|
||||||
else:
|
|
||||||
iv = processed_block
|
|
||||||
else:
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
result.append(self.__BitList_to_String(processed_block))
|
|
||||||
i += 8
|
|
||||||
if crypt_type == Des.DECRYPT and self.getPadding():
|
|
||||||
s = result[-1]
|
|
||||||
while s[-1] == self.getPadding():
|
|
||||||
s = s[:-1]
|
|
||||||
result[-1] = s
|
|
||||||
return ''.join(result)
|
|
||||||
def encrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.ENCRYPT)
|
|
||||||
def decrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.DECRYPT)
|
|
@ -1,198 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import ineptepub
|
|
||||||
import ignobleepub
|
|
||||||
import epubtest
|
|
||||||
import zipfix
|
|
||||||
import ineptpdf
|
|
||||||
import erdr2pml
|
|
||||||
import k4mobidedrm
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
def decryptepub(infile, outdir, rscpath):
|
|
||||||
errlog = ''
|
|
||||||
|
|
||||||
# first fix the epub to make sure we do not get errors
|
|
||||||
name, ext = os.path.splitext(os.path.basename(infile))
|
|
||||||
bpath = os.path.dirname(infile)
|
|
||||||
zippath = os.path.join(bpath,name + '_temp.zip')
|
|
||||||
rv = zipfix.repairBook(infile, zippath)
|
|
||||||
if rv != 0:
|
|
||||||
print "Error while trying to fix epub"
|
|
||||||
return rv
|
|
||||||
|
|
||||||
# determine a good name for the output file
|
|
||||||
outfile = os.path.join(outdir, name + '_nodrm.epub')
|
|
||||||
|
|
||||||
rv = 1
|
|
||||||
# first try with the Adobe adept epub
|
|
||||||
if ineptepub.adeptBook(zippath):
|
|
||||||
# try with any keyfiles (*.der) in the rscpath
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.der$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
keypath = os.path.join(rscpath, filename)
|
|
||||||
userkey = open(keypath,'rb').read()
|
|
||||||
try:
|
|
||||||
rv = ineptepub.decryptBook(userkey, zippath, outfile)
|
|
||||||
if rv == 0:
|
|
||||||
print "Decrypted Adobe ePub with key file {0}".format(filename)
|
|
||||||
break
|
|
||||||
except Exception, e:
|
|
||||||
errlog += traceback.format_exc()
|
|
||||||
errlog += str(e)
|
|
||||||
rv = 1
|
|
||||||
# now try with ignoble epub
|
|
||||||
elif ignobleepub.ignobleBook(zippath):
|
|
||||||
# try with any keyfiles (*.b64) in the rscpath
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.b64$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
keypath = os.path.join(rscpath, filename)
|
|
||||||
userkey = open(keypath,'r').read()
|
|
||||||
#print userkey
|
|
||||||
try:
|
|
||||||
rv = ignobleepub.decryptBook(userkey, zippath, outfile)
|
|
||||||
if rv == 0:
|
|
||||||
print "Decrypted B&N ePub with key file {0}".format(filename)
|
|
||||||
break
|
|
||||||
except Exception, e:
|
|
||||||
errlog += traceback.format_exc()
|
|
||||||
errlog += str(e)
|
|
||||||
rv = 1
|
|
||||||
else:
|
|
||||||
encryption = epubtest.encryption(zippath)
|
|
||||||
if encryption == "Unencrypted":
|
|
||||||
print "{0} is not DRMed.".format(name)
|
|
||||||
rv = 0
|
|
||||||
else:
|
|
||||||
print "{0} has an unknown encryption.".format(name)
|
|
||||||
|
|
||||||
os.remove(zippath)
|
|
||||||
if rv != 0:
|
|
||||||
print errlog
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def decryptpdf(infile, outdir, rscpath):
|
|
||||||
errlog = ''
|
|
||||||
rv = 1
|
|
||||||
|
|
||||||
# determine a good name for the output file
|
|
||||||
name, ext = os.path.splitext(os.path.basename(infile))
|
|
||||||
outfile = os.path.join(outdir, name + '_nodrm.pdf')
|
|
||||||
|
|
||||||
# try with any keyfiles (*.der) in the rscpath
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.der$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
keypath = os.path.join(rscpath, filename)
|
|
||||||
userkey = open(keypath,'rb').read()
|
|
||||||
try:
|
|
||||||
rv = ineptpdf.decryptBook(userkey, infile, outfile)
|
|
||||||
if rv == 0:
|
|
||||||
break
|
|
||||||
except Exception, e:
|
|
||||||
errlog += traceback.format_exc()
|
|
||||||
errlog += str(e)
|
|
||||||
rv = 1
|
|
||||||
|
|
||||||
if rv != 0:
|
|
||||||
print errlog
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def decryptpdb(infile, outdir, rscpath):
|
|
||||||
outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
|
|
||||||
outpath = os.path.join(outdir, outname)
|
|
||||||
rv = 1
|
|
||||||
socialpath = os.path.join(rscpath,'sdrmlist.txt')
|
|
||||||
if os.path.exists(socialpath):
|
|
||||||
keydata = file(socialpath,'r').read()
|
|
||||||
keydata = keydata.rstrip(os.linesep)
|
|
||||||
ar = keydata.split(',')
|
|
||||||
for i in ar:
|
|
||||||
try:
|
|
||||||
name, cc8 = i.split(':')
|
|
||||||
except ValueError:
|
|
||||||
print ' Error parsing user supplied social drm data.'
|
|
||||||
return 1
|
|
||||||
try:
|
|
||||||
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
|
||||||
except Exception, e:
|
|
||||||
errlog += traceback.format_exc()
|
|
||||||
errlog += str(e)
|
|
||||||
rv = 1
|
|
||||||
|
|
||||||
if rv == 0:
|
|
||||||
break
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def decryptk4mobi(infile, outdir, rscpath):
|
|
||||||
rv = 1
|
|
||||||
pidnums = []
|
|
||||||
pidspath = os.path.join(rscpath,'pidlist.txt')
|
|
||||||
if os.path.exists(pidspath):
|
|
||||||
pidstr = file(pidspath,'r').read()
|
|
||||||
pidstr = pidstr.rstrip(os.linesep)
|
|
||||||
pidstr = pidstr.strip()
|
|
||||||
if pidstr != '':
|
|
||||||
pidnums = pidstr.split(',')
|
|
||||||
serialnums = []
|
|
||||||
serialnumspath = os.path.join(rscpath,'seriallist.txt')
|
|
||||||
if os.path.exists(serialnumspath):
|
|
||||||
serialstr = file(serialnumspath,'r').read()
|
|
||||||
serialstr = serialstr.rstrip(os.linesep)
|
|
||||||
serialstr = serialstr.strip()
|
|
||||||
if serialstr != '':
|
|
||||||
serialnums = serialstr.split(',')
|
|
||||||
kDatabaseFiles = []
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.k4i$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
dpath = os.path.join(rscpath,filename)
|
|
||||||
kDatabaseFiles.append(dpath)
|
|
||||||
androidFiles = []
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.ab$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
dpath = os.path.join(rscpath,filename)
|
|
||||||
androidFiles.append(dpath)
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.db$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
dpath = os.path.join(rscpath,filename)
|
|
||||||
androidFiles.append(dpath)
|
|
||||||
files = os.listdir(rscpath)
|
|
||||||
filefilter = re.compile("\.xml$", re.IGNORECASE)
|
|
||||||
files = filter(filefilter.search, files)
|
|
||||||
if files:
|
|
||||||
for filename in files:
|
|
||||||
dpath = os.path.join(rscpath,filename)
|
|
||||||
androidFiles.append(dpath)
|
|
||||||
try:
|
|
||||||
rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
|
|
||||||
except Exception, e:
|
|
||||||
errlog += traceback.format_exc()
|
|
||||||
errlog += str(e)
|
|
||||||
rv = 1
|
|
||||||
|
|
||||||
return rv
|
|
@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import Tkinter
|
|
||||||
import Tkconstants
|
|
||||||
|
|
||||||
# basic scrolled text widget
|
|
||||||
class ScrolledText(Tkinter.Text):
|
|
||||||
def __init__(self, master=None, **kw):
|
|
||||||
self.frame = Tkinter.Frame(master)
|
|
||||||
self.vbar = Tkinter.Scrollbar(self.frame)
|
|
||||||
self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
|
|
||||||
kw.update({'yscrollcommand': self.vbar.set})
|
|
||||||
Tkinter.Text.__init__(self, self.frame, **kw)
|
|
||||||
self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
|
|
||||||
self.vbar['command'] = self.yview
|
|
||||||
# Copy geometry methods of self.frame without overriding Text
|
|
||||||
# methods = hack!
|
|
||||||
text_meths = vars(Tkinter.Text).keys()
|
|
||||||
methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
|
|
||||||
methods = set(methods).difference(text_meths)
|
|
||||||
for m in methods:
|
|
||||||
if m[0] != '_' and m != 'config' and m != 'configure':
|
|
||||||
setattr(self, m, getattr(self.frame, m))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.frame)
|
|
@ -1,77 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os, os.path
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
class SimplePrefsError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SimplePrefs(object):
|
|
||||||
def __init__(self, target, description):
|
|
||||||
self.prefs = {}
|
|
||||||
self.key2file={}
|
|
||||||
self.file2key={}
|
|
||||||
for keyfilemap in description:
|
|
||||||
[key, filename] = keyfilemap
|
|
||||||
self.key2file[key] = filename
|
|
||||||
self.file2key[filename] = key
|
|
||||||
self.target = target + 'Prefs'
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
import _winreg as winreg
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
|
||||||
prefdir = path + os.sep + self.target
|
|
||||||
elif sys.platform.startswith('darwin'):
|
|
||||||
home = os.getenv('HOME')
|
|
||||||
prefdir = os.path.join(home,'Library','Preferences','org.' + self.target)
|
|
||||||
else:
|
|
||||||
# linux and various flavors of unix
|
|
||||||
home = os.getenv('HOME')
|
|
||||||
prefdir = os.path.join(home,'.' + self.target)
|
|
||||||
if not os.path.exists(prefdir):
|
|
||||||
os.makedirs(prefdir)
|
|
||||||
self.prefdir = prefdir
|
|
||||||
self.prefs['dir'] = self.prefdir
|
|
||||||
self._loadPreferences()
|
|
||||||
|
|
||||||
def _loadPreferences(self):
|
|
||||||
filenames = os.listdir(self.prefdir)
|
|
||||||
for filename in filenames:
|
|
||||||
if filename in self.file2key:
|
|
||||||
key = self.file2key[filename]
|
|
||||||
filepath = os.path.join(self.prefdir,filename)
|
|
||||||
if os.path.isfile(filepath):
|
|
||||||
try :
|
|
||||||
data = file(filepath,'rb').read()
|
|
||||||
self.prefs[key] = data
|
|
||||||
except Exception, e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getPreferences(self):
|
|
||||||
return self.prefs
|
|
||||||
|
|
||||||
def setPreferences(self, newprefs={}):
|
|
||||||
if 'dir' not in newprefs:
|
|
||||||
raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
|
|
||||||
if newprefs['dir'] != self.prefs['dir']:
|
|
||||||
raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
|
|
||||||
for key in newprefs:
|
|
||||||
if key != 'dir':
|
|
||||||
if key in self.key2file:
|
|
||||||
filename = self.key2file[key]
|
|
||||||
filepath = os.path.join(self.prefdir,filename)
|
|
||||||
data = newprefs[key]
|
|
||||||
if data != None:
|
|
||||||
data = str(data)
|
|
||||||
if data == None or data == '':
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
os.remove(filepath)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
file(filepath,'wb').write(data)
|
|
||||||
except Exception, e:
|
|
||||||
pass
|
|
||||||
self.prefs = newprefs
|
|
||||||
return
|
|
@ -1,284 +0,0 @@
|
|||||||
#! /usr/bin/python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
# For use with Topaz Scripts Version 2.6
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
import re
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
class DocParser(object):
|
|
||||||
def __init__(self, flatxml, fontsize, ph, pw):
|
|
||||||
self.flatdoc = flatxml.split('\n')
|
|
||||||
self.fontsize = int(fontsize)
|
|
||||||
self.ph = int(ph) * 1.0
|
|
||||||
self.pw = int(pw) * 1.0
|
|
||||||
|
|
||||||
stags = {
|
|
||||||
'paragraph' : 'p',
|
|
||||||
'graphic' : '.graphic'
|
|
||||||
}
|
|
||||||
|
|
||||||
attr_val_map = {
|
|
||||||
'hang' : 'text-indent: ',
|
|
||||||
'indent' : 'text-indent: ',
|
|
||||||
'line-space' : 'line-height: ',
|
|
||||||
'margin-bottom' : 'margin-bottom: ',
|
|
||||||
'margin-left' : 'margin-left: ',
|
|
||||||
'margin-right' : 'margin-right: ',
|
|
||||||
'margin-top' : 'margin-top: ',
|
|
||||||
'space-after' : 'padding-bottom: ',
|
|
||||||
}
|
|
||||||
|
|
||||||
attr_str_map = {
|
|
||||||
'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
|
||||||
'align-left' : 'text-align: left;',
|
|
||||||
'align-right' : 'text-align: right;',
|
|
||||||
'align-justify' : 'text-align: justify;',
|
|
||||||
'display-inline' : 'display: inline;',
|
|
||||||
'pos-left' : 'text-align: left;',
|
|
||||||
'pos-right' : 'text-align: right;',
|
|
||||||
'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# find tag if within pos to end inclusive
|
|
||||||
def findinDoc(self, tagpath, pos, end) :
|
|
||||||
result = None
|
|
||||||
docList = self.flatdoc
|
|
||||||
cnt = len(docList)
|
|
||||||
if end == -1 :
|
|
||||||
end = cnt
|
|
||||||
else:
|
|
||||||
end = min(cnt,end)
|
|
||||||
foundat = -1
|
|
||||||
for j in xrange(pos, end):
|
|
||||||
item = docList[j]
|
|
||||||
if item.find('=') >= 0:
|
|
||||||
(name, argres) = item.split('=',1)
|
|
||||||
else :
|
|
||||||
name = item
|
|
||||||
argres = ''
|
|
||||||
if name.endswith(tagpath) :
|
|
||||||
result = argres
|
|
||||||
foundat = j
|
|
||||||
break
|
|
||||||
return foundat, result
|
|
||||||
|
|
||||||
|
|
||||||
# return list of start positions for the tagpath
|
|
||||||
def posinDoc(self, tagpath):
|
|
||||||
startpos = []
|
|
||||||
pos = 0
|
|
||||||
res = ""
|
|
||||||
while res != None :
|
|
||||||
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
|
||||||
if res != None :
|
|
||||||
startpos.append(foundpos)
|
|
||||||
pos = foundpos + 1
|
|
||||||
return startpos
|
|
||||||
|
|
||||||
# returns a vector of integers for the tagpath
|
|
||||||
def getData(self, tagpath, pos, end, clean=False):
|
|
||||||
if clean:
|
|
||||||
digits_only = re.compile(r'''([0-9]+)''')
|
|
||||||
argres=[]
|
|
||||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
|
||||||
if (argt != None) and (len(argt) > 0) :
|
|
||||||
argList = argt.split('|')
|
|
||||||
for strval in argList:
|
|
||||||
if clean:
|
|
||||||
m = re.search(digits_only, strval)
|
|
||||||
if m != None:
|
|
||||||
strval = m.group()
|
|
||||||
argres.append(int(strval))
|
|
||||||
return argres
|
|
||||||
|
|
||||||
def process(self):
|
|
||||||
|
|
||||||
classlst = ''
|
|
||||||
csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n'
|
|
||||||
csspage += '.cl-right { text-align: right; }\n'
|
|
||||||
csspage += '.cl-left { text-align: left; }\n'
|
|
||||||
csspage += '.cl-justify { text-align: justify; }\n'
|
|
||||||
|
|
||||||
# generate a list of each <style> starting point in the stylesheet
|
|
||||||
styleList= self.posinDoc('book.stylesheet.style')
|
|
||||||
stylecnt = len(styleList)
|
|
||||||
styleList.append(-1)
|
|
||||||
|
|
||||||
# process each style converting what you can
|
|
||||||
|
|
||||||
if debug: print ' ', 'Processing styles.'
|
|
||||||
for j in xrange(stylecnt):
|
|
||||||
if debug: print ' ', 'Processing style %d' %(j)
|
|
||||||
start = styleList[j]
|
|
||||||
end = styleList[j+1]
|
|
||||||
|
|
||||||
(pos, tag) = self.findinDoc('style._tag',start,end)
|
|
||||||
if tag == None :
|
|
||||||
(pos, tag) = self.findinDoc('style.type',start,end)
|
|
||||||
|
|
||||||
# Is this something we know how to convert to css
|
|
||||||
if tag in self.stags :
|
|
||||||
|
|
||||||
# get the style class
|
|
||||||
(pos, sclass) = self.findinDoc('style.class',start,end)
|
|
||||||
if sclass != None:
|
|
||||||
sclass = sclass.replace(' ','-')
|
|
||||||
sclass = '.cl-' + sclass.lower()
|
|
||||||
else :
|
|
||||||
sclass = ''
|
|
||||||
|
|
||||||
if debug: print 'sclass', sclass
|
|
||||||
|
|
||||||
# check for any "after class" specifiers
|
|
||||||
(pos, aftclass) = self.findinDoc('style._after_class',start,end)
|
|
||||||
if aftclass != None:
|
|
||||||
aftclass = aftclass.replace(' ','-')
|
|
||||||
aftclass = '.cl-' + aftclass.lower()
|
|
||||||
else :
|
|
||||||
aftclass = ''
|
|
||||||
|
|
||||||
if debug: print 'aftclass', aftclass
|
|
||||||
|
|
||||||
cssargs = {}
|
|
||||||
|
|
||||||
while True :
|
|
||||||
|
|
||||||
(pos1, attr) = self.findinDoc('style.rule.attr', start, end)
|
|
||||||
(pos2, val) = self.findinDoc('style.rule.value', start, end)
|
|
||||||
|
|
||||||
if debug: print 'attr', attr
|
|
||||||
if debug: print 'val', val
|
|
||||||
|
|
||||||
if attr == None : break
|
|
||||||
|
|
||||||
if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
|
|
||||||
# handle text based attributess
|
|
||||||
attr = attr + '-' + val
|
|
||||||
if attr in self.attr_str_map :
|
|
||||||
cssargs[attr] = (self.attr_str_map[attr], '')
|
|
||||||
else :
|
|
||||||
# handle value based attributes
|
|
||||||
if attr in self.attr_val_map :
|
|
||||||
name = self.attr_val_map[attr]
|
|
||||||
if attr in ('margin-bottom', 'margin-top', 'space-after') :
|
|
||||||
scale = self.ph
|
|
||||||
elif attr in ('margin-right', 'indent', 'margin-left', 'hang') :
|
|
||||||
scale = self.pw
|
|
||||||
elif attr == 'line-space':
|
|
||||||
scale = self.fontsize * 2.0
|
|
||||||
|
|
||||||
if val == "":
|
|
||||||
val = 0
|
|
||||||
|
|
||||||
if not ((attr == 'hang') and (int(val) == 0)):
|
|
||||||
try:
|
|
||||||
f = float(val)
|
|
||||||
except:
|
|
||||||
print "Warning: unrecognised val, ignoring"
|
|
||||||
val = 0
|
|
||||||
pv = float(val)/scale
|
|
||||||
cssargs[attr] = (self.attr_val_map[attr], pv)
|
|
||||||
keep = True
|
|
||||||
|
|
||||||
start = max(pos1, pos2) + 1
|
|
||||||
|
|
||||||
# disable all of the after class tags until I figure out how to handle them
|
|
||||||
if aftclass != "" : keep = False
|
|
||||||
|
|
||||||
if keep :
|
|
||||||
if debug: print 'keeping style'
|
|
||||||
# make sure line-space does not go below 100% or above 300% since
|
|
||||||
# it can be wacky in some styles
|
|
||||||
if 'line-space' in cssargs:
|
|
||||||
seg = cssargs['line-space'][0]
|
|
||||||
val = cssargs['line-space'][1]
|
|
||||||
if val < 1.0: val = 1.0
|
|
||||||
if val > 3.0: val = 3.0
|
|
||||||
del cssargs['line-space']
|
|
||||||
cssargs['line-space'] = (self.attr_val_map['line-space'], val)
|
|
||||||
|
|
||||||
|
|
||||||
# handle modifications for css style hanging indents
|
|
||||||
if 'hang' in cssargs:
|
|
||||||
hseg = cssargs['hang'][0]
|
|
||||||
hval = cssargs['hang'][1]
|
|
||||||
del cssargs['hang']
|
|
||||||
cssargs['hang'] = (self.attr_val_map['hang'], -hval)
|
|
||||||
mval = 0
|
|
||||||
mseg = 'margin-left: '
|
|
||||||
mval = hval
|
|
||||||
if 'margin-left' in cssargs:
|
|
||||||
mseg = cssargs['margin-left'][0]
|
|
||||||
mval = cssargs['margin-left'][1]
|
|
||||||
if mval < 0: mval = 0
|
|
||||||
mval = hval + mval
|
|
||||||
cssargs['margin-left'] = (mseg, mval)
|
|
||||||
if 'indent' in cssargs:
|
|
||||||
del cssargs['indent']
|
|
||||||
|
|
||||||
cssline = sclass + ' { '
|
|
||||||
for key in iter(cssargs):
|
|
||||||
mseg = cssargs[key][0]
|
|
||||||
mval = cssargs[key][1]
|
|
||||||
if mval == '':
|
|
||||||
cssline += mseg + ' '
|
|
||||||
else :
|
|
||||||
aseg = mseg + '%.1f%%;' % (mval * 100.0)
|
|
||||||
cssline += aseg + ' '
|
|
||||||
|
|
||||||
cssline += '}'
|
|
||||||
|
|
||||||
if sclass != '' :
|
|
||||||
classlst += sclass + '\n'
|
|
||||||
|
|
||||||
# handle special case of paragraph class used inside chapter heading
|
|
||||||
# and non-chapter headings
|
|
||||||
if sclass != '' :
|
|
||||||
ctype = sclass[4:7]
|
|
||||||
if ctype == 'ch1' :
|
|
||||||
csspage += 'h1' + cssline + '\n'
|
|
||||||
if ctype == 'ch2' :
|
|
||||||
csspage += 'h2' + cssline + '\n'
|
|
||||||
if ctype == 'ch3' :
|
|
||||||
csspage += 'h3' + cssline + '\n'
|
|
||||||
if ctype == 'h1-' :
|
|
||||||
csspage += 'h4' + cssline + '\n'
|
|
||||||
if ctype == 'h2-' :
|
|
||||||
csspage += 'h5' + cssline + '\n'
|
|
||||||
if ctype == 'h3_' :
|
|
||||||
csspage += 'h6' + cssline + '\n'
|
|
||||||
|
|
||||||
if cssline != ' { }':
|
|
||||||
csspage += self.stags[tag] + cssline + '\n'
|
|
||||||
|
|
||||||
|
|
||||||
return csspage, classlst
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def convert2CSS(flatxml, fontsize, ph, pw):
|
|
||||||
|
|
||||||
print ' ', 'Using font size:',fontsize
|
|
||||||
print ' ', 'Using page height:', ph
|
|
||||||
print ' ', 'Using page width:', pw
|
|
||||||
|
|
||||||
# create a document parser
|
|
||||||
dp = DocParser(flatxml, fontsize, ph, pw)
|
|
||||||
if debug: print ' ', 'Created DocParser.'
|
|
||||||
csspage = dp.process()
|
|
||||||
if debug: print ' ', 'Processed DocParser.'
|
|
||||||
return csspage
|
|
||||||
|
|
||||||
|
|
||||||
def getpageIDMap(flatxml):
|
|
||||||
dp = DocParser(flatxml, 0, 0, 0)
|
|
||||||
pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
|
|
||||||
return pageidnumbers
|
|
@ -1,148 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
import os, sys
|
|
||||||
import signal
|
|
||||||
import threading
|
|
||||||
import subprocess
|
|
||||||
from subprocess import Popen, PIPE, STDOUT
|
|
||||||
|
|
||||||
# **heavily** chopped up and modfied version of asyncproc.py
|
|
||||||
# to make it actually work on Windows as well as Mac/Linux
|
|
||||||
# For the original see:
|
|
||||||
# "http://www.lysator.liu.se/~bellman/download/"
|
|
||||||
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
|
||||||
# available under GPL version 3 or Later
|
|
||||||
|
|
||||||
# create an asynchronous subprocess whose output can be collected in
|
|
||||||
# a non-blocking manner
|
|
||||||
|
|
||||||
# What a mess! Have to use threads just to get non-blocking io
|
|
||||||
# in a cross-platform manner
|
|
||||||
|
|
||||||
# luckily all thread use is hidden within this class
|
|
||||||
|
|
||||||
class Process(object):
|
|
||||||
def __init__(self, *params, **kwparams):
|
|
||||||
if len(params) <= 3:
|
|
||||||
kwparams.setdefault('stdin', subprocess.PIPE)
|
|
||||||
if len(params) <= 4:
|
|
||||||
kwparams.setdefault('stdout', subprocess.PIPE)
|
|
||||||
if len(params) <= 5:
|
|
||||||
kwparams.setdefault('stderr', subprocess.PIPE)
|
|
||||||
self.__pending_input = []
|
|
||||||
self.__collected_outdata = []
|
|
||||||
self.__collected_errdata = []
|
|
||||||
self.__exitstatus = None
|
|
||||||
self.__lock = threading.Lock()
|
|
||||||
self.__inputsem = threading.Semaphore(0)
|
|
||||||
self.__quit = False
|
|
||||||
|
|
||||||
self.__process = subprocess.Popen(*params, **kwparams)
|
|
||||||
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.__stdin_thread = threading.Thread(
|
|
||||||
name="stdin-thread",
|
|
||||||
target=self.__feeder, args=(self.__pending_input,
|
|
||||||
self.__process.stdin))
|
|
||||||
self.__stdin_thread.setDaemon(True)
|
|
||||||
self.__stdin_thread.start()
|
|
||||||
|
|
||||||
if self.__process.stdout:
|
|
||||||
self.__stdout_thread = threading.Thread(
|
|
||||||
name="stdout-thread",
|
|
||||||
target=self.__reader, args=(self.__collected_outdata,
|
|
||||||
self.__process.stdout))
|
|
||||||
self.__stdout_thread.setDaemon(True)
|
|
||||||
self.__stdout_thread.start()
|
|
||||||
|
|
||||||
if self.__process.stderr:
|
|
||||||
self.__stderr_thread = threading.Thread(
|
|
||||||
name="stderr-thread",
|
|
||||||
target=self.__reader, args=(self.__collected_errdata,
|
|
||||||
self.__process.stderr))
|
|
||||||
self.__stderr_thread.setDaemon(True)
|
|
||||||
self.__stderr_thread.start()
|
|
||||||
|
|
||||||
def pid(self):
|
|
||||||
return self.__process.pid
|
|
||||||
|
|
||||||
def kill(self, signal):
|
|
||||||
self.__process.send_signal(signal)
|
|
||||||
|
|
||||||
# check on subprocess (pass in 'nowait') to act like poll
|
|
||||||
def wait(self, flag):
|
|
||||||
if flag.lower() == 'nowait':
|
|
||||||
rc = self.__process.poll()
|
|
||||||
else:
|
|
||||||
rc = self.__process.wait()
|
|
||||||
if rc != None:
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.closeinput()
|
|
||||||
if self.__process.stdout:
|
|
||||||
self.__stdout_thread.join()
|
|
||||||
if self.__process.stderr:
|
|
||||||
self.__stderr_thread.join()
|
|
||||||
return self.__process.returncode
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
if self.__process.stdin:
|
|
||||||
self.closeinput()
|
|
||||||
self.__process.terminate()
|
|
||||||
|
|
||||||
# thread gets data from subprocess stdout
|
|
||||||
def __reader(self, collector, source):
|
|
||||||
while True:
|
|
||||||
data = os.read(source.fileno(), 65536)
|
|
||||||
self.__lock.acquire()
|
|
||||||
collector.append(data)
|
|
||||||
self.__lock.release()
|
|
||||||
if data == "":
|
|
||||||
source.close()
|
|
||||||
break
|
|
||||||
return
|
|
||||||
|
|
||||||
# thread feeds data to subprocess stdin
|
|
||||||
def __feeder(self, pending, drain):
|
|
||||||
while True:
|
|
||||||
self.__inputsem.acquire()
|
|
||||||
self.__lock.acquire()
|
|
||||||
if not pending and self.__quit:
|
|
||||||
drain.close()
|
|
||||||
self.__lock.release()
|
|
||||||
break
|
|
||||||
data = pending.pop(0)
|
|
||||||
self.__lock.release()
|
|
||||||
drain.write(data)
|
|
||||||
|
|
||||||
# non-blocking read of data from subprocess stdout
|
|
||||||
def read(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
outdata = "".join(self.__collected_outdata)
|
|
||||||
del self.__collected_outdata[:]
|
|
||||||
self.__lock.release()
|
|
||||||
return outdata
|
|
||||||
|
|
||||||
# non-blocking read of data from subprocess stderr
|
|
||||||
def readerr(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
errdata = "".join(self.__collected_errdata)
|
|
||||||
del self.__collected_errdata[:]
|
|
||||||
self.__lock.release()
|
|
||||||
return errdata
|
|
||||||
|
|
||||||
# non-blocking write to stdin of subprocess
|
|
||||||
def write(self, data):
|
|
||||||
if self.__process.stdin is None:
|
|
||||||
raise ValueError("Writing to process with stdin not a pipe")
|
|
||||||
self.__lock.acquire()
|
|
||||||
self.__pending_input.append(data)
|
|
||||||
self.__inputsem.release()
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
# close stdinput of subprocess
|
|
||||||
def closeinput(self):
|
|
||||||
self.__lock.acquire()
|
|
||||||
self.__quit = True
|
|
||||||
self.__inputsem.release()
|
|
||||||
self.__lock.release()
|
|
@ -1,538 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# topazextract.py
|
|
||||||
# Mostly written by some_updates based on code from many others
|
|
||||||
|
|
||||||
# Changelog
|
|
||||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
|
||||||
# 5.0 - Fixed potential unicode problem with command line interface
|
|
||||||
|
|
||||||
__version__ = '5.0'
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os, csv, getopt
|
|
||||||
import zlib, zipfile, tempfile, shutil
|
|
||||||
import traceback
|
|
||||||
from struct import pack
|
|
||||||
from struct import unpack
|
|
||||||
from alfcrypto import Topaz_Cipher
|
|
||||||
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
|
||||||
if isinstance(data,unicode):
|
|
||||||
data = data.encode(self.encoding,"replace")
|
|
||||||
self.stream.write(data)
|
|
||||||
self.stream.flush()
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
return getattr(self.stream, attr)
|
|
||||||
|
|
||||||
iswindows = sys.platform.startswith('win')
|
|
||||||
isosx = sys.platform.startswith('darwin')
|
|
||||||
|
|
||||||
def unicode_argv():
|
|
||||||
if iswindows:
|
|
||||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
|
||||||
# strings.
|
|
||||||
|
|
||||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
|
||||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
|
||||||
# characters with '?'.
|
|
||||||
|
|
||||||
|
|
||||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
|
||||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
|
||||||
GetCommandLineW.argtypes = []
|
|
||||||
GetCommandLineW.restype = LPCWSTR
|
|
||||||
|
|
||||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
|
||||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
|
||||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
|
||||||
|
|
||||||
cmd = GetCommandLineW()
|
|
||||||
argc = c_int(0)
|
|
||||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
|
||||||
if argc.value > 0:
|
|
||||||
# Remove Python executable and commands if present
|
|
||||||
start = argc.value - len(sys.argv)
|
|
||||||
return [argv[i] for i in
|
|
||||||
xrange(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"]
|
|
||||||
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]
|
|
||||||
|
|
||||||
#global switch
|
|
||||||
debug = False
|
|
||||||
|
|
||||||
if 'calibre' in sys.modules:
|
|
||||||
inCalibre = True
|
|
||||||
from calibre_plugins.dedrm import kgenpids
|
|
||||||
else:
|
|
||||||
inCalibre = False
|
|
||||||
import kgenpids
|
|
||||||
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# recursive zip creation support routine
|
|
||||||
def zipUpDir(myzip, tdir, localname):
|
|
||||||
currentdir = tdir
|
|
||||||
if localname != u"":
|
|
||||||
currentdir = os.path.join(currentdir,localname)
|
|
||||||
list = os.listdir(currentdir)
|
|
||||||
for file in list:
|
|
||||||
afilename = file
|
|
||||||
localfilePath = os.path.join(localname, afilename)
|
|
||||||
realfilePath = os.path.join(currentdir,file)
|
|
||||||
if os.path.isfile(realfilePath):
|
|
||||||
myzip.write(realfilePath, localfilePath)
|
|
||||||
elif os.path.isdir(realfilePath):
|
|
||||||
zipUpDir(myzip, tdir, localfilePath)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Utility routines
|
|
||||||
#
|
|
||||||
|
|
||||||
# Get a 7 bit encoded number from file
|
|
||||||
def bookReadEncodedNumber(fo):
|
|
||||||
flag = False
|
|
||||||
data = ord(fo.read(1))
|
|
||||||
if data == 0xFF:
|
|
||||||
flag = True
|
|
||||||
data = ord(fo.read(1))
|
|
||||||
if data >= 0x80:
|
|
||||||
datax = (data & 0x7F)
|
|
||||||
while data >= 0x80 :
|
|
||||||
data = ord(fo.read(1))
|
|
||||||
datax = (datax <<7) + (data & 0x7F)
|
|
||||||
data = datax
|
|
||||||
if flag:
|
|
||||||
data = -data
|
|
||||||
return data
|
|
||||||
|
|
||||||
# Get a length prefixed string from file
|
|
||||||
def bookReadString(fo):
|
|
||||||
stringLength = bookReadEncodedNumber(fo)
|
|
||||||
return unpack(str(stringLength)+'s',fo.read(stringLength))[0]
|
|
||||||
|
|
||||||
#
|
|
||||||
# crypto routines
|
|
||||||
#
|
|
||||||
|
|
||||||
# Context initialisation for the Topaz Crypto
|
|
||||||
def topazCryptoInit(key):
|
|
||||||
return Topaz_Cipher().ctx_init(key)
|
|
||||||
|
|
||||||
# ctx1 = 0x0CAFFE19E
|
|
||||||
# for keyChar in key:
|
|
||||||
# keyByte = ord(keyChar)
|
|
||||||
# ctx2 = ctx1
|
|
||||||
# ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
|
|
||||||
# return [ctx1,ctx2]
|
|
||||||
|
|
||||||
# decrypt data with the context prepared by topazCryptoInit()
|
|
||||||
def topazCryptoDecrypt(data, ctx):
|
|
||||||
return Topaz_Cipher().decrypt(data, ctx)
|
|
||||||
# ctx1 = ctx[0]
|
|
||||||
# ctx2 = ctx[1]
|
|
||||||
# plainText = ""
|
|
||||||
# for dataChar in data:
|
|
||||||
# dataByte = ord(dataChar)
|
|
||||||
# m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
|
|
||||||
# ctx2 = ctx1
|
|
||||||
# ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
|
|
||||||
# plainText += chr(m)
|
|
||||||
# return plainText
|
|
||||||
|
|
||||||
# Decrypt data with the PID
|
|
||||||
def decryptRecord(data,PID):
|
|
||||||
ctx = topazCryptoInit(PID)
|
|
||||||
return topazCryptoDecrypt(data, ctx)
|
|
||||||
|
|
||||||
# Try to decrypt a dkey record (contains the bookPID)
|
|
||||||
def decryptDkeyRecord(data,PID):
|
|
||||||
record = decryptRecord(data,PID)
|
|
||||||
fields = unpack('3sB8sB8s3s',record)
|
|
||||||
if fields[0] != 'PID' or fields[5] != 'pid' :
|
|
||||||
raise DrmException(u"Didn't find PID magic numbers in record")
|
|
||||||
elif fields[1] != 8 or fields[3] != 8 :
|
|
||||||
raise DrmException(u"Record didn't contain correct length fields")
|
|
||||||
elif fields[2] != PID :
|
|
||||||
raise DrmException(u"Record didn't contain PID")
|
|
||||||
return fields[4]
|
|
||||||
|
|
||||||
# Decrypt all dkey records (contain the book PID)
|
|
||||||
def decryptDkeyRecords(data,PID):
|
|
||||||
nbKeyRecords = ord(data[0])
|
|
||||||
records = []
|
|
||||||
data = data[1:]
|
|
||||||
for i in range (0,nbKeyRecords):
|
|
||||||
length = ord(data[0])
|
|
||||||
try:
|
|
||||||
key = decryptDkeyRecord(data[1:length+1],PID)
|
|
||||||
records.append(key)
|
|
||||||
except DrmException:
|
|
||||||
pass
|
|
||||||
data = data[1+length:]
|
|
||||||
if len(records) == 0:
|
|
||||||
raise DrmException(u"BookKey Not Found")
|
|
||||||
return records
|
|
||||||
|
|
||||||
|
|
||||||
class TopazBook:
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.fo = file(filename, 'rb')
|
|
||||||
self.outdir = tempfile.mkdtemp()
|
|
||||||
# self.outdir = 'rawdat'
|
|
||||||
self.bookPayloadOffset = 0
|
|
||||||
self.bookHeaderRecords = {}
|
|
||||||
self.bookMetadata = {}
|
|
||||||
self.bookKey = None
|
|
||||||
magic = unpack('4s',self.fo.read(4))[0]
|
|
||||||
if magic != 'TPZ0':
|
|
||||||
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
|
|
||||||
self.parseTopazHeaders()
|
|
||||||
self.parseMetadata()
|
|
||||||
|
|
||||||
def parseTopazHeaders(self):
|
|
||||||
def bookReadHeaderRecordData():
|
|
||||||
# Read and return the data of one header record at the current book file position
|
|
||||||
# [[offset,decompressedLength,compressedLength],...]
|
|
||||||
nbValues = bookReadEncodedNumber(self.fo)
|
|
||||||
if debug: print "%d records in header " % nbValues,
|
|
||||||
values = []
|
|
||||||
for i in range (0,nbValues):
|
|
||||||
values.append([bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo),bookReadEncodedNumber(self.fo)])
|
|
||||||
return values
|
|
||||||
def parseTopazHeaderRecord():
|
|
||||||
# Read and parse one header record at the current book file position and return the associated data
|
|
||||||
# [[offset,decompressedLength,compressedLength],...]
|
|
||||||
if ord(self.fo.read(1)) != 0x63:
|
|
||||||
raise DrmException(u"Parse Error : Invalid Header")
|
|
||||||
tag = bookReadString(self.fo)
|
|
||||||
record = bookReadHeaderRecordData()
|
|
||||||
return [tag,record]
|
|
||||||
nbRecords = bookReadEncodedNumber(self.fo)
|
|
||||||
if debug: print "Headers: %d" % nbRecords
|
|
||||||
for i in range (0,nbRecords):
|
|
||||||
result = parseTopazHeaderRecord()
|
|
||||||
if debug: print result[0], ": ", result[1]
|
|
||||||
self.bookHeaderRecords[result[0]] = result[1]
|
|
||||||
if ord(self.fo.read(1)) != 0x64 :
|
|
||||||
raise DrmException(u"Parse Error : Invalid Header")
|
|
||||||
self.bookPayloadOffset = self.fo.tell()
|
|
||||||
|
|
||||||
def parseMetadata(self):
|
|
||||||
# Parse the metadata record from the book payload and return a list of [key,values]
|
|
||||||
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
|
|
||||||
tag = bookReadString(self.fo)
|
|
||||||
if tag != 'metadata' :
|
|
||||||
raise DrmException(u"Parse Error : Record Names Don't Match")
|
|
||||||
flags = ord(self.fo.read(1))
|
|
||||||
nbRecords = ord(self.fo.read(1))
|
|
||||||
if debug: print "Metadata Records: %d" % nbRecords
|
|
||||||
for i in range (0,nbRecords) :
|
|
||||||
keyval = bookReadString(self.fo)
|
|
||||||
content = bookReadString(self.fo)
|
|
||||||
if debug: print keyval
|
|
||||||
if debug: print content
|
|
||||||
self.bookMetadata[keyval] = content
|
|
||||||
return self.bookMetadata
|
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
|
||||||
keysRecord = self.bookMetadata.get('keys','')
|
|
||||||
keysRecordRecord = ''
|
|
||||||
if keysRecord != '':
|
|
||||||
keylst = keysRecord.split(',')
|
|
||||||
for keyval in keylst:
|
|
||||||
keysRecordRecord += self.bookMetadata.get(keyval,'')
|
|
||||||
return keysRecord, keysRecordRecord
|
|
||||||
|
|
||||||
def getBookTitle(self):
|
|
||||||
title = ''
|
|
||||||
if 'Title' in self.bookMetadata:
|
|
||||||
title = self.bookMetadata['Title']
|
|
||||||
return title.decode('utf-8')
|
|
||||||
|
|
||||||
def setBookKey(self, key):
|
|
||||||
self.bookKey = key
|
|
||||||
|
|
||||||
def getBookPayloadRecord(self, name, index):
|
|
||||||
# Get a record in the book payload, given its name and index.
|
|
||||||
# decrypted and decompressed if necessary
|
|
||||||
encrypted = False
|
|
||||||
compressed = False
|
|
||||||
try:
|
|
||||||
recordOffset = self.bookHeaderRecords[name][index][0]
|
|
||||||
except:
|
|
||||||
raise DrmException("Parse Error : Invalid Record, record not found")
|
|
||||||
|
|
||||||
self.fo.seek(self.bookPayloadOffset + recordOffset)
|
|
||||||
|
|
||||||
tag = bookReadString(self.fo)
|
|
||||||
if tag != name :
|
|
||||||
raise DrmException("Parse Error : Invalid Record, record name doesn't match")
|
|
||||||
|
|
||||||
recordIndex = bookReadEncodedNumber(self.fo)
|
|
||||||
if recordIndex < 0 :
|
|
||||||
encrypted = True
|
|
||||||
recordIndex = -recordIndex -1
|
|
||||||
|
|
||||||
if recordIndex != index :
|
|
||||||
raise DrmException("Parse Error : Invalid Record, index doesn't match")
|
|
||||||
|
|
||||||
if (self.bookHeaderRecords[name][index][2] > 0):
|
|
||||||
compressed = True
|
|
||||||
record = self.fo.read(self.bookHeaderRecords[name][index][2])
|
|
||||||
else:
|
|
||||||
record = self.fo.read(self.bookHeaderRecords[name][index][1])
|
|
||||||
|
|
||||||
if encrypted:
|
|
||||||
if self.bookKey:
|
|
||||||
ctx = topazCryptoInit(self.bookKey)
|
|
||||||
record = topazCryptoDecrypt(record,ctx)
|
|
||||||
else :
|
|
||||||
raise DrmException("Error: Attempt to decrypt without bookKey")
|
|
||||||
|
|
||||||
if compressed:
|
|
||||||
record = zlib.decompress(record)
|
|
||||||
|
|
||||||
return record
|
|
||||||
|
|
||||||
def processBook(self, pidlst):
|
|
||||||
raw = 0
|
|
||||||
fixedimage=True
|
|
||||||
try:
|
|
||||||
keydata = self.getBookPayloadRecord('dkey', 0)
|
|
||||||
except DrmException, e:
|
|
||||||
print u"no dkey record found, book may not be encrypted"
|
|
||||||
print u"attempting to extrct files without a book key"
|
|
||||||
self.createBookDirectory()
|
|
||||||
self.extractFiles()
|
|
||||||
print u"Successfully Extracted Topaz contents"
|
|
||||||
if inCalibre:
|
|
||||||
from calibre_plugins.dedrm import genbook
|
|
||||||
else:
|
|
||||||
import genbook
|
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
|
||||||
if rv == 0:
|
|
||||||
print u"Book Successfully generated."
|
|
||||||
return rv
|
|
||||||
|
|
||||||
# try each pid to decode the file
|
|
||||||
bookKey = None
|
|
||||||
for pid in pidlst:
|
|
||||||
# use 8 digit pids here
|
|
||||||
pid = pid[0:8]
|
|
||||||
print u"Trying: {0}".format(pid)
|
|
||||||
bookKeys = []
|
|
||||||
data = keydata
|
|
||||||
try:
|
|
||||||
bookKeys+=decryptDkeyRecords(data,pid)
|
|
||||||
except DrmException, e:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
bookKey = bookKeys[0]
|
|
||||||
print u"Book Key Found! ({0})".format(bookKey.encode('hex'))
|
|
||||||
break
|
|
||||||
|
|
||||||
if not bookKey:
|
|
||||||
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
|
|
||||||
|
|
||||||
self.setBookKey(bookKey)
|
|
||||||
self.createBookDirectory()
|
|
||||||
self.extractFiles()
|
|
||||||
print u"Successfully Extracted Topaz contents"
|
|
||||||
if inCalibre:
|
|
||||||
from calibre_plugins.dedrm import genbook
|
|
||||||
else:
|
|
||||||
import genbook
|
|
||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
|
||||||
if rv == 0:
|
|
||||||
print u"Book Successfully generated"
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def createBookDirectory(self):
|
|
||||||
outdir = self.outdir
|
|
||||||
# create output directory structure
|
|
||||||
if not os.path.exists(outdir):
|
|
||||||
os.makedirs(outdir)
|
|
||||||
destdir = os.path.join(outdir,u"img")
|
|
||||||
if not os.path.exists(destdir):
|
|
||||||
os.makedirs(destdir)
|
|
||||||
destdir = os.path.join(outdir,u"color_img")
|
|
||||||
if not os.path.exists(destdir):
|
|
||||||
os.makedirs(destdir)
|
|
||||||
destdir = os.path.join(outdir,u"page")
|
|
||||||
if not os.path.exists(destdir):
|
|
||||||
os.makedirs(destdir)
|
|
||||||
destdir = os.path.join(outdir,u"glyphs")
|
|
||||||
if not os.path.exists(destdir):
|
|
||||||
os.makedirs(destdir)
|
|
||||||
|
|
||||||
def extractFiles(self):
|
|
||||||
outdir = self.outdir
|
|
||||||
for headerRecord in self.bookHeaderRecords:
|
|
||||||
name = headerRecord
|
|
||||||
if name != 'dkey':
|
|
||||||
ext = u".dat"
|
|
||||||
if name == 'img': ext = u".jpg"
|
|
||||||
if name == 'color' : ext = u".jpg"
|
|
||||||
print u"Processing Section: {0}\n. . .".format(name),
|
|
||||||
for index in range (0,len(self.bookHeaderRecords[name])) :
|
|
||||||
fname = u"{0}{1:04d}{2}".format(name,index,ext)
|
|
||||||
destdir = outdir
|
|
||||||
if name == 'img':
|
|
||||||
destdir = os.path.join(outdir,u"img")
|
|
||||||
if name == 'color':
|
|
||||||
destdir = os.path.join(outdir,u"color_img")
|
|
||||||
if name == 'page':
|
|
||||||
destdir = os.path.join(outdir,u"page")
|
|
||||||
if name == 'glyphs':
|
|
||||||
destdir = os.path.join(outdir,u"glyphs")
|
|
||||||
outputFile = os.path.join(destdir,fname)
|
|
||||||
print u".",
|
|
||||||
record = self.getBookPayloadRecord(name,index)
|
|
||||||
if record != '':
|
|
||||||
file(outputFile, 'wb').write(record)
|
|
||||||
print u" "
|
|
||||||
|
|
||||||
def getFile(self, zipname):
|
|
||||||
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
|
||||||
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
|
|
||||||
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
|
|
||||||
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
|
|
||||||
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
|
|
||||||
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
|
|
||||||
zipUpDir(htmlzip, self.outdir, u"img")
|
|
||||||
htmlzip.close()
|
|
||||||
|
|
||||||
def getBookType(self):
|
|
||||||
return u"Topaz"
|
|
||||||
|
|
||||||
def getBookExtension(self):
|
|
||||||
return u".htmlz"
|
|
||||||
|
|
||||||
def getSVGZip(self, zipname):
|
|
||||||
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
|
||||||
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
|
|
||||||
zipUpDir(svgzip, self.outdir, u"svg")
|
|
||||||
zipUpDir(svgzip, self.outdir, u"img")
|
|
||||||
svgzip.close()
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
if os.path.isdir(self.outdir):
|
|
||||||
shutil.rmtree(self.outdir, True)
|
|
||||||
|
|
||||||
def usage(progname):
|
|
||||||
print u"Removes DRM protection from Topaz ebooks and extracts the contents"
|
|
||||||
print u"Usage:"
|
|
||||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
|
|
||||||
|
|
||||||
# Main
|
|
||||||
def cli_main():
|
|
||||||
argv=unicode_argv()
|
|
||||||
progname = os.path.basename(argv[0])
|
|
||||||
print u"TopazExtract v{0}.".format(__version__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
|
||||||
except getopt.GetoptError, err:
|
|
||||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
|
||||||
usage(progname)
|
|
||||||
return 1
|
|
||||||
if len(args)<2:
|
|
||||||
usage(progname)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
infile = args[0]
|
|
||||||
outdir = args[1]
|
|
||||||
if not os.path.isfile(infile):
|
|
||||||
print u"Input File {0} Does Not Exist.".format(infile)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if not os.path.exists(outdir):
|
|
||||||
print u"Output Directory {0} Does Not Exist.".format(outdir)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
kDatabaseFiles = []
|
|
||||||
serials = []
|
|
||||||
pids = []
|
|
||||||
|
|
||||||
for o, a in opts:
|
|
||||||
if o == '-k':
|
|
||||||
if a == None :
|
|
||||||
raise DrmException("Invalid parameter for -k")
|
|
||||||
kDatabaseFiles.append(a)
|
|
||||||
if o == '-p':
|
|
||||||
if a == None :
|
|
||||||
raise DrmException("Invalid parameter for -p")
|
|
||||||
pids = a.split(',')
|
|
||||||
if o == '-s':
|
|
||||||
if a == None :
|
|
||||||
raise DrmException("Invalid parameter for -s")
|
|
||||||
serials = [serial.replace(" ","") for serial in a.split(',')]
|
|
||||||
|
|
||||||
bookname = os.path.splitext(os.path.basename(infile))[0]
|
|
||||||
|
|
||||||
tb = TopazBook(infile)
|
|
||||||
title = tb.getBookTitle()
|
|
||||||
print u"Processing Book: {0}".format(title)
|
|
||||||
md1, md2 = tb.getPIDMetaInfo()
|
|
||||||
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
|
||||||
|
|
||||||
try:
|
|
||||||
print u"Decrypting Book"
|
|
||||||
tb.processBook(pids)
|
|
||||||
|
|
||||||
print u" Creating HTML ZIP Archive"
|
|
||||||
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
|
|
||||||
tb.getFile(zipname)
|
|
||||||
|
|
||||||
print u" Creating SVG ZIP Archive"
|
|
||||||
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
|
|
||||||
tb.getSVGZip(zipname)
|
|
||||||
|
|
||||||
# removing internal temporary directory of pieces
|
|
||||||
tb.cleanup()
|
|
||||||
|
|
||||||
except DrmException, e:
|
|
||||||
print u"Decryption failed\n{0}".format(traceback.format_exc())
|
|
||||||
|
|
||||||
try:
|
|
||||||
tb.cleanup()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return 1
|
|
||||||
|
|
||||||
except Exception, e:
|
|
||||||
print u"Decryption failed\m{0}".format(traceback.format_exc())
|
|
||||||
try:
|
|
||||||
tb.cleanup()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
sys.exit(cli_main())
|
|
@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
|
|
||||||
DETAILED_MESSAGE = \
|
|
||||||
'You have personal information stored in this plugin\'s customization '+ \
|
|
||||||
'string from a previous version of this plugin.\n\n'+ \
|
|
||||||
'This new version of the plugin can convert that info '+ \
|
|
||||||
'into key data that the new plugin can then use (which doesn\'t '+ \
|
|
||||||
'require personal information to be stored/displayed in an insecure '+ \
|
|
||||||
'manner like the old plugin did).\n\nIf you choose NOT to migrate this data at this time '+ \
|
|
||||||
'you will be prompted to save that personal data to a file elsewhere; and you\'ll have '+ \
|
|
||||||
'to manually re-configure this plugin with your information.\n\nEither way... ' + \
|
|
||||||
'this new version of the plugin will not be responsible for storing that personal '+ \
|
|
||||||
'info in plain sight any longer.'
|
|
||||||
|
|
||||||
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)
|
|
||||||
if caseless:
|
|
||||||
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
|
|
||||||
else:
|
|
||||||
return ud.normalize('NFC', str1) == ud.normalize('NFC', str2)
|
|
||||||
|
|
||||||
def parseCustString(keystuff):
|
|
||||||
userkeys = []
|
|
||||||
ar = keystuff.split(':')
|
|
||||||
for i in ar:
|
|
||||||
try:
|
|
||||||
name, ccn = i.split(',')
|
|
||||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
|
||||||
userkeys.append(generate_key(name, ccn))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return userkeys
|
|
@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
|
|
||||||
# Standard Python modules.
|
|
||||||
import os, sys, re, hashlib
|
|
||||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
|
||||||
|
|
||||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|
||||||
import subprocess
|
|
||||||
from subprocess import Popen, PIPE, STDOUT
|
|
||||||
|
|
||||||
import subasyncio
|
|
||||||
from subasyncio import Process
|
|
||||||
|
|
||||||
if extension == u".k4i":
|
|
||||||
import json
|
|
||||||
|
|
||||||
basepath, script = os.path.split(scriptpath)
|
|
||||||
print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)
|
|
||||||
|
|
||||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
|
||||||
if not os.path.exists(outdirpath):
|
|
||||||
os.makedirs(outdirpath)
|
|
||||||
|
|
||||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
|
||||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
|
||||||
else:
|
|
||||||
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
|
||||||
print u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
|
||||||
|
|
||||||
try:
|
|
||||||
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:
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
|
||||||
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
|
||||||
|
|
||||||
try:
|
|
||||||
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:
|
|
||||||
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
|
|
||||||
winekeys = []
|
|
||||||
# get any files with extension in the output dir
|
|
||||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
|
||||||
for filename in files:
|
|
||||||
try:
|
|
||||||
fpath = os.path.join(outdirpath, filename)
|
|
||||||
with open(fpath, 'rb') as keyfile:
|
|
||||||
if extension == u".k4i":
|
|
||||||
new_key_value = json.loads(keyfile.read())
|
|
||||||
else:
|
|
||||||
new_key_value = keyfile.read()
|
|
||||||
winekeys.append(new_key_value)
|
|
||||||
except:
|
|
||||||
print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
|
|
||||||
traceback.print_exc()
|
|
||||||
os.remove(fpath)
|
|
||||||
print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")
|
|
||||||
return winekeys
|
|
File diff suppressed because it is too large
Load Diff
@ -1,188 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# zipfix.py, version 1.1
|
|
||||||
# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf
|
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
|
||||||
# <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
# Revision history:
|
|
||||||
# 1.0 - Initial release
|
|
||||||
# 1.1 - Updated to handle zip file metadata correctly
|
|
||||||
|
|
||||||
"""
|
|
||||||
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
|
||||||
"""
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__version__ = "1.1"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import zlib
|
|
||||||
import zipfilerugged
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import getopt
|
|
||||||
from struct import unpack
|
|
||||||
|
|
||||||
|
|
||||||
_FILENAME_LEN_OFFSET = 26
|
|
||||||
_EXTRA_LEN_OFFSET = 28
|
|
||||||
_FILENAME_OFFSET = 30
|
|
||||||
_MAX_SIZE = 64 * 1024
|
|
||||||
_MIMETYPE = 'application/epub+zip'
|
|
||||||
|
|
||||||
class ZipInfo(zipfilerugged.ZipInfo):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if 'compress_type' in kwargs:
|
|
||||||
compress_type = kwargs.pop('compress_type')
|
|
||||||
super(ZipInfo, self).__init__(*args, **kwargs)
|
|
||||||
self.compress_type = compress_type
|
|
||||||
|
|
||||||
class fixZip:
|
|
||||||
def __init__(self, zinput, zoutput):
|
|
||||||
self.ztype = 'zip'
|
|
||||||
if zinput.lower().find('.epub') >= 0 :
|
|
||||||
self.ztype = 'epub'
|
|
||||||
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')
|
|
||||||
|
|
||||||
def getlocalname(self, zi):
|
|
||||||
local_header_offset = zi.header_offset
|
|
||||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
|
||||||
leninfo = self.bzf.read(2)
|
|
||||||
local_name_length, = unpack('<H', leninfo)
|
|
||||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
|
||||||
local_name = self.bzf.read(local_name_length)
|
|
||||||
return local_name
|
|
||||||
|
|
||||||
def uncompress(self, cmpdata):
|
|
||||||
dc = zlib.decompressobj(-15)
|
|
||||||
data = ''
|
|
||||||
while len(cmpdata) > 0:
|
|
||||||
if len(cmpdata) > _MAX_SIZE :
|
|
||||||
newdata = cmpdata[0:_MAX_SIZE]
|
|
||||||
cmpdata = cmpdata[_MAX_SIZE:]
|
|
||||||
else:
|
|
||||||
newdata = cmpdata
|
|
||||||
cmpdata = ''
|
|
||||||
newdata = dc.decompress(newdata)
|
|
||||||
unprocessed = dc.unconsumed_tail
|
|
||||||
if len(unprocessed) == 0:
|
|
||||||
newdata += dc.flush()
|
|
||||||
data += newdata
|
|
||||||
cmpdata += unprocessed
|
|
||||||
unprocessed = ''
|
|
||||||
return data
|
|
||||||
|
|
||||||
def getfiledata(self, zi):
|
|
||||||
# get file name length and exta data length to find start of file data
|
|
||||||
local_header_offset = zi.header_offset
|
|
||||||
|
|
||||||
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
|
||||||
leninfo = self.bzf.read(2)
|
|
||||||
local_name_length, = unpack('<H', leninfo)
|
|
||||||
|
|
||||||
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
|
||||||
exinfo = self.bzf.read(2)
|
|
||||||
extra_field_length, = unpack('<H', exinfo)
|
|
||||||
|
|
||||||
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
|
||||||
data = None
|
|
||||||
|
|
||||||
# if not compressed we are good to go
|
|
||||||
if zi.compress_type == zipfilerugged.ZIP_STORED:
|
|
||||||
data = self.bzf.read(zi.file_size)
|
|
||||||
|
|
||||||
# if compressed we must decompress it using zlib
|
|
||||||
if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
|
|
||||||
cmpdata = self.bzf.read(zi.compress_size)
|
|
||||||
data = self.uncompress(cmpdata)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def fix(self):
|
|
||||||
# get the zipinfo for each member of the input archive
|
|
||||||
# and copy member over to output archive
|
|
||||||
# if problems exist with local vs central filename, fix them
|
|
||||||
|
|
||||||
# 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.internal_attr = 1 # text file
|
|
||||||
try:
|
|
||||||
# if the mimetype is present, get its info, including time-stamp
|
|
||||||
oldmimeinfo = self.inzip.getinfo('mimetype')
|
|
||||||
# copy across useful fields
|
|
||||||
mimeinfo.date_time = oldmimeinfo.date_time
|
|
||||||
mimeinfo.comment = oldmimeinfo.comment
|
|
||||||
mimeinfo.extra = oldmimeinfo.extra
|
|
||||||
mimeinfo.internal_attr = oldmimeinfo.internal_attr
|
|
||||||
mimeinfo.external_attr = oldmimeinfo.external_attr
|
|
||||||
mimeinfo.create_system = oldmimeinfo.create_system
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.outzip.writestr(mimeinfo, _MIMETYPE)
|
|
||||||
|
|
||||||
# write the rest of the files
|
|
||||||
for zinfo in self.inzip.infolist():
|
|
||||||
if zinfo.filename != "mimetype" or self.ztype != 'epub':
|
|
||||||
data = None
|
|
||||||
try:
|
|
||||||
data = self.inzip.read(zinfo.filename)
|
|
||||||
except zipfilerugged.BadZipfile or zipfilerugged.error:
|
|
||||||
local_name = self.getlocalname(zinfo)
|
|
||||||
data = self.getfiledata(zinfo)
|
|
||||||
zinfo.filename = local_name
|
|
||||||
|
|
||||||
# create new ZipInfo with only the useful attributes from the old info
|
|
||||||
nzinfo = ZipInfo(zinfo.filename, zinfo.date_time, compress_type=zinfo.compress_type)
|
|
||||||
nzinfo.comment=zinfo.comment
|
|
||||||
nzinfo.extra=zinfo.extra
|
|
||||||
nzinfo.internal_attr=zinfo.internal_attr
|
|
||||||
nzinfo.external_attr=zinfo.external_attr
|
|
||||||
nzinfo.create_system=zinfo.create_system
|
|
||||||
self.outzip.writestr(nzinfo,data)
|
|
||||||
|
|
||||||
self.bzf.close()
|
|
||||||
self.inzip.close()
|
|
||||||
self.outzip.close()
|
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print """usage: zipfix.py inputzip outputzip
|
|
||||||
inputzip is the source zipfile to fix
|
|
||||||
outputzip is the fixed zip archive
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def repairBook(infile, outfile):
|
|
||||||
if not os.path.exists(infile):
|
|
||||||
print "Error: Input Zip File does not exist"
|
|
||||||
return 1
|
|
||||||
try:
|
|
||||||
fr = fixZip(infile, outfile)
|
|
||||||
fr.fix()
|
|
||||||
return 0
|
|
||||||
except Exception, e:
|
|
||||||
print "Error Occurred ", e
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
|
||||||
if len(argv)!=3:
|
|
||||||
usage()
|
|
||||||
return 1
|
|
||||||
infile = argv[1]
|
|
||||||
outfile = argv[2]
|
|
||||||
return repairBook(infile, outfile)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__' :
|
|
||||||
sys.exit(main())
|
|
Loading…
Reference in New Issue