]> xmof Git - DeDRM.git/commitdiff
tools v5.0
authorApprentice Alf <apprenticealf@gmail.com>
Tue, 6 Mar 2012 18:24:28 +0000 (18:24 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Fri, 6 Mar 2015 07:43:33 +0000 (07:43 +0000)
Introduction of alfcrypto library for speed
Reorganisation of archive plugins,apps,other

112 files changed:
Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py
Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py
Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py
Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py
Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py
Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py
Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py
Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt
Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py
Calibre_Plugins/K4MobiDeDRM_plugin/topazextract.py
Calibre_Plugins/k4mobidedrm_plugin.zip
Calibre_Plugins/k4mobidedrm_plugin/k4mutils.py
Calibre_Plugins/k4mobidedrm_plugin/k4pcutils.py
Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/aescbc.py [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/erdr2pml.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptkey.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mdumpkinfo.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mutils.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4pcutils.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto src.zip [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm_orig.py [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pbkdf2.py [new file with mode: 0644]
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/subasyncio.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/zipfix.py
DeDRM_Macintosh_Application/ReadMe_DeDRM.app.rtf [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/DeDRM_app.pyw
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/activitybar.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/aescbc.py [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.dll [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.exp [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.py [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto64.dll [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto_src.zip [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/convert2xml.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptepub.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdb.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/decryptpdf.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/erdr2pml.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/flatxml2html.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/flatxml2svg.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/genbook.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ignobleepub.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ignoblekeygen.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptepub.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptkey.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/ineptpdf.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mutils.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4pcutils.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/openssl_des.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/pbkdf2.py [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/pycrypto_des.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/python_des.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/simpleprefs.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/stylexml2css.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/subasyncio.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/zipfix.py
DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt
Other_Tools/Additional_Tools/FindTopazEbooks.pyw
Other_Tools/Additional_Tools/lib/mobidedrm.py
Other_Tools/Adobe_PDF_Tools/ineptkey.pyw
Other_Tools/Adobe_PDF_Tools/ineptpdf.pyw
Other_Tools/Adobe_ePub_Tools/ineptepub.pyw
Other_Tools/Adobe_ePub_Tools/ineptkey.pyw
Other_Tools/Barnes_and_Noble_EPUB_Tools/ignobleepub.pyw
Other_Tools/Barnes_and_Noble_EPUB_Tools/ignoblekeygen.pyw
Other_Tools/KindleBooks/lib/aescbc.py [new file with mode: 0644]
Other_Tools/KindleBooks/lib/alfcrypto.dll [new file with mode: 0644]
Other_Tools/KindleBooks/lib/alfcrypto.py [new file with mode: 0644]
Other_Tools/KindleBooks/lib/alfcrypto64.dll [new file with mode: 0644]
Other_Tools/KindleBooks/lib/alfcrypto_src.zip [new file with mode: 0644]
Other_Tools/KindleBooks/lib/convert2xml.py
Other_Tools/KindleBooks/lib/flatxml2html.py
Other_Tools/KindleBooks/lib/flatxml2svg.py
Other_Tools/KindleBooks/lib/genbook.py
Other_Tools/KindleBooks/lib/k4mobidedrm.py
Other_Tools/KindleBooks/lib/k4mutils.py
Other_Tools/KindleBooks/lib/k4pcutils.py
Other_Tools/KindleBooks/lib/libalfcrypto.dylib [new file with mode: 0644]
Other_Tools/KindleBooks/lib/mobidedrm.py
Other_Tools/KindleBooks/lib/stylexml2css.py
Other_Tools/KindleBooks/lib/subasyncio.py
Other_Tools/KindleBooks/lib/topazextract.py
Other_Tools/README_Kindle_for_iPad_iPhone_iPodTouch.txt
Other_Tools/ePub_Fixer/lib/zipfix.py
Other_Tools/eReader_PDB_Tools/lib/erdr2pml.py
Other_Tools/eReader_PDB_Tools/lib/openssl_des.py
Other_Tools/eReader_PDB_Tools/lib/pycrypto_des.py
ReadMe_First.txt

index 30c1e132ab0afb4e0901668eed8720a7e3d89e19..233b46255bd68c1918771e97d93c17b50f05564d 100644 (file)
@@ -4,28 +4,62 @@ from __future__ import with_statement
 
 from calibre.customize import FileTypePlugin
 from calibre.gui2 import is_ok_to_use_qt
+from calibre.utils.config import config_dir
+from calibre.constants import iswindows, isosx
 # from calibre.ptempfile import PersistentTemporaryDirectory
 
-from calibre_plugins.k4mobidedrm import kgenpids
-from calibre_plugins.k4mobidedrm import topazextract
-from calibre_plugins.k4mobidedrm import mobidedrm
 
 import sys
 import os
 import re
+from zipfile import ZipFile
 
 class K4DeDRM(FileTypePlugin):
     name                = 'K4PC, K4Mac, Kindle Mobi and Topaz DeDRM' # Name of the plugin
     description         = 'Removes DRM from Mobipocket, Kindle/Mobi, Kindle/Topaz and Kindle/Print Replica files. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
     supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
     author              = 'DiapDealer, SomeUpdates' # The author of this plugin
-    version             = (0, 3, 8)   # The version number of this plugin
+    version             = (0, 4, 1)   # The version number of this plugin
     file_types          = set(['prc','mobi','azw','azw1','azw4','tpz']) # The file types that this plugin will be applied to
     on_import           = True # Run this plugin during the import
     priority            = 210  # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
     minimum_calibre_version = (0, 7, 55)
+    
+    def initialize(self):
+        """
+        Dynamic modules can't be imported/loaded from a zipfile... so this routine
+        runs whenever the plugin gets initialized. This 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.
+        """
+        if iswindows:
+            names = ['alfcrypto.dll','alfcrypto64.dll']
+        elif isosx:
+            names = ['libalfcrypto.dylib']
+        else:
+            names = ['libalfcrypto32.so','libalfcrypto64.so']
+        lib_dict = self.load_resources(names)
+        self.alfdir = os.path.join(config_dir, 'alfcrypto')
+        if not os.path.exists(self.alfdir):
+            os.mkdir(self.alfdir)
+        for entry, data in lib_dict.items():
+            file_path = os.path.join(self.alfdir, entry)
+            with open(file_path,'wb') as f:
+                f.write(data)
 
     def run(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 these imports here so the custom libs can be
+        # extracted to the appropriate places beforehand these routines
+        # look for them.
+        from calibre_plugins.k4mobidedrm import kgenpids
+        from calibre_plugins.k4mobidedrm import topazextract
+        from calibre_plugins.k4mobidedrm import mobidedrm
+
         plug_ver = '.'.join(str(self.version).strip('()').replace(' ', '').split(','))
         k4 = True
         if sys.platform.startswith('linux'):
@@ -45,7 +79,7 @@ class K4DeDRM(FileTypePlugin):
                     serials.append(customvalue)
                 else:
                     print "%s is not a valid Kindle serial number or PID." % str(customvalue)
-                        
+
         # Load any kindle info files (*.info) included Calibre's config directory.
         try:
             # Find Calibre's configuration directory.
@@ -77,7 +111,7 @@ class K4DeDRM(FileTypePlugin):
 
         title = mb.getBookTitle()
         md1, md2 = mb.getPIDMetaInfo()
-        pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) 
+        pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
 
         try:
             mb.processBook(pidlst)
@@ -94,11 +128,11 @@ class K4DeDRM(FileTypePlugin):
         except topazextract.TpzDRMError, e:
             #if you reached here then no luck raise and exception
             if is_ok_to_use_qt():
-                    from PyQt4.Qt import QMessageBox
-                    d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM v%s Plugin" % plug_ver, "Error: " + str(e) + "... %s\n" % path_to_ebook)
-                    d.show()
-                    d.raise_()
-                    d.exec_()
+                from PyQt4.Qt import QMessageBox
+                d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM v%s Plugin" % plug_ver, "Error: " + str(e) + "... %s\n" % path_to_ebook)
+                d.show()
+                d.raise_()
+                d.exec_()
             raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e)))
 
         print "Success!"
@@ -117,3 +151,11 @@ class K4DeDRM(FileTypePlugin):
 
     def customization_help(self, gui=False):
         return 'Enter 10 character PIDs and/or Kindle serial numbers, use a comma (no spaces) to separate each PID or SerialNumber from the next.'
+
+    def load_resources(self, names):
+        ans = {}
+        with ZipFile(self.plugin_path, 'r') as zf:
+            for candidate in zf.namelist():
+                if candidate in names:
+                    ans[candidate] = zf.read(candidate)
+        return ans
\ No newline at end of file
index 0328206ac8b4d8ac5725f85e62cdc7b71febe53b..566751130103e0bd0b7a6961c8a9638c8fc3a5be 100644 (file)
-#! /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('&','&amp;')
-        str = str.replace('<','&lt;')
-        str = str.replace('>','&gt;')
-        str = str.replace('=','&#61;')
-        return str
-
-    def lookup(self,val):
-        if ((val >= 0) and (val < self.size)) :
-            self.pos = val
-            return self.stable[self.pos]
+#! /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:
-            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.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),
-
-        '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.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.type'      : (1, 'scalar_text', 0, 0),
-        'group._tag'      : (1, 'scalar_text', 0, 0),
-
-        'region'           : (1, 'snippets', 1, 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),
-
-        '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),
-
-        '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),
-
-
-        'word_semantic'           : (1, 'snippets', 1, 1),
-        'word_semantic.type'      : (1, 'scalar_text', 0, 0),
-        'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
-        'word_semantic.lastWord'  : (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.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'           : (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),
-
-        'extratokens'            : (1, 'snippets', 1, 0),
-        'extratokens.type'       : (1, 'scalar_text', 0, 0),
-        'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
-        'extratokens.lastGlyph'  : (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._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),
-
-     }
-
-
-    # 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
+            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:
-            result = []
-            if (self.debug):
-                print 'Unknown Token:', token
-            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)
-        result = indent + '<' + nodename + '>'
-        if len(argList) > 0:
-            argres = ''
-            for j in argList:
-                if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
-                else :
-                    argres += str(j) + ','
-            argres = argres[0:-1]
-            if argtype == 'snippets' :
-                result += 'snippets:' + argres
-            else :
-                result += argres
-        if len(subtagList) > 0 :
-            result += '\n'
-            for j in subtagList:
-                if len(j) > 0 :
-                    result += self.formatTag(j)
-            result += indent + '</' + nodename + '>\n'
+            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:
-            result += '</' + nodename + '>\n'
-        return result
-
-
-   # flatten tag
-    def flattenTag(self, node):
-        name = node[0]
-        subtagList = node[1]
-        argtype = node[2]
-        argList = node[3]
-        result = name
-        if (len(argList) > 0):
-            argres = ''
-            for j in argList:
-                if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
-                else :
-                    argres += str(j) + '|'
-            argres = argres[0:-1]
-            if argtype == 'snippets' :
-                result += '.snippets=' + argres
-            else :
-                result += '=' + argres
-        result += '\n'
-        for j in subtagList:
-            if len(j) > 0 :
-                result += self.flattenTag(j)
-        return result
-
-
-    # reduce create xml output
-    def formatDoc(self, flat_xml):
-        result = ''
-        for j in self.doc :
-            if len(j) > 0:
-                if flat_xml:
-                    result += self.flattenTag(j)
-                else:
-                    result += self.formatTag(j)
-        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)
+            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:
-                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
+                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
+
 
-    return xmlpage
+"""
+    AES_CBC Encryption Algorithm
+"""
 
-if __name__ == '__main__':
-    sys.exit(main(''))
+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'
index 3b32fc0a945bbae23fac4dc0146ad4cad9685752..26d740ddb0ed3be82128b3fbe0e45471b0e04f4b 100644 (file)
Binary files a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py and b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py differ
index 49cf6f5c81c5ab3bebed310c69dd601b69fcb62c..e25a0c8227ff869069f615b6cd5ad985c1164a7e 100644 (file)
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+#! /usr/bin/env python
 
-import sys
-import csv
-import os
-import getopt
+import sys, os
+import hmac
 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('|')
+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 = 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):
-    ml = ''
-    pp = PParser(gdict, flat_xml, meta_array)
-    ml += '<?xml version="1.0" standalone="no"?>\n'
-    if (raw):
-        ml += '<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
-        ml += '<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)
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
+            name_of_lib = 'alfcrypto64.dll'
     else:
-        ml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-        ml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n'
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
-        ml += '<script><![CDATA[\n'
-        ml += 'function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n'
-        ml += 'var dpi=%d;\n' % scaledpi
-        if (previd) :
-            ml += 'var prevpage="page%04d.xhtml";\n' % (previd)
-        if (nextid) :
-            ml += 'var nextpage="page%04d.xhtml";\n' % (nextid)
-        ml += 'var pw=%d;var ph=%d;' % (pp.pw, pp.ph)
-        ml += 'function zoomin(){dpi=dpi*(0.8);setsize();}\n'
-        ml += 'function zoomout(){dpi=dpi*1.25;setsize();}\n'
-        ml += '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'
-        ml += 'function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'var gt=gd();if(gt>0){dpi=gt;}\n'
-        ml += 'window.onload=setsize;\n'
-        ml += ']]></script>\n'
-        ml += '</head>\n'
-        ml += '<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n'
-        ml += '<div style="white-space:nowrap;">\n'
-        if previd == None:
-            ml += '<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'
+        if pointer_size == 4:
+            name_of_lib = 'libalfcrypto32.so'
         else:
-            ml += '<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'
-        
-        ml += '<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): 
-        ml += '<defs>\n'
-        gdefs = pp.getGlyphs()
-        for j in xrange(0,len(gdefs)):
-            ml += gdefs[j]
-        ml += '</defs>\n'
-    img = pp.getImages()
-    if (img != None):
-        for j in xrange(0,len(img)):
-            ml += img[j]
-    if (pp.gid != None): 
-        for j in xrange(0,len(pp.gid)):
-            ml += '<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)
-        ml += '<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n'
-    if (raw) :
-        ml += '</svg>'
-    else :
-        ml += '</svg></a>\n'
-        if nextid == None:
-            ml += '<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 :
-            ml += '<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'
-        ml += '</div>\n'
-        ml += '<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n'
-        ml += '</body>\n'
-        ml += '</html>\n'
-    return ml
+            name_of_lib = 'libalfcrypto64.so'
+    
+    libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found')
+
+    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 "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:
+                print "Bad key length!"
+                return None
+            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
+
+    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]
+
 
index 9ad87ea1800240af4a7f29881573d8ad1d0b65d5..7bef68eac0d0a751b0c7090f5b5427a1d5ab1a59 100644 (file)
Binary files a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py and b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py differ
index d962a029e69fc24286df87ccf19b359b03c337e5..269810cfa24541f8be10050e7192fb6211a45a98 100644 (file)
Binary files a/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py and b/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py differ
index abfc7e47f6140c907af2fc967d414ef703d3fce4..e5647f4bc3842abb1546088d41b2c899fa88c36a 100644 (file)
-#!/usr/bin/env python
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+# For use with Topaz Scripts Version 2.6
 
-from __future__ import with_statement
 import sys
-import os, csv
-import binascii
-import zlib
-import re
-from struct import pack, unpack, unpack_from
-
-class DrmException(Exception):
-    pass
-
-global charMap1
-global charMap3
-global charMap4
-
-if 'calibre' in sys.modules:
-    inCalibre = True
-else:
-    inCalibre = False
-
-if inCalibre:
-    if sys.platform.startswith('win'):
-        from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-
-    if sys.platform.startswith('darwin'):
-        from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-else:
-    if sys.platform.startswith('win'):
-        from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-
-    if sys.platform.startswith('darwin'):
-        from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-
-
-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
+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 :
-                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 getKindlePid(pidlst, rec209, token, serialnum):
-    # Compute book PID
-    pidHash = SHA1(serialnum+rec209+token)
-    bookPID = encodePID(pidHash)
-    bookPID = checksumPid(bookPID)
-    pidlst.append(bookPID)
-
-    # compute fixed pid for old pre 2.5 firmware update pid as well
-    bookPID = pidFromSerial(serialnum, 7) + "*"
-    bookPID = checksumPid(bookPID)
-    pidlst.append(bookPID)
-
-    return pidlst
-
-
-# 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(pidlst, rec209, token, kInfoFile):
-    global charMap1
-    kindleDatabase = None
-    try:
-        kindleDatabase = getDBfromFile(kInfoFile)
-    except Exception, message:
-        print(message)
-        kindleDatabase = None
-        pass
-    
-    if kindleDatabase == None :
-        return pidlst
-        
-    try:
-        # Get the Mazama Random number
-        MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
-
-        # Get the kindle account token
-        kindleAccountToken = kindleDatabase["kindle.account.tokens"]
-    except KeyError:
-        print "Keys not found in " + kInfoFile
-        return pidlst
-        
-    # Get the ID string used
-    encodedIDString = encodeHash(GetIDString(),charMap1)
-
-    # Get the current user name
-    encodedUsername = encodeHash(GetUserName(),charMap1)
-
-    # concat, hash and encode to calculate the DSN
-    DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
-       
-    # Compute the device PID (for which I can tell, is used for nothing).
-    table =  generatePidEncryptionTable()
-    devicePID = generateDevicePID(table,DSN,4)
-    devicePID = checksumPid(devicePID)
-    pidlst.append(devicePID)
-
-    # Compute book PIDs
-
-    # book pid
-    pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
-    bookPID = encodePID(pidHash)
-    bookPID = checksumPid(bookPID)
-    pidlst.append(bookPID)
-
-    # variant 1
-    pidHash = SHA1(kindleAccountToken+rec209+token)
-    bookPID = encodePID(pidHash)
-    bookPID = checksumPid(bookPID)
-    pidlst.append(bookPID)
-
-    # variant 2
-    pidHash = SHA1(DSN+rec209+token)
-    bookPID = encodePID(pidHash)
-    bookPID = checksumPid(bookPID)
-    pidlst.append(bookPID)
-
-    return pidlst
-
-def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
-    pidlst = []
-    if kInfoFiles is None:
-       kInfoFiles = []
-    if k4:
-        kInfoFiles = getKindleInfoFiles(kInfoFiles)
-    for infoFile in kInfoFiles:
-        pidlst = getK4Pids(pidlst, md1, md2, infoFile)
-    for serialnum in serials:
-        pidlst = getKindlePid(pidlst, md1, md2, serialnum)
-    for pid in pids:
-        pidlst.append(pid)
-    return pidlst
+                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'):
+                (cname, space) = argres.split('-',1)
+                if space == '' : space = '0'
+                if (cname == 'spaceafter') and (int(space) > 0) :
+                    word_class = 'sa'
+
+            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' :
+                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]
+                        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;">&nbsp;</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
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e66e9f3c76ec750d283d6dc12542011256a85db9 100644 (file)
@@ -0,0 +1,726 @@
+# standlone set of Mac OSX specific routines needed for KindleBooks
+
+from __future__ import with_statement
+
+import sys
+import os
+import os.path
+import re
+import copy
+import subprocess
+from struct import pack, unpack, unpack_from
+
+class DrmException(Exception):
+    pass
+
+
+# interface to needed routines in openssl's libcrypto
+def _load_crypto_libcrypto():
+    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
+    from ctypes.util import find_library
+
+    libcrypto = find_library('crypto')
+    if libcrypto is None:
+        raise DrmException('libcrypto not found')
+    libcrypto = CDLL(libcrypto)
+
+    # From OpenSSL's crypto aes header
+    #
+    # AES_ENCRYPT     1
+    # AES_DECRYPT     0
+    # AES_MAXNR 14 (in bytes)
+    # AES_BLOCK_SIZE 16 (in bytes)
+    # 
+    # 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);
+    #
+    # note:  the ivec string, and output buffer are both mutable
+    # 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
+    c_char_pp = POINTER(c_char_p)
+    c_int_p = POINTER(c_int)
+
+    class AES_KEY(Structure):
+        _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
+    AES_KEY_p = POINTER(AES_KEY)
+
+    def F(restype, name, argtypes):
+        func = getattr(libcrypto, name)
+        func.restype = restype
+        func.argtypes = argtypes
+        return func
+
+    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])
+
+    # From OpenSSL's Crypto evp/p5_crpt2.c
+    #
+    # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
+    #                        const unsigned char *salt, int saltlen, int iter,
+    #                        int keylen, unsigned char *out);
+
+    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])
+
+    class LibCrypto(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 DrmException('AES improper key used')
+                return
+            keyctx = self._keyctx = AES_KEY()
+            self._iv = iv
+            self._userkey = userkey
+            rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
+            if rv < 0:
+                raise DrmException('Failed to initialize AES key')
+
+        def decrypt(self, data):
+            out = create_string_buffer(len(data))
+            mutable_iv = create_string_buffer(self._iv, len(self._iv))
+            keyctx = self._keyctx
+            rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
+            if rv == 0:
+                raise DrmException('AES decryption failed')
+            return out.raw
+
+        def keyivgen(self, passwd, salt, iter, keylen):
+            saltlen = len(salt)
+            passlen = len(passwd)
+            out = create_string_buffer(keylen)
+            rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
+            return out.raw
+    return LibCrypto
+
+def _load_crypto():
+    LibCrypto = None
+    try:
+        LibCrypto = _load_crypto_libcrypto()
+    except (ImportError, DrmException):
+        pass
+    return LibCrypto
+
+LibCrypto = _load_crypto()
+
+#
+# Utility Routines
+#
+
+# 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()
+
+def SHA256(message):
+    ctx = hashlib.sha256()
+    ctx.update(message)
+    return ctx.digest()
+
+# Various character maps used to decrypt books. Probably supposed to act as obfuscation
+charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
+
+# For kinf approach of K4Mac 1.6.X or later
+# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# For Mac they seem to re-use charMap2 here
+charMap5 = charMap2
+
+# new in K4M 1.9.X
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+
+
+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
+
+# For K4M 1.6.X and later
+# generate table of prime number less than or equal to int n
+def primes(n):
+    if n==2: return [2]
+    elif n<2: return []
+    s=range(3,n+1,2)
+    mroot = n ** 0.5
+    half=(n+1)/2-1
+    i=0
+    m=3
+    while m <= mroot:
+        if s[i]:
+            j=(m*m-3)/2
+            s[j]=0
+            while j<half:
+                s[j]=0
+                j+=m
+        i=i+1
+        m=2*i+3
+    return [2]+[x for x in s if x]
+
+
+# uses a sub process to get the Hard Drive Serial Number using ioreg
+# returns with the serial number of drive whose BSD Name is "disk0"
+def GetVolumeSerialNumber():
+    sernum = os.getenv('MYSERIALNUMBER')
+    if sernum != None:
+        return sernum
+    cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p.communicate()
+    reslst = out1.split('\n')
+    cnt = len(reslst)
+    bsdname = None
+    sernum = None
+    foundIt = False
+    for j in xrange(cnt):
+        resline = reslst[j]
+        pp = resline.find('"Serial Number" = "')
+        if pp >= 0:
+            sernum = resline[pp+19:-1]
+            sernum = sernum.strip()
+        bb = resline.find('"BSD Name" = "')
+        if bb >= 0:
+            bsdname = resline[bb+14:-1]
+            bsdname = bsdname.strip()
+            if (bsdname == 'disk0') and (sernum != None):
+                foundIt = True
+                break
+    if not foundIt:
+        sernum = ''
+    return sernum
+
+def GetUserHomeAppSupKindleDirParitionName():
+    home = os.getenv('HOME')
+    dpath =  home + '/Library/Application Support/Kindle'
+    cmdline = '/sbin/mount'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p.communicate()
+    reslst = out1.split('\n')
+    cnt = len(reslst)
+    disk = ''
+    foundIt = False
+    for j in xrange(cnt):
+        resline = reslst[j]
+        if resline.startswith('/dev'):
+            (devpart, mpath) = resline.split(' on ')
+            dpart = devpart[5:]
+            pp = mpath.find('(')
+            if pp >= 0:
+                mpath = mpath[:pp-1]
+            if dpath.startswith(mpath):
+                disk = dpart
+    return disk
+
+# uses a sub process to get the UUID of the specified disk partition using ioreg
+def GetDiskPartitionUUID(diskpart):
+    uuidnum = os.getenv('MYUUIDNUMBER')
+    if uuidnum != None:
+        return uuidnum
+    cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p.communicate()
+    reslst = out1.split('\n')
+    cnt = len(reslst)
+    bsdname = None
+    uuidnum = None
+    foundIt = False
+    nest = 0
+    uuidnest = -1
+    partnest = -2
+    for j in xrange(cnt):
+        resline = reslst[j]
+        if resline.find('{') >= 0:
+            nest += 1
+        if resline.find('}') >= 0:
+            nest -= 1
+        pp = resline.find('"UUID" = "')
+        if pp >= 0:
+            uuidnum = resline[pp+10:-1]
+            uuidnum = uuidnum.strip()
+            uuidnest = nest
+            if partnest == uuidnest and uuidnest > 0:
+                foundIt = True
+                break
+        bb = resline.find('"BSD Name" = "')
+        if bb >= 0:
+            bsdname = resline[bb+14:-1]
+            bsdname = bsdname.strip()
+            if (bsdname == diskpart):
+                partnest = nest
+            else :
+                partnest = -2
+            if partnest == uuidnest and partnest > 0:
+                foundIt = True
+                break
+        if nest == 0:
+            partnest = -2
+            uuidnest = -1
+            uuidnum = None
+            bsdname = None
+    if not foundIt:
+        uuidnum = ''
+    return uuidnum
+
+def GetMACAddressMunged():
+    macnum = os.getenv('MYMACNUM')
+    if macnum != None:
+        return macnum
+    cmdline = '/sbin/ifconfig en0'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p.communicate()
+    reslst = out1.split('\n')
+    cnt = len(reslst)
+    macnum = None
+    foundIt = False
+    for j in xrange(cnt):
+        resline = reslst[j]
+        pp = resline.find('ether ')
+        if pp >= 0:
+            macnum = resline[pp+6:-1]
+            macnum = macnum.strip()
+            # print "original mac", macnum
+            # now munge it up the way Kindle app does
+            # by xoring it with 0xa5 and swapping elements 3 and 4
+            maclst = macnum.split(':')
+            n = len(maclst)
+            if n != 6:
+                fountIt = False
+                break
+            for i in range(6):
+                maclst[i] = int('0x' + maclst[i], 0)
+            mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+            mlst[5] = maclst[5] ^ 0xa5
+            mlst[4] = maclst[3] ^ 0xa5
+            mlst[3] = maclst[4] ^ 0xa5
+            mlst[2] = maclst[2] ^ 0xa5
+            mlst[1] = maclst[1] ^ 0xa5
+            mlst[0] = maclst[0] ^ 0xa5
+            macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
+            foundIt = True
+            break
+    if not foundIt:
+        macnum = ''
+    return macnum
+
+
+# uses unix env to get username instead of using sysctlbyname
+def GetUserName():
+    username = os.getenv('USER')
+    return username
+
+def isNewInstall():
+    home = os.getenv('HOME')
+    # soccer game fan anyone
+    dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
+    # print dpath, os.path.exists(dpath)
+    if os.path.exists(dpath):
+        return True
+    return False
+
+
+def GetIDString():
+    # K4Mac now has an extensive set of ids strings it uses
+    # in encoding pids and in creating unique passwords
+    # for use in its own version of CryptUnprotectDataV2
+
+    # BUT Amazon has now become nasty enough to detect when its app
+    # is being run under a debugger and actually changes code paths
+    # including which one of these strings is chosen, all to try
+    # to prevent reverse engineering
+
+    # Sad really ... they will only hurt their own sales ...
+    # true book lovers really want to keep their books forever
+    # and move them to their devices and DRM prevents that so they
+    # will just buy from someplace else that they can remove
+    # the DRM from
+
+    # Amazon should know by now that true book lover's are not like
+    # penniless kids that pirate music, we do not pirate books
+
+    if isNewInstall():
+        mungedmac = GetMACAddressMunged()
+        if len(mungedmac) > 7:
+            return mungedmac
+    sernum = GetVolumeSerialNumber()
+    if len(sernum) > 7:
+        return sernum
+    diskpart = GetUserHomeAppSupKindleDirParitionName()
+    uuidnum = GetDiskPartitionUUID(diskpart)
+    if len(uuidnum) > 7:
+        return uuidnum
+    mungedmac = GetMACAddressMunged()
+    if len(mungedmac) > 7:
+        return mungedmac
+    return '9999999999'
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used by Kindle for Mac versions < 1.6.0
+class CryptUnprotectData(object):
+    def __init__(self):
+        sernum = GetVolumeSerialNumber()
+        if sernum == '':
+            sernum = '9999999999'
+        sp = sernum + '!@#' + GetUserName()
+        passwdData = encode(SHA256(sp),charMap1)
+        salt = '16743'
+        self.crp = LibCrypto()
+        iter = 0x3e8
+        keylen = 0x80
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext,charMap1)
+        return cleartext
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.6.0
+class CryptUnprotectDataV2(object):
+    def __init__(self):
+        sp = GetUserName() + ':&%:' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap5)
+        # salt generation as per the code
+        salt = 0x0512981d * 2 * 1 * 1
+        salt = str(salt) + GetUserName()
+        salt = encode(salt,charMap5)
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap5)
+        return cleartext
+
+
+# unprotect the new header blob in .kinf2011
+# used in Kindle for Mac Version >= 1.9.0
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
+    crp = LibCrypto()
+    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
+    key = key_iv[0:32]
+    iv = key_iv[32:48]
+    crp.set_decrypt_key(key,iv)
+    cleartext = crp.decrypt(encryptedData)
+    return cleartext
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.9.0
+class CryptUnprotectDataV3(object):
+    def __init__(self, entropy):
+        sp = GetUserName() + '+@#$%+' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap2)
+        salt = entropy
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap2)
+        return cleartext
+
+
+# Locate the .kindle-info files
+def getKindleInfoFiles(kInfoFiles):
+    # first search for current .kindle-info files
+    home = os.getenv('HOME')
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
+    kinfopath = 'NONE'
+    found = False
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    # add any .rainier*-kinf files
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    # add any .kinf2011 files
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    if not found:
+        print('No kindle-info files have been found.')
+    return kInfoFiles
+
+# determine type of kindle info provided and return a
+# database of keynames and values
+def getDBfromFile(kInfoFile):
+    names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
+    DB = {}
+    cnt = 0
+    infoReader = open(kInfoFile, 'r')
+    hdr = infoReader.read(1)
+    data = infoReader.read()
+
+    if data.find('[') != -1 :
+
+        # older style kindle-info file
+        cud = CryptUnprotectData()
+        items = data.split('[')
+        for item in items:
+            if item != '':
+                keyhash, rawdata = item.split(':')
+                keyname = "unknown"
+                for name in names:
+                    if encodeHash(name,charMap2) == keyhash:
+                        keyname = name
+                        break
+                if keyname == "unknown":
+                    keyname = keyhash
+                encryptedValue = decode(rawdata,charMap2)
+                cleartext = cud.decrypt(encryptedValue)
+                DB[keyname] = cleartext
+                cnt = cnt + 1
+        if cnt == 0:
+            DB = None
+        return DB
+
+    if hdr == '/':
+
+        # else newer style .kinf file used by K4Mac >= 1.6.0
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+        cud = CryptUnprotectDataV2()
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+            keyname = "unknown"
+
+            # the raw keyhash string is also used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            # "entropy" not used for K4Mac only K4PC
+            # entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents) - largest prime number less than or equal to int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            noffset = contlen - primes(int(contlen/3))[-1]
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using charMap5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            cleartext = cud.decrypt(encryptedValue)
+            DB[keyname] = cleartext
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # the latest .kinf2011 version for K4M 1.9.1
+    # put back the hdr char, it is needed
+    data = hdr + data
+    data = data[:-1]
+    items = data.split('/')
+
+    # the headerblob is the encrypted information needed to build the entropy string
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, charMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+
+    # now extract the pieces in the same way
+    # this version is different from K4PC it scales the build number by multipying by 735
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
+
+    cud = CryptUnprotectDataV3(entropy)
+
+    # loop through the item records until all are processed
+    while len(items) > 0:
+
+        # get the first item record
+        item = items.pop(0)
+
+        # the first 32 chars of the first record of a group
+        # is the MD5 hash of the key name encoded by charMap5
+        keyhash = item[0:32]
+        keyname = "unknown"
+
+        # unlike K4PC the keyhash is not used in generating entropy
+        # entropy = SHA1(keyhash) + added_entropy
+        # entropy = added_entropy
+
+        # the remainder of the first record when decoded with charMap5
+        # has the ':' split char followed by the string representation
+        # of the number of records that follow
+        # and make up the contents
+        srcnt = decode(item[34:],charMap5)
+        rcnt = int(srcnt)
+
+        # read and store in rcnt records of data
+        # that make up the contents value
+        edlst = []
+        for i in xrange(rcnt):
+            item = items.pop(0)
+            edlst.append(item)
+
+        keyname = "unknown"
+        for name in names:
+            if encodeHash(name,testMap8) == keyhash:
+                keyname = name
+                break
+        if keyname == "unknown":
+            keyname = keyhash
+
+        # the testMap8 encoded contents data has had a length
+        # of chars (always odd) cut off of the front and moved
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
+        # CryptUnprotectData call from succeeding.
+
+        # The offset into the testMap8 encoded contents seems to be:
+        # len(contents) - largest prime number less than or equal to int(len(content)/3)
+        # (in other words split "about" 2/3rds of the way through)
+
+        # move first offsets chars to end to align for decode by testMap8
+        encdata = "".join(edlst)
+        contlen = len(encdata)
+
+        # now properly split and recombine
+        # by moving noffset chars from the start of the
+        # string to the end of the string
+        noffset = contlen - primes(int(contlen/3))[-1]
+        pfx = encdata[0:noffset]
+        encdata = encdata[noffset:]
+        encdata = encdata + pfx
+
+        # decode using testMap8 to get the CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = cud.decrypt(encryptedValue)
+        # print keyname
+        # print cleartext
+        DB[keyname] = cleartext
+        cnt = cnt + 1
+
+    if cnt == 0:
+        DB = None
+    return DB
index e30abfaa0c1742adf3903016a8256cee7a43e54d..a412a7b46460a6ef326d997564d6408ea378286f 100644 (file)
 #! /usr/bin/python
 # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-# For use with Topaz Scripts Version 2.6
 
-import csv
+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.k4mobidedrm import convert2xml
+    from calibre_plugins.k4mobidedrm import flatxml2html
+    from calibre_plugins.k4mobidedrm import flatxml2svg
+    from calibre_plugins.k4mobidedrm 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('&','&amp;')
+        str = str.replace('<','&lt;')
+        str = str.replace('>','&gt;')
+        str = str.replace('=','&#61;')
+        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 DocParser(object):
-    def __init__(self, flatxml, fontsize, ph, pw):
+class PageDimParser(object):
+    def __init__(self, flatxml):
         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
@@ -58,198 +142,568 @@ class DocParser(object):
         for j in xrange(pos, end):
             item = docList[j]
             if item.find('=') >= 0:
-                (name, argres) = item.split('=',1)
-            else : 
+                (name, argres) = item.split('=')
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            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
-
     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
 
-        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
-
-        for j in xrange(stylecnt):
-            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 = ''
-
-                # 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 = ''
-
-                cssargs = {}
-
-                while True :
-
-                    (pos1, attr) = self.findinDoc('style.rule.attr', start, end)
-                    (pos2, val) = self.findinDoc('style.rule.value', start, end)
-
-                    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 not ((attr == 'hang') and (int(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 :
-                    # 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
-
+def getPageDim(flatxml):
     # create a document parser
-    dp = DocParser(flatxml, fontsize, ph, pw)
-    csspage = dp.process()
-    return csspage
+    dp = PageDimParser(flatxml)
+    (ph, pw) = dp.process()
+    return ph, pw
 
-
-def getpageIDMap(flatxml):
-    dp = DocParser(flatxml, 0, 0, 0)
-    pageidnumbers = dp.getData('info.original.pid', 0, -1)
-    return pageidnumbers
+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('&','&amp;')
+    title = title.replace('<','&lt;')
+    title = title.replace('>','&gt;')
+    meta_array['Title'] = title
+    authors = meta_array.get('Authors','No Authors Provided')
+    authors = authors.replace('&','&amp;')
+    authors = authors.replace('<','&lt;')
+    authors = authors.replace('>','&gt;')
+    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
+    fontsize = '135'
+    if 'fontSize' in meta_array:
+        fontsize = meta_array['fontSize']
+
+    # also get the size of a normal text page
+    spage = '1'
+    if 'firstTextPage' in meta_array:
+        spage = meta_array['firstTextPage']
+    pnum = int(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(''))
index eed53e263ce9c750de783efa533ed6024cd6192f..828c6e2ef9da27aa6e1482f57a66337e9c705c3e 100644 (file)
@@ -1,5 +1,24 @@
 #!/usr/bin/env python
 
+from __future__ import with_statement
+
+# engine to remove drm from Kindle for Mac and Kindle for PC books
+# for personal use for archiving and converting your ebooks
+
+# PLEASE DO NOT PIRATE EBOOKS!
+
+# We want all authors and publishers, and eBook stores to live
+# long and prosperous lives but at the same time  we just want to
+# be able to read OUR books on whatever device we want and to keep
+# readable for a long, long time
+
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
+#    and many many others
+
+
+__version__ = '4.0'
+
 class Unbuffered:
     def __init__(self, stream):
         self.stream = stream
@@ -10,460 +29,184 @@ class Unbuffered:
         return getattr(self.stream, attr)
 
 import sys
+import os, csv, getopt
+import string
+import re
+import traceback
+
+buildXML = False
+
+class DrmException(Exception):
+    pass
 
 if 'calibre' in sys.modules:
     inCalibre = True
 else:
     inCalibre = False
 
-import os, csv, getopt
-import zlib, zipfile, tempfile, shutil
-from struct import pack
-from struct import unpack
-
-class TpzDRMError(Exception):
-    pass
-
-    
-# local support routines
 if inCalibre:
+    from calibre_plugins.k4mobidedrm import mobidedrm
+    from calibre_plugins.k4mobidedrm import topazextract
     from calibre_plugins.k4mobidedrm import kgenpids
-    from calibre_plugins.k4mobidedrm import genbook
 else:
+    import mobidedrm
+    import topazextract
     import kgenpids
-    import genbook
-
-
-# recursive zip creation support routine
-def zipUpDir(myzip, tdir, localname):
-    currentdir = tdir
-    if localname != "":
-        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]  
+# cleanup bytestring filenames
+# borrowed from calibre from calibre/src/calibre/__init__.py
+# added in removal of non-printing chars
+# and removal of . at start
+# convert spaces to underscores
+def cleanup_name(name):
+    _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
+    substitute='_'
+    one = ''.join(char for char in name if char in string.printable)
+    one = _filename_sanitize.sub(substitute, one)
+    one = re.sub(r'\s', ' ', one).strip()
+    one = re.sub(r'^\.+$', '_', one)
+    one = one.replace('..', substitute)
+    # Windows doesn't like path components that end with a period
+    if one.endswith('.'):
+        one = one[:-1]+substitute
+    # Mac and Unix don't like file names that begin with a full stop
+    if len(one) > 0 and one[0] == '.':
+        one = substitute+one[1:]
+    one = one.replace(' ','_')
+    return one
+
+def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
+    global buildXML
+
+    # handle the obvious cases at the beginning
+    if not os.path.isfile(infile):
+        print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
+        return 1
 
-#
-# crypto routines
-#
+    mobi = True
+    magic3 = file(infile,'rb').read(3)
+    if magic3 == 'TPZ':
+        mobi = False
+
+    bookname = os.path.splitext(os.path.basename(infile))[0]
+
+    if mobi:
+        mb = mobidedrm.MobiBook(infile)
+    else:
+        mb = topazextract.TopazBook(infile)
+
+    title = mb.getBookTitle()
+    print "Processing Book: ", title
+    filenametitle = cleanup_name(title)
+    outfilename = bookname
+    if len(outfilename)<=8 or len(filenametitle)<=8:
+        outfilename = outfilename + "_" + filenametitle
+    elif outfilename[:8] != filenametitle[:8]:
+        outfilename = outfilename[:8] + "_" + filenametitle
+
+    # avoid excessively long file names
+    if len(outfilename)>150:
+        outfilename = outfilename[:150]
+
+    # build pid list
+    md1, md2 = mb.getPIDMetaInfo()
+    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
+
+    try:
+        mb.processBook(pidlst)
 
-# Context initialisation for the Topaz Crypto
-def topazCryptoInit(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):
-    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 TpzDRMError("Didn't find PID magic numbers in record")
-    elif fields[1] != 8 or fields[3] != 8 :
-        raise TpzDRMError("Record didn't contain correct length fields")
-    elif fields[2] != PID :
-        raise TpzDRMError("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 TpzDRMError:
-            pass
-        data = data[1+length:]
-    if len(records) == 0:
-        raise TpzDRMError("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 TpzDRMError("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)
-            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 TpzDRMError("Parse Error : Invalid Header")
-            tag = bookReadString(self.fo)
-            record = bookReadHeaderRecordData()
-            return [tag,record]
-        nbRecords = bookReadEncodedNumber(self.fo)
-        for i in range (0,nbRecords):
-            result = parseTopazHeaderRecord()
-            # print result[0], result[1]
-            self.bookHeaderRecords[result[0]] = result[1]
-        if ord(self.fo.read(1))  != 0x64 :
-            raise TpzDRMError("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 TpzDRMError("Parse Error : Record Names Don't Match")
-        flags = ord(self.fo.read(1))
-        nbRecords = ord(self.fo.read(1))
-        # print nbRecords
-        for i in range (0,nbRecords) :
-            keyval = bookReadString(self.fo)
-            content = bookReadString(self.fo)
-            # print keyval
-            # 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
-
-    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 TpzDRMError("Parse Error : Invalid Record, record not found")
-
-        self.fo.seek(self.bookPayloadOffset + recordOffset)
-
-        tag = bookReadString(self.fo)
-        if tag != name :
-            raise TpzDRMError("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 TpzDRMError("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])
+    except mobidedrm.DrmException, e:
+        print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+        return 1
+    except topazextract.TpzDRMError, e:
+        print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+        return 1
+    except Exception, e:
+        print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+        return 1
+
+    if mobi:
+        if mb.getPrintReplica():
+            outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
         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 TpzDRMError("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 TpzDRMError, e:
-            print "no dkey record found, book may not be encrypted"
-            print "attempting to extrct files without a book key"
-            self.createBookDirectory()
-            self.extractFiles()
-            print "Successfully Extracted Topaz contents"
-            rv = genbook.generateBook(self.outdir, raw, fixedimage)
-            if rv == 0:
-                print "\nBook 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 "\nTrying: ", pid
-            bookKeys = []
-            data = keydata
-            try:
-                bookKeys+=decryptDkeyRecords(data,pid)
-            except TpzDRMError, e:
-                pass
-            else:
-                bookKey = bookKeys[0]
-                print "Book Key Found!"
-                break
-
-        if not bookKey:
-            raise TpzDRMError('Decryption Unsucessful; No valid pid found')
-
-        self.setBookKey(bookKey)
-        self.createBookDirectory()
-        self.extractFiles()
-        print "Successfully Extracted Topaz contents"
-        rv = genbook.generateBook(self.outdir, raw, fixedimage)
-        if rv == 0:
-            print "\nBook 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,'img')
-        if not os.path.exists(destdir):
-            os.makedirs(destdir)
-        destdir =  os.path.join(outdir,'color_img')
-        if not os.path.exists(destdir):
-            os.makedirs(destdir)
-        destdir =  os.path.join(outdir,'page')
-        if not os.path.exists(destdir):
-            os.makedirs(destdir)
-        destdir =  os.path.join(outdir,'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 = '.dat'
-                if name == 'img' : ext = '.jpg'
-                if name == 'color' : ext = '.jpg'
-                print "\nProcessing Section: %s " % name
-                for index in range (0,len(self.bookHeaderRecords[name])) :
-                    fnum = "%04d" % index
-                    fname = name + fnum + ext
-                    destdir = outdir
-                    if name == 'img':
-                        destdir =  os.path.join(outdir,'img')
-                    if name == 'color':
-                        destdir =  os.path.join(outdir,'color_img')
-                    if name == 'page':
-                        destdir =  os.path.join(outdir,'page')
-                    if name == 'glyphs':
-                        destdir =  os.path.join(outdir,'glyphs')
-                    outputFile = os.path.join(destdir,fname)
-                    print ".",
-                    record = self.getBookPayloadRecord(name,index)
-                    if record != '':
-                        file(outputFile, 'wb').write(record)
-        print " "
-
-    def getHTMLZip(self, zipname):
-        htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
-        htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html')
-        htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf')
-        if os.path.isfile(os.path.join(self.outdir,'cover.jpg')):
-            htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg')
-        htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css')
-        zipUpDir(htmlzip, self.outdir, 'img')
-        htmlzip.close()
-
-    def getSVGZip(self, zipname):
-        svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
-        svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml')
-        zipUpDir(svgzip, self.outdir, 'svg')
-        zipUpDir(svgzip, self.outdir, 'img')
-        svgzip.close()
-
-    def getXMLZip(self, zipname):
-        xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
-        targetdir = os.path.join(self.outdir,'xml')
-        zipUpDir(xmlzip, targetdir, '')
-        zipUpDir(xmlzip, self.outdir, 'img')
-        xmlzip.close()
-
-    def cleanup(self):
-        if os.path.isdir(self.outdir):
-            pass
-            # shutil.rmtree(self.outdir, True)
+            outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
+        mb.getMobiFile(outfile)
+        return 0
+
+    # topaz:
+    print "   Creating NoDRM HTMLZ Archive"
+    zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
+    mb.getHTMLZip(zipname)
+
+    print "   Creating SVG ZIP Archive"
+    zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
+    mb.getSVGZip(zipname)
+
+    if buildXML:
+        print "   Creating XML ZIP Archive"
+        zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
+        mb.getXMLZip(zipname)
+
+    # remove internal temporary directory of Topaz pieces
+    mb.cleanup()
+
+    return 0
+
 
 def usage(progname):
-    print "Removes DRM protection from Topaz ebooks and extract the contents"
+    print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks"
     print "Usage:"
     print "    %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir>  " % progname
-    
 
+#
 # Main
+#
 def main(argv=sys.argv):
     progname = os.path.basename(argv[0])
+
     k4 = False
-    pids = []
-    serials = []
     kInfoFiles = []
-    
+    serials = []
+    pids = []
+
+    print ('K4MobiDeDrm v%(__version__)s '
+           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
     except getopt.GetoptError, err:
         print str(err)
         usage(progname)
-        return 1
+        sys.exit(2)
     if len(args)<2:
         usage(progname)
-        return 1
-        
+        sys.exit(2)
+
     for o, a in opts:
         if o == "-k":
             if a == None :
-                print "Invalid parameter for -k"
-                return 1
+                raise DrmException("Invalid parameter for -k")
             kInfoFiles.append(a)
         if o == "-p":
             if a == None :
-                print "Invalid parameter for -p"
-                return 1
+                raise DrmException("Invalid parameter for -p")
             pids = a.split(',')
         if o == "-s":
             if a == None :
-                print "Invalid parameter for -s"
-                return 1
+                raise DrmException("Invalid parameter for -s")
             serials = a.split(',')
-    k4 = True
 
+    # try with built in Kindle Info files
+    k4 = True
+    if sys.platform.startswith('linux'):
+        k4 = False
+        kInfoFiles = None
     infile = args[0]
     outdir = args[1]
+    return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
 
-    if not os.path.isfile(infile):
-        print "Input File Does Not Exist"
-        return 1
-
-    bookname = os.path.splitext(os.path.basename(infile))[0]
-
-    tb = TopazBook(infile)
-    title = tb.getBookTitle()
-    print "Processing Book: ", title
-    keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) 
-
-    try:
-        print "Decrypting Book"
-        tb.processBook(pidlst)
-
-        print "   Creating HTML ZIP Archive"
-        zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz')
-        tb.getHTMLZip(zipname)
-
-        print "   Creating SVG ZIP Archive"
-        zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
-        tb.getSVGZip(zipname)
-
-        print "   Creating XML ZIP Archive"
-        zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
-        tb.getXMLZip(zipname)
-
-        # removing internal temporary directory of pieces
-        tb.cleanup()
-
-    except TpzDRMError, e:
-        print str(e)
-        # tb.cleanup()
-        return 1
-
-    except Exception, e:
-        print str(e)
-        # tb.cleanup
-        return 1
-
-    return 0
-                
 
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 5da04fc99cbd0a16f66ec20dc0085cef42ef9e0c..0e0e841f1e5317848e56aa08fb199b8cea9c0cf2 100644 (file)
Binary files a/Calibre_Plugins/k4mobidedrm_plugin.zip and b/Calibre_Plugins/k4mobidedrm_plugin.zip differ
index 7d5130cbf56a24828c2f542bcbaec1d982e60942..7be7a8a82b0c345412bd3ec93ac440c6dfd4851d 100644 (file)
-# standlone set of Mac OSX specific routines needed for KindleBooks
+#! /usr/bin/python
+
+"""
+
+Comprehensive Mazama Book DRM with Topaz Cryptography V2.2
+
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
+M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
+B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
+y2/pHuYme7U1TsgSjwIDAQAB
+-----END PUBLIC KEY-----
+
+"""
 
 from __future__ import with_statement
 
+import csv
 import sys
 import os
-import os.path
+import getopt
+import zlib
+from struct import pack
+from struct import unpack
+from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
+    create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
+    string_at, Structure, c_void_p, cast
+import _winreg as winreg
+import Tkinter
+import Tkconstants
+import tkMessageBox
+import traceback
+import hashlib
 
-import subprocess
-from struct import pack, unpack, unpack_from
+MAX_PATH = 255
 
-class DrmException(Exception):
-    pass
+kernel32 = windll.kernel32
+advapi32 = windll.advapi32
+crypt32 = windll.crypt32
 
+global kindleDatabase
+global bookFile
+global bookPayloadOffset
+global bookHeaderRecords
+global bookMetadata
+global bookKey
+global command
 
-# interface to needed routines in openssl's libcrypto
-def _load_crypto_libcrypto():
-    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
-    from ctypes.util import find_library
-
-    libcrypto = find_library('crypto')
-    if libcrypto is None:
-        raise DrmException('libcrypto not found')
-    libcrypto = CDLL(libcrypto)
-
-    AES_MAXNR = 14
-    c_char_pp = POINTER(c_char_p)
-    c_int_p = POINTER(c_int)
-
-    class AES_KEY(Structure):
-        _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
-    AES_KEY_p = POINTER(AES_KEY)
-    
-    def F(restype, name, argtypes):
-        func = getattr(libcrypto, name)
-        func.restype = restype
-        func.argtypes = argtypes
-        return func
-    
-    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])
-
-    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])
-    
-    class LibCrypto(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 DrmException('AES 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 DrmException('Failed to initialize AES key')
-
-        def decrypt(self, data):
-            out = create_string_buffer(len(data))
-            rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
-            if rv == 0:
-                raise DrmException('AES decryption failed')
-            return out.raw
-
-        def keyivgen(self, passwd, salt, iter, keylen):
-            saltlen = len(salt)
-            passlen = len(passwd)
-            out = create_string_buffer(keylen)
-            rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
-            return out.raw
-    return LibCrypto
-
-def _load_crypto():
-    LibCrypto = None
-    try:
-        LibCrypto = _load_crypto_libcrypto()
-    except (ImportError, DrmException):
-        pass
-    return LibCrypto
+#
+# Various character maps used to decrypt books. Probably supposed to act as obfuscation
+#
 
-LibCrypto = _load_crypto()
+charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
+charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
 
 #
-# Utility Routines
+# Exceptions for all the problems that might happen during the script
 #
 
-# crypto digestroutines
-import hashlib
+class CMBDTCError(Exception):
+    pass
+
+class CMBDTCFatal(Exception):
+    pass
+
+#
+# Stolen stuff
+#
+
+class DataBlob(Structure):
+    _fields_ = [('cbData', c_uint),
+                ('pbData', c_void_p)]
+DataBlob_p = POINTER(DataBlob)
+
+def GetSystemDirectory():
+    GetSystemDirectoryW = kernel32.GetSystemDirectoryW
+    GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
+    GetSystemDirectoryW.restype = c_uint
+    def GetSystemDirectory():
+        buffer = create_unicode_buffer(MAX_PATH + 1)
+        GetSystemDirectoryW(buffer, len(buffer))
+        return buffer.value
+    return GetSystemDirectory
+GetSystemDirectory = GetSystemDirectory()
+
+
+def GetVolumeSerialNumber():
+    GetVolumeInformationW = kernel32.GetVolumeInformationW
+    GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
+                                      POINTER(c_uint), POINTER(c_uint),
+                                      POINTER(c_uint), c_wchar_p, c_uint]
+    GetVolumeInformationW.restype = c_uint
+    def GetVolumeSerialNumber(path):
+        vsn = c_uint(0)
+        GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
+        return vsn.value
+    return GetVolumeSerialNumber
+GetVolumeSerialNumber = GetVolumeSerialNumber()
+
+
+def GetUserName():
+    GetUserNameW = advapi32.GetUserNameW
+    GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
+    GetUserNameW.restype = c_uint
+    def GetUserName():
+        buffer = create_unicode_buffer(32)
+        size = c_uint(len(buffer))
+        while not GetUserNameW(buffer, byref(size)):
+            buffer = create_unicode_buffer(len(buffer) * 2)
+            size.value = len(buffer)
+        return buffer.value.encode('utf-16-le')[::2]
+    return GetUserName
+GetUserName = GetUserName()
+
+
+def CryptUnprotectData():
+    _CryptUnprotectData = crypt32.CryptUnprotectData
+    _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
+                                   c_void_p, c_void_p, c_uint, DataBlob_p]
+    _CryptUnprotectData.restype = c_uint
+    def CryptUnprotectData(indata, entropy):
+        indatab = create_string_buffer(indata)
+        indata = DataBlob(len(indata), cast(indatab, c_void_p))
+        entropyb = create_string_buffer(entropy)
+        entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
+        outdata = DataBlob()
+        if not _CryptUnprotectData(byref(indata), None, byref(entropy),
+                                   None, None, 0, byref(outdata)):
+            raise CMBDTCFatal("Failed to Unprotect Data")
+        return string_at(outdata.pbData, outdata.cbData)
+    return CryptUnprotectData
+CryptUnprotectData = CryptUnprotectData()
+
+#
+# Returns the MD5 digest of "message"
+#
 
 def MD5(message):
     ctx = hashlib.md5()
     ctx.update(message)
     return ctx.digest()
 
+#
+# Returns the MD5 digest of "message"
+#
+
 def SHA1(message):
     ctx = hashlib.sha1()
     ctx.update(message)
     return ctx.digest()
 
-def SHA256(message):
-    ctx = hashlib.sha256()
-    ctx.update(message)
-    return ctx.digest()
-
-# Various character maps used to decrypt books. Probably supposed to act as obfuscation
-charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+#
+# Open the book file at path
+#
 
-# For kinf approach of K4PC/K4Mac
-# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
-# For Mac they seem to re-use charMap2 here
-charMap5 = charMap2
+def openBook(path):
+    try:
+        return open(path,'rb')
+    except:
+        raise CMBDTCFatal("Could not open book file: " + path)
+#
+# Encode the bytes in data with the characters in map
+#
 
 def encode(data, map):
     result = ""
@@ -128,424 +174,726 @@ def encode(data, map):
         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):
+    for i in range (0,len(data),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
+        value = (((high * 0x40) ^ 0x80) & 0xFF) + low
         result += pack("B",value)
     return result
 
-# For .kinf approach of K4PC and now K4Mac
-# generate table of prime number less than or equal to int n
-def primes(n):
-    if n==2: return [2]
-    elif n<2: return []
-    s=range(3,n+1,2)
-    mroot = n ** 0.5
-    half=(n+1)/2-1
-    i=0
-    m=3
-    while m <= mroot:
-        if s[i]:
-            j=(m*m-3)/2
-            s[j]=0
-            while j<half:
-                s[j]=0
-                j+=m
-        i=i+1
-        m=2*i+3
-    return [2]+[x for x in s if x]
-
-
-# uses a sub process to get the Hard Drive Serial Number using ioreg
-# returns with the serial number of drive whose BSD Name is "disk0"
-def GetVolumeSerialNumber():
-    sernum = os.getenv('MYSERIALNUMBER')
-    if sernum != None:
-        return sernum
-    cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p.communicate()
-    reslst = out1.split('\n')
-    cnt = len(reslst)
-    bsdname = None
-    sernum = None
-    foundIt = False
-    for j in xrange(cnt):
-        resline = reslst[j]
-        pp = resline.find('"Serial Number" = "')
-        if pp >= 0:
-            sernum = resline[pp+19:-1]
-            sernum = sernum.strip()
-        bb = resline.find('"BSD Name" = "')
-        if bb >= 0:
-            bsdname = resline[bb+14:-1]
-            bsdname = bsdname.strip()
-            if (bsdname == 'disk0') and (sernum != None):
-                foundIt = True
-                break
-    if not foundIt:
-        sernum = ''
-    return sernum
-
-def GetUserHomeAppSupKindleDirParitionName():
-    home = os.getenv('HOME')
-    dpath =  home + '/Library/Application Support/Kindle'
-    cmdline = '/sbin/mount'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p.communicate()
-    reslst = out1.split('\n')
-    cnt = len(reslst)
-    disk = ''
-    foundIt = False
-    for j in xrange(cnt):
-        resline = reslst[j]
-        if resline.startswith('/dev'):
-            (devpart, mpath) = resline.split(' on ')
-            dpart = devpart[5:]
-            pp = mpath.find('(')
-            if pp >= 0:
-                mpath = mpath[:pp-1]
-            if dpath.startswith(mpath):
-                disk = dpart
-    return disk
-
-# uses a sub process to get the UUID of the specified disk partition using ioreg
-def GetDiskPartitionUUID(diskpart):
-    uuidnum = os.getenv('MYUUIDNUMBER')
-    if uuidnum != None:
-        return uuidnum
-    cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p.communicate()
-    reslst = out1.split('\n')
-    cnt = len(reslst)
-    bsdname = None
-    uuidnum = None
-    foundIt = False
-    nest = 0
-    uuidnest = -1
-    partnest = -2
-    for j in xrange(cnt):
-        resline = reslst[j]
-        if resline.find('{') >= 0:
-            nest += 1
-        if resline.find('}') >= 0:
-            nest -= 1
-        pp = resline.find('"UUID" = "')
-        if pp >= 0:
-            uuidnum = resline[pp+10:-1]
-            uuidnum = uuidnum.strip()
-            uuidnest = nest
-            if partnest == uuidnest and uuidnest > 0:
-                foundIt = True
-                break
-        bb = resline.find('"BSD Name" = "')
-        if bb >= 0:
-            bsdname = resline[bb+14:-1]
-            bsdname = bsdname.strip()
-            if (bsdname == diskpart):
-                partnest = nest
-            else :
-                partnest = -2
-            if partnest == uuidnest and partnest > 0:
-                foundIt = True
-                break
-        if nest == 0:
-            partnest = -2
-            uuidnest = -1
-            uuidnum = None
-            bsdname = None
-    if not foundIt:
-        uuidnum = ''
-    return uuidnum
-    
-def GetMACAddressMunged():
-    macnum = os.getenv('MYMACNUM')
-    if macnum != None:
-        return macnum
-    cmdline = '/sbin/ifconfig en0'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p.communicate()
-    reslst = out1.split('\n')
-    cnt = len(reslst)
-    macnum = None
-    foundIt = False
-    for j in xrange(cnt):
-        resline = reslst[j]
-        pp = resline.find('ether ')
-        if pp >= 0:
-            macnum = resline[pp+6:-1]
-            macnum = macnum.strip()
-            # print "original mac", macnum
-            # now munge it up the way Kindle app does
-            # by xoring it with 0xa5 and swapping elements 3 and 4
-            maclst = macnum.split(':')
-            n = len(maclst)
-            if n != 6:
-                fountIt = False
-                break
-            for i in range(6):
-                maclst[i] = int('0x' + maclst[i], 0)
-            mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
-            mlst[5] = maclst[5] ^ 0xa5
-            mlst[4] = maclst[3] ^ 0xa5
-            mlst[3] = maclst[4] ^ 0xa5
-            mlst[2] = maclst[2] ^ 0xa5
-            mlst[1] = maclst[1] ^ 0xa5
-            mlst[0] = maclst[0] ^ 0xa5
-            macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
-            foundIt = True
-            break
-    if not foundIt:
-        macnum = ''
-    return macnum
+#
+# Locate and open the Kindle.info file (Hopefully in the way it is done in the Kindle application)
+#
 
+def openKindleInfo():
+    regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
+    path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
+    return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
 
-# uses unix env to get username instead of using sysctlbyname 
-def GetUserName():
-    username = os.getenv('USER')
-    return username
-
-
-# implements an Pseudo Mac Version of Windows built-in Crypto routine
-# used by Kindle for Mac versions < 1.6.0
-def CryptUnprotectData(encryptedData):
-    sernum = GetVolumeSerialNumber()
-    if sernum == '':
-        sernum = '9999999999'
-    sp = sernum + '!@#' + GetUserName()
-    passwdData = encode(SHA256(sp),charMap1)
-    salt = '16743'
-    iter = 0x3e8
-    keylen = 0x80
-    crp = LibCrypto()
-    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
-    key = key_iv[0:32]
-    iv = key_iv[32:48]
-    crp.set_decrypt_key(key,iv)
-    cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext,charMap1)
-    return cleartext
-
-
-def isNewInstall():
-    home = os.getenv('HOME')
-    # soccer game fan anyone
-    dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
-    # print dpath, os.path.exists(dpath)
-    if os.path.exists(dpath):
-        return True
-    return False
-    
-
-def GetIDString():
-    # K4Mac now has an extensive set of ids strings it uses
-    # in encoding pids and in creating unique passwords
-    # for use in its own version of CryptUnprotectDataV2
-
-    # BUT Amazon has now become nasty enough to detect when its app
-    # is being run under a debugger and actually changes code paths
-    # including which one of these strings is chosen, all to try 
-    # to prevent reverse engineering
-
-    # Sad really ... they will only hurt their own sales ...
-    # true book lovers really want to keep their books forever
-    # and move them to their devices and DRM prevents that so they 
-    # will just buy from someplace else that they can remove 
-    # the DRM from
-
-    # Amazon should know by now that true book lover's are not like
-    # penniless kids that pirate music, we do not pirate books
-
-    if isNewInstall():
-        mungedmac = GetMACAddressMunged()
-        if len(mungedmac) > 7:
-            return mungedmac
-    sernum = GetVolumeSerialNumber()
-    if len(sernum) > 7:
-        return sernum
-    diskpart = GetUserHomeAppSupKindleDirParitionName()
-    uuidnum = GetDiskPartitionUUID(diskpart)
-    if len(uuidnum) > 7:
-        return uuidnum
-    mungedmac = GetMACAddressMunged()
-    if len(mungedmac) > 7:
-        return mungedmac
-    return '9999999999'
-
-
-# implements an Pseudo Mac Version of Windows built-in Crypto routine
-# used for Kindle for Mac Versions >= 1.6.0
-def CryptUnprotectDataV2(encryptedData):
-    sp = GetUserName() + ':&%:' + GetIDString()
-    passwdData = encode(SHA256(sp),charMap5)
-    # salt generation as per the code
-    salt = 0x0512981d * 2 * 1 * 1
-    salt = str(salt) + GetUserName()
-    salt = encode(salt,charMap5)
-    crp = LibCrypto()
-    iter = 0x800
-    keylen = 0x400
-    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
-    key = key_iv[0:32]
-    iv = key_iv[32:48]
-    crp.set_decrypt_key(key,iv)
-    cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext, charMap5)
-    return cleartext
-
-
-# Locate the .kindle-info files
-def getKindleInfoFiles(kInfoFiles):
-    # first search for current .kindle-info files
-    home = os.getenv('HOME')
-    cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p1.communicate()
-    reslst = out1.split('\n')
-    kinfopath = 'NONE'
-    found = False
-    for resline in reslst:
-        if os.path.isfile(resline):
-            kInfoFiles.append(resline)
-            found = True
-    # add any .kinf files 
-    cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
-    out1, out2 = p1.communicate()
-    reslst = out1.split('\n')
-    for resline in reslst:
-        if os.path.isfile(resline):
-            kInfoFiles.append(resline)
-            found = True
-    if not found:
-        print('No kindle-info files have been found.')
-    return kInfoFiles
-
-# determine type of kindle info provided and return a 
-# database of keynames and values
-def getDBfromFile(kInfoFile):
-    names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
+#
+# Parse the Kindle.info file and return the records as a list of key-values
+#
+
+def parseKindleInfo():
     DB = {}
-    cnt = 0
-    infoReader = open(kInfoFile, 'r')
-    hdr = infoReader.read(1)
+    infoReader = openKindleInfo()
+    infoReader.read(1)
     data = infoReader.read()
+    items = data.split('{')
 
-    if data.find('[') != -1 :
-        # older style kindle-info file
-        items = data.split('[')
-        for item in items:
-            if item != '':
-                keyhash, rawdata = item.split(':')
-                keyname = "unknown"
-                for name in names:
-                    if encodeHash(name,charMap2) == keyhash:
-                        keyname = name
-                        break
-                if keyname == "unknown":
-                    keyname = keyhash
-                encryptedValue = decode(rawdata,charMap2)
-                cleartext = CryptUnprotectData(encryptedValue)
-                DB[keyname] = cleartext
-                cnt = cnt + 1
-        if cnt == 0:
-            DB = None
-        return DB
-
-    # else newer style .kinf file used by K4Mac >= 1.6.0
-    # the .kinf file uses "/" to separate it into records
-    # so remove the trailing "/" to make it easy to use split
-    data = data[:-1]
-    items = data.split('/')
-
-    # loop through the item records until all are processed
-    while len(items) > 0:
-    
-        # get the first item record
-        item = items.pop(0)
-    
-        # the first 32 chars of the first record of a group
-        # is the MD5 hash of the key name encoded by charMap5
-        keyhash = item[0:32]
-        keyname = "unknown"
-
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        # "entropy" not used for K4Mac only K4PC
-        # entropy = SHA1(keyhash)
-    
-        # the remainder of the first record when decoded with charMap5 
-        # has the ':' split char followed by the string representation
-        # of the number of records that follow
-        # and make up the contents
-        srcnt = decode(item[34:],charMap5)
-        rcnt = int(srcnt)
-    
-        # read and store in rcnt records of data
-        # that make up the contents value
-        edlst = []
-        for i in xrange(rcnt):
-            item = items.pop(0)
-            edlst.append(item)
-    
-        keyname = "unknown"
-        for name in names:
-            if encodeHash(name,charMap5) == keyhash:
-                keyname = name
-                break
-        if keyname == "unknown":
-            keyname = keyhash
-    
-        # the charMap5 encoded contents data has had a length 
-        # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
-        # CryptUnprotectData call from succeeding.
-    
-        # The offset into the charMap5 encoded contents seems to be:
-        # len(contents) - largest prime number less than or equal to int(len(content)/3)
-        # (in other words split "about" 2/3rds of the way through)
-    
-        # move first offsets chars to end to align for decode by charMap5
-        encdata = "".join(edlst)
-        contlen = len(encdata)
-
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
-        noffset = contlen - primes(int(contlen/3))[-1]
-        pfx = encdata[0:noffset]
-        encdata = encdata[noffset:]
-        encdata = encdata + pfx
-    
-        # decode using charMap5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        cleartext = CryptUnprotectDataV2(encryptedValue)
-        # Debugging
-        # print keyname
-        # print cleartext
-        # print cleartext.encode('hex')
-        # print
-        DB[keyname] = cleartext
-        cnt = cnt + 1
-
-    if cnt == 0:
-        DB = None
+    for item in items:
+        splito = item.split(':')
+        DB[splito[0]] =splito[1]
     return DB
+
+#
+# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
+#
+
+def findNameForHash(hash):
+    names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
+    result = ""
+    for name in names:
+        if hash == encodeHash(name, charMap2):
+            result = name
+            break
+    return name
+
+#
+# Print all the records from the kindle.info file (option -i)
+#
+
+def printKindleInfo():
+    for record in kindleDatabase:
+        name = findNameForHash(record)
+        if name != "" :
+            print (name)
+            print ("--------------------------\n")
+        else :
+            print ("Unknown Record")
+        print getKindleInfoValueForHash(record)
+        print "\n"
+#
+# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
+#
+
+def getKindleInfoValueForHash(hashedKey):
+    global kindleDatabase
+    encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
+    return CryptUnprotectData(encryptedValue,"")
+
+#
+#  Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
+#
+
+def getKindleInfoValueForKey(key):
+    return getKindleInfoValueForHash(encodeHash(key,charMap2))
+
+#
+# Get a 7 bit encoded number from the book file
+#
+
+def bookReadEncodedNumber():
+    flag = False
+    data = ord(bookFile.read(1))
+
+    if data == 0xFF:
+        flag = True
+        data = ord(bookFile.read(1))
+
+    if data >= 0x80:
+        datax = (data & 0x7F)
+        while data >= 0x80 :
+            data = ord(bookFile.read(1))
+            datax = (datax <<7) + (data & 0x7F)
+        data = datax
+
+    if flag:
+        data = -data
+    return data
+
+#
+# Encode a number in 7 bit format
+#
+
+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]
+
+#
+# Get a length prefixed string from the file
+#
+
+def bookReadString():
+    stringLength = bookReadEncodedNumber()
+    return unpack(str(stringLength)+"s",bookFile.read(stringLength))[0]
+
+#
+# Returns a length prefixed string
+#
+
+def lengthPrefixString(data):
+    return encodeNumber(len(data))+data
+
+
+#
+# Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
+#
+
+def bookReadHeaderRecordData():
+    nbValues = bookReadEncodedNumber()
+    values = []
+    for i in range (0,nbValues):
+        values.append([bookReadEncodedNumber(),bookReadEncodedNumber(),bookReadEncodedNumber()])
+    return values
+
+#
+# Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
+#
+
+def parseTopazHeaderRecord():
+    if ord(bookFile.read(1)) != 0x63:
+        raise CMBDTCFatal("Parse Error : Invalid Header")
+
+    tag = bookReadString()
+    record = bookReadHeaderRecordData()
+    return [tag,record]
+
+#
+# Parse the header of a Topaz file, get all the header records and the offset for the payload
+#
+
+def parseTopazHeader():
+    global bookHeaderRecords
+    global bookPayloadOffset
+    magic = unpack("4s",bookFile.read(4))[0]
+
+    if magic != 'TPZ0':
+        raise CMBDTCFatal("Parse Error : Invalid Header, not a Topaz file")
+
+    nbRecords = bookReadEncodedNumber()
+    bookHeaderRecords = {}
+
+    for i in range (0,nbRecords):
+        result = parseTopazHeaderRecord()
+        bookHeaderRecords[result[0]] = result[1]
+
+    if ord(bookFile.read(1))  != 0x64 :
+        raise CMBDTCFatal("Parse Error : Invalid Header")
+
+    bookPayloadOffset = bookFile.tell()
+
+#
+# Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
+#
+
+def getBookPayloadRecord(name, index):
+    encrypted = False
+
+    try:
+        recordOffset = bookHeaderRecords[name][index][0]
+    except:
+        raise CMBDTCFatal("Parse Error : Invalid Record, record not found")
+
+    bookFile.seek(bookPayloadOffset + recordOffset)
+
+    tag = bookReadString()
+    if tag != name :
+        raise CMBDTCFatal("Parse Error : Invalid Record, record name doesn't match")
+
+    recordIndex = bookReadEncodedNumber()
+
+    if recordIndex < 0 :
+        encrypted = True
+        recordIndex = -recordIndex -1
+
+    if recordIndex != index :
+        raise CMBDTCFatal("Parse Error : Invalid Record, index doesn't match")
+
+    if bookHeaderRecords[name][index][2] != 0 :
+        record = bookFile.read(bookHeaderRecords[name][index][2])
+    else:
+        record = bookFile.read(bookHeaderRecords[name][index][1])
+
+    if encrypted:
+        ctx = topazCryptoInit(bookKey)
+        record = topazCryptoDecrypt(record,ctx)
+
+    return record
+
+#
+# Extract, decrypt and decompress a book record indicated by name and index and print it or save it in "filename"
+#
+
+def extractBookPayloadRecord(name, index, filename):
+    compressed = False
+
+    try:
+        compressed = bookHeaderRecords[name][index][2] != 0
+        record = getBookPayloadRecord(name,index)
+    except:
+        print("Could not find record")
+
+    if compressed:
+        try:
+            record = zlib.decompress(record)
+        except:
+            raise CMBDTCFatal("Could not decompress record")
+
+    if filename != "":
+        try:
+            file = open(filename,"wb")
+            file.write(record)
+            file.close()
+        except:
+            raise CMBDTCFatal("Could not write to destination file")
+    else:
+        print(record)
+
+#
+# return next record [key,value] from the book metadata from the current book position
+#
+
+def readMetadataRecord():
+    return [bookReadString(),bookReadString()]
+
+#
+# Parse the metadata record from the book payload and return a list of [key,values]
+#
+
+def parseMetadata():
+    global bookHeaderRecords
+    global bookPayloadAddress
+    global bookMetadata
+    bookMetadata = {}
+    bookFile.seek(bookPayloadOffset + bookHeaderRecords["metadata"][0][0])
+    tag = bookReadString()
+    if tag != "metadata" :
+        raise CMBDTCFatal("Parse Error : Record Names Don't Match")
+
+    flags = ord(bookFile.read(1))
+    nbRecords = ord(bookFile.read(1))
+
+    for i in range (0,nbRecords) :
+        record =readMetadataRecord()
+        bookMetadata[record[0]] = record[1]
+
+#
+# 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
+
+#
+# Context initialisation for the Topaz Crypto
+#
+
+def topazCryptoInit(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):
+    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 a payload record with the PID
+#
+
+def decryptRecord(data,PID):
+    ctx = topazCryptoInit(PID)
+    return topazCryptoDecrypt(data, ctx)
+
+#
+# Try to decrypt a dkey record (contains the book PID)
+#
+
+def decryptDkeyRecord(data,PID):
+    record = decryptRecord(data,PID)
+    fields = unpack("3sB8sB8s3s",record)
+
+    if fields[0] != "PID" or fields[5] != "pid" :
+        raise CMBDTCError("Didn't find PID magic numbers in record")
+    elif fields[1] != 8 or fields[3] != 8 :
+        raise CMBDTCError("Record didn't contain correct length fields")
+    elif fields[2] != PID :
+        raise CMBDTCError("Record didn't contain PID")
+
+    return fields[4]
+
+#
+# Decrypt all the book's 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 CMBDTCError:
+            pass
+        data = data[1+length:]
+
+    return records
+
+#
+# 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):
+    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
+
+#
+# Create decrypted book payload
+#
+
+def createDecryptedPayload(payload):
+
+    # store data to be able to create the header later
+    headerData= []
+    currentOffset = 0
+
+    # Add social DRM to decrypted files
+
+    try:
+        data = getKindleInfoValueForKey("kindle.name.info")+":"+ getKindleInfoValueForKey("login")
+        if payload!= None:
+            payload.write(lengthPrefixString("sdrm"))
+            payload.write(encodeNumber(0))
+            payload.write(data)
+        else:
+            currentOffset += len(lengthPrefixString("sdrm"))
+            currentOffset += len(encodeNumber(0))
+            currentOffset += len(data)
+    except:
+        pass
+
+    for headerRecord in bookHeaderRecords:
+        name = headerRecord
+        newRecord = []
+
+        if name != "dkey" :
+
+            for index in range (0,len(bookHeaderRecords[name])) :
+                offset = currentOffset
+
+                if payload != None:
+                    # write tag
+                    payload.write(lengthPrefixString(name))
+                    # write data
+                    payload.write(encodeNumber(index))
+                    payload.write(getBookPayloadRecord(name, index))
+
+                else :
+                    currentOffset += len(lengthPrefixString(name))
+                    currentOffset += len(encodeNumber(index))
+                    currentOffset += len(getBookPayloadRecord(name, index))
+                    newRecord.append([offset,bookHeaderRecords[name][index][1],bookHeaderRecords[name][index][2]])
+
+        headerData.append([name,newRecord])
+
+
+
+    return headerData
+
+#
+# Create decrypted book
+#
+
+def createDecryptedBook(outputFile):
+    outputFile = open(outputFile,"wb")
+    # Write the payload in a temporary file
+    headerData = createDecryptedPayload(None)
+    outputFile.write("TPZ0")
+    outputFile.write(encodeNumber(len(headerData)))
+
+    for header in headerData :
+        outputFile.write(chr(0x63))
+        outputFile.write(lengthPrefixString(header[0]))
+        outputFile.write(encodeNumber(len(header[1])))
+        for numbers in header[1] :
+            outputFile.write(encodeNumber(numbers[0]))
+            outputFile.write(encodeNumber(numbers[1]))
+            outputFile.write(encodeNumber(numbers[2]))
+
+    outputFile.write(chr(0x64))
+    createDecryptedPayload(outputFile)
+    outputFile.close()
+
+#
+# Set the command to execute by the programm according to cmdLine parameters
+#
+
+def setCommand(name) :
+    global command
+    if command != "" :
+        raise CMBDTCFatal("Invalid command line parameters")
+    else :
+        command = name
+
+#
+# Program usage
+#
+
+def usage():
+    print("\nUsage:")
+    print("\nCMBDTC.py [options] bookFileName\n")
+    print("-p Adds a PID to the list of PIDs that are tried to decrypt the book key (can be used several times)")
+    print("-d Saves a decrypted copy of the book")
+    print("-r Prints or writes to disk a record indicated in the form name:index (e.g \"img:0\")")
+    print("-o Output file name to write records and decrypted books")
+    print("-v Verbose (can be used several times)")
+    print("-i Prints kindle.info database")
+
+#
+# Main
+#
+
+def main(argv=sys.argv):
+    global kindleDatabase
+    global bookMetadata
+    global bookKey
+    global bookFile
+    global command
+
+    progname = os.path.basename(argv[0])
+
+    verbose = 0
+    recordName = ""
+    recordIndex = 0
+    outputFile = ""
+    PIDs = []
+    kindleDatabase = None
+    command = ""
+
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "vdir:o:p:")
+    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 == "-v":
+            verbose+=1
+        if o == "-i":
+            setCommand("printInfo")
+        if o =="-o":
+            if a == None :
+                raise CMBDTCFatal("Invalid parameter for -o")
+            outputFile = a
+        if o =="-r":
+            setCommand("printRecord")
+            try:
+                recordName,recordIndex = a.split(':')
+            except:
+                raise CMBDTCFatal("Invalid parameter for -r")
+        if o =="-p":
+            PIDs.append(a)
+        if o =="-d":
+            setCommand("doit")
+
+    if command == "" :
+        raise CMBDTCFatal("No action supplied on command line")
+
+    #
+    # Read the encrypted database
+    #
+
+    try:
+        kindleDatabase = parseKindleInfo()
+    except Exception, message:
+        if verbose>0:
+            print(message)
+
+    if kindleDatabase != None :
+        if command == "printInfo" :
+            printKindleInfo()
+
+    #
+    # Compute the DSN
+    #
+
+    # Get the Mazama Random number
+        MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
+
+    # Get the HDD serial
+        encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
+
+    # Get the current user name
+        encodedUsername = encodeHash(GetUserName(),charMap1)
+
+    # concat, hash and encode
+        DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
+
+        if verbose >1:
+            print("DSN: " + DSN)
+
+    #
+    # Compute the device PID
+    #
+
+        table =  generatePidEncryptionTable()
+        devicePID = generateDevicePID(table,DSN,4)
+        PIDs.append(devicePID)
+
+        if verbose > 0:
+            print("Device PID: " + devicePID)
+
+    #
+    # Open book and parse metadata
+    #
+
+    if len(args) == 1:
+
+        bookFile = openBook(args[0])
+        parseTopazHeader()
+        parseMetadata()
+
+    #
+    # Compute book PID
+    #
+
+    # Get the account token
+
+        if kindleDatabase != None:
+            kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
+
+            if verbose >1:
+                print("Account Token: " + kindleAccountToken)
+
+            keysRecord = bookMetadata["keys"]
+            keysRecordRecord = bookMetadata[keysRecord]
+
+            pidHash = SHA1(DSN+kindleAccountToken+keysRecord+keysRecordRecord)
+
+            bookPID = encodePID(pidHash)
+            PIDs.append(bookPID)
+
+            if verbose > 0:
+                print ("Book PID: " + bookPID )
+
+    #
+    #  Decrypt book key
+    #
+
+        dkey = getBookPayloadRecord('dkey', 0)
+
+        bookKeys = []
+        for PID in PIDs :
+            bookKeys+=decryptDkeyRecords(dkey,PID)
+
+        if len(bookKeys) == 0 :
+            if verbose > 0 :
+                print ("Book key could not be found. Maybe this book is not registered with this device.")
+        else :
+            bookKey = bookKeys[0]
+            if verbose > 0:
+                print("Book key: " + bookKey.encode('hex'))
+
+
+
+            if command == "printRecord" :
+                extractBookPayloadRecord(recordName,int(recordIndex),outputFile)
+                if outputFile != "" and verbose>0 :
+                    print("Wrote record to file: "+outputFile)
+            elif command == "doit" :
+                if outputFile!="" :
+                    createDecryptedBook(outputFile)
+                    if verbose >0 :
+                        print ("Decrypted book saved. Don't pirate!")
+                elif verbose > 0:
+                    print("Output file name was not supplied.")
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
index 6acdd5c65c6b224a48221d3033a061b1cc570b71..98645372c97d251bbeda73e30138c9dbaaff6789 100644 (file)
-#!/usr/bin/env python
-# K4PC Windows specific routines
-
-from __future__ import with_statement
-
-import sys, os
-from struct import pack, unpack, unpack_from
-
-from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
-    create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
-    string_at, Structure, c_void_p, cast
-
-import _winreg as winreg
-
-MAX_PATH = 255
+#! /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
 
-kernel32 = windll.kernel32
-advapi32 = windll.advapi32
-crypt32 = windll.crypt32
+# 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
 
-import traceback
+    if negative:
+        result += chr(0xFF)
 
-# crypto digestroutines
-import hashlib
+    return result[::-1]
 
-def MD5(message):
-    ctx = hashlib.md5()
-    ctx.update(message)
-    return ctx.digest()
 
-def SHA1(message):
-    ctx = hashlib.sha1()
-    ctx.update(message)
-    return ctx.digest()
 
+# create / read  a length prefixed string from the file
 
-# simple primes table (<= n) calculator
-def primes(n): 
-    if n==2: return [2]
-    elif n<2: return []
-    s=range(3,n+1,2)
-    mroot = n ** 0.5
-    half=(n+1)/2-1
-    i=0
-    m=3
-    while m <= mroot:
-        if s[i]:
-            j=(m*m-3)/2
-            s[j]=0
-            while j<half:
-                s[j]=0
-                j+=m
-        i=i+1
-        m=2*i+3
-    return [2]+[x for x in s if x]
+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]
 
-# Various character maps used to decrypt kindle info values.
-# Probably supposed to act as obfuscation
-charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
-charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
 
-class DrmException(Exception):
-    pass
+# 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
 
-# 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]
+def convert(i):
+    result = ''
+    val = encodeNumber(i)
+    for j in xrange(len(val)):
+        c = ord(val[j:j+1])
+        result += '%02x' % c
     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
 
 
-# interface with Windows OS Routines
-class DataBlob(Structure):
-    _fields_ = [('cbData', c_uint),
-                ('pbData', c_void_p)]
-DataBlob_p = POINTER(DataBlob)
-
-
-def GetSystemDirectory():
-    GetSystemDirectoryW = kernel32.GetSystemDirectoryW
-    GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
-    GetSystemDirectoryW.restype = c_uint
-    def GetSystemDirectory():
-        buffer = create_unicode_buffer(MAX_PATH + 1)
-        GetSystemDirectoryW(buffer, len(buffer))
-        return buffer.value
-    return GetSystemDirectory
-GetSystemDirectory = GetSystemDirectory()
-
-def GetVolumeSerialNumber():
-    GetVolumeInformationW = kernel32.GetVolumeInformationW
-    GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
-                                      POINTER(c_uint), POINTER(c_uint),
-                                      POINTER(c_uint), c_wchar_p, c_uint]
-    GetVolumeInformationW.restype = c_uint
-    def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'):
-        vsn = c_uint(0)
-        GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
-        return str(vsn.value)
-    return GetVolumeSerialNumber
-GetVolumeSerialNumber = GetVolumeSerialNumber()
-
-def GetIDString():
-    return GetVolumeSerialNumber()
-
-def getLastError():
-    GetLastError = kernel32.GetLastError
-    GetLastError.argtypes = None
-    GetLastError.restype = c_uint
-    def getLastError():
-        return GetLastError()
-    return getLastError
-getLastError = getLastError()
-
-def GetUserName():
-    GetUserNameW = advapi32.GetUserNameW
-    GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
-    GetUserNameW.restype = c_uint
-    def GetUserName():
-        buffer = create_unicode_buffer(2)
-        size = c_uint(len(buffer))
-        while not GetUserNameW(buffer, byref(size)):
-            errcd = getLastError()
-            if errcd == 234:
-                # bad wine implementation up through wine 1.3.21
-                return "AlternateUserName"
-            buffer = create_unicode_buffer(len(buffer) * 2)
-            size.value = len(buffer)
-        return buffer.value.encode('utf-16-le')[::2]
-    return GetUserName
-GetUserName = GetUserName()
-
-def CryptUnprotectData():
-    _CryptUnprotectData = crypt32.CryptUnprotectData
-    _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
-                                   c_void_p, c_void_p, c_uint, DataBlob_p]
-    _CryptUnprotectData.restype = c_uint
-    def CryptUnprotectData(indata, entropy, flags):
-        indatab = create_string_buffer(indata)
-        indata = DataBlob(len(indata), cast(indatab, c_void_p))
-        entropyb = create_string_buffer(entropy)
-        entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
-        outdata = DataBlob()
-        if not _CryptUnprotectData(byref(indata), None, byref(entropy),
-                                   None, None, flags, byref(outdata)):
-            raise DrmException("Failed to Unprotect Data")
-        return string_at(outdata.pbData, outdata.cbData)
-    return CryptUnprotectData
-CryptUnprotectData = CryptUnprotectData()
-
-
-# Locate all of the kindle-info style files and return as list
-def getKindleInfoFiles(kInfoFiles):
-    regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
-    path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
-
-    # first look for older kindle-info files
-    kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
-    if not os.path.isfile(kinfopath):
-        print('No kindle.info files have not been found.')
-    else:
-        kInfoFiles.append(kinfopath)
-
-    # now look for newer (K4PC 1.5.0 and later rainier.2.1.1.kinf file
-
-    kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
-    if not os.path.isfile(kinfopath):
-        print('No K4PC 1.5.X .kinf files have not been found.')
-    else:
-        kInfoFiles.append(kinfopath)
-
-    # now look for even newer (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
-    kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
-    if not os.path.isfile(kinfopath):
-        print('No K4PC 1.6.X .kinf files have not been found.')
-    else:
-        kInfoFiles.append(kinfopath)
-
-    return kInfoFiles
-
-
-# determine type of kindle info provided and return a 
-# database of keynames and values
-def getDBfromFile(kInfoFile):
-    names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
-    DB = {}
-    cnt = 0
-    infoReader = open(kInfoFile, 'r')
-    hdr = infoReader.read(1)
-    data = infoReader.read()
-
-    if data.find('{') != -1 :
-
-        # older style kindle-info file
-        items = data.split('{')
-        for item in items:
-            if item != '':
-                keyhash, rawdata = item.split(':')
-                keyname = "unknown"
-                for name in names:
-                    if encodeHash(name,charMap2) == keyhash:
-                        keyname = name
-                        break
-                if keyname == "unknown":
-                    keyname = keyhash
-                encryptedValue = decode(rawdata,charMap2)
-                DB[keyname] = CryptUnprotectData(encryptedValue, "", 0)
-                cnt = cnt + 1
-        if cnt == 0:
-            DB = None
-        return DB
-
-    # else newer style .kinf file
-    # the .kinf file uses "/" to separate it into records
-    # so remove the trailing "/" to make it easy to use split
-    data = data[:-1]
-    items = data.split('/')
-
-    # loop through the item records until all are processed
-    while len(items) > 0:
-
-        # get the first item record
-        item = items.pop(0)
-
-        # the first 32 chars of the first record of a group
-        # is the MD5 hash of the key name encoded by charMap5
-        keyhash = item[0:32]
-
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        entropy = SHA1(keyhash)
-
-        # the remainder of the first record when decoded with charMap5 
-        # has the ':' split char followed by the string representation
-        # of the number of records that follow
-        # and make up the contents
-        srcnt = decode(item[34:],charMap5)
-        rcnt = int(srcnt)
-
-        # read and store in rcnt records of data
-        # that make up the contents value
-        edlst = []
-        for i in xrange(rcnt):
-            item = items.pop(0)
-            edlst.append(item)
-
-        keyname = "unknown"
-        for name in names:
-            if encodeHash(name,charMap5) == keyhash:
-                keyname = name
+# 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('&','&amp;')
+        str = str.replace('<','&lt;')
+        str = str.replace('>','&gt;')
+        str = str.replace('=','&#61;')
+        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.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),
+
+        '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.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.type'      : (1, 'scalar_text', 0, 0),
+        'group._tag'      : (1, 'scalar_text', 0, 0),
+
+        'region'           : (1, 'snippets', 1, 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),
+
+        '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),
+
+        '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.firstWord' : (1, 'scalar_number', 0, 0),
+        'word_semantic.lastWord'  : (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.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.type'       : (1, 'scalar_text', 0, 0),
+        'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
+        'extratokens.lastGlyph'  : (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._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),
+
+     }
+
+
+    # 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 keyname == "unknown":
-            keyname = keyhash
-
-        # the charMap5 encoded contents data has had a length 
-        # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
-        # CryptUnprotectData call from succeeding.
-
-        # The offset into the charMap5 encoded contents seems to be:
-        # len(contents) - largest prime number less than or equal to int(len(content)/3)
-        # (in other words split "about" 2/3rds of the way through)
-
-        # move first offsets chars to end to align for decode by charMap5
-        encdata = "".join(edlst)
-        contlen = len(encdata)
-        noffset = contlen - primes(int(contlen/3))[-1]
-
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
-        pfx = encdata[0:noffset]
-        encdata = encdata[noffset:]
-        encdata = encdata + pfx
-
-        # decode using Map5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
-        cnt = cnt + 1
-
-    if cnt == 0:
-        DB = None
-    return DB
 
+        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):
+                print 'Unknown Token:', token
+            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(''))
index 4d978b377ae0cb64cb057212b5d82b314117176a..4dfd6c7bbfae633633100610805ddbb13334fd46 100644 (file)
-#!/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
-#  0.01 - Initial version
-#  0.02 - Huffdic compressed books were not properly decrypted
-#  0.03 - Wasn't checking MOBI header length
-#  0.04 - Wasn't sanity checking size of data record
-#  0.05 - It seems that the extra data flags take two bytes not four
-#  0.06 - And that low bit does mean something after all :-)
-#  0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
-#  0.08 - ...and also not in Mobi header version < 6
-#  0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
-#  0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
-#         import filter it works when importing unencrypted files.
-#         Also now handles encrypted files that don't need a specific PID.
-#  0.11 - use autoflushed stdout and proper return values
-#  0.12 - Fix for problems with metadata import as Calibre plugin, report errors
-#  0.13 - Formatting fixes: retabbed file, removed trailing whitespace
-#         and extra blank lines, converted CR/LF pairs at ends of each line,
-#         and other cosmetic fixes.
-#  0.14 - Working out when the extra data flags are present has been problematic
-#         Versions 7 through 9 have tried to tweak the conditions, but have been
-#         only partially successful. Closer examination of lots of sample
-#         files reveals that a confusion has arisen because trailing data entries
-#         are not encrypted, but it turns out that the multibyte entries
-#         in utf8 file are encrypted. (Although neither kind gets compressed.)
-#         This knowledge leads to a simplification of the test for the 
-#         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
-#  0.15 - Now outputs 'heartbeat', and is also quicker for long files.
-#  0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
-#  0.17 - added modifications to support its use as an imported python module
-#         both inside calibre and also in other places (ie K4DeDRM tools)
-#  0.17a- disabled the standalone plugin feature since a plugin can not import
-#         a plugin
-#  0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
-#         Removed the disabled Calibre plug-in code
-#         Permit use of 8-digit PIDs
-#  0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
-#  0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
-#  0.21 - Added support for multiple pids
-#  0.22 - revised structure to hold MobiBook as a class to allow an extended interface
-#  0.23 - fixed problem with older files with no EXTH section 
-#  0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
-#  0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
-#  0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
-#  0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
-#  0.28 - slight additional changes to metadata token generation (None -> '')
-#  0.29 - It seems that the ideas about when multibyte trailing characters were
-#         included in the encryption were wrong. They are for DOC compressed
-#         files, but they are not for HUFF/CDIC compress files!
-#  0.30 - Modified interface slightly to work better with new calibre plugin style
-#  0.31 - The multibyte encrytion info is true for version 7 files too.
-#  0.32 - Added support for "Print Replica" Kindle ebooks
-
-__version__ = '0.32'
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
 
 import sys
-
-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)
-sys.stdout=Unbuffered(sys.stdout)
-
+import csv
 import os
-import struct
-import binascii
-
-class DrmException(Exception):
-    pass
-
-
-#
-# MobiBook Utility Routines
-#
-
-# Implementation of Pukall Cipher 1
-def PC1(key, src, decryption=True):
-    sum1 = 0;
-    sum2 = 0;
-    keyXorVal = 0;
-    if len(key)!=16:
-        print "Bad key length!"
-        return None
-    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
-
-def checksumPid(s):
-    letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
-    crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
-    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 getSizeOfTrailingDataEntries(ptr, size, flags):
-    def getSizeOfTrailingDataEntry(ptr, size):
-        bitpos, result = 0, 0
-        if size <= 0:
-            return result
-        while True:
-            v = ord(ptr[size-1])
-            result |= (v & 0x7F) << bitpos
-            bitpos += 7
-            size -= 1
-            if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
-                return result
-    num = 0
-    testflags = flags >> 1
-    while testflags:
-        if testflags & 1:
-            num += getSizeOfTrailingDataEntry(ptr, size - num)
-        testflags >>= 1
-    # Check the low bit to see if there's multibyte data present.
-    # if multibyte data is included in the encryped data, we'll
-    # have already cleared this flag.
-    if flags & 1:
-        num += (ord(ptr[size - num - 1]) & 0x3) + 1
-    return num
-
-
-
-class MobiBook:
-    def loadSection(self, section):
-        if (section + 1 == self.num_sections):
-            endoff = len(self.data_file)
+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:
-            endoff = self.sections[section + 1][0]
-        off = self.sections[section][0]
-        return self.data_file[off:endoff]
-
-    def __init__(self, infile):
-        print ('MobiDeDrm v%(__version__)s. '
-           'Copyright 2008-2011 The Dark Reverser et al.' % globals())
-
-        # initial sanity check on file
-        self.data_file = file(infile, 'rb').read()
-        self.mobi_data = ''
-        self.header = self.data_file[0:78]
-        if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
-            raise DrmException("invalid file format")
-        self.magic = self.header[0x3C:0x3C+8]
-        self.crypto_type = -1
-
-        # build up section offset and flag info
-        self.num_sections, = struct.unpack('>H', self.header[76:78])
-        self.sections = []
-        for i in xrange(self.num_sections):
-            offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
-            flags, val = a1, a2<<16|a3<<8|a4
-            self.sections.append( (offset, flags, val) )
-
-        # parse information from section 0
-        self.sect = self.loadSection(0)
-        self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
-        self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
-
-        if self.magic == 'TEXtREAd':
-            print "Book has format: ", self.magic
-            self.extra_data_flags = 0
-            self.mobi_length = 0
-            self.mobi_version = -1
-            self.meta_array = {}
-            return
-        self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
-        self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
-        self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
-        print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
-        self.extra_data_flags = 0
-        if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
-            self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
-            print "Extra Data Flags = %d" % self.extra_data_flags
-        if (self.compression != 17480):
-            # multibyte utf8 data is included in the encryption for PalmDoc compression
-            # so clear that byte so that we leave it to be decrypted.
-            self.extra_data_flags &= 0xFFFE
-
-        # if exth region exists parse it for metadata array
-        self.meta_array = {}
-        try:
-            exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
-            exth = 'NONE'
-            if exth_flag & 0x40:
-                exth = self.sect[16 + self.mobi_length:]
-            if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
-                nitems, = struct.unpack('>I', exth[8:12])
-                pos = 12
-                for i in xrange(nitems):
-                    type, size = struct.unpack('>II', exth[pos: pos + 8])
-                    content = exth[pos + 8: pos + size]
-                    self.meta_array[type] = content
-                    # reset the text to speech flag and clipping limit, if present
-                    if type == 401 and size == 9:
-                        # set clipping limit to 100%
-                        self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
-                    elif type == 404 and size == 9:
-                        # make sure text to speech is enabled
-                        self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
-                    # print type, size, content, content.encode('hex')
-                    pos += size
-        except:
-            self.meta_array = {}
-            pass
-        self.print_replica = False
-            
-    def getBookTitle(self):
-        codec_map = {
-            1252 : 'windows-1252',
-            65001 : 'utf-8',
-        }
-        title = ''
-        if 503 in self.meta_array:
-            title = self.meta_array[503]
-        else :
-            toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
-            tend = toff + tlen
-            title = self.sect[toff:tend]
-        if title == '':
-            title = self.header[:32]
-            title = title.split("\0")[0]
-        codec = 'windows-1252'
-        if self.mobi_codepage in codec_map.keys():
-            codec = codec_map[self.mobi_codepage]
-        return unicode(title, codec).encode('utf-8')
-
-    def getPIDMetaInfo(self):
-        rec209 = ''
-        token = ''
-        if 209 in self.meta_array:
-            rec209 = self.meta_array[209]
-            data = rec209
-            # The 209 data comes in five byte groups. Interpret the last four bytes
-            # of each group as a big endian unsigned integer to get a key value
-            # if that key exists in the meta_array, append its contents to the token
-            for i in xrange(0,len(data),5):
-                val,  = struct.unpack('>I',data[i+1:i+5])
-                sval = self.meta_array.get(val,'')
-                token += sval
-        return rec209, token
-
-    def patch(self, off, new):
-        self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
-
-    def patchSection(self, section, new, in_off = 0):
-        if (section + 1 == self.num_sections):
-            endoff = len(self.data_file)
-        else:
-            endoff = self.sections[section + 1][0]
-        off = self.sections[section][0]
-        assert off + in_off + len(new) <= endoff
-        self.patch(off + in_off, new)
-
-    def parseDRM(self, data, count, pidlist):
-        found_key = None
-        keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
-        for pid in pidlist:
-            bigpid = pid.ljust(16,'\0')
-            temp_key = PC1(keyvec1, bigpid, False)
-            temp_key_sum = sum(map(ord,temp_key)) & 0xff
-            found_key = None
-            for i in xrange(count):
-                verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
-                if cksum == temp_key_sum:
-                    cookie = PC1(temp_key, cookie)
-                    ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
-                    if verification == ver and (flags & 0x1F) == 1:
-                        found_key = finalkey
-                        break
-            if found_key != None:
+            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
-        if not found_key:
-            # Then try the default encoding that doesn't require a PID
-            pid = "00000000"
-            temp_key = keyvec1
-            temp_key_sum = sum(map(ord,temp_key)) & 0xff
-            for i in xrange(count):
-                verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
-                if cksum == temp_key_sum:
-                    cookie = PC1(temp_key, cookie)
-                    ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
-                    if verification == ver:
-                        found_key = finalkey
-                        break
-        return [found_key,pid]
-
-    def getMobiFile(self, outpath):
-        file(outpath,'wb').write(self.mobi_data)
-        
-    def getPrintReplica(self):
-        return self.print_replica
-
-    def processBook(self, pidlist):
-        crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
-        print 'Crypto Type is: ', crypto_type
-        self.crypto_type = crypto_type
-        if crypto_type == 0:
-            print "This book is not encrypted."
-            # we must still check for Print Replica
-            self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
-            self.mobi_data = self.data_file
-            return
-        if crypto_type != 2 and crypto_type != 1:
-            raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
-        if 406 in self.meta_array:
-            data406 = self.meta_array[406]
-            val406, = struct.unpack('>Q',data406)
-            if val406 != 0:
-                raise DrmException("Cannot decode library or rented ebooks.")
-
-        goodpids = []
-        for pid in pidlist:
-            if len(pid)==10:
-                if checksumPid(pid[0:-2]) != pid:
-                    print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
-                goodpids.append(pid[0:-2])
-            elif len(pid)==8:
-                goodpids.append(pid)
-
-        if self.crypto_type == 1:
-            t1_keyvec = "QDCVEPMU675RUBSZ"
-            if self.magic == 'TEXtREAd':
-                bookkey_data = self.sect[0x0E:0x0E+16]
-            elif self.mobi_version < 0:
-                bookkey_data = self.sect[0x90:0x90+16] 
+        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:
-                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] 
-            pid = "00000000"
-            found_key = PC1(t1_keyvec, bookkey_data)
-        else :
-            # calculate the keys
-            drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
-            if drm_count == 0:
-                raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
-            found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
-            if not found_key:
-                raise DrmException("No key found. Most likely the correct PID has not been given.")
-            # kill the drm keys
-            self.patchSection(0, "\0" * drm_size, drm_ptr)
-            # kill the drm pointers
-            self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
-            
-        if pid=="00000000":
-            print "File has default encryption, no specific PID."
+                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:
-            print "File is encoded with PID "+checksumPid(pid)+"."
-
-        # clear the crypto type
-        self.patchSection(0, "\0" * 2, 0xC)
-
-        # decrypt sections
-        print "Decrypting. Please wait . . .",
-        self.mobi_data = self.data_file[:self.sections[1][0]]
-        for i in xrange(1, self.records+1):
-            data = self.loadSection(i)
-            extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
-            if i%100 == 0:
-                print ".",
-            # print "record %d, extra_size %d" %(i,extra_size)
-            decoded_data = PC1(found_key, data[0:len(data) - extra_size])
-            if i==1:
-                self.print_replica = (decoded_data[0:4] == '%MOP')
-            self.mobi_data += decoded_data
-            if extra_size > 0:
-                self.mobi_data += data[-extra_size:]
-        if self.num_sections > self.records+1:
-            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
-        print "done"
-        return
-
-def getUnencryptedBook(infile,pid):
-    if not os.path.isfile(infile):
-        raise DrmException('Input File Not Found')
-    book = MobiBook(infile)
-    book.processBook([pid])
-    return book.mobi_data
-
-def getUnencryptedBookWithList(infile,pidlist):
-    if not os.path.isfile(infile):
-        raise DrmException('Input File Not Found')
-    book = MobiBook(infile)
-    book.processBook(pidlist)
-    return book.mobi_data
-
-
-def main(argv=sys.argv):
-    print ('MobiDeDrm v%(__version__)s. '
-        'Copyright 2008-2011 The Dark Reverser et al.' % globals())
-    if len(argv)<3 or len(argv)>4:
-        print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
-        print "Usage:"
-        print "    %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
-        return 1
+            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:
-        infile = argv[1]
-        outfile = argv[2]
-        if len(argv) is 4:
-            pidlist = argv[3].split(',')
+        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:
-            pidlist = {}
-        try:
-            stripped_file = getUnencryptedBookWithList(infile, pidlist)
-            file(outfile, 'wb').write(stripped_file)
-        except DrmException, e:
-            print "Error: %s" % e
-            return 1
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main())
+            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)
index 0271cec239d3858c59d03380d5a1eb10a3d23f48..e0c69e103a525121dcd5bf3dd925b6bdf0b1a046 100644 (file)
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM 3.1, Written 2010–2011 by Apprentice Alf and others.</string>
+       <string>DeDRM 5.0, Written 2010–2012 by Apprentice Alf and others.</string>
        <key>CFBundleIconFile</key>
        <string>droplet</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleName</key>
-       <string>DeDRM 3.1</string>
+       <string>DeDRM 5.0</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>3.1</string>
+       <string>5.0</string>
        <key>CFBundleSignature</key>
        <string>dplt</string>
        <key>LSMinimumSystemVersion</key>
@@ -52,7 +52,7 @@
                <key>positionOfDivider</key>
                <real>460</real>
                <key>savedFrame</key>
-               <string>39 106 1316 746 0 0 1440 878 </string>
+               <string>-2 132 1316 746 0 0 1440 878 </string>
                <key>selectedTabView</key>
                <string>event log</string>
        </dict>
index ab1cce372dd0258222ba4409ecbb841513186415..6844962d9bf21c0413d6ca3e50d2350454e8a11a 100644 (file)
Binary files a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt differ
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/aescbc.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/aescbc.py
new file mode 100644 (file)
index 0000000..5667511
--- /dev/null
@@ -0,0 +1,568 @@
+#! /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'
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/alfcrypto.py
new file mode 100644 (file)
index 0000000..e25a0c8
--- /dev/null
@@ -0,0 +1,290 @@
+#! /usr/bin/env python
+
+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'
+    
+    libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found')
+
+    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 "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:
+                print "Bad key length!"
+                return None
+            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
+
+    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]
+
+
index 0328206ac8b4d8ac5725f85e62cdc7b71febe53b..f16cdcc827c80742a746265c1fdebc7f067301bb 100644 (file)
@@ -23,7 +23,7 @@ from struct import unpack
 class TpzDRMError(Exception):
     pass
 
-# Get a 7 bit encoded number from string. The most 
+# Get a 7 bit encoded number from string. The most
 # significant byte comes first and has the high bit (8th) set
 
 def readEncodedNumber(file):
@@ -32,57 +32,57 @@ def readEncodedNumber(file):
     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)
-       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
-    
+        data = datax
+
     if flag:
-       data = -data
+        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]
-  
+    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
@@ -97,9 +97,9 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    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
 
@@ -265,6 +265,8 @@ class PageParser(object):
         '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),
@@ -284,6 +286,8 @@ class PageParser(object):
         '_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),
@@ -291,6 +295,8 @@ class PageParser(object):
         '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.type'       : (1, 'scalar_text', 0, 0),
@@ -376,14 +382,14 @@ class PageParser(object):
         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 
+    # 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):
@@ -416,7 +422,7 @@ class PageParser(object):
         return result
 
 
-    # process the next tag token, recursively handling subtags, 
+    # process the next tag token, recursively handling subtags,
     # arguments, and commands
     def procToken(self, token):
 
@@ -438,7 +444,7 @@ class PageParser(object):
 
         if known_token :
 
-            # handle subtags if present 
+            # handle subtags if present
             subtagres = []
             if (splcase == 1):
                 # this type of tag uses of escape marker 0x74 indicate subtag count
@@ -447,7 +453,7 @@ class PageParser(object):
                     subtags = 1
                     num_args = 0
 
-            if (subtags == 1): 
+            if (subtags == 1):
                 ntags = readEncodedNumber(self.fo)
                 if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
                 for j in xrange(ntags):
@@ -478,7 +484,7 @@ class PageParser(object):
             return result
 
         # all tokens that need to be processed should be in the hash
-        # table if it may indicate a problem, either new token 
+        # table if it may indicate a problem, either new token
         # or an out of sync condition
         else:
             result = []
@@ -530,7 +536,7 @@ class PageParser(object):
     # 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 
+    # 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
 
@@ -549,7 +555,7 @@ class PageParser(object):
         return result
 
 
-            
+
     # add full tag path to injected snippets
     def updateName(self, tag, prefix):
         name = tag[0]
@@ -577,7 +583,7 @@ class PageParser(object):
         argtype = tag[2]
         argList = tag[3]
         nsubtagList = []
-        if len(argList) > 0 : 
+        if len(argList) > 0 :
             for j in argList:
                 asnip = self.snippetList[j]
                 aso, atag = self.injectSnippets(asnip)
@@ -633,7 +639,7 @@ class PageParser(object):
         return result
 
 
-   # flatten tag
+    # flatten tag
     def flattenTag(self, node):
         name = node[0]
         subtagList = node[1]
@@ -712,7 +718,7 @@ class PageParser(object):
                 first_token = None
 
             v = self.getNext()
-            if (v == None): 
+            if (v == None):
                 break
 
             if (v == 0x72):
@@ -723,7 +729,7 @@ class PageParser(object):
                     self.doc.append(tag)
             else:
                 if self.debug:
-                    print "Main Loop:  Unknown value: %x" % v 
+                    print "Main Loop:  Unknown value: %x" % v
                 if (v == 0):
                     if (self.peek(1) == 0x5f):
                         skip = self.fo.read(1)
@@ -776,7 +782,7 @@ def usage():
 
 #
 # Main
-#   
+#
 
 def main(argv):
     dictFile = ""
@@ -797,11 +803,11 @@ def main(argv):
         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) 
-       
+        sys.exit(2)
+
     for o, a in opts:
         if o =="-d":
             debug=True
index f4a020258294d727439f8f9fd542150997e307f0..6bb8c37d20fe779e3741a74af2923e28e5e63634 100644 (file)
@@ -29,18 +29,17 @@ def usage(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')
+        print keyder.encode('base64')
     return 0
 
 
 if __name__ == '__main__':
     sys.exit(cli_main())
-   
\ No newline at end of file
index 7fefaf7125750b7c6ce448cafa0327727c07e035..8f958cd487a971e95ff6c6af979e618005b2a0ec 100644 (file)
@@ -16,7 +16,7 @@
 # Custom version 0.03 - no change to eReader support, only usability changes
 #   - start of pep-8 indentation (spaces not tab), fix trailing blanks
 #   - version variable, only one place to change
-#   - added main routine, now callable as a library/module, 
+#   - added main routine, now callable as a library/module,
 #     means tools can add optional support for ereader2html
 #   - outdir is no longer a mandatory parameter (defaults based on input name if missing)
 #   - time taken output to stdout
@@ -59,8 +59,8 @@
 #  0.18 - on Windows try PyCrypto first and OpenSSL next
 #  0.19 - Modify the interface to allow use of import
 #  0.20 - modify to allow use inside new interface for calibre plugins
-#  0.21 - Support eReader (drm) version 11. 
-#       - Don't reject dictionary format. 
+#  0.21 - Support eReader (drm) version 11.
+#       - Don't reject dictionary format.
 #       - Ignore sidebars for dictionaries (different format?)
 
 __version__='0.21'
@@ -178,7 +178,7 @@ def sanitizeFileName(s):
 def fixKey(key):
     def fixByte(b):
         return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
-    return     "".join([chr(fixByte(ord(a))) for a in key])
+    return      "".join([chr(fixByte(ord(a))) for a in key])
 
 def deXOR(text, sp, table):
     r=''
@@ -212,7 +212,7 @@ class EreaderProcessor(object):
             for i in xrange(len(data)):
                 j = (j + shuf) % len(data)
                 r[j] = data[i]
-            assert     len("".join(r)) == len(data)
+            assert      len("".join(r)) == len(data)
             return "".join(r)
         r = unshuff(input[0:-8], cookie_shuf)
 
@@ -314,7 +314,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(chaps, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             cv += '%d|%s\n' % (offset, name) 
+    #             cv += '%d|%s\n' % (offset, name)
     #     return cv
 
     # def getLinkNamePMLOffsetData(self):
@@ -326,7 +326,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(links, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             lv += '%d|%s\n' % (offset, name) 
+    #             lv += '%d|%s\n' % (offset, name)
     #     return lv
 
     # def getExpandedTextSizesData(self):
@@ -354,7 +354,7 @@ class EreaderProcessor(object):
         for i in xrange(self.num_text_pages):
             logging.debug('get page %d', i)
             r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
-             
+
         # now handle footnotes pages
         if self.num_footnote_pages > 0:
             r += '\n'
@@ -399,12 +399,12 @@ class EreaderProcessor(object):
         return r
 
 def cleanPML(pml):
-       # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
-       pml2 = pml
-       for k in xrange(128,256):
-               badChar = chr(k)
-               pml2 = pml2.replace(badChar, '\\a%03d' % k)
-       return pml2
+        # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
+    pml2 = pml
+    for k in xrange(128,256):
+        badChar = chr(k)
+        pml2 = pml2.replace(badChar, '\\a%03d' % k)
+    return pml2
 
 def convertEreaderToPml(infile, name, cc, outdir):
     if not os.path.exists(outdir):
@@ -435,7 +435,7 @@ def convertEreaderToPml(infile, name, cc, outdir):
     #     file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
 
 
-                
+
 def decryptBook(infile, outdir, name, cc, make_pmlz):
     if make_pmlz :
         # ignore specified outdir, use tempdir instead
@@ -468,7 +468,7 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
             shutil.rmtree(outdir, True)
             print 'output is %s' % zipname
         else :
-            print 'output in %s' % outdir 
+            print 'output in %s' % outdir
         print "done"
     except ValueError, e:
         print "Error: %s" % e
@@ -505,7 +505,7 @@ def main(argv=None):
             return 0
         elif o == "--make-pmlz":
             make_pmlz = True
-    
+
     print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
 
     if len(args)!=3 and len(args)!=4:
@@ -524,4 +524,3 @@ def main(argv=None):
 if __name__ == "__main__":
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 3b32fc0a945bbae23fac4dc0146ad4cad9685752..4e3ed73accb5765cacd089e0d6bf4b7b3bc22429 100644 (file)
@@ -68,7 +68,7 @@ class DocParser(object):
         ys = []
         gdefs = []
 
-        # get path defintions, positions, dimensions for each glyph 
+        # 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
@@ -79,7 +79,7 @@ class DocParser(object):
             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])
@@ -124,12 +124,12 @@ class DocParser(object):
             item = self.docList[pos]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            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
@@ -142,10 +142,10 @@ class DocParser(object):
             item = self.docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -182,13 +182,13 @@ class DocParser(object):
         # 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 
+        # 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(' ','-')
@@ -211,7 +211,7 @@ class DocParser(object):
         return nclass
 
 
-    # develop a sorted description of the starting positions of 
+    # develop a sorted description of the starting positions of
     # groups and regions on the page, as well as the page type
     def PageDescription(self):
 
@@ -267,7 +267,7 @@ class DocParser(object):
         result = []
 
         # paragraph
-        (pos, pclass) = self.findinDoc('paragraph.class',start,end) 
+        (pos, pclass) = self.findinDoc('paragraph.class',start,end)
 
         pclass = self.getClass(pclass)
 
@@ -281,17 +281,22 @@ class DocParser(object):
         if (sfirst != None) and (slast != None) :
             first = int(sfirst)
             last = int(slast)
-            
+
             makeImage = (regtype == 'vertical') or (regtype == 'table')
-            makeImage = makeImage or (extraglyphs != None) 
+            makeImage = makeImage or (extraglyphs != None)
             if self.fixedimage:
                 makeImage = makeImage or (regtype == 'fixed')
 
-            if (pclass != None): 
+            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):
@@ -332,10 +337,10 @@ class DocParser(object):
             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, 
+        # 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 = ''
@@ -344,7 +349,7 @@ class DocParser(object):
         if end == -1 :
             end = self.docSize
 
-        # seems some xml has last* coming before first* so we have to 
+        # seems some xml has last* coming before first* so we have to
         # handle any order
         sp_first = -1
         sp_last = -1
@@ -382,10 +387,10 @@ class DocParser(object):
                 ws_last = int(argres)
 
             elif name.endswith('word.class'):
-               (cname, space) = argres.split('-',1)
-               if space == '' : space = '0'
-               if (cname == 'spaceafter') and (int(space) > 0) :
-                   word_class = 'sa'
+                (cname, space) = argres.split('-',1)
+                if space == '' : space = '0'
+                if (cname == 'spaceafter') and (int(space) > 0) :
+                    word_class = 'sa'
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
@@ -416,11 +421,11 @@ class DocParser(object):
                     result.append(('ocr', wordnum))
                 ws_first = -1
                 ws_last = -1
-                              
+
             line += 1
 
         return pclass, result
-                            
+
 
     def buildParagraph(self, pclass, pdesc, type, regtype) :
         parares = ''
@@ -433,7 +438,7 @@ class DocParser(object):
         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 + '>'
 
@@ -462,7 +467,7 @@ class DocParser(object):
                         if linktype == 'external' :
                             linkhref = self.link_href[link-1]
                             linkhtml = '<a href="%s">' % linkhref
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkhtml = '<a href="#page%04d">' % ptarget
@@ -509,7 +514,7 @@ class DocParser(object):
 
             elif wtype == 'svg' :
                 sep = ''
-                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num 
+                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
                 parares += sep
 
         if len(sep) > 0 : parares = parares[0:-1]
@@ -551,7 +556,7 @@ class DocParser(object):
                             title = ''
                             alt_title = ''
                             linkpage = ''
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkpage = '%04d' % ptarget
@@ -584,7 +589,7 @@ class DocParser(object):
 
 
 
-    
+
     # walk the document tree collecting the information needed
     # to build an html page using the ocrText
 
@@ -602,8 +607,8 @@ class DocParser(object):
 
         # 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) 
-        
+        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)
@@ -631,24 +636,24 @@ class DocParser(object):
 
         # get a descriptions of the starting points of the regions
         # and groups on the page
-        (pagetype, pageDesc) = self.PageDescription() 
+        (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:
-                htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' 
+                htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="'
                 htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n'
                 anchorSet = True
 
@@ -660,7 +665,7 @@ class DocParser(object):
                         gcstr = ' class="' + grptype + '"'
                         htmlpage += '<div' + gcstr + '>'
                         inGroup = True
-                
+
             elif (etype == 'grpend'):
                 if inGroup:
                     htmlpage += '</div>\n'
@@ -676,7 +681,7 @@ class DocParser(object):
                             htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc)
                         else:
                             htmlpage += '<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:
index 49cf6f5c81c5ab3bebed310c69dd601b69fcb62c..d4b4f2c92f841352f1cd318beb1151843075ff0c 100644 (file)
@@ -15,7 +15,7 @@ class PParser(object):
         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')
@@ -26,7 +26,7 @@ class PParser(object):
         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:
@@ -215,9 +215,9 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array
             ml += '<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:
             ml += '<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'
-        
+
         ml += '<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): 
+    if (pp.gid != None):
         ml += '<defs>\n'
         gdefs = pp.getGlyphs()
         for j in xrange(0,len(gdefs)):
@@ -227,7 +227,7 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array
     if (img != None):
         for j in xrange(0,len(img)):
             ml += img[j]
-    if (pp.gid != None): 
+    if (pp.gid != None):
         for j in xrange(0,len(pp.gid)):
             ml += '<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):
@@ -247,4 +247,3 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array
         ml += '</body>\n'
         ml += '</html>\n'
     return ml
-
index 9ad87ea1800240af4a7f29881573d8ad1d0b65d5..48497c79d639e94d3139a1ae84cb6fb36efa96c2 100644 (file)
@@ -46,27 +46,27 @@ def readEncodedNumber(file):
     c = file.read(1)
     if (len(c) == 0):
         return None
-    data = ord(c)    
+    data = ord(c)
     if data == 0xFF:
-       flag = True
-       c = file.read(1)
-       if (len(c) == 0):
-           return None
-       data = ord(c)       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
 
-# Get a length prefixed string from the file 
+# Get a length prefixed string from the file
 def lengthPrefixString(data):
     return encodeNumber(len(data))+data
 
@@ -77,7 +77,7 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    return unpack(str(stringLength)+"s",sv)[0]
 
 def getMetaArray(metaFile):
     # parse the meta file
@@ -141,10 +141,10 @@ class PageDimParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=')
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -336,7 +336,7 @@ def generateBook(bookDir, raw, fixedimage):
     print 'Processing Meta Data and creating OPF'
     meta_array = getMetaArray(metaFile)
 
-    # replace special chars in title and authors like & < > 
+    # replace special chars in title and authors like & < >
     title = meta_array.get('Title','No Title Provided')
     title = title.replace('&','&amp;')
     title = title.replace('<','&lt;')
@@ -451,7 +451,7 @@ def generateBook(bookDir, raw, fixedimage):
     htmlstr += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'
     htmlstr += '<head>\n'
     htmlstr += '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n'
-    htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' 
+    htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
     htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
     htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
     if 'ASIN' in meta_array:
@@ -463,7 +463,7 @@ def generateBook(bookDir, raw, fixedimage):
 
     print 'Processing Pages'
     # Books are at 1440 DPI.  This is rendering at twice that size for
-    # readability when rendering to the screen.  
+    # readability when rendering to the screen.
     scaledpi = 1440.0
 
     filenames = os.listdir(pageDir)
@@ -486,13 +486,13 @@ def generateBook(bookDir, raw, fixedimage):
 
         # first get the html
         pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
-        tocentries += tocinfo 
+        tocentries += tocinfo
         htmlstr += pagehtml
 
     # finish up the html string and output it
     htmlstr += '</body>\n</html>\n'
     file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
-    
+
     print " "
     print 'Extracting Table of Contents from Amazon OCR'
 
@@ -663,7 +663,7 @@ def main(argv):
 
     if len(opts) == 0 and len(args) == 0 :
         usage()
-        return 1 
+        return 1
 
     raw = 0
     fixedimage = True
index a7c48c9620d0f3442b0903e76e36c36a305f1fc9..917aa4aaa16210a7532e733a4fd667633ad8a4d9 100644 (file)
@@ -14,7 +14,7 @@ from __future__ import with_statement
 #   2 - Added OS X support by using OpenSSL when available
 #   3 - screen out improper key lengths to prevent segfaults on Linux
 #   3.1 - Allow Windows versions of libcrypto to be found
-#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml 
+#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
 #   3.3 - On Windows try PyCrypto first and OpenSSL next
 #   3.4 - Modify interace to allow use with import
 
@@ -50,7 +50,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -58,13 +58,13 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
@@ -73,7 +73,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class AES(object):
         def __init__(self, userkey):
             self._blocksize = len(userkey)
@@ -84,7 +84,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -122,7 +122,7 @@ def _load_crypto():
 
 AES = _load_crypto()
 
+
 
 """
 Decrypt Barnes & Noble ADEPT encrypted EPUB books.
index cdedc48b698d5f6fcd3fb07478e7479580d410be..e7a78ea19a7d60c2b2679d3b548d5162ca193c12 100644 (file)
@@ -53,7 +53,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -61,28 +61,28 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
                             [c_char_p, c_int, AES_KEY_p])
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
     class AES(object):
-         def __init__(self, userkey, iv):
+        def __init__(self, userkey, iv):
             self._blocksize = len(userkey)
             self._iv = iv
             key = self._key = AES_KEY()
             rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES Encrypt key')
-    
-         def encrypt(self, data):
+
+        def encrypt(self, data):
             out = create_string_buffer(len(data))
             rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
             if rv == 0:
index 48a75f996cdd84139fbcab9e9506cf8c68d1eef7..018736acd74626a3ac075f1659e565b28cfbb2aa 100644 (file)
@@ -67,25 +67,25 @@ def _load_crypto_libcrypto():
 
     RSA_NO_PADDING = 3
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
                           [RSA_p, c_char_pp, c_long])
     RSA_size = F(c_int, 'RSA_size', [RSA_p])
@@ -97,7 +97,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class RSA(object):
         def __init__(self, der):
             buf = create_string_buffer(der)
@@ -105,7 +105,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -114,7 +114,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -130,7 +130,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise ADEPTError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -148,13 +148,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -164,22 +164,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -189,19 +189,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -209,13 +209,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -224,7 +224,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -252,7 +252,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
index 8eab14fb0200772d2f27cf6fc65239a99a6f7e92..da61e77eb4ab05593ecd0a6f4a5a1700edf71a09 100644 (file)
@@ -76,13 +76,13 @@ if sys.platform.startswith('win'):
             _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                         ('rounds', c_int)]
         AES_KEY_p = POINTER(AES_KEY)
-    
+
         def F(restype, name, argtypes):
             func = getattr(libcrypto, name)
             func.restype = restype
             func.argtypes = argtypes
             return func
-    
+
         AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
                                 [c_char_p, c_int, AES_KEY_p])
         AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -427,8 +427,8 @@ def extractKeyfile(keypath):
         print "Key generation Error: " + str(e)
         return 1
     except Exception, e:
-       print "General Error: " + str(e)
-       return 1
+        print "General Error: " + str(e)
+        return 1
     if not success:
         return 1
     return 0
index c9a419be6bde6744851ce001a7879c75404b88b0..f710ea06a0efcf3dfefc31c30d1ce2bddd7adab1 100644 (file)
@@ -4,7 +4,7 @@
 from __future__ import with_statement
 
 # To run this program install Python 2.6 from http://www.python.org/download/
-# and OpenSSL (already installed on Mac OS X and Linux) OR 
+# and OpenSSL (already installed on Mac OS X and Linux) OR
 # PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
 # (make sure to install the version for Python 2.6).  Save this script file as
 # ineptpdf.pyw and double-click on it to run it.
@@ -83,7 +83,7 @@ def _load_crypto_libcrypto():
     AES_MAXNR = 14
 
     RSA_NO_PADDING = 3
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -98,13 +98,13 @@ def _load_crypto_libcrypto():
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
@@ -125,7 +125,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -134,7 +134,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[1:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -196,13 +196,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -212,22 +212,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -237,19 +237,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -257,13 +257,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -272,7 +272,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -315,7 +315,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
@@ -410,7 +410,7 @@ class PSLiteral(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         name = []
         for char in self.name:
@@ -429,22 +429,22 @@ class PSKeyword(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         return self.name
 
 # PSSymbolTable
 class PSSymbolTable(object):
-    
+
     '''
     Symbol table that stores PSLiteral or PSKeyword.
     '''
-    
+
     def __init__(self, classe):
         self.dic = {}
         self.classe = classe
         return
-    
+
     def intern(self, name):
         if name in self.dic:
             lit = self.dic[name]
@@ -514,11 +514,11 @@ class PSBaseParser(object):
 
     def flush(self):
         return
-    
+
     def close(self):
         self.flush()
         return
-    
+
     def tell(self):
         return self.bufpos+self.charpos
 
@@ -554,7 +554,7 @@ class PSBaseParser(object):
             raise PSEOF('Unexpected EOF')
         self.charpos = 0
         return
-    
+
     def parse_main(self, s, i):
         m = NONSPC.search(s, i)
         if not m:
@@ -589,11 +589,11 @@ class PSBaseParser(object):
             return (self.parse_wclose, j+1)
         self.add_token(KWD(c))
         return (self.parse_main, j+1)
-                            
+
     def add_token(self, obj):
         self.tokens.append((self.tokenstart, obj))
         return
-    
+
     def parse_comment(self, s, i):
         m = EOL.search(s, i)
         if not m:
@@ -604,7 +604,7 @@ class PSBaseParser(object):
         # We ignore comments.
         #self.tokens.append(self.token)
         return (self.parse_main, j)
-    
+
     def parse_literal(self, s, i):
         m = END_LITERAL.search(s, i)
         if not m:
@@ -618,7 +618,7 @@ class PSBaseParser(object):
             return (self.parse_literal_hex, j+1)
         self.add_token(LIT(self.token))
         return (self.parse_main, j)
-    
+
     def parse_literal_hex(self, s, i):
         c = s[i]
         if HEX.match(c) and len(self.hex) < 2:
@@ -653,7 +653,7 @@ class PSBaseParser(object):
         self.token += s[i:j]
         self.add_token(float(self.token))
         return (self.parse_main, j)
-    
+
     def parse_keyword(self, s, i):
         m = END_KEYWORD.search(s, i)
         if not m:
@@ -801,7 +801,7 @@ class PSStackParser(PSBaseParser):
         PSBaseParser.__init__(self, fp)
         self.reset()
         return
-    
+
     def reset(self):
         self.context = []
         self.curtype = None
@@ -842,10 +842,10 @@ class PSStackParser(PSBaseParser):
 
     def do_keyword(self, pos, token):
         return
-    
+
     def nextobject(self, direct=False):
         '''
-        Yields a list of objects: keywords, literals, strings, 
+        Yields a list of objects: keywords, literals, strings,
         numbers, arrays and dictionaries. Arrays and dictionaries
         are represented as Python sequence and dictionaries.
         '''
@@ -914,7 +914,7 @@ class PDFNotImplementedError(PSException): pass
 ##  PDFObjRef
 ##
 class PDFObjRef(PDFObject):
-    
+
     def __init__(self, doc, objid, genno):
         if objid == 0:
             if STRICT:
@@ -1029,25 +1029,25 @@ def stream_value(x):
 
 # ascii85decode(data)
 def ascii85decode(data):
-  n = b = 0
-  out = ''
-  for c in data:
-    if '!' <= c and c <= 'u':
-      n += 1
-      b = b*85+(ord(c)-33)
-      if n == 5:
-        out += struct.pack('>L',b)
-        n = b = 0
-    elif c == 'z':
-      assert n == 0
-      out += '\0\0\0\0'
-    elif c == '~':
-      if n:
-        for _ in range(5-n):
-          b = b*85+84
-        out += struct.pack('>L',b)[:n-1]
-      break
-  return out
+    n = b = 0
+    out = ''
+    for c in data:
+        if '!' <= c and c <= 'u':
+            n += 1
+            b = b*85+(ord(c)-33)
+            if n == 5:
+                out += struct.pack('>L',b)
+                n = b = 0
+        elif c == 'z':
+            assert n == 0
+            out += '\0\0\0\0'
+        elif c == '~':
+            if n:
+                for _ in range(5-n):
+                    b = b*85+84
+                out += struct.pack('>L',b)[:n-1]
+            break
+    return out
 
 
 ##  PDFStream type
@@ -1064,7 +1064,7 @@ class PDFStream(PDFObject):
         else:
             if eol in ('\r', '\n', '\r\n'):
                 rawdata = rawdata[:length]
-                
+
         self.dic = dic
         self.rawdata = rawdata
         self.decipher = decipher
@@ -1078,7 +1078,7 @@ class PDFStream(PDFObject):
         self.objid = objid
         self.genno = genno
         return
-    
+
     def __repr__(self):
         if self.rawdata:
             return '<PDFStream(%r): raw=%d, %r>' % \
@@ -1162,7 +1162,7 @@ class PDFStream(PDFObject):
             data = self.decipher(self.objid, self.genno, data)
         return data
 
-        
+
 ##  PDF Exceptions
 ##
 class PDFSyntaxError(PDFException): pass
@@ -1227,7 +1227,7 @@ class PDFXRef(object):
                 self.offsets[objid] = (int(genno), int(pos))
         self.load_trailer(parser)
         return
-    
+
     KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
     def load_trailer(self, parser):
         try:
@@ -1268,7 +1268,7 @@ class PDFXRefStream(object):
         for first, size in self.index:
             for objid in xrange(first, first + size):
                 yield objid
-    
+
     def load(self, parser, debug=0):
         (_,objid) = parser.nexttoken() # ignored
         (_,genno) = parser.nexttoken() # ignored
@@ -1286,7 +1286,7 @@ class PDFXRefStream(object):
         self.entlen = self.fl1+self.fl2+self.fl3
         self.trailer = stream.dic
         return
-    
+
     def getpos(self, objid):
         offset = 0
         for first, size in self.index:
@@ -1337,7 +1337,7 @@ class PDFDocument(object):
         self.parser = parser
         # The document is set to be temporarily ready during collecting
         # all the basic information about the document, e.g.
-        # the header, the encryption information, and the access rights 
+        # the header, the encryption information, and the access rights
         # for the document.
         self.ready = True
         # Retrieve the information of each header that was appended
@@ -1413,7 +1413,7 @@ class PDFDocument(object):
         length = int_value(param.get('Length', 0)) / 8
         edcdata = str_value(param.get('EDCData')).decode('base64')
         pdrllic = str_value(param.get('PDRLLic')).decode('base64')
-        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')          
+        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
         edclist = []
         for pair in edcdata.split('\n'):
             edclist.append(pair)
@@ -1433,9 +1433,9 @@ class PDFDocument(object):
             raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
         else:
             cutter = -1 * ord(pdrlpol[-1])
-            pdrlpol = pdrlpol[:cutter]            
+            pdrlpol = pdrlpol[:cutter]
         return plaintext[:16]
-    
+
     PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
                        '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
     # experimental aes pw support
@@ -1455,14 +1455,14 @@ class PDFDocument(object):
             EncMetadata = str_value(param['EncryptMetadata'])
         except:
             EncMetadata = 'True'
-        self.is_printable = bool(P & 4)        
+        self.is_printable = bool(P & 4)
         self.is_modifiable = bool(P & 8)
         self.is_extractable = bool(P & 16)
         self.is_annotationable = bool(P & 32)
         self.is_formsenabled = bool(P & 256)
         self.is_textextractable = bool(P & 512)
         self.is_assemblable = bool(P & 1024)
-        self.is_formprintable = bool(P & 2048) 
+        self.is_formprintable = bool(P & 2048)
         # Algorithm 3.2
         password = (password+self.PASSWORD_PADDING)[:32] # 1
         hash = hashlib.md5(password) # 2
@@ -1537,10 +1537,10 @@ class PDFDocument(object):
         if length > 0:
             if len(bookkey) == length:
                 if ebx_V == 3:
-                    V = 3        
+                    V = 3
                 else:
                     V = 2
-            elif len(bookkey) == length + 1:  
+            elif len(bookkey) == length + 1:
                 V = ord(bookkey[0])
                 bookkey = bookkey[1:]
             else:
@@ -1554,7 +1554,7 @@ class PDFDocument(object):
             print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
             print "bookkey[0] is %d" % ord(bookkey[0])
             if ebx_V == 3:
-                V = 3        
+                V = 3
             else:
                 V = 2
         self.decrypt_key = bookkey
@@ -1571,7 +1571,7 @@ class PDFDocument(object):
         hash = hashlib.md5(key)
         key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
         return key
-    
+
     def genkey_v3(self, objid, genno):
         objid = struct.pack('<L', objid ^ 0x3569ac)
         genno = struct.pack('<L', genno ^ 0xca96)
@@ -1611,14 +1611,14 @@ class PDFDocument(object):
         #print cutter
         plaintext = plaintext[:cutter]
         return plaintext
-    
+
     def decrypt_rc4(self, objid, genno, data):
         key = self.genkey(objid, genno)
         return ARC4.new(key).decrypt(data)
 
 
     KEYWORD_OBJ = PSKeywordTable.intern('obj')
-    
+
     def getobj(self, objid):
         if not self.ready:
             raise PDFException('PDFDocument not initialized')
@@ -1688,7 +1688,7 @@ class PDFDocument(object):
 ##                    if x:
 ##                        objid1 = x[-2]
 ##                        genno = x[-1]
-##                
+##
                 if kwd is not self.KEYWORD_OBJ:
                     raise PDFSyntaxError(
                         'Invalid object spec: offset=%r' % index)
@@ -1700,7 +1700,7 @@ class PDFDocument(object):
             self.objs[objid] = obj
         return obj
 
-                
+
 class PDFObjStmRef(object):
     maxindex = 0
     def __init__(self, objid, stmid, index):
@@ -1710,7 +1710,7 @@ class PDFObjStmRef(object):
         if index > PDFObjStmRef.maxindex:
             PDFObjStmRef.maxindex = index
 
-    
+
 ##  PDFParser
 ##
 class PDFParser(PSStackParser):
@@ -1736,7 +1736,7 @@ class PDFParser(PSStackParser):
         if token is self.KEYWORD_ENDOBJ:
             self.add_results(*self.pop(4))
             return
-        
+
         if token is self.KEYWORD_R:
             # reference to indirect object
             try:
@@ -1747,7 +1747,7 @@ class PDFParser(PSStackParser):
             except PSSyntaxError:
                 pass
             return
-            
+
         if token is self.KEYWORD_STREAM:
             # stream object
             ((_,dic),) = self.pop(1)
@@ -1787,7 +1787,7 @@ class PDFParser(PSStackParser):
             obj = PDFStream(dic, data, self.doc.decipher)
             self.push((pos, obj))
             return
-        
+
         # others
         self.push((pos, token))
         return
@@ -1823,7 +1823,7 @@ class PDFParser(PSStackParser):
             xref.load(self)
         else:
             if token is not self.KEYWORD_XREF:
-                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' % 
+                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
                                      (pos, token))
             self.nextline()
             xref = PDFXRef()
@@ -1838,7 +1838,7 @@ class PDFParser(PSStackParser):
             pos = int_value(trailer['Prev'])
             self.read_xref_from(pos, xrefs)
         return
-        
+
     # read xref tables and trailers
     def read_xref(self):
         xrefs = []
@@ -1957,7 +1957,7 @@ class PDFSerializer(object):
                     self.write("%010d 00000 n \n" % xrefs[objid][0])
                 else:
                     self.write("%010d %05d f \n" % (0, 65535))
-            
+
             self.write('trailer\n')
             self.serialize_object(trailer)
             self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
@@ -1977,7 +1977,7 @@ class PDFSerializer(object):
             while maxindex >= power:
                 fl3 += 1
                 power *= 256
-                    
+
             index = []
             first = None
             prev = None
@@ -2004,14 +2004,14 @@ class PDFSerializer(object):
                     # we force all generation numbers to be 0
                     # f3 = objref[1]
                     f3 = 0
-                
+
                 data.append(struct.pack('>B', f1))
                 data.append(struct.pack('>L', f2)[-fl2:])
                 data.append(struct.pack('>L', f3)[-fl3:])
             index.extend((first, prev - first + 1))
             data = zlib.compress(''.join(data))
             dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
-                   'W': [1, fl2, fl3], 'Length': len(data), 
+                   'W': [1, fl2, fl3], 'Length': len(data),
                    'Filter': LITERALS_FLATE_DECODE[0],
                    'Root': trailer['Root'],}
             if 'Info' in trailer:
@@ -2033,9 +2033,9 @@ class PDFSerializer(object):
         string = string.replace(')', r'\)')
          # get rid of ciando id
         regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
-        if regularexp.match(string): return ('http://www.ciando.com') 
+        if regularexp.match(string): return ('http://www.ciando.com')
         return string
-    
+
     def serialize_object(self, obj):
         if isinstance(obj, dict):
             # Correct malformed Mac OS resource forks for Stanza
@@ -2059,21 +2059,21 @@ class PDFSerializer(object):
         elif isinstance(obj, bool):
             if self.last.isalnum():
                 self.write(' ')
-            self.write(str(obj).lower())            
+            self.write(str(obj).lower())
         elif isinstance(obj, (int, long, float)):
             if self.last.isalnum():
                 self.write(' ')
             self.write(str(obj))
         elif isinstance(obj, PDFObjRef):
             if self.last.isalnum():
-                self.write(' ')            
+                self.write(' ')
             self.write('%d %d R' % (obj.objid, 0))
         elif isinstance(obj, PDFStream):
             ### If we don't generate cross ref streams the object streams
             ### are no longer useful, as we have extracted all objects from
             ### them. Therefore leave them out from the output.
             if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
-                    self.write('(deleted)')
+                self.write('(deleted)')
             else:
                 data = obj.get_decdata()
                 self.serialize_object(obj.dic)
@@ -2085,7 +2085,7 @@ class PDFSerializer(object):
             if data[0].isalnum() and self.last.isalnum():
                 self.write(' ')
             self.write(data)
-    
+
     def serialize_indirect(self, objid, obj):
         self.write('%d 0 obj' % (objid,))
         self.serialize_object(obj)
@@ -2097,7 +2097,7 @@ class PDFSerializer(object):
 class DecryptionDialog(Tkinter.Frame):
     def __init__(self, root):
         Tkinter.Frame.__init__(self, root, border=5)
-        ltext='Select file for decryption\n'        
+        ltext='Select file for decryption\n'
         self.status = Tkinter.Label(self, text=ltext)
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
@@ -2123,7 +2123,7 @@ class DecryptionDialog(Tkinter.Frame):
         button.grid(row=2, column=2)
         buttons = Tkinter.Frame(self)
         buttons.pack()
-  
+
 
         botton = Tkinter.Button(
             buttons, text="Decrypt", width=10, command=self.decrypt)
@@ -2132,7 +2132,7 @@ class DecryptionDialog(Tkinter.Frame):
         button = Tkinter.Button(
             buttons, text="Quit", width=10, command=self.quit)
         button.pack(side=Tkconstants.RIGHT)
-         
+
 
     def get_keypath(self):
         keypath = tkFileDialog.askopenfilename(
index da200ee4374322dda3db472f9d3b5032c6e8a605..44881ebd22c91a1c1f4c7e41f9cbe6536465a9fc 100644 (file)
@@ -1,9 +1,9 @@
 # engine to remove drm from Kindle for Mac books
 # for personal use for archiving and converting your ebooks
-#  PLEASE DO NOT PIRATE! 
+#  PLEASE DO NOT PIRATE!
 # We want all authors and Publishers, and eBook stores to live long and prosperous lives
 #
-# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle, 
+# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle,
 #    unswindle, DiapDealer, some_updates and many many others
 
 from __future__ import with_statement
@@ -75,20 +75,20 @@ def _load_crypto_libcrypto():
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
-    PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', 
+    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])
-    
+
     class LibCrypto(object):
         def __init__(self):
             self._blocksize = 0
@@ -168,7 +168,7 @@ def GetVolumeSerialNumber():
         sernum = '9999999999'
     return sernum
 
-# uses unix env to get username instead of using sysctlbyname 
+# uses unix env to get username instead of using sysctlbyname
 def GetUserName():
     username = os.getenv('USER')
     return username
@@ -183,7 +183,7 @@ global kindleDatabase
 
 # Various character maps used to decrypt books. Probably supposed to act as obfuscation
 charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
 charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
 
@@ -197,7 +197,7 @@ def encode(data, 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)
@@ -254,7 +254,7 @@ def getKindleInfoValueForHash(hashedKey):
     encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
     cleartext = CryptUnprotectData(encryptedValue)
     return decode(cleartext, charMap1)
+
 #  Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
 def getKindleInfoValueForKey(key):
     return getKindleInfoValueForHash(encodeHash(key,charMap2))
@@ -265,10 +265,10 @@ def findNameForHash(hash):
     result = ""
     for name in names:
         if hash == encodeHash(name, charMap2):
-           result = name
-           break
+            result = name
+            break
     return result
-    
+
 # Print all the records from the kindle.info file (option -i)
 def printKindleInfo():
     for record in kindleDatabase:
@@ -284,7 +284,7 @@ def printKindleInfo():
 #
 # PID generation routines
 #
-  
+
 # Returns two bit at offset from a bit field
 def getTwoBitsFromBitField(bitField,offset):
     byteNumber = offset // 4
@@ -293,10 +293,10 @@ def getTwoBitsFromBitField(bitField,offset):
 
 # 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
-     
+    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
@@ -304,29 +304,29 @@ def encodePID(hash):
     for position in range (0,8):
         PID += charMap3[getSixBitsFromBitField(hash,position)]
     return PID
-    
+
+
 #
 # Main
-#   
+#
 
 def main(argv=sys.argv):
     global kindleDatabase
-    
+
     kindleDatabase = None
 
     #
     # Read the encrypted database
     #
-    
+
     try:
         kindleDatabase = parseKindleInfo()
     except Exception, message:
         print(message)
-    
+
     if kindleDatabase != None :
         printKindleInfo()
-     
+
     return 0
 
 if __name__ == '__main__':
index d962a029e69fc24286df87ccf19b359b03c337e5..bba23c74d5b2019d0dbf1708e1c9eda6c75d2fa7 100644 (file)
@@ -5,19 +5,19 @@ from __future__ import with_statement
 # engine to remove drm from Kindle for Mac and Kindle for PC books
 # for personal use for archiving and converting your ebooks
 
-# PLEASE DO NOT PIRATE EBOOKS! 
+# PLEASE DO NOT PIRATE EBOOKS!
 
 # We want all authors and publishers, and eBook stores to live
-# long and prosperous lives but at the same time  we just want to 
-# be able to read OUR books on whatever device we want and to keep 
+# long and prosperous lives but at the same time  we just want to
+# be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
 
-#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
-#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
 #    and many many others
 
 
-__version__ = '3.9'
+__version__ = '4.0'
 
 class Unbuffered:
     def __init__(self, stream):
@@ -50,7 +50,7 @@ else:
     import mobidedrm
     import topazextract
     import kgenpids
-        
+
 
 # cleanup bytestring filenames
 # borrowed from calibre from calibre/src/calibre/__init__.py
@@ -100,14 +100,14 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         outfilename = outfilename + "_" + filenametitle
     elif outfilename[:8] != filenametitle[:8]:
         outfilename = outfilename[:8] + "_" + filenametitle
-        
+
     # avoid excessively long file names
     if len(outfilename)>150:
         outfilename = outfilename[:150]
 
     # build pid list
     md1, md2 = mb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
 
     try:
         mb.processBook(pidlst)
@@ -128,9 +128,9 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         else:
             outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
         mb.getMobiFile(outfile)
-        return 0            
+        return 0
 
-    # topaz: 
+    # topaz:
     print "   Creating NoDRM HTMLZ Archive"
     zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
     mb.getHTMLZip(zipname)
@@ -156,7 +156,7 @@ def usage(progname):
 
 #
 # Main
-#   
+#
 def main(argv=sys.argv):
     progname = os.path.basename(argv[0])
 
@@ -164,9 +164,9 @@ def main(argv=sys.argv):
     kInfoFiles = []
     serials = []
     pids = []
-    
+
     print ('K4MobiDeDrm v%(__version__)s '
-          'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
 
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
@@ -177,7 +177,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         sys.exit(2)
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -195,8 +195,8 @@ def main(argv=sys.argv):
     # try with built in Kindle Info files
     k4 = True
     if sys.platform.startswith('linux'):
-       k4 = False
-       kInfoFiles = None
+        k4 = False
+        kInfoFiles = None
     infile = args[0]
     outdir = args[1]
     return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
@@ -205,4 +205,3 @@ def main(argv=sys.argv):
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 7d5130cbf56a24828c2f542bcbaec1d982e60942..69976541db3d7b0ee4a5f074d34b12c856033134 100644 (file)
@@ -5,7 +5,8 @@ from __future__ import with_statement
 import sys
 import os
 import os.path
-
+import re
+import copy
 import subprocess
 from struct import pack, unpack, unpack_from
 
@@ -24,6 +25,25 @@ def _load_crypto_libcrypto():
         raise DrmException('libcrypto not found')
     libcrypto = CDLL(libcrypto)
 
+    # From OpenSSL's crypto aes header
+    #
+    # AES_ENCRYPT     1
+    # AES_DECRYPT     0
+    # AES_MAXNR 14 (in bytes)
+    # AES_BLOCK_SIZE 16 (in bytes)
+    # 
+    # 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);
+    #
+    # note:  the ivec string, and output buffer are mutable
+    # 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
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
@@ -31,25 +51,31 @@ def _load_crypto_libcrypto():
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
-    PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', 
+    # From OpenSSL's Crypto evp/p5_crpt2.c
+    #
+    # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
+    #                        const unsigned char *salt, int saltlen, int iter,
+    #                        int keylen, unsigned char *out);
+
+    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])
-    
+
     class LibCrypto(object):
         def __init__(self):
             self._blocksize = 0
             self._keyctx = None
-            self.iv = 0
+            self._iv = 0
 
         def set_decrypt_key(self, userkey, iv):
             self._blocksize = len(userkey)
@@ -57,14 +83,17 @@ def _load_crypto_libcrypto():
                 raise DrmException('AES improper key used')
                 return
             keyctx = self._keyctx = AES_KEY()
-            self.iv = iv
+            self._iv = iv
+            self._userkey = userkey
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
             if rv < 0:
                 raise DrmException('Failed to initialize AES key')
 
         def decrypt(self, data):
             out = create_string_buffer(len(data))
-            rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
+            mutable_iv = create_string_buffer(self._iv, len(self._iv))
+            keyctx = self._keyctx
+            rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
             if rv == 0:
                 raise DrmException('AES decryption failed')
             return out.raw
@@ -111,13 +140,17 @@ def SHA256(message):
 
 # Various character maps used to decrypt books. Probably supposed to act as obfuscation
 charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
 
-# For kinf approach of K4PC/K4Mac
+# For kinf approach of K4Mac 1.6.X or later
 # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
 # For Mac they seem to re-use charMap2 here
 charMap5 = charMap2
 
+# new in K4M 1.9.X
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+
+
 def encode(data, map):
     result = ""
     for char in data:
@@ -144,7 +177,7 @@ def decode(data,map):
         result += pack("B",value)
     return result
 
-# For .kinf approach of K4PC and now K4Mac
+# For K4M 1.6.X and later
 # generate table of prime number less than or equal to int n
 def primes(n):
     if n==2: return [2]
@@ -271,7 +304,7 @@ def GetDiskPartitionUUID(diskpart):
     if not foundIt:
         uuidnum = ''
     return uuidnum
-    
+
 def GetMACAddressMunged():
     macnum = os.getenv('MYMACNUM')
     if macnum != None:
@@ -315,33 +348,11 @@ def GetMACAddressMunged():
     return macnum
 
 
-# uses unix env to get username instead of using sysctlbyname 
+# uses unix env to get username instead of using sysctlbyname
 def GetUserName():
     username = os.getenv('USER')
     return username
 
-
-# implements an Pseudo Mac Version of Windows built-in Crypto routine
-# used by Kindle for Mac versions < 1.6.0
-def CryptUnprotectData(encryptedData):
-    sernum = GetVolumeSerialNumber()
-    if sernum == '':
-        sernum = '9999999999'
-    sp = sernum + '!@#' + GetUserName()
-    passwdData = encode(SHA256(sp),charMap1)
-    salt = '16743'
-    iter = 0x3e8
-    keylen = 0x80
-    crp = LibCrypto()
-    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
-    key = key_iv[0:32]
-    iv = key_iv[32:48]
-    crp.set_decrypt_key(key,iv)
-    cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext,charMap1)
-    return cleartext
-
-
 def isNewInstall():
     home = os.getenv('HOME')
     # soccer game fan anyone
@@ -350,7 +361,7 @@ def isNewInstall():
     if os.path.exists(dpath):
         return True
     return False
-    
+
 
 def GetIDString():
     # K4Mac now has an extensive set of ids strings it uses
@@ -359,13 +370,13 @@ def GetIDString():
 
     # BUT Amazon has now become nasty enough to detect when its app
     # is being run under a debugger and actually changes code paths
-    # including which one of these strings is chosen, all to try 
+    # including which one of these strings is chosen, all to try
     # to prevent reverse engineering
 
     # Sad really ... they will only hurt their own sales ...
     # true book lovers really want to keep their books forever
-    # and move them to their devices and DRM prevents that so they 
-    # will just buy from someplace else that they can remove 
+    # and move them to their devices and DRM prevents that so they
+    # will just buy from someplace else that they can remove
     # the DRM from
 
     # Amazon should know by now that true book lover's are not like
@@ -388,27 +399,91 @@ def GetIDString():
     return '9999999999'
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used by Kindle for Mac versions < 1.6.0
+class CryptUnprotectData(object):
+    def __init__(self):
+        sernum = GetVolumeSerialNumber()
+        if sernum == '':
+            sernum = '9999999999'
+        sp = sernum + '!@#' + GetUserName()
+        passwdData = encode(SHA256(sp),charMap1)
+        salt = '16743'
+        self.crp = LibCrypto()
+        iter = 0x3e8
+        keylen = 0x80
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext,charMap1)
+        return cleartext
+
+
 # implements an Pseudo Mac Version of Windows built-in Crypto routine
 # used for Kindle for Mac Versions >= 1.6.0
-def CryptUnprotectDataV2(encryptedData):
-    sp = GetUserName() + ':&%:' + GetIDString()
-    passwdData = encode(SHA256(sp),charMap5)
-    # salt generation as per the code
-    salt = 0x0512981d * 2 * 1 * 1
-    salt = str(salt) + GetUserName()
-    salt = encode(salt,charMap5)
+class CryptUnprotectDataV2(object):
+    def __init__(self):
+        sp = GetUserName() + ':&%:' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap5)
+        # salt generation as per the code
+        salt = 0x0512981d * 2 * 1 * 1
+        salt = str(salt) + GetUserName()
+        salt = encode(salt,charMap5)
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap5)
+        return cleartext
+
+
+# unprotect the new header blob in .kinf2011
+# used in Kindle for Mac Version >= 1.9.0
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
     crp = LibCrypto()
-    iter = 0x800
-    keylen = 0x400
     key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
     key = key_iv[0:32]
     iv = key_iv[32:48]
     crp.set_decrypt_key(key,iv)
     cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext, charMap5)
     return cleartext
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.9.0
+class CryptUnprotectDataV3(object):
+    def __init__(self, entropy):
+        sp = GetUserName() + '+@#$%+' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap2)
+        salt = entropy
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap2)
+        return cleartext
+
+
 # Locate the .kindle-info files
 def getKindleInfoFiles(kInfoFiles):
     # first search for current .kindle-info files
@@ -424,12 +499,22 @@ def getKindleInfoFiles(kInfoFiles):
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
             found = True
-    # add any .kinf files 
+    # add any .rainier*-kinf files
     cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
     cmdline = cmdline.encode(sys.getfilesystemencoding())
     p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
     out1, out2 = p1.communicate()
     reslst = out1.split('\n')
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    # add any .kinf2011 files
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
     for resline in reslst:
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
@@ -438,7 +523,7 @@ def getKindleInfoFiles(kInfoFiles):
         print('No kindle-info files have been found.')
     return kInfoFiles
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -449,7 +534,9 @@ def getDBfromFile(kInfoFile):
     data = infoReader.read()
 
     if data.find('[') != -1 :
+
         # older style kindle-info file
+        cud = CryptUnprotectData()
         items = data.split('[')
         for item in items:
             if item != '':
@@ -462,87 +549,175 @@ def getDBfromFile(kInfoFile):
                 if keyname == "unknown":
                     keyname = keyhash
                 encryptedValue = decode(rawdata,charMap2)
-                cleartext = CryptUnprotectData(encryptedValue)
+                cleartext = cud.decrypt(encryptedValue)
                 DB[keyname] = cleartext
                 cnt = cnt + 1
         if cnt == 0:
             DB = None
         return DB
 
-    # else newer style .kinf file used by K4Mac >= 1.6.0
-    # the .kinf file uses "/" to separate it into records
-    # so remove the trailing "/" to make it easy to use split
+    if hdr == '/':
+
+        # else newer style .kinf file used by K4Mac >= 1.6.0
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+        cud = CryptUnprotectDataV2()
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+            keyname = "unknown"
+
+            # the raw keyhash string is also used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            # "entropy" not used for K4Mac only K4PC
+            # entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents) - largest prime number less than or equal to int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            noffset = contlen - primes(int(contlen/3))[-1]
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using charMap5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            cleartext = cud.decrypt(encryptedValue)
+            DB[keyname] = cleartext
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # the latest .kinf2011 version for K4M 1.9.1
+    # put back the hdr char, it is needed
+    data = hdr + data
     data = data[:-1]
     items = data.split('/')
 
+    # the headerblob is the encrypted information needed to build the entropy string
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, charMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+
+    # now extract the pieces in the same way
+    # this version is different from K4PC it scales the build number by multipying by 735
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
+
+    cud = CryptUnprotectDataV3(entropy)
+
     # loop through the item records until all are processed
     while len(items) > 0:
-    
+
         # get the first item record
         item = items.pop(0)
-    
+
         # the first 32 chars of the first record of a group
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
         keyname = "unknown"
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        # "entropy" not used for K4Mac only K4PC
-        # entropy = SHA1(keyhash)
-    
-        # the remainder of the first record when decoded with charMap5 
+        # unlike K4PC the keyhash is not used in generating entropy
+        # entropy = SHA1(keyhash) + added_entropy
+        # entropy = added_entropy
+
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
         srcnt = decode(item[34:],charMap5)
         rcnt = int(srcnt)
-    
+
         # read and store in rcnt records of data
         # that make up the contents value
         edlst = []
         for i in xrange(rcnt):
             item = items.pop(0)
             edlst.append(item)
-    
+
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
         if keyname == "unknown":
             keyname = keyhash
-    
-        # the charMap5 encoded contents data has had a length 
+
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
-    
-        # The offset into the charMap5 encoded contents seems to be:
+
+        # The offset into the testMap8 encoded contents seems to be:
         # len(contents) - largest prime number less than or equal to int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
-    
-        # move first offsets chars to end to align for decode by charMap5
+
+        # move first offsets chars to end to align for decode by testMap8
         encdata = "".join(edlst)
         contlen = len(encdata)
 
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
+        # now properly split and recombine
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         noffset = contlen - primes(int(contlen/3))[-1]
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
-    
-        # decode using charMap5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        cleartext = CryptUnprotectDataV2(encryptedValue)
-        # Debugging
+
+        # decode using testMap8 to get the CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = cud.decrypt(encryptedValue)
         # print keyname
         # print cleartext
-        # print cleartext.encode('hex')
-        # print
         DB[keyname] = cleartext
         cnt = cnt + 1
 
index 6acdd5c65c6b224a48221d3033a061b1cc570b71..640961382a333925731503355926bcd33df8e3e4 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-import sys, os
+import sys, os, re
 from struct import pack, unpack, unpack_from
 
 from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -11,9 +11,7 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
     string_at, Structure, c_void_p, cast
 
 import _winreg as winreg
-
 MAX_PATH = 255
-
 kernel32 = windll.kernel32
 advapi32 = windll.advapi32
 crypt32 = windll.crypt32
@@ -33,9 +31,39 @@ def SHA1(message):
     ctx.update(message)
     return ctx.digest()
 
+def SHA256(message):
+    ctx = hashlib.sha256()
+    ctx.update(message)
+    return ctx.digest()
+
+# For K4PC 1.9.X
+# need to use routines from openssl
+#    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])
+#    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])
+# but the user may not have openssl installed or their version is a hacked one that was shipped
+# with many ethernet cards that used software instead of hardware routines
+# so using pure python implementations
+from pbkdf2 import pbkdf2
+import aescbc
+
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
+    key_iv = pbkdf2(passwdData, salt, iter, keylen)
+    key = key_iv[0:32]
+    iv = key_iv[32:48]
+    aes=aescbc.AES_CBC(key, aescbc.noPadding() ,32)
+    cleartext = aes.decrypt(iv + encryptedData)
+    return cleartext
+
+
 
 # simple primes table (<= n) calculator
-def primes(n): 
+def primes(n):
     if n==2: return [2]
     elif n<2: return []
     s=range(3,n+1,2)
@@ -59,6 +87,10 @@ def primes(n):
 # Probably supposed to act as obfuscation
 charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
 charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# New maps in K4PC 1.9.0
+testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
 
 class DrmException(Exception):
     pass
@@ -73,7 +105,7 @@ def encode(data, 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)
@@ -165,7 +197,8 @@ def CryptUnprotectData():
         outdata = DataBlob()
         if not _CryptUnprotectData(byref(indata), None, byref(entropy),
                                    None, None, flags, byref(outdata)):
-            raise DrmException("Failed to Unprotect Data")
+            # raise DrmException("Failed to Unprotect Data")
+            return 'failed'
         return string_at(outdata.pbData, outdata.cbData)
     return CryptUnprotectData
 CryptUnprotectData = CryptUnprotectData()
@@ -198,10 +231,17 @@ def getKindleInfoFiles(kInfoFiles):
     else:
         kInfoFiles.append(kinfopath)
 
+    # now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
+    kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
+    if not os.path.isfile(kinfopath):
+        print('No K4PC 1.9.X .kinf files have not been found.')
+    else:
+        kInfoFiles.append(kinfopath)
+
     return kInfoFiles
 
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -232,12 +272,97 @@ def getDBfromFile(kInfoFile):
             DB = None
         return DB
 
-    # else newer style .kinf file
+    if hdr == '/':
+        # else rainier-2-1-1 .kinf file
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+
+            # the raw keyhash string is used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents)-largest prime number <=  int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+            noffset = contlen - primes(int(contlen/3))[-1]
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using Map5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # else newest .kinf2011 style .kinf file
     # the .kinf file uses "/" to separate it into records
     # so remove the trailing "/" to make it easy to use split
-    data = data[:-1]
+    # need to put back the first char read because it it part
+    # of the added entropy blob
+    data = hdr + data[:-1]
     items = data.split('/')
 
+    # starts with and encoded and encrypted header blob
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, testMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+    # now extract the pieces that form the added entropy
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        added_entropy = m.group(2) + m.group(4)
+
+
     # loop through the item records until all are processed
     while len(items) > 0:
 
@@ -248,11 +373,11 @@ def getDBfromFile(kInfoFile):
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        entropy = SHA1(keyhash)
+        # the sha1 of raw keyhash string is used to create entropy along
+        # with the added entropy provided above from the headerblob
+        entropy = SHA1(keyhash) + added_entropy
 
-        # the remainder of the first record when decoded with charMap5 
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
@@ -266,43 +391,39 @@ def getDBfromFile(kInfoFile):
             item = items.pop(0)
             edlst.append(item)
 
+        # key names now use the new testMap8 encoding
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
-        if keyname == "unknown":
-            keyname = keyhash
 
-        # the charMap5 encoded contents data has had a length 
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
 
-        # The offset into the charMap5 encoded contents seems to be:
-        # len(contents) - largest prime number less than or equal to int(len(content)/3)
+        # The offset into the testMap8 encoded contents seems to be:
+        # len(contents)-largest prime number <=  int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
 
-        # move first offsets chars to end to align for decode by charMap5
+        # move first offsets chars to end to align for decode by testMap8
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         encdata = "".join(edlst)
         contlen = len(encdata)
         noffset = contlen - primes(int(contlen/3))[-1]
-
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
 
-        # decode using Map5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+        # decode using new testMap8 to get the original CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+        DB[keyname] = cleartext
         cnt = cnt + 1
 
     if cnt == 0:
         DB = None
     return DB
-
-
index abfc7e47f6140c907af2fc967d414ef703d3fce4..c4e45be1daedaaa1a7ca1a0af4da6c2dc1745202 100644 (file)
@@ -62,7 +62,7 @@ def encode(data, 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)
@@ -78,11 +78,11 @@ def decode(data,map):
         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
@@ -91,10 +91,10 @@ def getTwoBitsFromBitField(bitField,offset):
 
 # 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
-     
+    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
@@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
 def generatePidSeed(table,dsn) :
     value = 0
     for counter in range (0,4) :
-       index = (ord(dsn[counter]) ^ value) &0xFF
-       value = (value >> 8) ^ table[index]
+        index = (ord(dsn[counter]) ^ value) &0xFF
+        value = (value >> 8) ^ table[index]
     return value
 
 # Generate the device PID
@@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
     return pidAscii
 
 def crc32(s):
-  return (~binascii.crc32(s,-1))&0xFFFFFFFF 
+    return (~binascii.crc32(s,-1))&0xFFFFFFFF
 
 # convert from 8 digit PID to 10 digit PID with checksum
 def checksumPid(s):
@@ -204,10 +204,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
         print(message)
         kindleDatabase = None
         pass
-    
+
     if kindleDatabase == None :
         return pidlst
-        
+
     try:
         # Get the Mazama Random number
         MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
@@ -217,7 +217,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
     except KeyError:
         print "Keys not found in " + kInfoFile
         return pidlst
-        
+
     # Get the ID string used
     encodedIDString = encodeHash(GetIDString(),charMap1)
 
@@ -226,7 +226,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
 
     # concat, hash and encode to calculate the DSN
     DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
-       
+
     # Compute the device PID (for which I can tell, is used for nothing).
     table =  generatePidEncryptionTable()
     devicePID = generateDevicePID(table,DSN,4)
@@ -258,7 +258,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
 def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
     pidlst = []
     if kInfoFiles is None:
-       kInfoFiles = []
+        kInfoFiles = []
     if k4:
         kInfoFiles = getKindleInfoFiles(kInfoFiles)
     for infoFile in kInfoFiles:
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto src.zip b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto src.zip
new file mode 100644 (file)
index 0000000..d40a3bf
Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto src.zip differ
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib
new file mode 100644 (file)
index 0000000..01c348c
Binary files /dev/null and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib differ
index 4d978b377ae0cb64cb057212b5d82b314117176a..0a6b25ef4bc7dce592da7ff6d9128f10685ec6ff 100644 (file)
@@ -27,8 +27,8 @@
 #         files reveals that a confusion has arisen because trailing data entries
 #         are not encrypted, but it turns out that the multibyte entries
 #         in utf8 file are encrypted. (Although neither kind gets compressed.)
-#         This knowledge leads to a simplification of the test for the 
-#         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#         This knowledge leads to a simplification of the test for the
+#         trailing data byte flags - version 5 and higher AND header size >= 0xE4.
 #  0.15 - Now outputs 'heartbeat', and is also quicker for long files.
 #  0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
 #  0.17 - added modifications to support its use as an imported python module
@@ -42,7 +42,7 @@
 #  0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
 #  0.21 - Added support for multiple pids
 #  0.22 - revised structure to hold MobiBook as a class to allow an extended interface
-#  0.23 - fixed problem with older files with no EXTH section 
+#  0.23 - fixed problem with older files with no EXTH section
 #  0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
 #  0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
 #  0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
 #  0.30 - Modified interface slightly to work better with new calibre plugin style
 #  0.31 - The multibyte encrytion info is true for version 7 files too.
 #  0.32 - Added support for "Print Replica" Kindle ebooks
+#  0.33 - Performance improvements for large files (concatenation)
+#  0.34 - Performance improvements in decryption (libalfcrypto)
 
-__version__ = '0.32'
+__version__ = '0.34'
 
 import sys
 
@@ -72,6 +74,7 @@ sys.stdout=Unbuffered(sys.stdout)
 import os
 import struct
 import binascii
+from alfcrypto import Pukall_Cipher
 
 class DrmException(Exception):
     pass
@@ -83,36 +86,37 @@ class DrmException(Exception):
 
 # Implementation of Pukall Cipher 1
 def PC1(key, src, decryption=True):
-    sum1 = 0;
-    sum2 = 0;
-    keyXorVal = 0;
-    if len(key)!=16:
-        print "Bad key length!"
-        return None
-    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
+    return Pukall_Cipher().PC1(key,src,decryption)
+#     sum1 = 0;
+#     sum2 = 0;
+#     keyXorVal = 0;
+#     if len(key)!=16:
+#         print "Bad key length!"
+#         return None
+#     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
 
 def checksumPid(s):
     letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
@@ -236,7 +240,7 @@ class MobiBook:
             self.meta_array = {}
             pass
         self.print_replica = False
-            
+
     def getBookTitle(self):
         codec_map = {
             1252 : 'windows-1252',
@@ -319,7 +323,7 @@ class MobiBook:
 
     def getMobiFile(self, outpath):
         file(outpath,'wb').write(self.mobi_data)
-        
+
     def getPrintReplica(self):
         return self.print_replica
 
@@ -355,9 +359,9 @@ class MobiBook:
             if self.magic == 'TEXtREAd':
                 bookkey_data = self.sect[0x0E:0x0E+16]
             elif self.mobi_version < 0:
-                bookkey_data = self.sect[0x90:0x90+16] 
+                bookkey_data = self.sect[0x90:0x90+16]
             else:
-                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] 
+                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
             pid = "00000000"
             found_key = PC1(t1_keyvec, bookkey_data)
         else :
@@ -372,7 +376,7 @@ class MobiBook:
             self.patchSection(0, "\0" * drm_size, drm_ptr)
             # kill the drm pointers
             self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
-            
+
         if pid=="00000000":
             print "File has default encryption, no specific PID."
         else:
@@ -383,7 +387,8 @@ class MobiBook:
 
         # decrypt sections
         print "Decrypting. Please wait . . .",
-        self.mobi_data = self.data_file[:self.sections[1][0]]
+        mobidataList = []
+        mobidataList.append(self.data_file[:self.sections[1][0]])
         for i in xrange(1, self.records+1):
             data = self.loadSection(i)
             extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,11 +398,12 @@ class MobiBook:
             decoded_data = PC1(found_key, data[0:len(data) - extra_size])
             if i==1:
                 self.print_replica = (decoded_data[0:4] == '%MOP')
-            self.mobi_data += decoded_data
+            mobidataList.append(decoded_data)
             if extra_size > 0:
-                self.mobi_data += data[-extra_size:]
+                mobidataList.append(data[-extra_size:])
         if self.num_sections > self.records+1:
-            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
+            mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
+        self.mobi_data = "".join(mobidataList)
         print "done"
         return
 
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm_orig.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm_orig.py
new file mode 100644 (file)
index 0000000..9f0fad8
--- /dev/null
@@ -0,0 +1,444 @@
+#!/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
+#  0.01 - Initial version
+#  0.02 - Huffdic compressed books were not properly decrypted
+#  0.03 - Wasn't checking MOBI header length
+#  0.04 - Wasn't sanity checking size of data record
+#  0.05 - It seems that the extra data flags take two bytes not four
+#  0.06 - And that low bit does mean something after all :-)
+#  0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
+#  0.08 - ...and also not in Mobi header version < 6
+#  0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
+#  0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
+#         import filter it works when importing unencrypted files.
+#         Also now handles encrypted files that don't need a specific PID.
+#  0.11 - use autoflushed stdout and proper return values
+#  0.12 - Fix for problems with metadata import as Calibre plugin, report errors
+#  0.13 - Formatting fixes: retabbed file, removed trailing whitespace
+#         and extra blank lines, converted CR/LF pairs at ends of each line,
+#         and other cosmetic fixes.
+#  0.14 - Working out when the extra data flags are present has been problematic
+#         Versions 7 through 9 have tried to tweak the conditions, but have been
+#         only partially successful. Closer examination of lots of sample
+#         files reveals that a confusion has arisen because trailing data entries
+#         are not encrypted, but it turns out that the multibyte entries
+#         in utf8 file are encrypted. (Although neither kind gets compressed.)
+#         This knowledge leads to a simplification of the test for the
+#         trailing data byte flags - version 5 and higher AND header size >= 0xE4.
+#  0.15 - Now outputs 'heartbeat', and is also quicker for long files.
+#  0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
+#  0.17 - added modifications to support its use as an imported python module
+#         both inside calibre and also in other places (ie K4DeDRM tools)
+#  0.17a- disabled the standalone plugin feature since a plugin can not import
+#         a plugin
+#  0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
+#         Removed the disabled Calibre plug-in code
+#         Permit use of 8-digit PIDs
+#  0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
+#  0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
+#  0.21 - Added support for multiple pids
+#  0.22 - revised structure to hold MobiBook as a class to allow an extended interface
+#  0.23 - fixed problem with older files with no EXTH section
+#  0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
+#  0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
+#  0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
+#  0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
+#  0.28 - slight additional changes to metadata token generation (None -> '')
+#  0.29 - It seems that the ideas about when multibyte trailing characters were
+#         included in the encryption were wrong. They are for DOC compressed
+#         files, but they are not for HUFF/CDIC compress files!
+#  0.30 - Modified interface slightly to work better with new calibre plugin style
+#  0.31 - The multibyte encrytion info is true for version 7 files too.
+#  0.32 - Added support for "Print Replica" Kindle ebooks
+
+__version__ = '0.32'
+
+import sys
+
+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)
+sys.stdout=Unbuffered(sys.stdout)
+
+import os
+import struct
+import binascii
+
+class DrmException(Exception):
+    pass
+
+
+#
+# MobiBook Utility Routines
+#
+
+# Implementation of Pukall Cipher 1
+def PC1(key, src, decryption=True):
+    sum1 = 0;
+    sum2 = 0;
+    keyXorVal = 0;
+    if len(key)!=16:
+        print "Bad key length!"
+        return None
+    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
+
+def checksumPid(s):
+    letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+    crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
+    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 getSizeOfTrailingDataEntries(ptr, size, flags):
+    def getSizeOfTrailingDataEntry(ptr, size):
+        bitpos, result = 0, 0
+        if size <= 0:
+            return result
+        while True:
+            v = ord(ptr[size-1])
+            result |= (v & 0x7F) << bitpos
+            bitpos += 7
+            size -= 1
+            if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
+                return result
+    num = 0
+    testflags = flags >> 1
+    while testflags:
+        if testflags & 1:
+            num += getSizeOfTrailingDataEntry(ptr, size - num)
+        testflags >>= 1
+    # Check the low bit to see if there's multibyte data present.
+    # if multibyte data is included in the encryped data, we'll
+    # have already cleared this flag.
+    if flags & 1:
+        num += (ord(ptr[size - num - 1]) & 0x3) + 1
+    return num
+
+
+
+class MobiBook:
+    def loadSection(self, section):
+        if (section + 1 == self.num_sections):
+            endoff = len(self.data_file)
+        else:
+            endoff = self.sections[section + 1][0]
+        off = self.sections[section][0]
+        return self.data_file[off:endoff]
+
+    def __init__(self, infile):
+        print ('MobiDeDrm v%(__version__)s. '
+           'Copyright 2008-2011 The Dark Reverser et al.' % globals())
+
+        # initial sanity check on file
+        self.data_file = file(infile, 'rb').read()
+        self.mobi_data = ''
+        self.header = self.data_file[0:78]
+        if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
+            raise DrmException("invalid file format")
+        self.magic = self.header[0x3C:0x3C+8]
+        self.crypto_type = -1
+
+        # build up section offset and flag info
+        self.num_sections, = struct.unpack('>H', self.header[76:78])
+        self.sections = []
+        for i in xrange(self.num_sections):
+            offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
+            flags, val = a1, a2<<16|a3<<8|a4
+            self.sections.append( (offset, flags, val) )
+
+        # parse information from section 0
+        self.sect = self.loadSection(0)
+        self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
+        self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
+
+        if self.magic == 'TEXtREAd':
+            print "Book has format: ", self.magic
+            self.extra_data_flags = 0
+            self.mobi_length = 0
+            self.mobi_version = -1
+            self.meta_array = {}
+            return
+        self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
+        self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
+        self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
+        print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
+        self.extra_data_flags = 0
+        if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
+            self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
+            print "Extra Data Flags = %d" % self.extra_data_flags
+        if (self.compression != 17480):
+            # multibyte utf8 data is included in the encryption for PalmDoc compression
+            # so clear that byte so that we leave it to be decrypted.
+            self.extra_data_flags &= 0xFFFE
+
+        # if exth region exists parse it for metadata array
+        self.meta_array = {}
+        try:
+            exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
+            exth = 'NONE'
+            if exth_flag & 0x40:
+                exth = self.sect[16 + self.mobi_length:]
+            if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
+                nitems, = struct.unpack('>I', exth[8:12])
+                pos = 12
+                for i in xrange(nitems):
+                    type, size = struct.unpack('>II', exth[pos: pos + 8])
+                    content = exth[pos + 8: pos + size]
+                    self.meta_array[type] = content
+                    # reset the text to speech flag and clipping limit, if present
+                    if type == 401 and size == 9:
+                        # set clipping limit to 100%
+                        self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
+                    elif type == 404 and size == 9:
+                        # make sure text to speech is enabled
+                        self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
+                    # print type, size, content, content.encode('hex')
+                    pos += size
+        except:
+            self.meta_array = {}
+            pass
+        self.print_replica = False
+
+    def getBookTitle(self):
+        codec_map = {
+            1252 : 'windows-1252',
+            65001 : 'utf-8',
+        }
+        title = ''
+        if 503 in self.meta_array:
+            title = self.meta_array[503]
+        else :
+            toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
+            tend = toff + tlen
+            title = self.sect[toff:tend]
+        if title == '':
+            title = self.header[:32]
+            title = title.split("\0")[0]
+        codec = 'windows-1252'
+        if self.mobi_codepage in codec_map.keys():
+            codec = codec_map[self.mobi_codepage]
+        return unicode(title, codec).encode('utf-8')
+
+    def getPIDMetaInfo(self):
+        rec209 = ''
+        token = ''
+        if 209 in self.meta_array:
+            rec209 = self.meta_array[209]
+            data = rec209
+            # The 209 data comes in five byte groups. Interpret the last four bytes
+            # of each group as a big endian unsigned integer to get a key value
+            # if that key exists in the meta_array, append its contents to the token
+            for i in xrange(0,len(data),5):
+                val,  = struct.unpack('>I',data[i+1:i+5])
+                sval = self.meta_array.get(val,'')
+                token += sval
+        return rec209, token
+
+    def patch(self, off, new):
+        self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
+
+    def patchSection(self, section, new, in_off = 0):
+        if (section + 1 == self.num_sections):
+            endoff = len(self.data_file)
+        else:
+            endoff = self.sections[section + 1][0]
+        off = self.sections[section][0]
+        assert off + in_off + len(new) <= endoff
+        self.patch(off + in_off, new)
+
+    def parseDRM(self, data, count, pidlist):
+        found_key = None
+        keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+        for pid in pidlist:
+            bigpid = pid.ljust(16,'\0')
+            temp_key = PC1(keyvec1, bigpid, False)
+            temp_key_sum = sum(map(ord,temp_key)) & 0xff
+            found_key = None
+            for i in xrange(count):
+                verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+                if cksum == temp_key_sum:
+                    cookie = PC1(temp_key, cookie)
+                    ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+                    if verification == ver and (flags & 0x1F) == 1:
+                        found_key = finalkey
+                        break
+            if found_key != None:
+                break
+        if not found_key:
+            # Then try the default encoding that doesn't require a PID
+            pid = "00000000"
+            temp_key = keyvec1
+            temp_key_sum = sum(map(ord,temp_key)) & 0xff
+            for i in xrange(count):
+                verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+                if cksum == temp_key_sum:
+                    cookie = PC1(temp_key, cookie)
+                    ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+                    if verification == ver:
+                        found_key = finalkey
+                        break
+        return [found_key,pid]
+
+    def getMobiFile(self, outpath):
+        file(outpath,'wb').write(self.mobi_data)
+
+    def getPrintReplica(self):
+        return self.print_replica
+
+    def processBook(self, pidlist):
+        crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
+        print 'Crypto Type is: ', crypto_type
+        self.crypto_type = crypto_type
+        if crypto_type == 0:
+            print "This book is not encrypted."
+            # we must still check for Print Replica
+            self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
+            self.mobi_data = self.data_file
+            return
+        if crypto_type != 2 and crypto_type != 1:
+            raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
+        if 406 in self.meta_array:
+            data406 = self.meta_array[406]
+            val406, = struct.unpack('>Q',data406)
+            if val406 != 0:
+                raise DrmException("Cannot decode library or rented ebooks.")
+
+        goodpids = []
+        for pid in pidlist:
+            if len(pid)==10:
+                if checksumPid(pid[0:-2]) != pid:
+                    print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
+                goodpids.append(pid[0:-2])
+            elif len(pid)==8:
+                goodpids.append(pid)
+
+        if self.crypto_type == 1:
+            t1_keyvec = "QDCVEPMU675RUBSZ"
+            if self.magic == 'TEXtREAd':
+                bookkey_data = self.sect[0x0E:0x0E+16]
+            elif self.mobi_version < 0:
+                bookkey_data = self.sect[0x90:0x90+16]
+            else:
+                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
+            pid = "00000000"
+            found_key = PC1(t1_keyvec, bookkey_data)
+        else :
+            # calculate the keys
+            drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
+            if drm_count == 0:
+                raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
+            found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
+            if not found_key:
+                raise DrmException("No key found. Most likely the correct PID has not been given.")
+            # kill the drm keys
+            self.patchSection(0, "\0" * drm_size, drm_ptr)
+            # kill the drm pointers
+            self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
+
+        if pid=="00000000":
+            print "File has default encryption, no specific PID."
+        else:
+            print "File is encoded with PID "+checksumPid(pid)+"."
+
+        # clear the crypto type
+        self.patchSection(0, "\0" * 2, 0xC)
+
+        # decrypt sections
+        print "Decrypting. Please wait . . .",
+        self.mobi_data = self.data_file[:self.sections[1][0]]
+        for i in xrange(1, self.records+1):
+            data = self.loadSection(i)
+            extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
+            if i%100 == 0:
+                print ".",
+            # print "record %d, extra_size %d" %(i,extra_size)
+            decoded_data = PC1(found_key, data[0:len(data) - extra_size])
+            if i==1:
+                self.print_replica = (decoded_data[0:4] == '%MOP')
+            self.mobi_data += decoded_data
+            if extra_size > 0:
+                self.mobi_data += data[-extra_size:]
+        if self.num_sections > self.records+1:
+            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
+        print "done"
+        return
+
+def getUnencryptedBook(infile,pid):
+    if not os.path.isfile(infile):
+        raise DrmException('Input File Not Found')
+    book = MobiBook(infile)
+    book.processBook([pid])
+    return book.mobi_data
+
+def getUnencryptedBookWithList(infile,pidlist):
+    if not os.path.isfile(infile):
+        raise DrmException('Input File Not Found')
+    book = MobiBook(infile)
+    book.processBook(pidlist)
+    return book.mobi_data
+
+
+def main(argv=sys.argv):
+    print ('MobiDeDrm v%(__version__)s. '
+        'Copyright 2008-2011 The Dark Reverser et al.' % globals())
+    if len(argv)<3 or len(argv)>4:
+        print "Removes protection from Kindle/Mobipocket and Kindle/Print Replica ebooks"
+        print "Usage:"
+        print "    %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
+        return 1
+    else:
+        infile = argv[1]
+        outfile = argv[2]
+        if len(argv) is 4:
+            pidlist = argv[3].split(',')
+        else:
+            pidlist = {}
+        try:
+            stripped_file = getUnencryptedBookWithList(infile, pidlist)
+            file(outfile, 'wb').write(stripped_file)
+        except DrmException, e:
+            print "Error: %s" % e
+            return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
index 8a044fa89a0641c156c5c5c18f6d4a9067bf5d9b..a4a40ca859417988ad76623ac65f8d3df2698fba 100644 (file)
@@ -18,7 +18,7 @@ def load_libcrypto():
         return None
 
     libcrypto = CDLL(libcrypto)
-    
+
     # typedef struct DES_ks
     #     {
     #     union
@@ -30,7 +30,7 @@ def load_libcrypto():
     #         } ks[16];
     #     } DES_key_schedule;
 
-    # just create a big enough place to hold everything 
+    # 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),
@@ -61,7 +61,7 @@ def load_libcrypto():
     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 :
@@ -87,4 +87,3 @@ def load_libcrypto():
             return ''.join(result)
 
     return DES
-
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pbkdf2.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pbkdf2.py
new file mode 100644 (file)
index 0000000..65220a9
--- /dev/null
@@ -0,0 +1,68 @@
+# A simple implementation of pbkdf2 using stock python modules. See RFC2898
+# for details. Basically, it derives a key from a password and salt.
+
+# Copyright 2004 Matt Johnston <matt @ ucc asn au>
+# Copyright 2009 Daniel Holth <dholth@fastmail.fm>
+# This code may be freely used and modified for any purpose.
+
+# Revision history
+# v0.1  October 2004    - Initial release
+# v0.2  8 March 2007    - Make usable with hashlib in Python 2.5 and use
+# v0.3  ""                 the correct digest_size rather than always 20
+# v0.4  Oct 2009        - Rescue from chandler svn, test and optimize.
+
+import sys
+import hmac
+from struct import pack
+try:
+    # only in python 2.5
+    import hashlib
+    sha = hashlib.sha1
+    md5 = hashlib.md5
+    sha256 = hashlib.sha256
+except ImportError: # pragma: NO COVERAGE
+    # fallback
+    import sha
+    import md5
+
+# this is what you want to call.
+def pbkdf2( password, salt, itercount, keylen, hashfn = sha ):
+    try:
+        # depending whether the hashfn is from hashlib or sha/md5
+        digest_size = hashfn().digest_size
+    except TypeError: # pragma: NO COVERAGE
+        digest_size = hashfn.digest_size
+    # l - number of output blocks to produce
+    l = keylen / digest_size
+    if keylen % digest_size != 0:
+        l += 1
+
+    h = hmac.new( password, None, hashfn )
+
+    T = ""
+    for i in range(1, l+1):
+        T += pbkdf2_F( h, salt, itercount, i )
+
+    return T[0: keylen]
+
+def xorstr( a, b ):
+    if len(a) != len(b):
+        raise ValueError("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()
+
+# Helper as per the spec. h is a hmac which has been created seeded with the
+# password, it will be copy()ed and not modified.
+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
index e30abfaa0c1742adf3903016a8256cee7a43e54d..adbac4988187e1dc4eb4ce403eb931039069ff6b 100644 (file)
@@ -6,6 +6,7 @@ import csv
 import sys
 import os
 import getopt
+import re
 from struct import pack
 from struct import unpack
 
@@ -43,8 +44,8 @@ class DocParser(object):
         '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
@@ -59,10 +60,10 @@ class DocParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -82,12 +83,19 @@ class DocParser(object):
         return startpos
 
     # returns a vector of integers for the tagpath
-    def getData(self, tagpath, pos, end):
+    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('|')
-            argres = [ int(strval) for strval in argList]
+            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):
@@ -112,7 +120,7 @@ class DocParser(object):
             (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 :
 
@@ -121,7 +129,7 @@ class DocParser(object):
                 if sclass != None:
                     sclass = sclass.replace(' ','-')
                     sclass = '.cl-' + sclass.lower()
-                else : 
+                else :
                     sclass = ''
 
                 # check for any "after class" specifiers
@@ -129,7 +137,7 @@ class DocParser(object):
                 if aftclass != None:
                     aftclass = aftclass.replace(' ','-')
                     aftclass = '.cl-' + aftclass.lower()
-                else : 
+                else :
                     aftclass = ''
 
                 cssargs = {}
@@ -140,7 +148,7 @@ class DocParser(object):
                     (pos2, val) = self.findinDoc('style.rule.value', start, end)
 
                     if attr == None : break
-                    
+
                     if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
                         # handle text based attributess
                         attr = attr + '-' + val
@@ -168,7 +176,7 @@ class DocParser(object):
                 if aftclass != "" : keep = False
 
                 if keep :
-                    # make sure line-space does not go below 100% or above 300% since 
+                    # 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]
@@ -178,7 +186,7 @@ class DocParser(object):
                         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]
@@ -211,7 +219,7 @@ class DocParser(object):
 
                     if sclass != '' :
                         classlst += sclass + '\n'
-                    
+
                     # handle special case of paragraph class used inside chapter heading
                     # and non-chapter headings
                     if sclass != '' :
@@ -232,7 +240,7 @@ class DocParser(object):
                     if cssline != ' { }':
                         csspage += self.stags[tag] + cssline + '\n'
 
-                
+
         return csspage, classlst
 
 
@@ -251,5 +259,5 @@ def convert2CSS(flatxml, fontsize, ph, pw):
 
 def getpageIDMap(flatxml):
     dp = DocParser(flatxml, 0, 0, 0)
-    pageidnumbers = dp.getData('info.original.pid', 0, -1)
+    pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
     return pageidnumbers
index ed13aa1b7917bd7923efa09367d1ef2304b472e7..de084d303fcc72ce25e9213ab87f8f1d91bfdb03 100644 (file)
@@ -52,7 +52,7 @@ class Process(object):
             self.__stdout_thread = threading.Thread(
                 name="stdout-thread",
                 target=self.__reader, args=(self.__collected_outdata,
-                                           self.__process.stdout))
+                                            self.__process.stdout))
             self.__stdout_thread.setDaemon(True)
             self.__stdout_thread.start()
 
@@ -60,7 +60,7 @@ class Process(object):
             self.__stderr_thread = threading.Thread(
                 name="stderr-thread",
                 target=self.__reader, args=(self.__collected_errdata,
-                                           self.__process.stderr))
+                                            self.__process.stderr))
             self.__stderr_thread.setDaemon(True)
             self.__stderr_thread.start()
 
@@ -146,4 +146,3 @@ class Process(object):
         self.__quit = True
         self.__inputsem.release()
         self.__lock.release()
-
index eed53e263ce9c750de783efa533ed6024cd6192f..6f53dc4e4230ff43e23d97cc820539e7df7a9700 100644 (file)
@@ -20,11 +20,12 @@ import os, csv, getopt
 import zlib, zipfile, tempfile, shutil
 from struct import pack
 from struct import unpack
+from alfcrypto import Topaz_Cipher
 
 class TpzDRMError(Exception):
     pass
 
-    
+
 # local support routines
 if inCalibre:
     from calibre_plugins.k4mobidedrm import kgenpids
@@ -58,22 +59,22 @@ def bookReadEncodedNumber(fo):
     flag = False
     data = ord(fo.read(1))
     if data == 0xFF:
-       flag = True
-       data = ord(fo.read(1))
+        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 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
-    
-# Get a length prefixed string from file 
+
+# Get a length prefixed string from file
 def bookReadString(fo):
     stringLength = bookReadEncodedNumber(fo)
-    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]  
+    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
 
 #
 # crypto routines
@@ -81,25 +82,28 @@ def bookReadString(fo):
 
 # Context initialisation for the Topaz Crypto
 def topazCryptoInit(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]
-    
+    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):
-    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
+    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):
@@ -153,7 +157,7 @@ class TopazBook:
 
     def parseTopazHeaders(self):
         def bookReadHeaderRecordData():
-            # Read and return the data of one header record at the current book file position 
+            # Read and return the data of one header record at the current book file position
             # [[offset,decompressedLength,compressedLength],...]
             nbValues = bookReadEncodedNumber(self.fo)
             values = []
@@ -213,11 +217,11 @@ class TopazBook:
         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 
+        # Get a record in the book payload, given its name and index.
+        # decrypted and decompressed if necessary
         encrypted = False
         compressed = False
-        try: 
+        try:
             recordOffset = self.bookHeaderRecords[name][index][0]
         except:
             raise TpzDRMError("Parse Error : Invalid Record, record not found")
@@ -268,8 +272,8 @@ class TopazBook:
             rv = genbook.generateBook(self.outdir, raw, fixedimage)
             if rv == 0:
                 print "\nBook Successfully generated"
-            return rv            
-    
+            return rv
+
         # try each pid to decode the file
         bookKey = None
         for pid in pidlst:
@@ -297,7 +301,7 @@ class TopazBook:
         rv = genbook.generateBook(self.outdir, raw, fixedimage)
         if rv == 0:
             print "\nBook Successfully generated"
-        return rv            
+        return rv
 
     def createBookDirectory(self):
         outdir = self.outdir
@@ -378,7 +382,7 @@ def usage(progname):
     print "Removes DRM protection from Topaz ebooks and extract the contents"
     print "Usage:"
     print "    %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir>  " % progname
-    
+
 
 # Main
 def main(argv=sys.argv):
@@ -387,7 +391,7 @@ def main(argv=sys.argv):
     pids = []
     serials = []
     kInfoFiles = []
-    
+
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
     except getopt.GetoptError, err:
@@ -397,7 +401,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         return 1
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -429,7 +433,7 @@ def main(argv=sys.argv):
     title = tb.getBookTitle()
     print "Processing Book: ", title
     keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles)
 
     try:
         print "Decrypting Book"
@@ -461,9 +465,8 @@ def main(argv=sys.argv):
         return 1
 
     return 0
-                
+
 
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index a1aafde23e57ee26d332d06ee6bd71dfc53d6ed8..523ef1a2109cb6d41dcaaec8175672484cccff9c 100644 (file)
@@ -30,8 +30,8 @@ class fixZip:
         self.inzip = zipfile.ZipFile(zinput,'r')
         self.outzip = zipfile.ZipFile(zoutput,'w')
         # open the input zip for reading only as a raw file
-       self.bzf = file(zinput,'rb')
-        
+        self.bzf = file(zinput,'rb')
+
     def getlocalname(self, zi):
         local_header_offset = zi.header_offset
         self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
@@ -86,7 +86,7 @@ class fixZip:
 
         return data
 
-        
+
 
     def fix(self):
         # get the zipinfo for each member of the input archive
@@ -103,7 +103,7 @@ class fixZip:
             if zinfo.filename != "mimetype" or self.ztype == '.zip':
                 data = None
                 nzinfo = zinfo
-                try: 
+                try:
                     data = self.inzip.read(zinfo.filename)
                 except zipfile.BadZipfile or zipfile.error:
                     local_name = self.getlocalname(zinfo)
@@ -126,7 +126,7 @@ def usage():
      inputzip is the source zipfile to fix
      outputzip is the fixed zip archive
     """
-    
+
 
 def repairBook(infile, outfile):
     if not os.path.exists(infile):
@@ -152,5 +152,3 @@ def main(argv=sys.argv):
 
 if __name__ == '__main__' :
     sys.exit(main())
-
-
diff --git a/DeDRM_Macintosh_Application/ReadMe_DeDRM.app.rtf b/DeDRM_Macintosh_Application/ReadMe_DeDRM.app.rtf
new file mode 100644 (file)
index 0000000..d9e582e
--- /dev/null
@@ -0,0 +1,48 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
+{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\paperw11900\paperh16840\margl1440\margr1440\vieww10320\viewh9840\viewkind0
+\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
+
+\f0\b\fs24 \cf0 ReadMe_DeDRM_X.X
+\b0 \
+\
+\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qj\pardirnatural
+\cf0 DeDRM_X.X is an AppleScript droplet that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM droplet to have the DRM removed.  It repackages the all the "tools" DeDRM python software in one easy to use program that remembers preferences and settings.\
+\
+It should work without manual configuration with Kindle for Mac ebooks and Adobe Adept epub and pdf ebooks.\
+\
+To remove the DRM from standalone Kindle ebooks, eReader pdb ebooks, Barnes and Noble epubs, and Mobipocket ebooks requires the user to double-click the DeDRM droplet and set some additional Preferences including:\
+\
+Kindle 16 digit Serial Number\
+Barnes & Noble key files (bnepubkey.b64)\
+eReader Social DRM: (Name:Last 8 digits of CC number)\
+MobiPocket, Kindle for iPhone/iPad/iPodTouch  10 digit PID\
+Location for DRM-free ebooks.\
+\
+Once these preferences have been set, the user can simply drag and drop ebooks onto the DeDRM droplet to remove the DRM.\
+\
+This program requires Mac OS X 10.5, 10.5 or 10.7 (Leopard, Snow Leopard or Lion)\r\
+\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\ql\qnatural\pardirnatural
+\cf0 \
+\
+\
+
+\b Installation\
+
+\b0 \
+1. From tools_vX.X\\DeDRM_Applications\\, double click on DeDRM_X.X.zip to extract its contents. \
+\
+2. Move the resulting DeDRM X.X.app AppleScript droplet to whereever you keep you other applications. (Typically your Applications folder.)\
+\
+3. Optionally drag it into your dock, to make it easily available.\
+\
+\
+\
+
+\b Use\
+
+\b0 \
+1. To set the preferences simply double-click the Applescript droplet in your Applications folder or click on its icon in your dock, and follow the instructions in the dialogs.\
+\
+2. Drag & Drop DRMed ebooks or folders containing DRMed ebooks onto the Application, either in your Applications folder, or the icon in your dock.}
\ No newline at end of file
index 6d9af630527e39c3f4c1b31779cbabbcdc57d5b0..98aa9d37222acaf96f43fdeaa15e796a3729def2 100644 (file)
@@ -18,6 +18,9 @@ from subasyncio import Process
 import re
 import simpleprefs
 
+
+__version__ = '5.0'
+
 class DrmException(Exception):
     pass
 
@@ -29,7 +32,7 @@ class MainApp(Tk):
         self.apphome = apphome
         # preference settings
         # [dictionary key, file in preferences directory where info is stored]
-        description = [ ['pids'   , 'pidlist.txt'   ], 
+        description = [ ['pids'   , 'pidlist.txt'   ],
                         ['serials', 'seriallist.txt'],
                         ['sdrms'  , 'sdrmlist.txt'  ],
                         ['outdir' , 'outdir.txt'    ]]
@@ -123,7 +126,7 @@ class PrefsDialog(Toplevel):
             self.bnkpath.insert(0, path)
         button = Tkinter.Button(body, text="...", command=self.get_bnkpath)
         button.grid(row=1, column=2)
-        
+
         Tkinter.Label(body, text='Additional kindle.info or .kinf file').grid(row=2, sticky=Tkconstants.E)
         self.altinfopath = Tkinter.Entry(body, width=50)
         self.altinfopath.grid(row=2, column=1, sticky=sticky)
@@ -180,7 +183,7 @@ class PrefsDialog(Toplevel):
         self.bookpath.grid(row=9, column=1, sticky=sticky)
         button = Tkinter.Button(body, text="...", command=self.get_bookpath)
         button.grid(row=9, column=2)
-   
+
         Tkinter.Label(body, font=("Helvetica", "10", "italic"), text='*To DeDRM multiple ebooks simultaneously, set your preferences and quit.\nThen drag and drop ebooks or folders onto the DeDRM_Drop_Target').grid(row=10, column=1, sticky=Tkconstants.E)
 
         Tkinter.Label(body, text='').grid(row=11, column=0, columnspan=2, sticky=Tkconstants.E)
@@ -365,7 +368,7 @@ class ConvDialog(Toplevel):
     def conversion_done(self):
         self.hide()
         self.master.alldone()
-        
+
     def processBooks(self):
         while self.running == 'inactive':
             rscpath = self.prefs_array['dir']
@@ -429,7 +432,7 @@ class ConvDialog(Toplevel):
             # nothing to wait for so just return
             return
         poll = self.p2.wait('nowait')
-        if poll != None: 
+        if poll != None:
             self.bar.stop()
             if poll == 0:
                 msg = 'Success\n'
@@ -451,7 +454,7 @@ class ConvDialog(Toplevel):
             self.running = 'inactive'
             self.after(50,self.processBooks)
             return
-        # make sure we get invoked again by event loop after interval 
+        # make sure we get invoked again by event loop after interval
         self.stext.after(self.interval,self.processPipe)
         return
 
@@ -481,7 +484,7 @@ def runit(apphome, ncmd, nparms):
     if sys.platform.startswith('win'):
         search_path = os.environ['PATH']
         search_path = search_path.lower()
-        if search_path.find('python') < 0: 
+        if search_path.find('python') < 0:
             # if no python hope that win registry finds what is associated with py extension
             cmdline = '"' + os.path.join(apphome, ncmd) + '" '
     cmdline += nparms
@@ -585,4 +588,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
index d2289c94babc2ee9323b41e2049349a1282b252a..b21c01dc3d051bceb07fb0465e8b1fe6ee57f28c 100644 (file)
@@ -7,7 +7,7 @@ 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._master = master
         self._interval = interval
         self._maximum = length
         self._startx = 0
@@ -24,7 +24,7 @@ class ActivityBar(Tkinter.Frame):
                                     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
@@ -64,7 +64,7 @@ class ActivityBar(Tkinter.Frame):
     def stop(self):
         self._running = False
         self._set()
-        
+
     def _step(self):
         if self._running:
             stepsize = self._barwidth / 4
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/aescbc.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/aescbc.py
new file mode 100644 (file)
index 0000000..5667511
--- /dev/null
@@ -0,0 +1,568 @@
+#! /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'
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.dll b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.dll
new file mode 100644 (file)
index 0000000..26d740d
Binary files /dev/null and b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.dll differ
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.exp b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.exp
new file mode 100644 (file)
index 0000000..08b8cdb
Binary files /dev/null and b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.exp differ
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto.py
new file mode 100644 (file)
index 0000000..e25a0c8
--- /dev/null
@@ -0,0 +1,290 @@
+#! /usr/bin/env python
+
+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'
+    
+    libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found')
+
+    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 "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:
+                print "Bad key length!"
+                return None
+            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
+
+    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]
+
+
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto64.dll b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto64.dll
new file mode 100644 (file)
index 0000000..7bef68e
Binary files /dev/null and b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto64.dll differ
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto_src.zip b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto_src.zip
new file mode 100644 (file)
index 0000000..269810c
Binary files /dev/null and b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/alfcrypto_src.zip differ
index 0328206ac8b4d8ac5725f85e62cdc7b71febe53b..98645372c97d251bbeda73e30138c9dbaaff6789 100644 (file)
@@ -23,7 +23,7 @@ from struct import unpack
 class TpzDRMError(Exception):
     pass
 
-# Get a 7 bit encoded number from string. The most 
+# Get a 7 bit encoded number from string. The most
 # significant byte comes first and has the high bit (8th) set
 
 def readEncodedNumber(file):
@@ -32,57 +32,57 @@ def readEncodedNumber(file):
     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)
-       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
-    
+        data = datax
+
     if flag:
-       data = -data
+        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]
-  
+    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
@@ -97,9 +97,9 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    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
 
@@ -265,6 +265,8 @@ class PageParser(object):
         '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),
@@ -284,6 +286,8 @@ class PageParser(object):
         '_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),
@@ -291,6 +295,8 @@ class PageParser(object):
         '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.type'       : (1, 'scalar_text', 0, 0),
@@ -376,14 +382,14 @@ class PageParser(object):
         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 
+    # 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):
@@ -416,7 +422,7 @@ class PageParser(object):
         return result
 
 
-    # process the next tag token, recursively handling subtags, 
+    # process the next tag token, recursively handling subtags,
     # arguments, and commands
     def procToken(self, token):
 
@@ -438,7 +444,7 @@ class PageParser(object):
 
         if known_token :
 
-            # handle subtags if present 
+            # handle subtags if present
             subtagres = []
             if (splcase == 1):
                 # this type of tag uses of escape marker 0x74 indicate subtag count
@@ -447,7 +453,7 @@ class PageParser(object):
                     subtags = 1
                     num_args = 0
 
-            if (subtags == 1): 
+            if (subtags == 1):
                 ntags = readEncodedNumber(self.fo)
                 if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
                 for j in xrange(ntags):
@@ -478,7 +484,7 @@ class PageParser(object):
             return result
 
         # all tokens that need to be processed should be in the hash
-        # table if it may indicate a problem, either new token 
+        # table if it may indicate a problem, either new token
         # or an out of sync condition
         else:
             result = []
@@ -530,7 +536,7 @@ class PageParser(object):
     # 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 
+    # 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
 
@@ -549,7 +555,7 @@ class PageParser(object):
         return result
 
 
-            
+
     # add full tag path to injected snippets
     def updateName(self, tag, prefix):
         name = tag[0]
@@ -577,7 +583,7 @@ class PageParser(object):
         argtype = tag[2]
         argList = tag[3]
         nsubtagList = []
-        if len(argList) > 0 : 
+        if len(argList) > 0 :
             for j in argList:
                 asnip = self.snippetList[j]
                 aso, atag = self.injectSnippets(asnip)
@@ -609,65 +615,70 @@ class PageParser(object):
         nodename = fullpathname.pop()
         ilvl = len(fullpathname)
         indent = ' ' * (3 * ilvl)
-        result = indent + '<' + nodename + '>'
+        rlst = []
+        rlst.append(indent + '<' + nodename + '>')
         if len(argList) > 0:
-            argres = ''
+            alst = []
             for j in argList:
                 if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
+                    alst.append(j + '|')
                 else :
-                    argres += str(j) + ','
+                    alst.append(str(j) + ',')
+            argres = "".join(alst)
             argres = argres[0:-1]
             if argtype == 'snippets' :
-                result += 'snippets:' + argres
+                rlst.append('snippets:' + argres)
             else :
-                result += argres
+                rlst.append(argres)
         if len(subtagList) > 0 :
-            result += '\n'
+            rlst.append('\n')
             for j in subtagList:
                 if len(j) > 0 :
-                    result += self.formatTag(j)
-            result += indent + '</' + nodename + '>\n'
+                    rlst.append(self.formatTag(j))
+            rlst.append(indent + '</' + nodename + '>\n')
         else:
-            result += '</' + nodename + '>\n'
-        return result
+            rlst.append('</' + nodename + '>\n')
+        return "".join(rlst)
 
 
-   # flatten tag
+    # flatten tag
     def flattenTag(self, node):
         name = node[0]
         subtagList = node[1]
         argtype = node[2]
         argList = node[3]
-        result = name
+        rlst = []
+        rlst.append(name)
         if (len(argList) > 0):
-            argres = ''
+            alst = []
             for j in argList:
                 if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
+                    alst.append(j + '|')
                 else :
-                    argres += str(j) + '|'
+                    alst.append(str(j) + '|')
+            argres = "".join(alst)
             argres = argres[0:-1]
             if argtype == 'snippets' :
-                result += '.snippets=' + argres
+                rlst.append('.snippets=' + argres)
             else :
-                result += '=' + argres
-        result += '\n'
+                rlst.append('=' + argres)
+        rlst.append('\n')
         for j in subtagList:
             if len(j) > 0 :
-                result += self.flattenTag(j)
-        return result
+                rlst.append(self.flattenTag(j))
+        return "".join(rlst)
 
 
     # reduce create xml output
     def formatDoc(self, flat_xml):
-        result = ''
+        rlst = []
         for j in self.doc :
             if len(j) > 0:
                 if flat_xml:
-                    result += self.flattenTag(j)
+                    rlst.append(self.flattenTag(j))
                 else:
-                    result += self.formatTag(j)
+                    rlst.append(self.formatTag(j))
+        result = "".join(rlst)
         if self.debug : print result
         return result
 
@@ -712,7 +723,7 @@ class PageParser(object):
                 first_token = None
 
             v = self.getNext()
-            if (v == None): 
+            if (v == None):
                 break
 
             if (v == 0x72):
@@ -723,7 +734,7 @@ class PageParser(object):
                     self.doc.append(tag)
             else:
                 if self.debug:
-                    print "Main Loop:  Unknown value: %x" % v 
+                    print "Main Loop:  Unknown value: %x" % v
                 if (v == 0):
                     if (self.peek(1) == 0x5f):
                         skip = self.fo.read(1)
@@ -776,7 +787,7 @@ def usage():
 
 #
 # Main
-#   
+#
 
 def main(argv):
     dictFile = ""
@@ -797,11 +808,11 @@ def main(argv):
         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) 
-       
+        sys.exit(2)
+
     for o, a in opts:
         if o =="-d":
             debug=True
index 93713e4291976c37bac11a1ed34e66883e506327..e64c860610596e96e935eb49d8f5554b64898911 100644 (file)
@@ -86,4 +86,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
index 93b4d86c917aa7f1e6c69c594fa7536071f5f178..12b8c1044d07a0b641f4cf272bb8a7b1c4cab451 100644 (file)
@@ -43,4 +43,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
index 82d222a7a395554ce0f70528d0177e0d0080d4a9..ddaeacdcdc22eaaac6509be52d643abd9552e629 100644 (file)
@@ -25,11 +25,11 @@ def main(argv=sys.argv):
     rscpath = args[2]
     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)
@@ -52,4 +52,3 @@ def main(argv=sys.argv):
 
 if __name__ == "__main__":
     sys.exit(main())
-
index 7fefaf7125750b7c6ce448cafa0327727c07e035..8f958cd487a971e95ff6c6af979e618005b2a0ec 100644 (file)
@@ -16,7 +16,7 @@
 # Custom version 0.03 - no change to eReader support, only usability changes
 #   - start of pep-8 indentation (spaces not tab), fix trailing blanks
 #   - version variable, only one place to change
-#   - added main routine, now callable as a library/module, 
+#   - added main routine, now callable as a library/module,
 #     means tools can add optional support for ereader2html
 #   - outdir is no longer a mandatory parameter (defaults based on input name if missing)
 #   - time taken output to stdout
@@ -59,8 +59,8 @@
 #  0.18 - on Windows try PyCrypto first and OpenSSL next
 #  0.19 - Modify the interface to allow use of import
 #  0.20 - modify to allow use inside new interface for calibre plugins
-#  0.21 - Support eReader (drm) version 11. 
-#       - Don't reject dictionary format. 
+#  0.21 - Support eReader (drm) version 11.
+#       - Don't reject dictionary format.
 #       - Ignore sidebars for dictionaries (different format?)
 
 __version__='0.21'
@@ -178,7 +178,7 @@ def sanitizeFileName(s):
 def fixKey(key):
     def fixByte(b):
         return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
-    return     "".join([chr(fixByte(ord(a))) for a in key])
+    return      "".join([chr(fixByte(ord(a))) for a in key])
 
 def deXOR(text, sp, table):
     r=''
@@ -212,7 +212,7 @@ class EreaderProcessor(object):
             for i in xrange(len(data)):
                 j = (j + shuf) % len(data)
                 r[j] = data[i]
-            assert     len("".join(r)) == len(data)
+            assert      len("".join(r)) == len(data)
             return "".join(r)
         r = unshuff(input[0:-8], cookie_shuf)
 
@@ -314,7 +314,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(chaps, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             cv += '%d|%s\n' % (offset, name) 
+    #             cv += '%d|%s\n' % (offset, name)
     #     return cv
 
     # def getLinkNamePMLOffsetData(self):
@@ -326,7 +326,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(links, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             lv += '%d|%s\n' % (offset, name) 
+    #             lv += '%d|%s\n' % (offset, name)
     #     return lv
 
     # def getExpandedTextSizesData(self):
@@ -354,7 +354,7 @@ class EreaderProcessor(object):
         for i in xrange(self.num_text_pages):
             logging.debug('get page %d', i)
             r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
-             
+
         # now handle footnotes pages
         if self.num_footnote_pages > 0:
             r += '\n'
@@ -399,12 +399,12 @@ class EreaderProcessor(object):
         return r
 
 def cleanPML(pml):
-       # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
-       pml2 = pml
-       for k in xrange(128,256):
-               badChar = chr(k)
-               pml2 = pml2.replace(badChar, '\\a%03d' % k)
-       return pml2
+        # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
+    pml2 = pml
+    for k in xrange(128,256):
+        badChar = chr(k)
+        pml2 = pml2.replace(badChar, '\\a%03d' % k)
+    return pml2
 
 def convertEreaderToPml(infile, name, cc, outdir):
     if not os.path.exists(outdir):
@@ -435,7 +435,7 @@ def convertEreaderToPml(infile, name, cc, outdir):
     #     file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
 
 
-                
+
 def decryptBook(infile, outdir, name, cc, make_pmlz):
     if make_pmlz :
         # ignore specified outdir, use tempdir instead
@@ -468,7 +468,7 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
             shutil.rmtree(outdir, True)
             print 'output is %s' % zipname
         else :
-            print 'output in %s' % outdir 
+            print 'output in %s' % outdir
         print "done"
     except ValueError, e:
         print "Error: %s" % e
@@ -505,7 +505,7 @@ def main(argv=None):
             return 0
         elif o == "--make-pmlz":
             make_pmlz = True
-    
+
     print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
 
     if len(args)!=3 and len(args)!=4:
@@ -524,4 +524,3 @@ def main(argv=None):
 if __name__ == "__main__":
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 3b32fc0a945bbae23fac4dc0146ad4cad9685752..e5647f4bc3842abb1546088d41b2c899fa88c36a 100644 (file)
@@ -68,7 +68,7 @@ class DocParser(object):
         ys = []
         gdefs = []
 
-        # get path defintions, positions, dimensions for each glyph 
+        # 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
@@ -79,7 +79,7 @@ class DocParser(object):
             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])
@@ -124,12 +124,12 @@ class DocParser(object):
             item = self.docList[pos]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            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
@@ -142,10 +142,10 @@ class DocParser(object):
             item = self.docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -182,13 +182,13 @@ class DocParser(object):
         # 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 
+        # 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(' ','-')
@@ -211,7 +211,7 @@ class DocParser(object):
         return nclass
 
 
-    # develop a sorted description of the starting positions of 
+    # develop a sorted description of the starting positions of
     # groups and regions on the page, as well as the page type
     def PageDescription(self):
 
@@ -267,7 +267,7 @@ class DocParser(object):
         result = []
 
         # paragraph
-        (pos, pclass) = self.findinDoc('paragraph.class',start,end) 
+        (pos, pclass) = self.findinDoc('paragraph.class',start,end)
 
         pclass = self.getClass(pclass)
 
@@ -281,17 +281,22 @@ class DocParser(object):
         if (sfirst != None) and (slast != None) :
             first = int(sfirst)
             last = int(slast)
-            
+
             makeImage = (regtype == 'vertical') or (regtype == 'table')
-            makeImage = makeImage or (extraglyphs != None) 
+            makeImage = makeImage or (extraglyphs != None)
             if self.fixedimage:
                 makeImage = makeImage or (regtype == 'fixed')
 
-            if (pclass != None): 
+            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):
@@ -332,10 +337,10 @@ class DocParser(object):
             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, 
+        # 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 = ''
@@ -344,7 +349,7 @@ class DocParser(object):
         if end == -1 :
             end = self.docSize
 
-        # seems some xml has last* coming before first* so we have to 
+        # seems some xml has last* coming before first* so we have to
         # handle any order
         sp_first = -1
         sp_last = -1
@@ -382,10 +387,10 @@ class DocParser(object):
                 ws_last = int(argres)
 
             elif name.endswith('word.class'):
-               (cname, space) = argres.split('-',1)
-               if space == '' : space = '0'
-               if (cname == 'spaceafter') and (int(space) > 0) :
-                   word_class = 'sa'
+                (cname, space) = argres.split('-',1)
+                if space == '' : space = '0'
+                if (cname == 'spaceafter') and (int(space) > 0) :
+                    word_class = 'sa'
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
@@ -416,11 +421,11 @@ class DocParser(object):
                     result.append(('ocr', wordnum))
                 ws_first = -1
                 ws_last = -1
-                              
+
             line += 1
 
         return pclass, result
-                            
+
 
     def buildParagraph(self, pclass, pdesc, type, regtype) :
         parares = ''
@@ -433,7 +438,7 @@ class DocParser(object):
         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 + '>'
 
@@ -462,7 +467,7 @@ class DocParser(object):
                         if linktype == 'external' :
                             linkhref = self.link_href[link-1]
                             linkhtml = '<a href="%s">' % linkhref
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkhtml = '<a href="#page%04d">' % ptarget
@@ -509,7 +514,7 @@ class DocParser(object):
 
             elif wtype == 'svg' :
                 sep = ''
-                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num 
+                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
                 parares += sep
 
         if len(sep) > 0 : parares = parares[0:-1]
@@ -551,7 +556,7 @@ class DocParser(object):
                             title = ''
                             alt_title = ''
                             linkpage = ''
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkpage = '%04d' % ptarget
@@ -584,14 +589,14 @@ class DocParser(object):
 
 
 
-    
+
     # walk the document tree collecting the information needed
     # to build an html page using the ocrText
 
     def process(self):
 
-        htmlpage = ''
         tocinfo = ''
+        hlst = []
 
         # get the ocr text
         (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
@@ -602,8 +607,8 @@ class DocParser(object):
 
         # 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) 
-        
+        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)
@@ -631,25 +636,25 @@ class DocParser(object):
 
         # get a descriptions of the starting points of the regions
         # and groups on the page
-        (pagetype, pageDesc) = self.PageDescription() 
+        (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:
-                htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' 
-                htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n'
+                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
@@ -658,12 +663,12 @@ class DocParser(object):
                 if grptype != None:
                     if grptype == 'graphic':
                         gcstr = ' class="' + grptype + '"'
-                        htmlpage += '<div' + gcstr + '>'
+                        hlst.append('<div' + gcstr + '>')
                         inGroup = True
-                
+
             elif (etype == 'grpend'):
                 if inGroup:
-                    htmlpage += '</div>\n'
+                    hlst.append('</div>\n')
                     inGroup = False
 
             else:
@@ -673,25 +678,25 @@ class DocParser(object):
                     (pos, simgsrc) = self.findinDoc('img.src',start,end)
                     if simgsrc:
                         if inGroup:
-                            htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc)
+                            hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
                         else:
-                            htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)
-            
+                            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:
-                        htmlpage += '<div style="page-break-after: always;">&nbsp;</div>\n'
+                        hlst.append('<div style="page-break-after: always;">&nbsp;</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'
-                        htmlpage += '<' + tag + ' class="' + pclass + '">'
+                        hlst.append('<' + tag + ' class="' + pclass + '">')
                     else:
-                        htmlpage += '<' + tag + '>'
-                    htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                    htmlpage += '</' + tag + '>'
+                        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'
@@ -705,11 +710,11 @@ class DocParser(object):
                         if pclass[3:6] == 'h1-' : tag = 'h4'
                         if pclass[3:6] == 'h2-' : tag = 'h5'
                         if pclass[3:6] == 'h3-' : tag = 'h6'
-                        htmlpage += '<' + tag + ' class="' + pclass + '">'
-                        htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                        htmlpage += '</' + tag + '>'
+                        hlst.append('<' + tag + ' class="' + pclass + '">')
+                        hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+                        hlst.append('</' + tag + '>')
                     else :
-                        htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                        hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
                 elif (regtype == 'tocentry') :
                     ptype = 'full'
@@ -718,7 +723,7 @@ class DocParser(object):
                         first_para_continued = False
                     (pclass, pdesc) = self.getParaDescription(start,end, regtype)
                     tocinfo += self.buildTOCEntry(pdesc)
-                    htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                    hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
                 elif (regtype == 'vertical') or (regtype == 'table') :
                     ptype = 'full'
@@ -728,13 +733,13 @@ class DocParser(object):
                         ptype = 'end'
                         first_para_continued = False
                     (pclass, pdesc) = self.getParaDescription(start, end, regtype)
-                    htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                    hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
 
                 elif (regtype == 'synth_fcvr.center'):
                     (pos, simgsrc) = self.findinDoc('img.src',start,end)
                     if simgsrc:
-                        htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)
+                        hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
 
                 else :
                     print '          Making region type', regtype,
@@ -760,18 +765,19 @@ class DocParser(object):
                             if pclass[3:6] == 'h1-' : tag = 'h4'
                             if pclass[3:6] == 'h2-' : tag = 'h5'
                             if pclass[3:6] == 'h3-' : tag = 'h6'
-                            htmlpage += '<' + tag + ' class="' + pclass + '">'
-                            htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                            htmlpage += '</' + tag + '>'
+                            hlst.append('<' + tag + ' class="' + pclass + '">')
+                            hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+                            hlst.append('</' + tag + '>')
                         else :
-                            htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                            hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
                     else :
                         print ' a "graphic" region'
                         (pos, simgsrc) = self.findinDoc('img.src',start,end)
                         if simgsrc:
-                            htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(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]
index 49cf6f5c81c5ab3bebed310c69dd601b69fcb62c..4dfd6c7bbfae633633100610805ddbb13334fd46 100644 (file)
@@ -15,7 +15,7 @@ class PParser(object):
         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')
@@ -26,7 +26,7 @@ class PParser(object):
         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:
@@ -181,70 +181,69 @@ class PParser(object):
 
 
 def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
-    ml = ''
+    mlst = []
     pp = PParser(gdict, flat_xml, meta_array)
-    ml += '<?xml version="1.0" standalone="no"?>\n'
+    mlst.append('<?xml version="1.0" standalone="no"?>\n')
     if (raw):
-        ml += '<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
-        ml += '<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)
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
+        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:
-        ml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-        ml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n'
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
-        ml += '<script><![CDATA[\n'
-        ml += 'function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n'
-        ml += 'var dpi=%d;\n' % scaledpi
+        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) :
-            ml += 'var prevpage="page%04d.xhtml";\n' % (previd)
+            mlst.append('var prevpage="page%04d.xhtml";\n' % (previd))
         if (nextid) :
-            ml += 'var nextpage="page%04d.xhtml";\n' % (nextid)
-        ml += 'var pw=%d;var ph=%d;' % (pp.pw, pp.ph)
-        ml += 'function zoomin(){dpi=dpi*(0.8);setsize();}\n'
-        ml += 'function zoomout(){dpi=dpi*1.25;setsize();}\n'
-        ml += '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'
-        ml += 'function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'var gt=gd();if(gt>0){dpi=gt;}\n'
-        ml += 'window.onload=setsize;\n'
-        ml += ']]></script>\n'
-        ml += '</head>\n'
-        ml += '<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n'
-        ml += '<div style="white-space:nowrap;">\n'
+            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:
-            ml += '<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'
+            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:
-            ml += '<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'
-        
-        ml += '<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): 
-        ml += '<defs>\n'
+            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)):
-            ml += gdefs[j]
-        ml += '</defs>\n'
+            mlst.append(gdefs[j])
+        mlst.append('</defs>\n')
     img = pp.getImages()
     if (img != None):
         for j in xrange(0,len(img)):
-            ml += img[j]
-    if (pp.gid != None): 
+            mlst.append(img[j])
+    if (pp.gid != None):
         for j in xrange(0,len(pp.gid)):
-            ml += '<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j])
+            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)
-        ml += '<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n'
+        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) :
-        ml += '</svg>'
+        mlst.append('</svg>')
     else :
-        ml += '</svg></a>\n'
+        mlst.append('</svg></a>\n')
         if nextid == None:
-            ml += '<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'
+            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 :
-            ml += '<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'
-        ml += '</div>\n'
-        ml += '<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n'
-        ml += '</body>\n'
-        ml += '</html>\n'
-    return ml
-
+            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)
index 9ad87ea1800240af4a7f29881573d8ad1d0b65d5..a412a7b46460a6ef326d997564d6408ea378286f 100644 (file)
@@ -39,6 +39,8 @@ else :
     import flatxml2svg
     import stylexml2css
 
+# global switch
+buildXML = False
 
 # Get a 7 bit encoded number from a file
 def readEncodedNumber(file):
@@ -46,27 +48,27 @@ def readEncodedNumber(file):
     c = file.read(1)
     if (len(c) == 0):
         return None
-    data = ord(c)    
+    data = ord(c)
     if data == 0xFF:
-       flag = True
-       c = file.read(1)
-       if (len(c) == 0):
-           return None
-       data = ord(c)       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
 
-# Get a length prefixed string from the file 
+# Get a length prefixed string from the file
 def lengthPrefixString(data):
     return encodeNumber(len(data))+data
 
@@ -77,7 +79,7 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    return unpack(str(stringLength)+"s",sv)[0]
 
 def getMetaArray(metaFile):
     # parse the meta file
@@ -141,10 +143,10 @@ class PageDimParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=')
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -298,9 +300,10 @@ def generateBook(bookDir, raw, fixedimage):
     if not os.path.exists(svgDir) :
         os.makedirs(svgDir)
 
-    xmlDir = os.path.join(bookDir,'xml')
-    if not os.path.exists(xmlDir) :
-        os.makedirs(xmlDir)
+    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) :
@@ -336,7 +339,7 @@ def generateBook(bookDir, raw, fixedimage):
     print 'Processing Meta Data and creating OPF'
     meta_array = getMetaArray(metaFile)
 
-    # replace special chars in title and authors like & < > 
+    # replace special chars in title and authors like & < >
     title = meta_array.get('Title','No Title Provided')
     title = title.replace('&','&amp;')
     title = title.replace('<','&lt;')
@@ -348,11 +351,14 @@ def generateBook(bookDir, raw, fixedimage):
     authors = authors.replace('>','&gt;')
     meta_array['Authors'] = authors
 
-    xname = os.path.join(xmlDir, 'metadata.xml')
-    metastr = ''
-    for key in meta_array:
-        metastr += '<meta name="' + key + '" content="' + meta_array[key] + '" />\n'
-    file(xname, 'wb').write(metastr)
+    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
@@ -404,8 +410,9 @@ def generateBook(bookDir, raw, fixedimage):
     # now get the css info
     cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
     file(xname, 'wb').write(cssstr)
-    xname = os.path.join(xmlDir, 'other0000.xml')
-    file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
+    if buildXML:
+        xname = os.path.join(xmlDir, 'other0000.xml')
+        file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
 
     print 'Processing Glyphs'
     gd = GlyphDict()
@@ -425,8 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
         fname = os.path.join(glyphsDir,filename)
         flat_xml = convert2xml.fromData(dict, fname)
 
-        xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
-        file(xname, 'wb').write(convert2xml.getXML(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):
@@ -441,29 +449,29 @@ def generateBook(bookDir, raw, fixedimage):
     glyfile.close()
     print " "
 
-    # build up tocentries while processing html
-    tocentries = ''
 
     # start up the html
+    # also build up tocentries while processing html
     htmlFileName = "book.html"
-    htmlstr = '<?xml version="1.0" encoding="utf-8"?>\n'
-    htmlstr += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n'
-    htmlstr += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'
-    htmlstr += '<head>\n'
-    htmlstr += '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n'
-    htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' 
-    htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        hlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
-    htmlstr += '</head>\n<body>\n'
+        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.  
+    # readability when rendering to the screen.
     scaledpi = 1440.0
 
     filenames = os.listdir(pageDir)
@@ -471,6 +479,7 @@ def generateBook(bookDir, raw, fixedimage):
     numfiles = len(filenames)
 
     xmllst = []
+    elst = []
 
     for filename in filenames:
         # print '     ', filename
@@ -481,45 +490,51 @@ def generateBook(bookDir, raw, fixedimage):
         # keep flat_xml for later svg processing
         xmllst.append(flat_xml)
 
-        xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
-        file(xname, 'wb').write(convert2xml.getXML(dict, fname))
+        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)
-        tocentries += tocinfo 
-        htmlstr += pagehtml
+        elst.append(tocinfo)
+        hlst.append(pagehtml)
 
     # finish up the html string and output it
-    htmlstr += '</body>\n</html>\n'
+    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
-    tochtml = '<?xml version="1.0" encoding="utf-8"?>\n'
-    tochtml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-    tochtml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >'
-    tochtml += '<head>\n'
-    tochtml += '<title>' + meta_array['Title'] + '</title>\n'
-    tochtml += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    tochtml += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        tochtml += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        tlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        tochtml += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    tochtml += '</head>\n'
-    tochtml += '<body>\n'
+        tlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
+    tlst.append('</head>\n')
+    tlst.append('<body>\n')
 
-    tochtml += '<h2>Table of Contents</h2>\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
 
-    tochtml += '<h3><a href="' + startname + '">Start of Book</a></h3>\n'
+    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:
@@ -530,30 +545,32 @@ def generateBook(bookDir, raw, fixedimage):
             fname = 'page%04d.svg' % id
         else:
             fname = 'page%04d.xhtml' % id
-        tochtml += '<h3><a href="'+ fname + '">' + title + '</a></h3>\n'
-    tochtml += '</body>\n'
-    tochtml += '</html>\n'
+        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
-    svgindex = '<?xml version="1.0" encoding="utf-8"?>\n'
-    svgindex += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-    svgindex += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >'
-    svgindex += '<head>\n'
-    svgindex += '<title>' + meta_array['Title'] + '</title>\n'
-    svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        slst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    svgindex += '</head>\n'
-    svgindex += '<body>\n'
+        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"
-    svgindex += '<h2>List of Pages</h2>\n'
-    svgindex += '<div>\n'
+    slst.append('<h2>List of Pages</h2>\n')
+    slst.append('<div>\n')
     idlst = sorted(pageIDMap.keys())
     numids = len(idlst)
     cnt = len(idlst)
@@ -566,49 +583,54 @@ def generateBook(bookDir, raw, fixedimage):
             nextid = None
         print '.',
         pagelst = pageIDMap[pageid]
-        flat_svg = ''
+        flst = []
         for page in pagelst:
-            flat_svg += xmllst[page]
+            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')
-            svgindex += '<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid)
+            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')
-            svgindex += '<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid)
+            slst.append('<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid))
         previd = pageid
         pfile.write(svgxml)
         pfile.close()
         counter += 1
-    svgindex += '</div>\n'
-    svgindex += '<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n'
-    svgindex += '</body>\n</html>\n'
+    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')
-    opfstr = '<?xml version="1.0" encoding="utf-8"?>\n'
-    opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
+    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
-    opfstr += '   <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
+    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:
-        opfstr += '      <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
+        olst.append('      <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
     if 'ASIN' in meta_array:
-        opfstr += '      <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
+        olst.append('      <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
     if 'oASIN' in meta_array:
-        opfstr += '      <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
-    opfstr += '      <dc:title>' + meta_array['Title'] + '</dc:title>\n'
-    opfstr += '      <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
-    opfstr += '      <dc:language>en</dc:language>\n'
-    opfstr += '      <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n'
+        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:
-        opfstr += '      <meta name="cover" content="bookcover"/>\n'
-    opfstr += '   </metadata>\n'
-    opfstr += '<manifest>\n'
-    opfstr += '   <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n'
-    opfstr += '   <item id="stylesheet" href="style.css" media-type="text/css"/>\n'
+        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)
@@ -618,17 +640,19 @@ def generateBook(bookDir, raw, fixedimage):
             imgext = 'jpeg'
         if imgext == '.svg':
             imgext = 'svg+xml'
-        opfstr += '   <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n'
+        olst.append('   <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n')
     if isCover:
-        opfstr += '   <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n'
-    opfstr += '</manifest>\n'
+        olst.append('   <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n')
+    olst.append('</manifest>\n')
     # adding spine
-    opfstr += '<spine>\n   <itemref idref="book" />\n</spine>\n'
+    olst.append('<spine>\n   <itemref idref="book" />\n</spine>\n')
     if isCover:
-        opfstr += '   <guide>\n'
-        opfstr += '      <reference href="cover.jpg" type="cover" title="Cover"/>\n'
-        opfstr += '   </guide>\n'
-    opfstr += '</package>\n'
+        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'
@@ -649,7 +673,6 @@ def usage():
 
 def main(argv):
     bookDir = ''
-
     if len(argv) == 0:
         argv = sys.argv
 
@@ -663,7 +686,7 @@ def main(argv):
 
     if len(opts) == 0 and len(args) == 0 :
         usage()
-        return 1 
+        return 1
 
     raw = 0
     fixedimage = True
index a7c48c9620d0f3442b0903e76e36c36a305f1fc9..917aa4aaa16210a7532e733a4fd667633ad8a4d9 100644 (file)
@@ -14,7 +14,7 @@ from __future__ import with_statement
 #   2 - Added OS X support by using OpenSSL when available
 #   3 - screen out improper key lengths to prevent segfaults on Linux
 #   3.1 - Allow Windows versions of libcrypto to be found
-#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml 
+#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
 #   3.3 - On Windows try PyCrypto first and OpenSSL next
 #   3.4 - Modify interace to allow use with import
 
@@ -50,7 +50,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -58,13 +58,13 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
@@ -73,7 +73,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class AES(object):
         def __init__(self, userkey):
             self._blocksize = len(userkey)
@@ -84,7 +84,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -122,7 +122,7 @@ def _load_crypto():
 
 AES = _load_crypto()
 
+
 
 """
 Decrypt Barnes & Noble ADEPT encrypted EPUB books.
index cdedc48b698d5f6fcd3fb07478e7479580d410be..e7a78ea19a7d60c2b2679d3b548d5162ca193c12 100644 (file)
@@ -53,7 +53,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -61,28 +61,28 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
                             [c_char_p, c_int, AES_KEY_p])
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
     class AES(object):
-         def __init__(self, userkey, iv):
+        def __init__(self, userkey, iv):
             self._blocksize = len(userkey)
             self._iv = iv
             key = self._key = AES_KEY()
             rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES Encrypt key')
-    
-         def encrypt(self, data):
+
+        def encrypt(self, data):
             out = create_string_buffer(len(data))
             rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
             if rv == 0:
index 48a75f996cdd84139fbcab9e9506cf8c68d1eef7..018736acd74626a3ac075f1659e565b28cfbb2aa 100644 (file)
@@ -67,25 +67,25 @@ def _load_crypto_libcrypto():
 
     RSA_NO_PADDING = 3
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
                           [RSA_p, c_char_pp, c_long])
     RSA_size = F(c_int, 'RSA_size', [RSA_p])
@@ -97,7 +97,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class RSA(object):
         def __init__(self, der):
             buf = create_string_buffer(der)
@@ -105,7 +105,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -114,7 +114,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -130,7 +130,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise ADEPTError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -148,13 +148,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -164,22 +164,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -189,19 +189,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -209,13 +209,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -224,7 +224,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -252,7 +252,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
index 8eab14fb0200772d2f27cf6fc65239a99a6f7e92..da61e77eb4ab05593ecd0a6f4a5a1700edf71a09 100644 (file)
@@ -76,13 +76,13 @@ if sys.platform.startswith('win'):
             _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                         ('rounds', c_int)]
         AES_KEY_p = POINTER(AES_KEY)
-    
+
         def F(restype, name, argtypes):
             func = getattr(libcrypto, name)
             func.restype = restype
             func.argtypes = argtypes
             return func
-    
+
         AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
                                 [c_char_p, c_int, AES_KEY_p])
         AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -427,8 +427,8 @@ def extractKeyfile(keypath):
         print "Key generation Error: " + str(e)
         return 1
     except Exception, e:
-       print "General Error: " + str(e)
-       return 1
+        print "General Error: " + str(e)
+        return 1
     if not success:
         return 1
     return 0
index c9a419be6bde6744851ce001a7879c75404b88b0..1437708372d3020386223b0069267af88bb1737d 100644 (file)
@@ -4,7 +4,7 @@
 from __future__ import with_statement
 
 # To run this program install Python 2.6 from http://www.python.org/download/
-# and OpenSSL (already installed on Mac OS X and Linux) OR 
+# and OpenSSL (already installed on Mac OS X and Linux) OR
 # PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
 # (make sure to install the version for Python 2.6).  Save this script file as
 # ineptpdf.pyw and double-click on it to run it.
@@ -83,7 +83,7 @@ def _load_crypto_libcrypto():
     AES_MAXNR = 14
 
     RSA_NO_PADDING = 3
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -98,13 +98,13 @@ def _load_crypto_libcrypto():
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
@@ -125,7 +125,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -134,7 +134,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[1:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -196,13 +196,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -212,22 +212,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -237,19 +237,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -257,13 +257,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -272,7 +272,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -293,6 +293,7 @@ def _load_crypto_pycrypto():
             return self._arc4.decrypt(data)
 
     class AES(object):
+        MODE_CBC = _AES.MODE_CBC
         @classmethod
         def new(cls, userkey, mode, iv):
             self = AES()
@@ -315,7 +316,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
@@ -410,7 +411,7 @@ class PSLiteral(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         name = []
         for char in self.name:
@@ -429,22 +430,22 @@ class PSKeyword(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         return self.name
 
 # PSSymbolTable
 class PSSymbolTable(object):
-    
+
     '''
     Symbol table that stores PSLiteral or PSKeyword.
     '''
-    
+
     def __init__(self, classe):
         self.dic = {}
         self.classe = classe
         return
-    
+
     def intern(self, name):
         if name in self.dic:
             lit = self.dic[name]
@@ -514,11 +515,11 @@ class PSBaseParser(object):
 
     def flush(self):
         return
-    
+
     def close(self):
         self.flush()
         return
-    
+
     def tell(self):
         return self.bufpos+self.charpos
 
@@ -554,7 +555,7 @@ class PSBaseParser(object):
             raise PSEOF('Unexpected EOF')
         self.charpos = 0
         return
-    
+
     def parse_main(self, s, i):
         m = NONSPC.search(s, i)
         if not m:
@@ -589,11 +590,11 @@ class PSBaseParser(object):
             return (self.parse_wclose, j+1)
         self.add_token(KWD(c))
         return (self.parse_main, j+1)
-                            
+
     def add_token(self, obj):
         self.tokens.append((self.tokenstart, obj))
         return
-    
+
     def parse_comment(self, s, i):
         m = EOL.search(s, i)
         if not m:
@@ -604,7 +605,7 @@ class PSBaseParser(object):
         # We ignore comments.
         #self.tokens.append(self.token)
         return (self.parse_main, j)
-    
+
     def parse_literal(self, s, i):
         m = END_LITERAL.search(s, i)
         if not m:
@@ -618,7 +619,7 @@ class PSBaseParser(object):
             return (self.parse_literal_hex, j+1)
         self.add_token(LIT(self.token))
         return (self.parse_main, j)
-    
+
     def parse_literal_hex(self, s, i):
         c = s[i]
         if HEX.match(c) and len(self.hex) < 2:
@@ -653,7 +654,7 @@ class PSBaseParser(object):
         self.token += s[i:j]
         self.add_token(float(self.token))
         return (self.parse_main, j)
-    
+
     def parse_keyword(self, s, i):
         m = END_KEYWORD.search(s, i)
         if not m:
@@ -801,7 +802,7 @@ class PSStackParser(PSBaseParser):
         PSBaseParser.__init__(self, fp)
         self.reset()
         return
-    
+
     def reset(self):
         self.context = []
         self.curtype = None
@@ -842,10 +843,10 @@ class PSStackParser(PSBaseParser):
 
     def do_keyword(self, pos, token):
         return
-    
+
     def nextobject(self, direct=False):
         '''
-        Yields a list of objects: keywords, literals, strings, 
+        Yields a list of objects: keywords, literals, strings,
         numbers, arrays and dictionaries. Arrays and dictionaries
         are represented as Python sequence and dictionaries.
         '''
@@ -914,7 +915,7 @@ class PDFNotImplementedError(PSException): pass
 ##  PDFObjRef
 ##
 class PDFObjRef(PDFObject):
-    
+
     def __init__(self, doc, objid, genno):
         if objid == 0:
             if STRICT:
@@ -1029,25 +1030,25 @@ def stream_value(x):
 
 # ascii85decode(data)
 def ascii85decode(data):
-  n = b = 0
-  out = ''
-  for c in data:
-    if '!' <= c and c <= 'u':
-      n += 1
-      b = b*85+(ord(c)-33)
-      if n == 5:
-        out += struct.pack('>L',b)
-        n = b = 0
-    elif c == 'z':
-      assert n == 0
-      out += '\0\0\0\0'
-    elif c == '~':
-      if n:
-        for _ in range(5-n):
-          b = b*85+84
-        out += struct.pack('>L',b)[:n-1]
-      break
-  return out
+    n = b = 0
+    out = ''
+    for c in data:
+        if '!' <= c and c <= 'u':
+            n += 1
+            b = b*85+(ord(c)-33)
+            if n == 5:
+                out += struct.pack('>L',b)
+                n = b = 0
+        elif c == 'z':
+            assert n == 0
+            out += '\0\0\0\0'
+        elif c == '~':
+            if n:
+                for _ in range(5-n):
+                    b = b*85+84
+                out += struct.pack('>L',b)[:n-1]
+            break
+    return out
 
 
 ##  PDFStream type
@@ -1064,7 +1065,7 @@ class PDFStream(PDFObject):
         else:
             if eol in ('\r', '\n', '\r\n'):
                 rawdata = rawdata[:length]
-                
+
         self.dic = dic
         self.rawdata = rawdata
         self.decipher = decipher
@@ -1078,7 +1079,7 @@ class PDFStream(PDFObject):
         self.objid = objid
         self.genno = genno
         return
-    
+
     def __repr__(self):
         if self.rawdata:
             return '<PDFStream(%r): raw=%d, %r>' % \
@@ -1162,7 +1163,7 @@ class PDFStream(PDFObject):
             data = self.decipher(self.objid, self.genno, data)
         return data
 
-        
+
 ##  PDF Exceptions
 ##
 class PDFSyntaxError(PDFException): pass
@@ -1227,7 +1228,7 @@ class PDFXRef(object):
                 self.offsets[objid] = (int(genno), int(pos))
         self.load_trailer(parser)
         return
-    
+
     KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
     def load_trailer(self, parser):
         try:
@@ -1268,7 +1269,7 @@ class PDFXRefStream(object):
         for first, size in self.index:
             for objid in xrange(first, first + size):
                 yield objid
-    
+
     def load(self, parser, debug=0):
         (_,objid) = parser.nexttoken() # ignored
         (_,genno) = parser.nexttoken() # ignored
@@ -1286,7 +1287,7 @@ class PDFXRefStream(object):
         self.entlen = self.fl1+self.fl2+self.fl3
         self.trailer = stream.dic
         return
-    
+
     def getpos(self, objid):
         offset = 0
         for first, size in self.index:
@@ -1337,7 +1338,7 @@ class PDFDocument(object):
         self.parser = parser
         # The document is set to be temporarily ready during collecting
         # all the basic information about the document, e.g.
-        # the header, the encryption information, and the access rights 
+        # the header, the encryption information, and the access rights
         # for the document.
         self.ready = True
         # Retrieve the information of each header that was appended
@@ -1413,7 +1414,7 @@ class PDFDocument(object):
         length = int_value(param.get('Length', 0)) / 8
         edcdata = str_value(param.get('EDCData')).decode('base64')
         pdrllic = str_value(param.get('PDRLLic')).decode('base64')
-        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')          
+        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
         edclist = []
         for pair in edcdata.split('\n'):
             edclist.append(pair)
@@ -1433,9 +1434,9 @@ class PDFDocument(object):
             raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
         else:
             cutter = -1 * ord(pdrlpol[-1])
-            pdrlpol = pdrlpol[:cutter]            
+            pdrlpol = pdrlpol[:cutter]
         return plaintext[:16]
-    
+
     PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
                        '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
     # experimental aes pw support
@@ -1455,14 +1456,14 @@ class PDFDocument(object):
             EncMetadata = str_value(param['EncryptMetadata'])
         except:
             EncMetadata = 'True'
-        self.is_printable = bool(P & 4)        
+        self.is_printable = bool(P & 4)
         self.is_modifiable = bool(P & 8)
         self.is_extractable = bool(P & 16)
         self.is_annotationable = bool(P & 32)
         self.is_formsenabled = bool(P & 256)
         self.is_textextractable = bool(P & 512)
         self.is_assemblable = bool(P & 1024)
-        self.is_formprintable = bool(P & 2048) 
+        self.is_formprintable = bool(P & 2048)
         # Algorithm 3.2
         password = (password+self.PASSWORD_PADDING)[:32] # 1
         hash = hashlib.md5(password) # 2
@@ -1537,10 +1538,10 @@ class PDFDocument(object):
         if length > 0:
             if len(bookkey) == length:
                 if ebx_V == 3:
-                    V = 3        
+                    V = 3
                 else:
                     V = 2
-            elif len(bookkey) == length + 1:  
+            elif len(bookkey) == length + 1:
                 V = ord(bookkey[0])
                 bookkey = bookkey[1:]
             else:
@@ -1554,7 +1555,7 @@ class PDFDocument(object):
             print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
             print "bookkey[0] is %d" % ord(bookkey[0])
             if ebx_V == 3:
-                V = 3        
+                V = 3
             else:
                 V = 2
         self.decrypt_key = bookkey
@@ -1571,7 +1572,7 @@ class PDFDocument(object):
         hash = hashlib.md5(key)
         key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
         return key
-    
+
     def genkey_v3(self, objid, genno):
         objid = struct.pack('<L', objid ^ 0x3569ac)
         genno = struct.pack('<L', genno ^ 0xca96)
@@ -1611,14 +1612,14 @@ class PDFDocument(object):
         #print cutter
         plaintext = plaintext[:cutter]
         return plaintext
-    
+
     def decrypt_rc4(self, objid, genno, data):
         key = self.genkey(objid, genno)
         return ARC4.new(key).decrypt(data)
 
 
     KEYWORD_OBJ = PSKeywordTable.intern('obj')
-    
+
     def getobj(self, objid):
         if not self.ready:
             raise PDFException('PDFDocument not initialized')
@@ -1688,7 +1689,7 @@ class PDFDocument(object):
 ##                    if x:
 ##                        objid1 = x[-2]
 ##                        genno = x[-1]
-##                
+##
                 if kwd is not self.KEYWORD_OBJ:
                     raise PDFSyntaxError(
                         'Invalid object spec: offset=%r' % index)
@@ -1700,7 +1701,7 @@ class PDFDocument(object):
             self.objs[objid] = obj
         return obj
 
-                
+
 class PDFObjStmRef(object):
     maxindex = 0
     def __init__(self, objid, stmid, index):
@@ -1710,7 +1711,7 @@ class PDFObjStmRef(object):
         if index > PDFObjStmRef.maxindex:
             PDFObjStmRef.maxindex = index
 
-    
+
 ##  PDFParser
 ##
 class PDFParser(PSStackParser):
@@ -1736,7 +1737,7 @@ class PDFParser(PSStackParser):
         if token is self.KEYWORD_ENDOBJ:
             self.add_results(*self.pop(4))
             return
-        
+
         if token is self.KEYWORD_R:
             # reference to indirect object
             try:
@@ -1747,7 +1748,7 @@ class PDFParser(PSStackParser):
             except PSSyntaxError:
                 pass
             return
-            
+
         if token is self.KEYWORD_STREAM:
             # stream object
             ((_,dic),) = self.pop(1)
@@ -1787,7 +1788,7 @@ class PDFParser(PSStackParser):
             obj = PDFStream(dic, data, self.doc.decipher)
             self.push((pos, obj))
             return
-        
+
         # others
         self.push((pos, token))
         return
@@ -1823,7 +1824,7 @@ class PDFParser(PSStackParser):
             xref.load(self)
         else:
             if token is not self.KEYWORD_XREF:
-                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' % 
+                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
                                      (pos, token))
             self.nextline()
             xref = PDFXRef()
@@ -1838,7 +1839,7 @@ class PDFParser(PSStackParser):
             pos = int_value(trailer['Prev'])
             self.read_xref_from(pos, xrefs)
         return
-        
+
     # read xref tables and trailers
     def read_xref(self):
         xrefs = []
@@ -1957,7 +1958,7 @@ class PDFSerializer(object):
                     self.write("%010d 00000 n \n" % xrefs[objid][0])
                 else:
                     self.write("%010d %05d f \n" % (0, 65535))
-            
+
             self.write('trailer\n')
             self.serialize_object(trailer)
             self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
@@ -1977,7 +1978,7 @@ class PDFSerializer(object):
             while maxindex >= power:
                 fl3 += 1
                 power *= 256
-                    
+
             index = []
             first = None
             prev = None
@@ -2004,14 +2005,14 @@ class PDFSerializer(object):
                     # we force all generation numbers to be 0
                     # f3 = objref[1]
                     f3 = 0
-                
+
                 data.append(struct.pack('>B', f1))
                 data.append(struct.pack('>L', f2)[-fl2:])
                 data.append(struct.pack('>L', f3)[-fl3:])
             index.extend((first, prev - first + 1))
             data = zlib.compress(''.join(data))
             dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
-                   'W': [1, fl2, fl3], 'Length': len(data), 
+                   'W': [1, fl2, fl3], 'Length': len(data),
                    'Filter': LITERALS_FLATE_DECODE[0],
                    'Root': trailer['Root'],}
             if 'Info' in trailer:
@@ -2033,9 +2034,9 @@ class PDFSerializer(object):
         string = string.replace(')', r'\)')
          # get rid of ciando id
         regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
-        if regularexp.match(string): return ('http://www.ciando.com') 
+        if regularexp.match(string): return ('http://www.ciando.com')
         return string
-    
+
     def serialize_object(self, obj):
         if isinstance(obj, dict):
             # Correct malformed Mac OS resource forks for Stanza
@@ -2059,21 +2060,21 @@ class PDFSerializer(object):
         elif isinstance(obj, bool):
             if self.last.isalnum():
                 self.write(' ')
-            self.write(str(obj).lower())            
+            self.write(str(obj).lower())
         elif isinstance(obj, (int, long, float)):
             if self.last.isalnum():
                 self.write(' ')
             self.write(str(obj))
         elif isinstance(obj, PDFObjRef):
             if self.last.isalnum():
-                self.write(' ')            
+                self.write(' ')
             self.write('%d %d R' % (obj.objid, 0))
         elif isinstance(obj, PDFStream):
             ### If we don't generate cross ref streams the object streams
             ### are no longer useful, as we have extracted all objects from
             ### them. Therefore leave them out from the output.
             if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
-                    self.write('(deleted)')
+                self.write('(deleted)')
             else:
                 data = obj.get_decdata()
                 self.serialize_object(obj.dic)
@@ -2085,7 +2086,7 @@ class PDFSerializer(object):
             if data[0].isalnum() and self.last.isalnum():
                 self.write(' ')
             self.write(data)
-    
+
     def serialize_indirect(self, objid, obj):
         self.write('%d 0 obj' % (objid,))
         self.serialize_object(obj)
@@ -2097,7 +2098,7 @@ class PDFSerializer(object):
 class DecryptionDialog(Tkinter.Frame):
     def __init__(self, root):
         Tkinter.Frame.__init__(self, root, border=5)
-        ltext='Select file for decryption\n'        
+        ltext='Select file for decryption\n'
         self.status = Tkinter.Label(self, text=ltext)
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
@@ -2123,7 +2124,7 @@ class DecryptionDialog(Tkinter.Frame):
         button.grid(row=2, column=2)
         buttons = Tkinter.Frame(self)
         buttons.pack()
-  
+
 
         botton = Tkinter.Button(
             buttons, text="Decrypt", width=10, command=self.decrypt)
@@ -2132,7 +2133,7 @@ class DecryptionDialog(Tkinter.Frame):
         button = Tkinter.Button(
             buttons, text="Quit", width=10, command=self.quit)
         button.pack(side=Tkconstants.RIGHT)
-         
+
 
     def get_keypath(self):
         keypath = tkFileDialog.askopenfilename(
index d962a029e69fc24286df87ccf19b359b03c337e5..828c6e2ef9da27aa6e1482f57a66337e9c705c3e 100644 (file)
@@ -5,19 +5,19 @@ from __future__ import with_statement
 # engine to remove drm from Kindle for Mac and Kindle for PC books
 # for personal use for archiving and converting your ebooks
 
-# PLEASE DO NOT PIRATE EBOOKS! 
+# PLEASE DO NOT PIRATE EBOOKS!
 
 # We want all authors and publishers, and eBook stores to live
-# long and prosperous lives but at the same time  we just want to 
-# be able to read OUR books on whatever device we want and to keep 
+# long and prosperous lives but at the same time  we just want to
+# be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
 
-#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
-#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
 #    and many many others
 
 
-__version__ = '3.9'
+__version__ = '4.0'
 
 class Unbuffered:
     def __init__(self, stream):
@@ -34,6 +34,8 @@ import string
 import re
 import traceback
 
+buildXML = False
+
 class DrmException(Exception):
     pass
 
@@ -50,7 +52,7 @@ else:
     import mobidedrm
     import topazextract
     import kgenpids
-        
+
 
 # cleanup bytestring filenames
 # borrowed from calibre from calibre/src/calibre/__init__.py
@@ -75,6 +77,8 @@ def cleanup_name(name):
     return one
 
 def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
+    global buildXML
+
     # handle the obvious cases at the beginning
     if not os.path.isfile(infile):
         print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
@@ -100,14 +104,14 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         outfilename = outfilename + "_" + filenametitle
     elif outfilename[:8] != filenametitle[:8]:
         outfilename = outfilename[:8] + "_" + filenametitle
-        
+
     # avoid excessively long file names
     if len(outfilename)>150:
         outfilename = outfilename[:150]
 
     # build pid list
     md1, md2 = mb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
 
     try:
         mb.processBook(pidlst)
@@ -128,9 +132,9 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         else:
             outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
         mb.getMobiFile(outfile)
-        return 0            
+        return 0
 
-    # topaz: 
+    # topaz:
     print "   Creating NoDRM HTMLZ Archive"
     zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
     mb.getHTMLZip(zipname)
@@ -139,9 +143,10 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
     zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
     mb.getSVGZip(zipname)
 
-    print "   Creating XML ZIP Archive"
-    zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
-    mb.getXMLZip(zipname)
+    if buildXML:
+        print "   Creating XML ZIP Archive"
+        zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
+        mb.getXMLZip(zipname)
 
     # remove internal temporary directory of Topaz pieces
     mb.cleanup()
@@ -156,7 +161,7 @@ def usage(progname):
 
 #
 # Main
-#   
+#
 def main(argv=sys.argv):
     progname = os.path.basename(argv[0])
 
@@ -164,9 +169,9 @@ def main(argv=sys.argv):
     kInfoFiles = []
     serials = []
     pids = []
-    
+
     print ('K4MobiDeDrm v%(__version__)s '
-          'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
 
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
@@ -177,7 +182,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         sys.exit(2)
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -195,8 +200,8 @@ def main(argv=sys.argv):
     # try with built in Kindle Info files
     k4 = True
     if sys.platform.startswith('linux'):
-       k4 = False
-       kInfoFiles = None
+        k4 = False
+        kInfoFiles = None
     infile = args[0]
     outdir = args[1]
     return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
@@ -205,4 +210,3 @@ def main(argv=sys.argv):
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 7d5130cbf56a24828c2f542bcbaec1d982e60942..69976541db3d7b0ee4a5f074d34b12c856033134 100644 (file)
@@ -5,7 +5,8 @@ from __future__ import with_statement
 import sys
 import os
 import os.path
-
+import re
+import copy
 import subprocess
 from struct import pack, unpack, unpack_from
 
@@ -24,6 +25,25 @@ def _load_crypto_libcrypto():
         raise DrmException('libcrypto not found')
     libcrypto = CDLL(libcrypto)
 
+    # From OpenSSL's crypto aes header
+    #
+    # AES_ENCRYPT     1
+    # AES_DECRYPT     0
+    # AES_MAXNR 14 (in bytes)
+    # AES_BLOCK_SIZE 16 (in bytes)
+    # 
+    # 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);
+    #
+    # note:  the ivec string, and output buffer are mutable
+    # 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
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
@@ -31,25 +51,31 @@ def _load_crypto_libcrypto():
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
-    PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', 
+    # From OpenSSL's Crypto evp/p5_crpt2.c
+    #
+    # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
+    #                        const unsigned char *salt, int saltlen, int iter,
+    #                        int keylen, unsigned char *out);
+
+    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])
-    
+
     class LibCrypto(object):
         def __init__(self):
             self._blocksize = 0
             self._keyctx = None
-            self.iv = 0
+            self._iv = 0
 
         def set_decrypt_key(self, userkey, iv):
             self._blocksize = len(userkey)
@@ -57,14 +83,17 @@ def _load_crypto_libcrypto():
                 raise DrmException('AES improper key used')
                 return
             keyctx = self._keyctx = AES_KEY()
-            self.iv = iv
+            self._iv = iv
+            self._userkey = userkey
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
             if rv < 0:
                 raise DrmException('Failed to initialize AES key')
 
         def decrypt(self, data):
             out = create_string_buffer(len(data))
-            rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
+            mutable_iv = create_string_buffer(self._iv, len(self._iv))
+            keyctx = self._keyctx
+            rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
             if rv == 0:
                 raise DrmException('AES decryption failed')
             return out.raw
@@ -111,13 +140,17 @@ def SHA256(message):
 
 # Various character maps used to decrypt books. Probably supposed to act as obfuscation
 charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
 
-# For kinf approach of K4PC/K4Mac
+# For kinf approach of K4Mac 1.6.X or later
 # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
 # For Mac they seem to re-use charMap2 here
 charMap5 = charMap2
 
+# new in K4M 1.9.X
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+
+
 def encode(data, map):
     result = ""
     for char in data:
@@ -144,7 +177,7 @@ def decode(data,map):
         result += pack("B",value)
     return result
 
-# For .kinf approach of K4PC and now K4Mac
+# For K4M 1.6.X and later
 # generate table of prime number less than or equal to int n
 def primes(n):
     if n==2: return [2]
@@ -271,7 +304,7 @@ def GetDiskPartitionUUID(diskpart):
     if not foundIt:
         uuidnum = ''
     return uuidnum
-    
+
 def GetMACAddressMunged():
     macnum = os.getenv('MYMACNUM')
     if macnum != None:
@@ -315,33 +348,11 @@ def GetMACAddressMunged():
     return macnum
 
 
-# uses unix env to get username instead of using sysctlbyname 
+# uses unix env to get username instead of using sysctlbyname
 def GetUserName():
     username = os.getenv('USER')
     return username
 
-
-# implements an Pseudo Mac Version of Windows built-in Crypto routine
-# used by Kindle for Mac versions < 1.6.0
-def CryptUnprotectData(encryptedData):
-    sernum = GetVolumeSerialNumber()
-    if sernum == '':
-        sernum = '9999999999'
-    sp = sernum + '!@#' + GetUserName()
-    passwdData = encode(SHA256(sp),charMap1)
-    salt = '16743'
-    iter = 0x3e8
-    keylen = 0x80
-    crp = LibCrypto()
-    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
-    key = key_iv[0:32]
-    iv = key_iv[32:48]
-    crp.set_decrypt_key(key,iv)
-    cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext,charMap1)
-    return cleartext
-
-
 def isNewInstall():
     home = os.getenv('HOME')
     # soccer game fan anyone
@@ -350,7 +361,7 @@ def isNewInstall():
     if os.path.exists(dpath):
         return True
     return False
-    
+
 
 def GetIDString():
     # K4Mac now has an extensive set of ids strings it uses
@@ -359,13 +370,13 @@ def GetIDString():
 
     # BUT Amazon has now become nasty enough to detect when its app
     # is being run under a debugger and actually changes code paths
-    # including which one of these strings is chosen, all to try 
+    # including which one of these strings is chosen, all to try
     # to prevent reverse engineering
 
     # Sad really ... they will only hurt their own sales ...
     # true book lovers really want to keep their books forever
-    # and move them to their devices and DRM prevents that so they 
-    # will just buy from someplace else that they can remove 
+    # and move them to their devices and DRM prevents that so they
+    # will just buy from someplace else that they can remove
     # the DRM from
 
     # Amazon should know by now that true book lover's are not like
@@ -388,27 +399,91 @@ def GetIDString():
     return '9999999999'
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used by Kindle for Mac versions < 1.6.0
+class CryptUnprotectData(object):
+    def __init__(self):
+        sernum = GetVolumeSerialNumber()
+        if sernum == '':
+            sernum = '9999999999'
+        sp = sernum + '!@#' + GetUserName()
+        passwdData = encode(SHA256(sp),charMap1)
+        salt = '16743'
+        self.crp = LibCrypto()
+        iter = 0x3e8
+        keylen = 0x80
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext,charMap1)
+        return cleartext
+
+
 # implements an Pseudo Mac Version of Windows built-in Crypto routine
 # used for Kindle for Mac Versions >= 1.6.0
-def CryptUnprotectDataV2(encryptedData):
-    sp = GetUserName() + ':&%:' + GetIDString()
-    passwdData = encode(SHA256(sp),charMap5)
-    # salt generation as per the code
-    salt = 0x0512981d * 2 * 1 * 1
-    salt = str(salt) + GetUserName()
-    salt = encode(salt,charMap5)
+class CryptUnprotectDataV2(object):
+    def __init__(self):
+        sp = GetUserName() + ':&%:' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap5)
+        # salt generation as per the code
+        salt = 0x0512981d * 2 * 1 * 1
+        salt = str(salt) + GetUserName()
+        salt = encode(salt,charMap5)
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap5)
+        return cleartext
+
+
+# unprotect the new header blob in .kinf2011
+# used in Kindle for Mac Version >= 1.9.0
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
     crp = LibCrypto()
-    iter = 0x800
-    keylen = 0x400
     key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
     key = key_iv[0:32]
     iv = key_iv[32:48]
     crp.set_decrypt_key(key,iv)
     cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext, charMap5)
     return cleartext
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.9.0
+class CryptUnprotectDataV3(object):
+    def __init__(self, entropy):
+        sp = GetUserName() + '+@#$%+' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap2)
+        salt = entropy
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap2)
+        return cleartext
+
+
 # Locate the .kindle-info files
 def getKindleInfoFiles(kInfoFiles):
     # first search for current .kindle-info files
@@ -424,12 +499,22 @@ def getKindleInfoFiles(kInfoFiles):
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
             found = True
-    # add any .kinf files 
+    # add any .rainier*-kinf files
     cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
     cmdline = cmdline.encode(sys.getfilesystemencoding())
     p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
     out1, out2 = p1.communicate()
     reslst = out1.split('\n')
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    # add any .kinf2011 files
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
     for resline in reslst:
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
@@ -438,7 +523,7 @@ def getKindleInfoFiles(kInfoFiles):
         print('No kindle-info files have been found.')
     return kInfoFiles
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -449,7 +534,9 @@ def getDBfromFile(kInfoFile):
     data = infoReader.read()
 
     if data.find('[') != -1 :
+
         # older style kindle-info file
+        cud = CryptUnprotectData()
         items = data.split('[')
         for item in items:
             if item != '':
@@ -462,87 +549,175 @@ def getDBfromFile(kInfoFile):
                 if keyname == "unknown":
                     keyname = keyhash
                 encryptedValue = decode(rawdata,charMap2)
-                cleartext = CryptUnprotectData(encryptedValue)
+                cleartext = cud.decrypt(encryptedValue)
                 DB[keyname] = cleartext
                 cnt = cnt + 1
         if cnt == 0:
             DB = None
         return DB
 
-    # else newer style .kinf file used by K4Mac >= 1.6.0
-    # the .kinf file uses "/" to separate it into records
-    # so remove the trailing "/" to make it easy to use split
+    if hdr == '/':
+
+        # else newer style .kinf file used by K4Mac >= 1.6.0
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+        cud = CryptUnprotectDataV2()
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+            keyname = "unknown"
+
+            # the raw keyhash string is also used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            # "entropy" not used for K4Mac only K4PC
+            # entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents) - largest prime number less than or equal to int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            noffset = contlen - primes(int(contlen/3))[-1]
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using charMap5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            cleartext = cud.decrypt(encryptedValue)
+            DB[keyname] = cleartext
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # the latest .kinf2011 version for K4M 1.9.1
+    # put back the hdr char, it is needed
+    data = hdr + data
     data = data[:-1]
     items = data.split('/')
 
+    # the headerblob is the encrypted information needed to build the entropy string
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, charMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+
+    # now extract the pieces in the same way
+    # this version is different from K4PC it scales the build number by multipying by 735
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
+
+    cud = CryptUnprotectDataV3(entropy)
+
     # loop through the item records until all are processed
     while len(items) > 0:
-    
+
         # get the first item record
         item = items.pop(0)
-    
+
         # the first 32 chars of the first record of a group
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
         keyname = "unknown"
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        # "entropy" not used for K4Mac only K4PC
-        # entropy = SHA1(keyhash)
-    
-        # the remainder of the first record when decoded with charMap5 
+        # unlike K4PC the keyhash is not used in generating entropy
+        # entropy = SHA1(keyhash) + added_entropy
+        # entropy = added_entropy
+
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
         srcnt = decode(item[34:],charMap5)
         rcnt = int(srcnt)
-    
+
         # read and store in rcnt records of data
         # that make up the contents value
         edlst = []
         for i in xrange(rcnt):
             item = items.pop(0)
             edlst.append(item)
-    
+
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
         if keyname == "unknown":
             keyname = keyhash
-    
-        # the charMap5 encoded contents data has had a length 
+
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
-    
-        # The offset into the charMap5 encoded contents seems to be:
+
+        # The offset into the testMap8 encoded contents seems to be:
         # len(contents) - largest prime number less than or equal to int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
-    
-        # move first offsets chars to end to align for decode by charMap5
+
+        # move first offsets chars to end to align for decode by testMap8
         encdata = "".join(edlst)
         contlen = len(encdata)
 
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
+        # now properly split and recombine
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         noffset = contlen - primes(int(contlen/3))[-1]
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
-    
-        # decode using charMap5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        cleartext = CryptUnprotectDataV2(encryptedValue)
-        # Debugging
+
+        # decode using testMap8 to get the CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = cud.decrypt(encryptedValue)
         # print keyname
         # print cleartext
-        # print cleartext.encode('hex')
-        # print
         DB[keyname] = cleartext
         cnt = cnt + 1
 
index 6acdd5c65c6b224a48221d3033a061b1cc570b71..880b0f7323723edeca09b59d66ac0932016519ef 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-import sys, os
+import sys, os, re
 from struct import pack, unpack, unpack_from
 
 from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -11,9 +11,7 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
     string_at, Structure, c_void_p, cast
 
 import _winreg as winreg
-
 MAX_PATH = 255
-
 kernel32 = windll.kernel32
 advapi32 = windll.advapi32
 crypt32 = windll.crypt32
@@ -33,9 +31,35 @@ def SHA1(message):
     ctx.update(message)
     return ctx.digest()
 
+def SHA256(message):
+    ctx = hashlib.sha256()
+    ctx.update(message)
+    return ctx.digest()
+
+# For K4PC 1.9.X
+# use routines in alfcrypto:
+#    AES_cbc_encrypt
+#    AES_set_decrypt_key
+#    PKCS5_PBKDF2_HMAC_SHA1
+
+from alfcrypto import AES_CBC, KeyIVGen
+
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
+    key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
+    key = key_iv[0:32]
+    iv = key_iv[32:48]
+    aes=AES_CBC()
+    aes.set_decrypt_key(key, iv)
+    cleartext = aes.decrypt(encryptedData)
+    return cleartext
+
 
 # simple primes table (<= n) calculator
-def primes(n): 
+def primes(n):
     if n==2: return [2]
     elif n<2: return []
     s=range(3,n+1,2)
@@ -59,6 +83,10 @@ def primes(n):
 # Probably supposed to act as obfuscation
 charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
 charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# New maps in K4PC 1.9.0
+testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
 
 class DrmException(Exception):
     pass
@@ -73,7 +101,7 @@ def encode(data, 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)
@@ -165,7 +193,8 @@ def CryptUnprotectData():
         outdata = DataBlob()
         if not _CryptUnprotectData(byref(indata), None, byref(entropy),
                                    None, None, flags, byref(outdata)):
-            raise DrmException("Failed to Unprotect Data")
+            # raise DrmException("Failed to Unprotect Data")
+            return 'failed'
         return string_at(outdata.pbData, outdata.cbData)
     return CryptUnprotectData
 CryptUnprotectData = CryptUnprotectData()
@@ -198,10 +227,17 @@ def getKindleInfoFiles(kInfoFiles):
     else:
         kInfoFiles.append(kinfopath)
 
+    # now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
+    kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
+    if not os.path.isfile(kinfopath):
+        print('No K4PC 1.9.X .kinf files have not been found.')
+    else:
+        kInfoFiles.append(kinfopath)
+
     return kInfoFiles
 
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -232,12 +268,97 @@ def getDBfromFile(kInfoFile):
             DB = None
         return DB
 
-    # else newer style .kinf file
+    if hdr == '/':
+        # else rainier-2-1-1 .kinf file
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+
+            # the raw keyhash string is used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents)-largest prime number <=  int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+            noffset = contlen - primes(int(contlen/3))[-1]
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using Map5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # else newest .kinf2011 style .kinf file
     # the .kinf file uses "/" to separate it into records
     # so remove the trailing "/" to make it easy to use split
-    data = data[:-1]
+    # need to put back the first char read because it it part
+    # of the added entropy blob
+    data = hdr + data[:-1]
     items = data.split('/')
 
+    # starts with and encoded and encrypted header blob
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, testMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+    # now extract the pieces that form the added entropy
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        added_entropy = m.group(2) + m.group(4)
+
+
     # loop through the item records until all are processed
     while len(items) > 0:
 
@@ -248,11 +369,11 @@ def getDBfromFile(kInfoFile):
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        entropy = SHA1(keyhash)
+        # the sha1 of raw keyhash string is used to create entropy along
+        # with the added entropy provided above from the headerblob
+        entropy = SHA1(keyhash) + added_entropy
 
-        # the remainder of the first record when decoded with charMap5 
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
@@ -266,43 +387,39 @@ def getDBfromFile(kInfoFile):
             item = items.pop(0)
             edlst.append(item)
 
+        # key names now use the new testMap8 encoding
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
-        if keyname == "unknown":
-            keyname = keyhash
 
-        # the charMap5 encoded contents data has had a length 
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
 
-        # The offset into the charMap5 encoded contents seems to be:
-        # len(contents) - largest prime number less than or equal to int(len(content)/3)
+        # The offset into the testMap8 encoded contents seems to be:
+        # len(contents)-largest prime number <=  int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
 
-        # move first offsets chars to end to align for decode by charMap5
+        # move first offsets chars to end to align for decode by testMap8
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         encdata = "".join(edlst)
         contlen = len(encdata)
         noffset = contlen - primes(int(contlen/3))[-1]
-
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
 
-        # decode using Map5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+        # decode using new testMap8 to get the original CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+        DB[keyname] = cleartext
         cnt = cnt + 1
 
     if cnt == 0:
         DB = None
     return DB
-
-
index abfc7e47f6140c907af2fc967d414ef703d3fce4..c4e45be1daedaaa1a7ca1a0af4da6c2dc1745202 100644 (file)
@@ -62,7 +62,7 @@ def encode(data, 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)
@@ -78,11 +78,11 @@ def decode(data,map):
         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
@@ -91,10 +91,10 @@ def getTwoBitsFromBitField(bitField,offset):
 
 # 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
-     
+    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
@@ -121,8 +121,8 @@ def generatePidEncryptionTable() :
 def generatePidSeed(table,dsn) :
     value = 0
     for counter in range (0,4) :
-       index = (ord(dsn[counter]) ^ value) &0xFF
-       value = (value >> 8) ^ table[index]
+        index = (ord(dsn[counter]) ^ value) &0xFF
+        value = (value >> 8) ^ table[index]
     return value
 
 # Generate the device PID
@@ -141,7 +141,7 @@ def generateDevicePID(table,dsn,nbRoll):
     return pidAscii
 
 def crc32(s):
-  return (~binascii.crc32(s,-1))&0xFFFFFFFF 
+    return (~binascii.crc32(s,-1))&0xFFFFFFFF
 
 # convert from 8 digit PID to 10 digit PID with checksum
 def checksumPid(s):
@@ -204,10 +204,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
         print(message)
         kindleDatabase = None
         pass
-    
+
     if kindleDatabase == None :
         return pidlst
-        
+
     try:
         # Get the Mazama Random number
         MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
@@ -217,7 +217,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
     except KeyError:
         print "Keys not found in " + kInfoFile
         return pidlst
-        
+
     # Get the ID string used
     encodedIDString = encodeHash(GetIDString(),charMap1)
 
@@ -226,7 +226,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
 
     # concat, hash and encode to calculate the DSN
     DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
-       
+
     # Compute the device PID (for which I can tell, is used for nothing).
     table =  generatePidEncryptionTable()
     devicePID = generateDevicePID(table,DSN,4)
@@ -258,7 +258,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
 def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
     pidlst = []
     if kInfoFiles is None:
-       kInfoFiles = []
+        kInfoFiles = []
     if k4:
         kInfoFiles = getKindleInfoFiles(kInfoFiles)
     for infoFile in kInfoFiles:
index 4d978b377ae0cb64cb057212b5d82b314117176a..0a6b25ef4bc7dce592da7ff6d9128f10685ec6ff 100644 (file)
@@ -27,8 +27,8 @@
 #         files reveals that a confusion has arisen because trailing data entries
 #         are not encrypted, but it turns out that the multibyte entries
 #         in utf8 file are encrypted. (Although neither kind gets compressed.)
-#         This knowledge leads to a simplification of the test for the 
-#         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#         This knowledge leads to a simplification of the test for the
+#         trailing data byte flags - version 5 and higher AND header size >= 0xE4.
 #  0.15 - Now outputs 'heartbeat', and is also quicker for long files.
 #  0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
 #  0.17 - added modifications to support its use as an imported python module
@@ -42,7 +42,7 @@
 #  0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
 #  0.21 - Added support for multiple pids
 #  0.22 - revised structure to hold MobiBook as a class to allow an extended interface
-#  0.23 - fixed problem with older files with no EXTH section 
+#  0.23 - fixed problem with older files with no EXTH section
 #  0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
 #  0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
 #  0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
 #  0.30 - Modified interface slightly to work better with new calibre plugin style
 #  0.31 - The multibyte encrytion info is true for version 7 files too.
 #  0.32 - Added support for "Print Replica" Kindle ebooks
+#  0.33 - Performance improvements for large files (concatenation)
+#  0.34 - Performance improvements in decryption (libalfcrypto)
 
-__version__ = '0.32'
+__version__ = '0.34'
 
 import sys
 
@@ -72,6 +74,7 @@ sys.stdout=Unbuffered(sys.stdout)
 import os
 import struct
 import binascii
+from alfcrypto import Pukall_Cipher
 
 class DrmException(Exception):
     pass
@@ -83,36 +86,37 @@ class DrmException(Exception):
 
 # Implementation of Pukall Cipher 1
 def PC1(key, src, decryption=True):
-    sum1 = 0;
-    sum2 = 0;
-    keyXorVal = 0;
-    if len(key)!=16:
-        print "Bad key length!"
-        return None
-    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
+    return Pukall_Cipher().PC1(key,src,decryption)
+#     sum1 = 0;
+#     sum2 = 0;
+#     keyXorVal = 0;
+#     if len(key)!=16:
+#         print "Bad key length!"
+#         return None
+#     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
 
 def checksumPid(s):
     letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
@@ -236,7 +240,7 @@ class MobiBook:
             self.meta_array = {}
             pass
         self.print_replica = False
-            
+
     def getBookTitle(self):
         codec_map = {
             1252 : 'windows-1252',
@@ -319,7 +323,7 @@ class MobiBook:
 
     def getMobiFile(self, outpath):
         file(outpath,'wb').write(self.mobi_data)
-        
+
     def getPrintReplica(self):
         return self.print_replica
 
@@ -355,9 +359,9 @@ class MobiBook:
             if self.magic == 'TEXtREAd':
                 bookkey_data = self.sect[0x0E:0x0E+16]
             elif self.mobi_version < 0:
-                bookkey_data = self.sect[0x90:0x90+16] 
+                bookkey_data = self.sect[0x90:0x90+16]
             else:
-                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] 
+                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
             pid = "00000000"
             found_key = PC1(t1_keyvec, bookkey_data)
         else :
@@ -372,7 +376,7 @@ class MobiBook:
             self.patchSection(0, "\0" * drm_size, drm_ptr)
             # kill the drm pointers
             self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
-            
+
         if pid=="00000000":
             print "File has default encryption, no specific PID."
         else:
@@ -383,7 +387,8 @@ class MobiBook:
 
         # decrypt sections
         print "Decrypting. Please wait . . .",
-        self.mobi_data = self.data_file[:self.sections[1][0]]
+        mobidataList = []
+        mobidataList.append(self.data_file[:self.sections[1][0]])
         for i in xrange(1, self.records+1):
             data = self.loadSection(i)
             extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,11 +398,12 @@ class MobiBook:
             decoded_data = PC1(found_key, data[0:len(data) - extra_size])
             if i==1:
                 self.print_replica = (decoded_data[0:4] == '%MOP')
-            self.mobi_data += decoded_data
+            mobidataList.append(decoded_data)
             if extra_size > 0:
-                self.mobi_data += data[-extra_size:]
+                mobidataList.append(data[-extra_size:])
         if self.num_sections > self.records+1:
-            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
+            mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
+        self.mobi_data = "".join(mobidataList)
         print "done"
         return
 
index 8a044fa89a0641c156c5c5c18f6d4a9067bf5d9b..a4a40ca859417988ad76623ac65f8d3df2698fba 100644 (file)
@@ -18,7 +18,7 @@ def load_libcrypto():
         return None
 
     libcrypto = CDLL(libcrypto)
-    
+
     # typedef struct DES_ks
     #     {
     #     union
@@ -30,7 +30,7 @@ def load_libcrypto():
     #         } ks[16];
     #     } DES_key_schedule;
 
-    # just create a big enough place to hold everything 
+    # 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),
@@ -61,7 +61,7 @@ def load_libcrypto():
     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 :
@@ -87,4 +87,3 @@ def load_libcrypto():
             return ''.join(result)
 
     return DES
-
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/pbkdf2.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/pbkdf2.py
new file mode 100644 (file)
index 0000000..65220a9
--- /dev/null
@@ -0,0 +1,68 @@
+# A simple implementation of pbkdf2 using stock python modules. See RFC2898
+# for details. Basically, it derives a key from a password and salt.
+
+# Copyright 2004 Matt Johnston <matt @ ucc asn au>
+# Copyright 2009 Daniel Holth <dholth@fastmail.fm>
+# This code may be freely used and modified for any purpose.
+
+# Revision history
+# v0.1  October 2004    - Initial release
+# v0.2  8 March 2007    - Make usable with hashlib in Python 2.5 and use
+# v0.3  ""                 the correct digest_size rather than always 20
+# v0.4  Oct 2009        - Rescue from chandler svn, test and optimize.
+
+import sys
+import hmac
+from struct import pack
+try:
+    # only in python 2.5
+    import hashlib
+    sha = hashlib.sha1
+    md5 = hashlib.md5
+    sha256 = hashlib.sha256
+except ImportError: # pragma: NO COVERAGE
+    # fallback
+    import sha
+    import md5
+
+# this is what you want to call.
+def pbkdf2( password, salt, itercount, keylen, hashfn = sha ):
+    try:
+        # depending whether the hashfn is from hashlib or sha/md5
+        digest_size = hashfn().digest_size
+    except TypeError: # pragma: NO COVERAGE
+        digest_size = hashfn.digest_size
+    # l - number of output blocks to produce
+    l = keylen / digest_size
+    if keylen % digest_size != 0:
+        l += 1
+
+    h = hmac.new( password, None, hashfn )
+
+    T = ""
+    for i in range(1, l+1):
+        T += pbkdf2_F( h, salt, itercount, i )
+
+    return T[0: keylen]
+
+def xorstr( a, b ):
+    if len(a) != len(b):
+        raise ValueError("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()
+
+# Helper as per the spec. h is a hmac which has been created seeded with the
+# password, it will be copy()ed and not modified.
+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
index 81502c80f564043dd03cc4811edfb78ffe413e9b..80d7d65064f42919e0b182680fafba5baeb0a5b3 100644 (file)
@@ -28,4 +28,3 @@ def load_pycrypto():
                 i += 8
             return ''.join(result)
     return DES
-
index cfb4f5916f9dcf8e8b5e5bec1401363978b810a4..bd029048baaaf5a2c85c602e9bc0dc86f1573fce 100644 (file)
@@ -2,8 +2,8 @@
 # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
 import sys
 
-ECB =  0
-CBC =  1
+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,
@@ -11,13 +11,13 @@ class Des(object):
          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]
+        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,
@@ -61,8 +61,8 @@ class Des(object):
         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
+    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.")
@@ -74,7 +74,7 @@ class Des(object):
             self.setIV(IV)
         self.L = []
         self.R = []
-        self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)
+        self.Kn = [ [0] * 48 ] * 16     # 16 48-bit keys (K1 - K16)
         self.final = []
         self.setKey(key)
     def getKey(self):
index 10919d203ed5ea994ecb382ce7fd15fd015c5aa8..08099444ab2bd8da6b44781a4849d5f8b0989662 100644 (file)
@@ -43,7 +43,7 @@ class SimplePrefs(object):
                 key = self.file2key[filename]
                 filepath = os.path.join(self.prefdir,filename)
                 if os.path.isfile(filepath):
-                    try : 
+                    try :
                         data = file(filepath,'rb').read()
                         self.prefs[key] = data
                     except Exception, e:
@@ -75,4 +75,3 @@ class SimplePrefs(object):
                             pass
         self.prefs = newprefs
         return
-
index e30abfaa0c1742adf3903016a8256cee7a43e54d..adbac4988187e1dc4eb4ce403eb931039069ff6b 100644 (file)
@@ -6,6 +6,7 @@ import csv
 import sys
 import os
 import getopt
+import re
 from struct import pack
 from struct import unpack
 
@@ -43,8 +44,8 @@ class DocParser(object):
         '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
@@ -59,10 +60,10 @@ class DocParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -82,12 +83,19 @@ class DocParser(object):
         return startpos
 
     # returns a vector of integers for the tagpath
-    def getData(self, tagpath, pos, end):
+    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('|')
-            argres = [ int(strval) for strval in argList]
+            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):
@@ -112,7 +120,7 @@ class DocParser(object):
             (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 :
 
@@ -121,7 +129,7 @@ class DocParser(object):
                 if sclass != None:
                     sclass = sclass.replace(' ','-')
                     sclass = '.cl-' + sclass.lower()
-                else : 
+                else :
                     sclass = ''
 
                 # check for any "after class" specifiers
@@ -129,7 +137,7 @@ class DocParser(object):
                 if aftclass != None:
                     aftclass = aftclass.replace(' ','-')
                     aftclass = '.cl-' + aftclass.lower()
-                else : 
+                else :
                     aftclass = ''
 
                 cssargs = {}
@@ -140,7 +148,7 @@ class DocParser(object):
                     (pos2, val) = self.findinDoc('style.rule.value', start, end)
 
                     if attr == None : break
-                    
+
                     if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
                         # handle text based attributess
                         attr = attr + '-' + val
@@ -168,7 +176,7 @@ class DocParser(object):
                 if aftclass != "" : keep = False
 
                 if keep :
-                    # make sure line-space does not go below 100% or above 300% since 
+                    # 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]
@@ -178,7 +186,7 @@ class DocParser(object):
                         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]
@@ -211,7 +219,7 @@ class DocParser(object):
 
                     if sclass != '' :
                         classlst += sclass + '\n'
-                    
+
                     # handle special case of paragraph class used inside chapter heading
                     # and non-chapter headings
                     if sclass != '' :
@@ -232,7 +240,7 @@ class DocParser(object):
                     if cssline != ' { }':
                         csspage += self.stags[tag] + cssline + '\n'
 
-                
+
         return csspage, classlst
 
 
@@ -251,5 +259,5 @@ def convert2CSS(flatxml, fontsize, ph, pw):
 
 def getpageIDMap(flatxml):
     dp = DocParser(flatxml, 0, 0, 0)
-    pageidnumbers = dp.getData('info.original.pid', 0, -1)
+    pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
     return pageidnumbers
index ed13aa1b7917bd7923efa09367d1ef2304b472e7..de084d303fcc72ce25e9213ab87f8f1d91bfdb03 100644 (file)
@@ -52,7 +52,7 @@ class Process(object):
             self.__stdout_thread = threading.Thread(
                 name="stdout-thread",
                 target=self.__reader, args=(self.__collected_outdata,
-                                           self.__process.stdout))
+                                            self.__process.stdout))
             self.__stdout_thread.setDaemon(True)
             self.__stdout_thread.start()
 
@@ -60,7 +60,7 @@ class Process(object):
             self.__stderr_thread = threading.Thread(
                 name="stderr-thread",
                 target=self.__reader, args=(self.__collected_errdata,
-                                           self.__process.stderr))
+                                            self.__process.stderr))
             self.__stderr_thread.setDaemon(True)
             self.__stderr_thread.start()
 
@@ -146,4 +146,3 @@ class Process(object):
         self.__quit = True
         self.__inputsem.release()
         self.__lock.release()
-
index eed53e263ce9c750de783efa533ed6024cd6192f..6afb7daf1c7af8d52f8a0839b9d31fdea3a87978 100644 (file)
@@ -16,15 +16,18 @@ if 'calibre' in sys.modules:
 else:
     inCalibre = False
 
+buildXML = False
+
 import os, csv, getopt
 import zlib, zipfile, tempfile, shutil
 from struct import pack
 from struct import unpack
+from alfcrypto import Topaz_Cipher
 
 class TpzDRMError(Exception):
     pass
 
-    
+
 # local support routines
 if inCalibre:
     from calibre_plugins.k4mobidedrm import kgenpids
@@ -58,22 +61,22 @@ def bookReadEncodedNumber(fo):
     flag = False
     data = ord(fo.read(1))
     if data == 0xFF:
-       flag = True
-       data = ord(fo.read(1))
+        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 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
-    
-# Get a length prefixed string from file 
+
+# Get a length prefixed string from file
 def bookReadString(fo):
     stringLength = bookReadEncodedNumber(fo)
-    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]  
+    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
 
 #
 # crypto routines
@@ -81,25 +84,28 @@ def bookReadString(fo):
 
 # Context initialisation for the Topaz Crypto
 def topazCryptoInit(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]
-    
+    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):
-    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
+    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):
@@ -153,7 +159,7 @@ class TopazBook:
 
     def parseTopazHeaders(self):
         def bookReadHeaderRecordData():
-            # Read and return the data of one header record at the current book file position 
+            # Read and return the data of one header record at the current book file position
             # [[offset,decompressedLength,compressedLength],...]
             nbValues = bookReadEncodedNumber(self.fo)
             values = []
@@ -213,11 +219,11 @@ class TopazBook:
         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 
+        # Get a record in the book payload, given its name and index.
+        # decrypted and decompressed if necessary
         encrypted = False
         compressed = False
-        try: 
+        try:
             recordOffset = self.bookHeaderRecords[name][index][0]
         except:
             raise TpzDRMError("Parse Error : Invalid Record, record not found")
@@ -268,8 +274,8 @@ class TopazBook:
             rv = genbook.generateBook(self.outdir, raw, fixedimage)
             if rv == 0:
                 print "\nBook Successfully generated"
-            return rv            
-    
+            return rv
+
         # try each pid to decode the file
         bookKey = None
         for pid in pidlst:
@@ -297,7 +303,7 @@ class TopazBook:
         rv = genbook.generateBook(self.outdir, raw, fixedimage)
         if rv == 0:
             print "\nBook Successfully generated"
-        return rv            
+        return rv
 
     def createBookDirectory(self):
         outdir = self.outdir
@@ -361,7 +367,7 @@ class TopazBook:
         zipUpDir(svgzip, self.outdir, 'svg')
         zipUpDir(svgzip, self.outdir, 'img')
         svgzip.close()
-
+    
     def getXMLZip(self, zipname):
         xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
         targetdir = os.path.join(self.outdir,'xml')
@@ -371,23 +377,23 @@ class TopazBook:
 
     def cleanup(self):
         if os.path.isdir(self.outdir):
-            pass
-            # shutil.rmtree(self.outdir, True)
+            shutil.rmtree(self.outdir, True)
 
 def usage(progname):
     print "Removes DRM protection from Topaz ebooks and extract the contents"
     print "Usage:"
     print "    %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir>  " % progname
-    
+
 
 # Main
 def main(argv=sys.argv):
+    global buildXML
     progname = os.path.basename(argv[0])
     k4 = False
     pids = []
     serials = []
     kInfoFiles = []
-    
+
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
     except getopt.GetoptError, err:
@@ -397,7 +403,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         return 1
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -429,7 +435,7 @@ def main(argv=sys.argv):
     title = tb.getBookTitle()
     print "Processing Book: ", title
     keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles)
 
     try:
         print "Decrypting Book"
@@ -443,9 +449,10 @@ def main(argv=sys.argv):
         zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
         tb.getSVGZip(zipname)
 
-        print "   Creating XML ZIP Archive"
-        zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
-        tb.getXMLZip(zipname)
+        if buildXML:
+            print "   Creating XML ZIP Archive"
+            zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
+            tb.getXMLZip(zipname)
 
         # removing internal temporary directory of pieces
         tb.cleanup()
@@ -461,9 +468,8 @@ def main(argv=sys.argv):
         return 1
 
     return 0
-                
+
 
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index a1aafde23e57ee26d332d06ee6bd71dfc53d6ed8..523ef1a2109cb6d41dcaaec8175672484cccff9c 100644 (file)
@@ -30,8 +30,8 @@ class fixZip:
         self.inzip = zipfile.ZipFile(zinput,'r')
         self.outzip = zipfile.ZipFile(zoutput,'w')
         # open the input zip for reading only as a raw file
-       self.bzf = file(zinput,'rb')
-        
+        self.bzf = file(zinput,'rb')
+
     def getlocalname(self, zi):
         local_header_offset = zi.header_offset
         self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
@@ -86,7 +86,7 @@ class fixZip:
 
         return data
 
-        
+
 
     def fix(self):
         # get the zipinfo for each member of the input archive
@@ -103,7 +103,7 @@ class fixZip:
             if zinfo.filename != "mimetype" or self.ztype == '.zip':
                 data = None
                 nzinfo = zinfo
-                try: 
+                try:
                     data = self.inzip.read(zinfo.filename)
                 except zipfile.BadZipfile or zipfile.error:
                     local_name = self.getlocalname(zinfo)
@@ -126,7 +126,7 @@ def usage():
      inputzip is the source zipfile to fix
      outputzip is the fixed zip archive
     """
-    
+
 
 def repairBook(infile, outfile):
     if not os.path.exists(infile):
@@ -152,5 +152,3 @@ def main(argv=sys.argv):
 
 if __name__ == '__main__' :
     sys.exit(main())
-
-
index b260625efe2589d4f9dfc5eb2670451ebcd3ec84..b8f5c6af6a15e6298d6d5bb3363c81bba799592e 100644 (file)
@@ -1,7 +1,7 @@
-ReadMe_DeDRM_WinApp_vX.X
+ReadMe_DeDRM_vX.X_WinApp
 -----------------------
 
-DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed.  It repackages the"tools" python software in one easy to use program.
+DeDRM_vX.X_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed.  It repackages the"tools" python software in one easy to use program that remembers preferences and settings.
 
 It should work out of the box with Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
 
@@ -21,9 +21,9 @@ This program requires that the proper 32 bit version of Python 2.X (tested with
 Installation
 ------------
 
-1. Download the latest DeDRM_WinApp_vx.x.zip and fully Extract its contents. 
+1. From tools_vX.X\DeDRM_Applications\, right click on DeDRM_v_X.X_WinApp.zip and fully Extract its contents. 
 
-2. Move the resulting DeDRM_WinApp_vX.X folder to whereever you keep you other programs.
+2. Move the resulting DeDRM_vX.X_WinApp folder to whereever you keep you other programs.
    (I typically use an "Applications" folder inside of my home directory)
 
 3. Open the folder, and create a short-cut to DeDRM_Drop_Target and move that short-cut to your Desktop.
@@ -33,19 +33,18 @@ Installation
 
 If you already have a correct version of Python and PyCrypto installed and in your path, you are ready to go!
 
-
-
-If not, see where you can get these additional pieces.
+If not, see below.
 
 
 Installing Python on Windows
 ----------------------------
-I strongly recommend installing ActiveState’s Active Python, Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python.  It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything.
+I strongly recommend fully installing ActiveState’s Active Python, free Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python.  It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything.
 
-1. Download ActivePython 2.7.1 for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.1 for Windows (64-bit, x64) verson, even if you are running 64-bit Windows.
+1. Download ActivePython 2.7.X for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.X for Windows (64-bit, x64) verson, even if you are running 64-bit Windows.
 
 2. When it has finished downloading, run the installer. Accept the default options.
 
+
 Installing PyCrypto on Windows
 ------------------------------
 PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foord’s blog.
index 6a0df3057666dbdcfb858f2c0307aa7884da51c0..8628eac114e29b3dd6ee008f34d5c06afb2d9ff7 100644 (file)
 
 # ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them.
 
-# PLEASE DO NOT PIRATE EBOOKS! 
+# PLEASE DO NOT PIRATE EBOOKS!
 
 # We want all authors and publishers, and eBook stores to live
-# long and prosperous lives but at the same time  we just want to 
-# be able to read OUR books on whatever device we want and to keep 
+# long and prosperous lives but at the same time  we just want to
+# be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
 
-#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
-#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
 #    and many many others
 
 # Revision history:
@@ -71,17 +71,17 @@ def cli_main(argv=sys.argv, obj=None):
     if len(argv) != 2:
         print "usage: %s DIRECTORY" % (progname,)
         return 1
-    
+
     if obj == None:
         print "\nTopaz search results:\n"
     else:
         obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n")
-        
+
     inpath = argv[1]
     files = os.listdir(inpath)
     filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE)
     files = filter(filefilter.search, files)
-    
+
     if files:
         topazcount = 0
         totalcount = 0
@@ -136,14 +136,14 @@ def cli_main(argv=sys.argv, obj=None):
         else:
             msg = "No typical Topaz file extensions found in %s.\n\n" % inpath
             obj.stext.insert(Tkconstants.END,msg)
-    
+
     return 0
 
 
 class DecryptionDialog(Tkinter.Frame):
     def __init__(self, root):
         Tkinter.Frame.__init__(self, root, border=5)
-        ltext='Search a directory for Topaz eBooks\n'        
+        ltext='Search a directory for Topaz eBooks\n'
         self.status = Tkinter.Label(self, text=ltext)
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
@@ -162,7 +162,7 @@ class DecryptionDialog(Tkinter.Frame):
         #self.stext.insert(Tkconstants.END,msg1)
         buttons = Tkinter.Frame(self)
         buttons.pack()
-  
+
 
         self.botton = Tkinter.Button(
             buttons, text="Search", width=10, command=self.search)
@@ -171,7 +171,7 @@ class DecryptionDialog(Tkinter.Frame):
         self.button = Tkinter.Button(
             buttons, text="Quit", width=10, command=self.quit)
         self.button.pack(side=Tkconstants.RIGHT)
-        
+
     def get_inpath(self):
         cwd = os.getcwdu()
         cwd = cwd.encode('utf-8')
@@ -183,8 +183,8 @@ class DecryptionDialog(Tkinter.Frame):
             self.inpath.delete(0, Tkconstants.END)
             self.inpath.insert(0, inpath)
         return
-        
-    
+
+
     def search(self):
         inpath = self.inpath.get()
         if not inpath or not os.path.exists(inpath):
@@ -213,4 +213,4 @@ def gui_main():
 if __name__ == '__main__':
     if len(sys.argv) > 1:
         sys.exit(cli_main())
-    sys.exit(gui_main())
\ No newline at end of file
+    sys.exit(gui_main())
index 4d978b377ae0cb64cb057212b5d82b314117176a..6c9a46506461eadb9bd60ab288aa1e7b4b6ce9ad 100644 (file)
@@ -54,8 +54,9 @@
 #  0.30 - Modified interface slightly to work better with new calibre plugin style
 #  0.31 - The multibyte encrytion info is true for version 7 files too.
 #  0.32 - Added support for "Print Replica" Kindle ebooks
+#  0.33 - Performance improvements for large files (concatenation)
 
-__version__ = '0.32'
+__version__ = '0.33'
 
 import sys
 
@@ -383,7 +384,8 @@ class MobiBook:
 
         # decrypt sections
         print "Decrypting. Please wait . . .",
-        self.mobi_data = self.data_file[:self.sections[1][0]]
+        mobidataList = []
+        mobidataList.append(self.data_file[:self.sections[1][0]])
         for i in xrange(1, self.records+1):
             data = self.loadSection(i)
             extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,11 +395,12 @@ class MobiBook:
             decoded_data = PC1(found_key, data[0:len(data) - extra_size])
             if i==1:
                 self.print_replica = (decoded_data[0:4] == '%MOP')
-            self.mobi_data += decoded_data
+            mobidataList.append(decoded_data)
             if extra_size > 0:
-                self.mobi_data += data[-extra_size:]
+                mobidataList.append(data[-extra_size:])
         if self.num_sections > self.records+1:
-            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
+            mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
+        self.mobi_data = "".join(mobidataList)
         print "done"
         return
 
index 8eab14fb0200772d2f27cf6fc65239a99a6f7e92..da61e77eb4ab05593ecd0a6f4a5a1700edf71a09 100644 (file)
@@ -76,13 +76,13 @@ if sys.platform.startswith('win'):
             _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                         ('rounds', c_int)]
         AES_KEY_p = POINTER(AES_KEY)
-    
+
         def F(restype, name, argtypes):
             func = getattr(libcrypto, name)
             func.restype = restype
             func.argtypes = argtypes
             return func
-    
+
         AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
                                 [c_char_p, c_int, AES_KEY_p])
         AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -427,8 +427,8 @@ def extractKeyfile(keypath):
         print "Key generation Error: " + str(e)
         return 1
     except Exception, e:
-       print "General Error: " + str(e)
-       return 1
+        print "General Error: " + str(e)
+        return 1
     if not success:
         return 1
     return 0
index c9a419be6bde6744851ce001a7879c75404b88b0..1437708372d3020386223b0069267af88bb1737d 100644 (file)
@@ -4,7 +4,7 @@
 from __future__ import with_statement
 
 # To run this program install Python 2.6 from http://www.python.org/download/
-# and OpenSSL (already installed on Mac OS X and Linux) OR 
+# and OpenSSL (already installed on Mac OS X and Linux) OR
 # PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
 # (make sure to install the version for Python 2.6).  Save this script file as
 # ineptpdf.pyw and double-click on it to run it.
@@ -83,7 +83,7 @@ def _load_crypto_libcrypto():
     AES_MAXNR = 14
 
     RSA_NO_PADDING = 3
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -98,13 +98,13 @@ def _load_crypto_libcrypto():
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
@@ -125,7 +125,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -134,7 +134,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[1:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -196,13 +196,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -212,22 +212,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -237,19 +237,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -257,13 +257,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -272,7 +272,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -293,6 +293,7 @@ def _load_crypto_pycrypto():
             return self._arc4.decrypt(data)
 
     class AES(object):
+        MODE_CBC = _AES.MODE_CBC
         @classmethod
         def new(cls, userkey, mode, iv):
             self = AES()
@@ -315,7 +316,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
@@ -410,7 +411,7 @@ class PSLiteral(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         name = []
         for char in self.name:
@@ -429,22 +430,22 @@ class PSKeyword(PSObject):
     def __init__(self, name):
         self.name = name
         return
-    
+
     def __repr__(self):
         return self.name
 
 # PSSymbolTable
 class PSSymbolTable(object):
-    
+
     '''
     Symbol table that stores PSLiteral or PSKeyword.
     '''
-    
+
     def __init__(self, classe):
         self.dic = {}
         self.classe = classe
         return
-    
+
     def intern(self, name):
         if name in self.dic:
             lit = self.dic[name]
@@ -514,11 +515,11 @@ class PSBaseParser(object):
 
     def flush(self):
         return
-    
+
     def close(self):
         self.flush()
         return
-    
+
     def tell(self):
         return self.bufpos+self.charpos
 
@@ -554,7 +555,7 @@ class PSBaseParser(object):
             raise PSEOF('Unexpected EOF')
         self.charpos = 0
         return
-    
+
     def parse_main(self, s, i):
         m = NONSPC.search(s, i)
         if not m:
@@ -589,11 +590,11 @@ class PSBaseParser(object):
             return (self.parse_wclose, j+1)
         self.add_token(KWD(c))
         return (self.parse_main, j+1)
-                            
+
     def add_token(self, obj):
         self.tokens.append((self.tokenstart, obj))
         return
-    
+
     def parse_comment(self, s, i):
         m = EOL.search(s, i)
         if not m:
@@ -604,7 +605,7 @@ class PSBaseParser(object):
         # We ignore comments.
         #self.tokens.append(self.token)
         return (self.parse_main, j)
-    
+
     def parse_literal(self, s, i):
         m = END_LITERAL.search(s, i)
         if not m:
@@ -618,7 +619,7 @@ class PSBaseParser(object):
             return (self.parse_literal_hex, j+1)
         self.add_token(LIT(self.token))
         return (self.parse_main, j)
-    
+
     def parse_literal_hex(self, s, i):
         c = s[i]
         if HEX.match(c) and len(self.hex) < 2:
@@ -653,7 +654,7 @@ class PSBaseParser(object):
         self.token += s[i:j]
         self.add_token(float(self.token))
         return (self.parse_main, j)
-    
+
     def parse_keyword(self, s, i):
         m = END_KEYWORD.search(s, i)
         if not m:
@@ -801,7 +802,7 @@ class PSStackParser(PSBaseParser):
         PSBaseParser.__init__(self, fp)
         self.reset()
         return
-    
+
     def reset(self):
         self.context = []
         self.curtype = None
@@ -842,10 +843,10 @@ class PSStackParser(PSBaseParser):
 
     def do_keyword(self, pos, token):
         return
-    
+
     def nextobject(self, direct=False):
         '''
-        Yields a list of objects: keywords, literals, strings, 
+        Yields a list of objects: keywords, literals, strings,
         numbers, arrays and dictionaries. Arrays and dictionaries
         are represented as Python sequence and dictionaries.
         '''
@@ -914,7 +915,7 @@ class PDFNotImplementedError(PSException): pass
 ##  PDFObjRef
 ##
 class PDFObjRef(PDFObject):
-    
+
     def __init__(self, doc, objid, genno):
         if objid == 0:
             if STRICT:
@@ -1029,25 +1030,25 @@ def stream_value(x):
 
 # ascii85decode(data)
 def ascii85decode(data):
-  n = b = 0
-  out = ''
-  for c in data:
-    if '!' <= c and c <= 'u':
-      n += 1
-      b = b*85+(ord(c)-33)
-      if n == 5:
-        out += struct.pack('>L',b)
-        n = b = 0
-    elif c == 'z':
-      assert n == 0
-      out += '\0\0\0\0'
-    elif c == '~':
-      if n:
-        for _ in range(5-n):
-          b = b*85+84
-        out += struct.pack('>L',b)[:n-1]
-      break
-  return out
+    n = b = 0
+    out = ''
+    for c in data:
+        if '!' <= c and c <= 'u':
+            n += 1
+            b = b*85+(ord(c)-33)
+            if n == 5:
+                out += struct.pack('>L',b)
+                n = b = 0
+        elif c == 'z':
+            assert n == 0
+            out += '\0\0\0\0'
+        elif c == '~':
+            if n:
+                for _ in range(5-n):
+                    b = b*85+84
+                out += struct.pack('>L',b)[:n-1]
+            break
+    return out
 
 
 ##  PDFStream type
@@ -1064,7 +1065,7 @@ class PDFStream(PDFObject):
         else:
             if eol in ('\r', '\n', '\r\n'):
                 rawdata = rawdata[:length]
-                
+
         self.dic = dic
         self.rawdata = rawdata
         self.decipher = decipher
@@ -1078,7 +1079,7 @@ class PDFStream(PDFObject):
         self.objid = objid
         self.genno = genno
         return
-    
+
     def __repr__(self):
         if self.rawdata:
             return '<PDFStream(%r): raw=%d, %r>' % \
@@ -1162,7 +1163,7 @@ class PDFStream(PDFObject):
             data = self.decipher(self.objid, self.genno, data)
         return data
 
-        
+
 ##  PDF Exceptions
 ##
 class PDFSyntaxError(PDFException): pass
@@ -1227,7 +1228,7 @@ class PDFXRef(object):
                 self.offsets[objid] = (int(genno), int(pos))
         self.load_trailer(parser)
         return
-    
+
     KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
     def load_trailer(self, parser):
         try:
@@ -1268,7 +1269,7 @@ class PDFXRefStream(object):
         for first, size in self.index:
             for objid in xrange(first, first + size):
                 yield objid
-    
+
     def load(self, parser, debug=0):
         (_,objid) = parser.nexttoken() # ignored
         (_,genno) = parser.nexttoken() # ignored
@@ -1286,7 +1287,7 @@ class PDFXRefStream(object):
         self.entlen = self.fl1+self.fl2+self.fl3
         self.trailer = stream.dic
         return
-    
+
     def getpos(self, objid):
         offset = 0
         for first, size in self.index:
@@ -1337,7 +1338,7 @@ class PDFDocument(object):
         self.parser = parser
         # The document is set to be temporarily ready during collecting
         # all the basic information about the document, e.g.
-        # the header, the encryption information, and the access rights 
+        # the header, the encryption information, and the access rights
         # for the document.
         self.ready = True
         # Retrieve the information of each header that was appended
@@ -1413,7 +1414,7 @@ class PDFDocument(object):
         length = int_value(param.get('Length', 0)) / 8
         edcdata = str_value(param.get('EDCData')).decode('base64')
         pdrllic = str_value(param.get('PDRLLic')).decode('base64')
-        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')          
+        pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
         edclist = []
         for pair in edcdata.split('\n'):
             edclist.append(pair)
@@ -1433,9 +1434,9 @@ class PDFDocument(object):
             raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
         else:
             cutter = -1 * ord(pdrlpol[-1])
-            pdrlpol = pdrlpol[:cutter]            
+            pdrlpol = pdrlpol[:cutter]
         return plaintext[:16]
-    
+
     PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
                        '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
     # experimental aes pw support
@@ -1455,14 +1456,14 @@ class PDFDocument(object):
             EncMetadata = str_value(param['EncryptMetadata'])
         except:
             EncMetadata = 'True'
-        self.is_printable = bool(P & 4)        
+        self.is_printable = bool(P & 4)
         self.is_modifiable = bool(P & 8)
         self.is_extractable = bool(P & 16)
         self.is_annotationable = bool(P & 32)
         self.is_formsenabled = bool(P & 256)
         self.is_textextractable = bool(P & 512)
         self.is_assemblable = bool(P & 1024)
-        self.is_formprintable = bool(P & 2048) 
+        self.is_formprintable = bool(P & 2048)
         # Algorithm 3.2
         password = (password+self.PASSWORD_PADDING)[:32] # 1
         hash = hashlib.md5(password) # 2
@@ -1537,10 +1538,10 @@ class PDFDocument(object):
         if length > 0:
             if len(bookkey) == length:
                 if ebx_V == 3:
-                    V = 3        
+                    V = 3
                 else:
                     V = 2
-            elif len(bookkey) == length + 1:  
+            elif len(bookkey) == length + 1:
                 V = ord(bookkey[0])
                 bookkey = bookkey[1:]
             else:
@@ -1554,7 +1555,7 @@ class PDFDocument(object):
             print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
             print "bookkey[0] is %d" % ord(bookkey[0])
             if ebx_V == 3:
-                V = 3        
+                V = 3
             else:
                 V = 2
         self.decrypt_key = bookkey
@@ -1571,7 +1572,7 @@ class PDFDocument(object):
         hash = hashlib.md5(key)
         key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
         return key
-    
+
     def genkey_v3(self, objid, genno):
         objid = struct.pack('<L', objid ^ 0x3569ac)
         genno = struct.pack('<L', genno ^ 0xca96)
@@ -1611,14 +1612,14 @@ class PDFDocument(object):
         #print cutter
         plaintext = plaintext[:cutter]
         return plaintext
-    
+
     def decrypt_rc4(self, objid, genno, data):
         key = self.genkey(objid, genno)
         return ARC4.new(key).decrypt(data)
 
 
     KEYWORD_OBJ = PSKeywordTable.intern('obj')
-    
+
     def getobj(self, objid):
         if not self.ready:
             raise PDFException('PDFDocument not initialized')
@@ -1688,7 +1689,7 @@ class PDFDocument(object):
 ##                    if x:
 ##                        objid1 = x[-2]
 ##                        genno = x[-1]
-##                
+##
                 if kwd is not self.KEYWORD_OBJ:
                     raise PDFSyntaxError(
                         'Invalid object spec: offset=%r' % index)
@@ -1700,7 +1701,7 @@ class PDFDocument(object):
             self.objs[objid] = obj
         return obj
 
-                
+
 class PDFObjStmRef(object):
     maxindex = 0
     def __init__(self, objid, stmid, index):
@@ -1710,7 +1711,7 @@ class PDFObjStmRef(object):
         if index > PDFObjStmRef.maxindex:
             PDFObjStmRef.maxindex = index
 
-    
+
 ##  PDFParser
 ##
 class PDFParser(PSStackParser):
@@ -1736,7 +1737,7 @@ class PDFParser(PSStackParser):
         if token is self.KEYWORD_ENDOBJ:
             self.add_results(*self.pop(4))
             return
-        
+
         if token is self.KEYWORD_R:
             # reference to indirect object
             try:
@@ -1747,7 +1748,7 @@ class PDFParser(PSStackParser):
             except PSSyntaxError:
                 pass
             return
-            
+
         if token is self.KEYWORD_STREAM:
             # stream object
             ((_,dic),) = self.pop(1)
@@ -1787,7 +1788,7 @@ class PDFParser(PSStackParser):
             obj = PDFStream(dic, data, self.doc.decipher)
             self.push((pos, obj))
             return
-        
+
         # others
         self.push((pos, token))
         return
@@ -1823,7 +1824,7 @@ class PDFParser(PSStackParser):
             xref.load(self)
         else:
             if token is not self.KEYWORD_XREF:
-                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' % 
+                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
                                      (pos, token))
             self.nextline()
             xref = PDFXRef()
@@ -1838,7 +1839,7 @@ class PDFParser(PSStackParser):
             pos = int_value(trailer['Prev'])
             self.read_xref_from(pos, xrefs)
         return
-        
+
     # read xref tables and trailers
     def read_xref(self):
         xrefs = []
@@ -1957,7 +1958,7 @@ class PDFSerializer(object):
                     self.write("%010d 00000 n \n" % xrefs[objid][0])
                 else:
                     self.write("%010d %05d f \n" % (0, 65535))
-            
+
             self.write('trailer\n')
             self.serialize_object(trailer)
             self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
@@ -1977,7 +1978,7 @@ class PDFSerializer(object):
             while maxindex >= power:
                 fl3 += 1
                 power *= 256
-                    
+
             index = []
             first = None
             prev = None
@@ -2004,14 +2005,14 @@ class PDFSerializer(object):
                     # we force all generation numbers to be 0
                     # f3 = objref[1]
                     f3 = 0
-                
+
                 data.append(struct.pack('>B', f1))
                 data.append(struct.pack('>L', f2)[-fl2:])
                 data.append(struct.pack('>L', f3)[-fl3:])
             index.extend((first, prev - first + 1))
             data = zlib.compress(''.join(data))
             dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
-                   'W': [1, fl2, fl3], 'Length': len(data), 
+                   'W': [1, fl2, fl3], 'Length': len(data),
                    'Filter': LITERALS_FLATE_DECODE[0],
                    'Root': trailer['Root'],}
             if 'Info' in trailer:
@@ -2033,9 +2034,9 @@ class PDFSerializer(object):
         string = string.replace(')', r'\)')
          # get rid of ciando id
         regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
-        if regularexp.match(string): return ('http://www.ciando.com') 
+        if regularexp.match(string): return ('http://www.ciando.com')
         return string
-    
+
     def serialize_object(self, obj):
         if isinstance(obj, dict):
             # Correct malformed Mac OS resource forks for Stanza
@@ -2059,21 +2060,21 @@ class PDFSerializer(object):
         elif isinstance(obj, bool):
             if self.last.isalnum():
                 self.write(' ')
-            self.write(str(obj).lower())            
+            self.write(str(obj).lower())
         elif isinstance(obj, (int, long, float)):
             if self.last.isalnum():
                 self.write(' ')
             self.write(str(obj))
         elif isinstance(obj, PDFObjRef):
             if self.last.isalnum():
-                self.write(' ')            
+                self.write(' ')
             self.write('%d %d R' % (obj.objid, 0))
         elif isinstance(obj, PDFStream):
             ### If we don't generate cross ref streams the object streams
             ### are no longer useful, as we have extracted all objects from
             ### them. Therefore leave them out from the output.
             if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
-                    self.write('(deleted)')
+                self.write('(deleted)')
             else:
                 data = obj.get_decdata()
                 self.serialize_object(obj.dic)
@@ -2085,7 +2086,7 @@ class PDFSerializer(object):
             if data[0].isalnum() and self.last.isalnum():
                 self.write(' ')
             self.write(data)
-    
+
     def serialize_indirect(self, objid, obj):
         self.write('%d 0 obj' % (objid,))
         self.serialize_object(obj)
@@ -2097,7 +2098,7 @@ class PDFSerializer(object):
 class DecryptionDialog(Tkinter.Frame):
     def __init__(self, root):
         Tkinter.Frame.__init__(self, root, border=5)
-        ltext='Select file for decryption\n'        
+        ltext='Select file for decryption\n'
         self.status = Tkinter.Label(self, text=ltext)
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
@@ -2123,7 +2124,7 @@ class DecryptionDialog(Tkinter.Frame):
         button.grid(row=2, column=2)
         buttons = Tkinter.Frame(self)
         buttons.pack()
-  
+
 
         botton = Tkinter.Button(
             buttons, text="Decrypt", width=10, command=self.decrypt)
@@ -2132,7 +2133,7 @@ class DecryptionDialog(Tkinter.Frame):
         button = Tkinter.Button(
             buttons, text="Quit", width=10, command=self.quit)
         button.pack(side=Tkconstants.RIGHT)
-         
+
 
     def get_keypath(self):
         keypath = tkFileDialog.askopenfilename(
index 48a75f996cdd84139fbcab9e9506cf8c68d1eef7..018736acd74626a3ac075f1659e565b28cfbb2aa 100644 (file)
@@ -67,25 +67,25 @@ def _load_crypto_libcrypto():
 
     RSA_NO_PADDING = 3
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
     class RSA(Structure):
         pass
     RSA_p = POINTER(RSA)
-    
+
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
                           [RSA_p, c_char_pp, c_long])
     RSA_size = F(c_int, 'RSA_size', [RSA_p])
@@ -97,7 +97,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class RSA(object):
         def __init__(self, der):
             buf = create_string_buffer(der)
@@ -105,7 +105,7 @@ def _load_crypto_libcrypto():
             rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
             if rsa is None:
                 raise ADEPTError('Error parsing ADEPT user key DER')
-        
+
         def decrypt(self, from_):
             rsa = self._rsa
             to = create_string_buffer(RSA_size(rsa))
@@ -114,7 +114,7 @@ def _load_crypto_libcrypto():
             if dlen < 0:
                 raise ADEPTError('RSA decryption failed')
             return to[:dlen]
-    
+
         def __del__(self):
             if self._rsa is not None:
                 RSA_free(self._rsa)
@@ -130,7 +130,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise ADEPTError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -148,13 +148,13 @@ def _load_crypto_pycrypto():
     # ASN.1 parsing code from tlslite
     class ASN1Error(Exception):
         pass
-    
+
     class ASN1Parser(object):
         class Parser(object):
             def __init__(self, bytes):
                 self.bytes = bytes
                 self.index = 0
-    
+
             def get(self, length):
                 if self.index + length > len(self.bytes):
                     raise ASN1Error("Error decoding ASN.1")
@@ -164,22 +164,22 @@ def _load_crypto_pycrypto():
                     x |= self.bytes[self.index]
                     self.index += 1
                 return x
-    
+
             def getFixBytes(self, lengthBytes):
                 bytes = self.bytes[self.index : self.index+lengthBytes]
                 self.index += lengthBytes
                 return bytes
-    
+
             def getVarBytes(self, lengthLength):
                 lengthBytes = self.get(lengthLength)
                 return self.getFixBytes(lengthBytes)
-    
+
             def getFixList(self, length, lengthList):
                 l = [0] * lengthList
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def getVarList(self, length, lengthLength):
                 lengthList = self.get(lengthLength)
                 if lengthList % length != 0:
@@ -189,19 +189,19 @@ def _load_crypto_pycrypto():
                 for x in range(lengthList):
                     l[x] = self.get(length)
                 return l
-    
+
             def startLengthCheck(self, lengthLength):
                 self.lengthCheck = self.get(lengthLength)
                 self.indexCheck = self.index
-    
+
             def setLengthCheck(self, length):
                 self.lengthCheck = length
                 self.indexCheck = self.index
-    
+
             def stopLengthCheck(self):
                 if (self.index - self.indexCheck) != self.lengthCheck:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
             def atLengthCheck(self):
                 if (self.index - self.indexCheck) < self.lengthCheck:
                     return False
@@ -209,13 +209,13 @@ def _load_crypto_pycrypto():
                     return True
                 else:
                     raise ASN1Error("Error decoding ASN.1")
-    
+
         def __init__(self, bytes):
             p = self.Parser(bytes)
             p.get(1)
             self.length = self._getASN1Length(p)
             self.value = p.getFixBytes(self.length)
-    
+
         def getChild(self, which):
             p = self.Parser(self.value)
             for x in range(which+1):
@@ -224,7 +224,7 @@ def _load_crypto_pycrypto():
                 length = self._getASN1Length(p)
                 p.getFixBytes(length)
             return ASN1Parser(p.bytes[markIndex:p.index])
-    
+
         def _getASN1Length(self, p):
             firstLength = p.get(1)
             if firstLength<=127:
@@ -252,7 +252,7 @@ def _load_crypto_pycrypto():
             for byte in bytes:
                 total = (total << 8) + byte
             return total
-    
+
         def decrypt(self, data):
             return self._rsa.decrypt(data)
 
index 8eab14fb0200772d2f27cf6fc65239a99a6f7e92..da61e77eb4ab05593ecd0a6f4a5a1700edf71a09 100644 (file)
@@ -76,13 +76,13 @@ if sys.platform.startswith('win'):
             _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                         ('rounds', c_int)]
         AES_KEY_p = POINTER(AES_KEY)
-    
+
         def F(restype, name, argtypes):
             func = getattr(libcrypto, name)
             func.restype = restype
             func.argtypes = argtypes
             return func
-    
+
         AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
                                 [c_char_p, c_int, AES_KEY_p])
         AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -427,8 +427,8 @@ def extractKeyfile(keypath):
         print "Key generation Error: " + str(e)
         return 1
     except Exception, e:
-       print "General Error: " + str(e)
-       return 1
+        print "General Error: " + str(e)
+        return 1
     if not success:
         return 1
     return 0
index a7c48c9620d0f3442b0903e76e36c36a305f1fc9..917aa4aaa16210a7532e733a4fd667633ad8a4d9 100644 (file)
@@ -14,7 +14,7 @@ from __future__ import with_statement
 #   2 - Added OS X support by using OpenSSL when available
 #   3 - screen out improper key lengths to prevent segfaults on Linux
 #   3.1 - Allow Windows versions of libcrypto to be found
-#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml 
+#   3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
 #   3.3 - On Windows try PyCrypto first and OpenSSL next
 #   3.4 - Modify interace to allow use with import
 
@@ -50,7 +50,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -58,13 +58,13 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
@@ -73,7 +73,7 @@ def _load_crypto_libcrypto():
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
-    
+
     class AES(object):
         def __init__(self, userkey):
             self._blocksize = len(userkey)
@@ -84,7 +84,7 @@ def _load_crypto_libcrypto():
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES key')
-    
+
         def decrypt(self, data):
             out = create_string_buffer(len(data))
             iv = ("\x00" * self._blocksize)
@@ -122,7 +122,7 @@ def _load_crypto():
 
 AES = _load_crypto()
 
+
 
 """
 Decrypt Barnes & Noble ADEPT encrypted EPUB books.
index cdedc48b698d5f6fcd3fb07478e7479580d410be..e7a78ea19a7d60c2b2679d3b548d5162ca193c12 100644 (file)
@@ -53,7 +53,7 @@ def _load_crypto_libcrypto():
     libcrypto = CDLL(libcrypto)
 
     AES_MAXNR = 14
-    
+
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
 
@@ -61,28 +61,28 @@ def _load_crypto_libcrypto():
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
                     ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
                             [c_char_p, c_int, AES_KEY_p])
     AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
                         [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
                          c_int])
     class AES(object):
-         def __init__(self, userkey, iv):
+        def __init__(self, userkey, iv):
             self._blocksize = len(userkey)
             self._iv = iv
             key = self._key = AES_KEY()
             rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
                 raise IGNOBLEError('Failed to initialize AES Encrypt key')
-    
-         def encrypt(self, data):
+
+        def encrypt(self, data):
             out = create_string_buffer(len(data))
             rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
             if rv == 0:
diff --git a/Other_Tools/KindleBooks/lib/aescbc.py b/Other_Tools/KindleBooks/lib/aescbc.py
new file mode 100644 (file)
index 0000000..5667511
--- /dev/null
@@ -0,0 +1,568 @@
+#! /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'
diff --git a/Other_Tools/KindleBooks/lib/alfcrypto.dll b/Other_Tools/KindleBooks/lib/alfcrypto.dll
new file mode 100644 (file)
index 0000000..26d740d
Binary files /dev/null and b/Other_Tools/KindleBooks/lib/alfcrypto.dll differ
diff --git a/Other_Tools/KindleBooks/lib/alfcrypto.py b/Other_Tools/KindleBooks/lib/alfcrypto.py
new file mode 100644 (file)
index 0000000..e25a0c8
--- /dev/null
@@ -0,0 +1,290 @@
+#! /usr/bin/env python
+
+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'
+    
+    libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found')
+
+    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 "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:
+                print "Bad key length!"
+                return None
+            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
+
+    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]
+
+
diff --git a/Other_Tools/KindleBooks/lib/alfcrypto64.dll b/Other_Tools/KindleBooks/lib/alfcrypto64.dll
new file mode 100644 (file)
index 0000000..7bef68e
Binary files /dev/null and b/Other_Tools/KindleBooks/lib/alfcrypto64.dll differ
diff --git a/Other_Tools/KindleBooks/lib/alfcrypto_src.zip b/Other_Tools/KindleBooks/lib/alfcrypto_src.zip
new file mode 100644 (file)
index 0000000..269810c
Binary files /dev/null and b/Other_Tools/KindleBooks/lib/alfcrypto_src.zip differ
index 0328206ac8b4d8ac5725f85e62cdc7b71febe53b..98645372c97d251bbeda73e30138c9dbaaff6789 100644 (file)
@@ -23,7 +23,7 @@ from struct import unpack
 class TpzDRMError(Exception):
     pass
 
-# Get a 7 bit encoded number from string. The most 
+# Get a 7 bit encoded number from string. The most
 # significant byte comes first and has the high bit (8th) set
 
 def readEncodedNumber(file):
@@ -32,57 +32,57 @@ def readEncodedNumber(file):
     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)
-       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
-    
+        data = datax
+
     if flag:
-       data = -data
+        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]
-  
+    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
@@ -97,9 +97,9 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    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
 
@@ -265,6 +265,8 @@ class PageParser(object):
         '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),
@@ -284,6 +286,8 @@ class PageParser(object):
         '_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),
@@ -291,6 +295,8 @@ class PageParser(object):
         '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.type'       : (1, 'scalar_text', 0, 0),
@@ -376,14 +382,14 @@ class PageParser(object):
         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 
+    # 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):
@@ -416,7 +422,7 @@ class PageParser(object):
         return result
 
 
-    # process the next tag token, recursively handling subtags, 
+    # process the next tag token, recursively handling subtags,
     # arguments, and commands
     def procToken(self, token):
 
@@ -438,7 +444,7 @@ class PageParser(object):
 
         if known_token :
 
-            # handle subtags if present 
+            # handle subtags if present
             subtagres = []
             if (splcase == 1):
                 # this type of tag uses of escape marker 0x74 indicate subtag count
@@ -447,7 +453,7 @@ class PageParser(object):
                     subtags = 1
                     num_args = 0
 
-            if (subtags == 1): 
+            if (subtags == 1):
                 ntags = readEncodedNumber(self.fo)
                 if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
                 for j in xrange(ntags):
@@ -478,7 +484,7 @@ class PageParser(object):
             return result
 
         # all tokens that need to be processed should be in the hash
-        # table if it may indicate a problem, either new token 
+        # table if it may indicate a problem, either new token
         # or an out of sync condition
         else:
             result = []
@@ -530,7 +536,7 @@ class PageParser(object):
     # 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 
+    # 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
 
@@ -549,7 +555,7 @@ class PageParser(object):
         return result
 
 
-            
+
     # add full tag path to injected snippets
     def updateName(self, tag, prefix):
         name = tag[0]
@@ -577,7 +583,7 @@ class PageParser(object):
         argtype = tag[2]
         argList = tag[3]
         nsubtagList = []
-        if len(argList) > 0 : 
+        if len(argList) > 0 :
             for j in argList:
                 asnip = self.snippetList[j]
                 aso, atag = self.injectSnippets(asnip)
@@ -609,65 +615,70 @@ class PageParser(object):
         nodename = fullpathname.pop()
         ilvl = len(fullpathname)
         indent = ' ' * (3 * ilvl)
-        result = indent + '<' + nodename + '>'
+        rlst = []
+        rlst.append(indent + '<' + nodename + '>')
         if len(argList) > 0:
-            argres = ''
+            alst = []
             for j in argList:
                 if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
+                    alst.append(j + '|')
                 else :
-                    argres += str(j) + ','
+                    alst.append(str(j) + ',')
+            argres = "".join(alst)
             argres = argres[0:-1]
             if argtype == 'snippets' :
-                result += 'snippets:' + argres
+                rlst.append('snippets:' + argres)
             else :
-                result += argres
+                rlst.append(argres)
         if len(subtagList) > 0 :
-            result += '\n'
+            rlst.append('\n')
             for j in subtagList:
                 if len(j) > 0 :
-                    result += self.formatTag(j)
-            result += indent + '</' + nodename + '>\n'
+                    rlst.append(self.formatTag(j))
+            rlst.append(indent + '</' + nodename + '>\n')
         else:
-            result += '</' + nodename + '>\n'
-        return result
+            rlst.append('</' + nodename + '>\n')
+        return "".join(rlst)
 
 
-   # flatten tag
+    # flatten tag
     def flattenTag(self, node):
         name = node[0]
         subtagList = node[1]
         argtype = node[2]
         argList = node[3]
-        result = name
+        rlst = []
+        rlst.append(name)
         if (len(argList) > 0):
-            argres = ''
+            alst = []
             for j in argList:
                 if (argtype == 'text') or (argtype == 'scalar_text') :
-                    argres += j + '|'
+                    alst.append(j + '|')
                 else :
-                    argres += str(j) + '|'
+                    alst.append(str(j) + '|')
+            argres = "".join(alst)
             argres = argres[0:-1]
             if argtype == 'snippets' :
-                result += '.snippets=' + argres
+                rlst.append('.snippets=' + argres)
             else :
-                result += '=' + argres
-        result += '\n'
+                rlst.append('=' + argres)
+        rlst.append('\n')
         for j in subtagList:
             if len(j) > 0 :
-                result += self.flattenTag(j)
-        return result
+                rlst.append(self.flattenTag(j))
+        return "".join(rlst)
 
 
     # reduce create xml output
     def formatDoc(self, flat_xml):
-        result = ''
+        rlst = []
         for j in self.doc :
             if len(j) > 0:
                 if flat_xml:
-                    result += self.flattenTag(j)
+                    rlst.append(self.flattenTag(j))
                 else:
-                    result += self.formatTag(j)
+                    rlst.append(self.formatTag(j))
+        result = "".join(rlst)
         if self.debug : print result
         return result
 
@@ -712,7 +723,7 @@ class PageParser(object):
                 first_token = None
 
             v = self.getNext()
-            if (v == None): 
+            if (v == None):
                 break
 
             if (v == 0x72):
@@ -723,7 +734,7 @@ class PageParser(object):
                     self.doc.append(tag)
             else:
                 if self.debug:
-                    print "Main Loop:  Unknown value: %x" % v 
+                    print "Main Loop:  Unknown value: %x" % v
                 if (v == 0):
                     if (self.peek(1) == 0x5f):
                         skip = self.fo.read(1)
@@ -776,7 +787,7 @@ def usage():
 
 #
 # Main
-#   
+#
 
 def main(argv):
     dictFile = ""
@@ -797,11 +808,11 @@ def main(argv):
         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) 
-       
+        sys.exit(2)
+
     for o, a in opts:
         if o =="-d":
             debug=True
index 3b32fc0a945bbae23fac4dc0146ad4cad9685752..e5647f4bc3842abb1546088d41b2c899fa88c36a 100644 (file)
@@ -68,7 +68,7 @@ class DocParser(object):
         ys = []
         gdefs = []
 
-        # get path defintions, positions, dimensions for each glyph 
+        # 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
@@ -79,7 +79,7 @@ class DocParser(object):
             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])
@@ -124,12 +124,12 @@ class DocParser(object):
             item = self.docList[pos]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            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
@@ -142,10 +142,10 @@ class DocParser(object):
             item = self.docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -182,13 +182,13 @@ class DocParser(object):
         # 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 
+        # 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(' ','-')
@@ -211,7 +211,7 @@ class DocParser(object):
         return nclass
 
 
-    # develop a sorted description of the starting positions of 
+    # develop a sorted description of the starting positions of
     # groups and regions on the page, as well as the page type
     def PageDescription(self):
 
@@ -267,7 +267,7 @@ class DocParser(object):
         result = []
 
         # paragraph
-        (pos, pclass) = self.findinDoc('paragraph.class',start,end) 
+        (pos, pclass) = self.findinDoc('paragraph.class',start,end)
 
         pclass = self.getClass(pclass)
 
@@ -281,17 +281,22 @@ class DocParser(object):
         if (sfirst != None) and (slast != None) :
             first = int(sfirst)
             last = int(slast)
-            
+
             makeImage = (regtype == 'vertical') or (regtype == 'table')
-            makeImage = makeImage or (extraglyphs != None) 
+            makeImage = makeImage or (extraglyphs != None)
             if self.fixedimage:
                 makeImage = makeImage or (regtype == 'fixed')
 
-            if (pclass != None): 
+            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):
@@ -332,10 +337,10 @@ class DocParser(object):
             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, 
+        # 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 = ''
@@ -344,7 +349,7 @@ class DocParser(object):
         if end == -1 :
             end = self.docSize
 
-        # seems some xml has last* coming before first* so we have to 
+        # seems some xml has last* coming before first* so we have to
         # handle any order
         sp_first = -1
         sp_last = -1
@@ -382,10 +387,10 @@ class DocParser(object):
                 ws_last = int(argres)
 
             elif name.endswith('word.class'):
-               (cname, space) = argres.split('-',1)
-               if space == '' : space = '0'
-               if (cname == 'spaceafter') and (int(space) > 0) :
-                   word_class = 'sa'
+                (cname, space) = argres.split('-',1)
+                if space == '' : space = '0'
+                if (cname == 'spaceafter') and (int(space) > 0) :
+                    word_class = 'sa'
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
@@ -416,11 +421,11 @@ class DocParser(object):
                     result.append(('ocr', wordnum))
                 ws_first = -1
                 ws_last = -1
-                              
+
             line += 1
 
         return pclass, result
-                            
+
 
     def buildParagraph(self, pclass, pdesc, type, regtype) :
         parares = ''
@@ -433,7 +438,7 @@ class DocParser(object):
         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 + '>'
 
@@ -462,7 +467,7 @@ class DocParser(object):
                         if linktype == 'external' :
                             linkhref = self.link_href[link-1]
                             linkhtml = '<a href="%s">' % linkhref
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkhtml = '<a href="#page%04d">' % ptarget
@@ -509,7 +514,7 @@ class DocParser(object):
 
             elif wtype == 'svg' :
                 sep = ''
-                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num 
+                parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
                 parares += sep
 
         if len(sep) > 0 : parares = parares[0:-1]
@@ -551,7 +556,7 @@ class DocParser(object):
                             title = ''
                             alt_title = ''
                             linkpage = ''
-                        else : 
+                        else :
                             if len(self.link_page) >= link :
                                 ptarget = self.link_page[link-1] - 1
                                 linkpage = '%04d' % ptarget
@@ -584,14 +589,14 @@ class DocParser(object):
 
 
 
-    
+
     # walk the document tree collecting the information needed
     # to build an html page using the ocrText
 
     def process(self):
 
-        htmlpage = ''
         tocinfo = ''
+        hlst = []
 
         # get the ocr text
         (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
@@ -602,8 +607,8 @@ class DocParser(object):
 
         # 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) 
-        
+        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)
@@ -631,25 +636,25 @@ class DocParser(object):
 
         # get a descriptions of the starting points of the regions
         # and groups on the page
-        (pagetype, pageDesc) = self.PageDescription() 
+        (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:
-                htmlpage += '<div style="visibility: hidden; height: 0; width: 0;" id="' 
-                htmlpage += self.id + '" title="pagetype_' + pagetype + '"></div>\n'
+                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
@@ -658,12 +663,12 @@ class DocParser(object):
                 if grptype != None:
                     if grptype == 'graphic':
                         gcstr = ' class="' + grptype + '"'
-                        htmlpage += '<div' + gcstr + '>'
+                        hlst.append('<div' + gcstr + '>')
                         inGroup = True
-                
+
             elif (etype == 'grpend'):
                 if inGroup:
-                    htmlpage += '</div>\n'
+                    hlst.append('</div>\n')
                     inGroup = False
 
             else:
@@ -673,25 +678,25 @@ class DocParser(object):
                     (pos, simgsrc) = self.findinDoc('img.src',start,end)
                     if simgsrc:
                         if inGroup:
-                            htmlpage += '<img src="img/img%04d.jpg" alt="" />' % int(simgsrc)
+                            hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
                         else:
-                            htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)
-            
+                            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:
-                        htmlpage += '<div style="page-break-after: always;">&nbsp;</div>\n'
+                        hlst.append('<div style="page-break-after: always;">&nbsp;</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'
-                        htmlpage += '<' + tag + ' class="' + pclass + '">'
+                        hlst.append('<' + tag + ' class="' + pclass + '">')
                     else:
-                        htmlpage += '<' + tag + '>'
-                    htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                    htmlpage += '</' + tag + '>'
+                        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'
@@ -705,11 +710,11 @@ class DocParser(object):
                         if pclass[3:6] == 'h1-' : tag = 'h4'
                         if pclass[3:6] == 'h2-' : tag = 'h5'
                         if pclass[3:6] == 'h3-' : tag = 'h6'
-                        htmlpage += '<' + tag + ' class="' + pclass + '">'
-                        htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                        htmlpage += '</' + tag + '>'
+                        hlst.append('<' + tag + ' class="' + pclass + '">')
+                        hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+                        hlst.append('</' + tag + '>')
                     else :
-                        htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                        hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
                 elif (regtype == 'tocentry') :
                     ptype = 'full'
@@ -718,7 +723,7 @@ class DocParser(object):
                         first_para_continued = False
                     (pclass, pdesc) = self.getParaDescription(start,end, regtype)
                     tocinfo += self.buildTOCEntry(pdesc)
-                    htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                    hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
                 elif (regtype == 'vertical') or (regtype == 'table') :
                     ptype = 'full'
@@ -728,13 +733,13 @@ class DocParser(object):
                         ptype = 'end'
                         first_para_continued = False
                     (pclass, pdesc) = self.getParaDescription(start, end, regtype)
-                    htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                    hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
 
 
                 elif (regtype == 'synth_fcvr.center'):
                     (pos, simgsrc) = self.findinDoc('img.src',start,end)
                     if simgsrc:
-                        htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc)
+                        hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
 
                 else :
                     print '          Making region type', regtype,
@@ -760,18 +765,19 @@ class DocParser(object):
                             if pclass[3:6] == 'h1-' : tag = 'h4'
                             if pclass[3:6] == 'h2-' : tag = 'h5'
                             if pclass[3:6] == 'h3-' : tag = 'h6'
-                            htmlpage += '<' + tag + ' class="' + pclass + '">'
-                            htmlpage += self.buildParagraph(pclass, pdesc, 'middle', regtype)
-                            htmlpage += '</' + tag + '>'
+                            hlst.append('<' + tag + ' class="' + pclass + '">')
+                            hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+                            hlst.append('</' + tag + '>')
                         else :
-                            htmlpage += self.buildParagraph(pclass, pdesc, ptype, regtype)
+                            hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
                     else :
                         print ' a "graphic" region'
                         (pos, simgsrc) = self.findinDoc('img.src',start,end)
                         if simgsrc:
-                            htmlpage += '<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(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]
index 49cf6f5c81c5ab3bebed310c69dd601b69fcb62c..4dfd6c7bbfae633633100610805ddbb13334fd46 100644 (file)
@@ -15,7 +15,7 @@ class PParser(object):
         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')
@@ -26,7 +26,7 @@ class PParser(object):
         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:
@@ -181,70 +181,69 @@ class PParser(object):
 
 
 def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
-    ml = ''
+    mlst = []
     pp = PParser(gdict, flat_xml, meta_array)
-    ml += '<?xml version="1.0" standalone="no"?>\n'
+    mlst.append('<?xml version="1.0" standalone="no"?>\n')
     if (raw):
-        ml += '<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
-        ml += '<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)
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
+        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:
-        ml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-        ml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" ><head>\n'
-        ml += '<title>Page %d - %s by %s</title>\n' % (pageid, meta_array['Title'],meta_array['Authors'])
-        ml += '<script><![CDATA[\n'
-        ml += 'function gd(){var p=window.location.href.replace(/^.*\?dpi=(\d+).*$/i,"$1");return p;}\n'
-        ml += 'var dpi=%d;\n' % scaledpi
+        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) :
-            ml += 'var prevpage="page%04d.xhtml";\n' % (previd)
+            mlst.append('var prevpage="page%04d.xhtml";\n' % (previd))
         if (nextid) :
-            ml += 'var nextpage="page%04d.xhtml";\n' % (nextid)
-        ml += 'var pw=%d;var ph=%d;' % (pp.pw, pp.ph)
-        ml += 'function zoomin(){dpi=dpi*(0.8);setsize();}\n'
-        ml += 'function zoomout(){dpi=dpi*1.25;setsize();}\n'
-        ml += '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'
-        ml += 'function ppage(){window.location.href=prevpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'function npage(){window.location.href=nextpage+"?dpi="+Math.round(dpi);}\n'
-        ml += 'var gt=gd();if(gt>0){dpi=gt;}\n'
-        ml += 'window.onload=setsize;\n'
-        ml += ']]></script>\n'
-        ml += '</head>\n'
-        ml += '<body onLoad="setsize();" style="background-color:#777;text-align:center;">\n'
-        ml += '<div style="white-space:nowrap;">\n'
+            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:
-            ml += '<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'
+            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:
-            ml += '<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'
-        
-        ml += '<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): 
-        ml += '<defs>\n'
+            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)):
-            ml += gdefs[j]
-        ml += '</defs>\n'
+            mlst.append(gdefs[j])
+        mlst.append('</defs>\n')
     img = pp.getImages()
     if (img != None):
         for j in xrange(0,len(img)):
-            ml += img[j]
-    if (pp.gid != None): 
+            mlst.append(img[j])
+    if (pp.gid != None):
         for j in xrange(0,len(pp.gid)):
-            ml += '<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j])
+            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)
-        ml += '<text x="' + xpos + '" y="' + ypos + '" font-size="' + meta_array['fontSize'] + '" font-family="Helvetica" stroke="black">This page intentionally left blank.</text>\n'
+        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) :
-        ml += '</svg>'
+        mlst.append('</svg>')
     else :
-        ml += '</svg></a>\n'
+        mlst.append('</svg></a>\n')
         if nextid == None:
-            ml += '<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'
+            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 :
-            ml += '<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'
-        ml += '</div>\n'
-        ml += '<div><a href="javascript:zoomin();">zoom in</a> - <a href="javascript:zoomout();">zoom out</a></div>\n'
-        ml += '</body>\n'
-        ml += '</html>\n'
-    return ml
-
+            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)
index 9ad87ea1800240af4a7f29881573d8ad1d0b65d5..a412a7b46460a6ef326d997564d6408ea378286f 100644 (file)
@@ -39,6 +39,8 @@ else :
     import flatxml2svg
     import stylexml2css
 
+# global switch
+buildXML = False
 
 # Get a 7 bit encoded number from a file
 def readEncodedNumber(file):
@@ -46,27 +48,27 @@ def readEncodedNumber(file):
     c = file.read(1)
     if (len(c) == 0):
         return None
-    data = ord(c)    
+    data = ord(c)
     if data == 0xFF:
-       flag = True
-       c = file.read(1)
-       if (len(c) == 0):
-           return None
-       data = ord(c)       
+        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): 
+            if (len(c) == 0):
                 return None
             data = ord(c)
             datax = (datax <<7) + (data & 0x7F)
-        data = datax 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
 
-# Get a length prefixed string from the file 
+# Get a length prefixed string from the file
 def lengthPrefixString(data):
     return encodeNumber(len(data))+data
 
@@ -77,7 +79,7 @@ def readString(file):
     sv = file.read(stringLength)
     if (len(sv)  != stringLength):
         return ""
-    return unpack(str(stringLength)+"s",sv)[0]  
+    return unpack(str(stringLength)+"s",sv)[0]
 
 def getMetaArray(metaFile):
     # parse the meta file
@@ -141,10 +143,10 @@ class PageDimParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=')
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -298,9 +300,10 @@ def generateBook(bookDir, raw, fixedimage):
     if not os.path.exists(svgDir) :
         os.makedirs(svgDir)
 
-    xmlDir = os.path.join(bookDir,'xml')
-    if not os.path.exists(xmlDir) :
-        os.makedirs(xmlDir)
+    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) :
@@ -336,7 +339,7 @@ def generateBook(bookDir, raw, fixedimage):
     print 'Processing Meta Data and creating OPF'
     meta_array = getMetaArray(metaFile)
 
-    # replace special chars in title and authors like & < > 
+    # replace special chars in title and authors like & < >
     title = meta_array.get('Title','No Title Provided')
     title = title.replace('&','&amp;')
     title = title.replace('<','&lt;')
@@ -348,11 +351,14 @@ def generateBook(bookDir, raw, fixedimage):
     authors = authors.replace('>','&gt;')
     meta_array['Authors'] = authors
 
-    xname = os.path.join(xmlDir, 'metadata.xml')
-    metastr = ''
-    for key in meta_array:
-        metastr += '<meta name="' + key + '" content="' + meta_array[key] + '" />\n'
-    file(xname, 'wb').write(metastr)
+    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
@@ -404,8 +410,9 @@ def generateBook(bookDir, raw, fixedimage):
     # now get the css info
     cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
     file(xname, 'wb').write(cssstr)
-    xname = os.path.join(xmlDir, 'other0000.xml')
-    file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
+    if buildXML:
+        xname = os.path.join(xmlDir, 'other0000.xml')
+        file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
 
     print 'Processing Glyphs'
     gd = GlyphDict()
@@ -425,8 +432,9 @@ def generateBook(bookDir, raw, fixedimage):
         fname = os.path.join(glyphsDir,filename)
         flat_xml = convert2xml.fromData(dict, fname)
 
-        xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
-        file(xname, 'wb').write(convert2xml.getXML(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):
@@ -441,29 +449,29 @@ def generateBook(bookDir, raw, fixedimage):
     glyfile.close()
     print " "
 
-    # build up tocentries while processing html
-    tocentries = ''
 
     # start up the html
+    # also build up tocentries while processing html
     htmlFileName = "book.html"
-    htmlstr = '<?xml version="1.0" encoding="utf-8"?>\n'
-    htmlstr += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1 Strict//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">\n'
-    htmlstr += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'
-    htmlstr += '<head>\n'
-    htmlstr += '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n'
-    htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' 
-    htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        hlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
-    htmlstr += '</head>\n<body>\n'
+        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.  
+    # readability when rendering to the screen.
     scaledpi = 1440.0
 
     filenames = os.listdir(pageDir)
@@ -471,6 +479,7 @@ def generateBook(bookDir, raw, fixedimage):
     numfiles = len(filenames)
 
     xmllst = []
+    elst = []
 
     for filename in filenames:
         # print '     ', filename
@@ -481,45 +490,51 @@ def generateBook(bookDir, raw, fixedimage):
         # keep flat_xml for later svg processing
         xmllst.append(flat_xml)
 
-        xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
-        file(xname, 'wb').write(convert2xml.getXML(dict, fname))
+        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)
-        tocentries += tocinfo 
-        htmlstr += pagehtml
+        elst.append(tocinfo)
+        hlst.append(pagehtml)
 
     # finish up the html string and output it
-    htmlstr += '</body>\n</html>\n'
+    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
-    tochtml = '<?xml version="1.0" encoding="utf-8"?>\n'
-    tochtml += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-    tochtml += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >'
-    tochtml += '<head>\n'
-    tochtml += '<title>' + meta_array['Title'] + '</title>\n'
-    tochtml += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    tochtml += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        tochtml += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        tlst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        tochtml += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    tochtml += '</head>\n'
-    tochtml += '<body>\n'
+        tlst.append('<meta name="GUID" content="' + meta_array['GUID'] + '" />\n')
+    tlst.append('</head>\n')
+    tlst.append('<body>\n')
 
-    tochtml += '<h2>Table of Contents</h2>\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
 
-    tochtml += '<h3><a href="' + startname + '">Start of Book</a></h3>\n'
+    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:
@@ -530,30 +545,32 @@ def generateBook(bookDir, raw, fixedimage):
             fname = 'page%04d.svg' % id
         else:
             fname = 'page%04d.xhtml' % id
-        tochtml += '<h3><a href="'+ fname + '">' + title + '</a></h3>\n'
-    tochtml += '</body>\n'
-    tochtml += '</html>\n'
+        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
-    svgindex = '<?xml version="1.0" encoding="utf-8"?>\n'
-    svgindex += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-    svgindex += '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >'
-    svgindex += '<head>\n'
-    svgindex += '<title>' + meta_array['Title'] + '</title>\n'
-    svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
-    svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
+    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:
-        svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
+        slst.append('<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n')
     if 'GUID' in meta_array:
-        svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
-    svgindex += '</head>\n'
-    svgindex += '<body>\n'
+        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"
-    svgindex += '<h2>List of Pages</h2>\n'
-    svgindex += '<div>\n'
+    slst.append('<h2>List of Pages</h2>\n')
+    slst.append('<div>\n')
     idlst = sorted(pageIDMap.keys())
     numids = len(idlst)
     cnt = len(idlst)
@@ -566,49 +583,54 @@ def generateBook(bookDir, raw, fixedimage):
             nextid = None
         print '.',
         pagelst = pageIDMap[pageid]
-        flat_svg = ''
+        flst = []
         for page in pagelst:
-            flat_svg += xmllst[page]
+            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')
-            svgindex += '<a href="svg/page%04d.svg">Page %d</a>\n' % (pageid, pageid)
+            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')
-            svgindex += '<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid)
+            slst.append('<a href="svg/page%04d.xhtml">Page %d</a>\n' % (pageid, pageid))
         previd = pageid
         pfile.write(svgxml)
         pfile.close()
         counter += 1
-    svgindex += '</div>\n'
-    svgindex += '<h2><a href="svg/toc.xhtml">Table of Contents</a></h2>\n'
-    svgindex += '</body>\n</html>\n'
+    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')
-    opfstr = '<?xml version="1.0" encoding="utf-8"?>\n'
-    opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
+    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
-    opfstr += '   <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
+    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:
-        opfstr += '      <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
+        olst.append('      <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
     if 'ASIN' in meta_array:
-        opfstr += '      <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
+        olst.append('      <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
     if 'oASIN' in meta_array:
-        opfstr += '      <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
-    opfstr += '      <dc:title>' + meta_array['Title'] + '</dc:title>\n'
-    opfstr += '      <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
-    opfstr += '      <dc:language>en</dc:language>\n'
-    opfstr += '      <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n'
+        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:
-        opfstr += '      <meta name="cover" content="bookcover"/>\n'
-    opfstr += '   </metadata>\n'
-    opfstr += '<manifest>\n'
-    opfstr += '   <item id="book" href="book.html" media-type="application/xhtml+xml"/>\n'
-    opfstr += '   <item id="stylesheet" href="style.css" media-type="text/css"/>\n'
+        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)
@@ -618,17 +640,19 @@ def generateBook(bookDir, raw, fixedimage):
             imgext = 'jpeg'
         if imgext == '.svg':
             imgext = 'svg+xml'
-        opfstr += '   <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n'
+        olst.append('   <item id="' + imgname + '" href="img/' + filename + '" media-type="image/' + imgext + '"/>\n')
     if isCover:
-        opfstr += '   <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n'
-    opfstr += '</manifest>\n'
+        olst.append('   <item id="bookcover" href="cover.jpg" media-type="image/jpeg" />\n')
+    olst.append('</manifest>\n')
     # adding spine
-    opfstr += '<spine>\n   <itemref idref="book" />\n</spine>\n'
+    olst.append('<spine>\n   <itemref idref="book" />\n</spine>\n')
     if isCover:
-        opfstr += '   <guide>\n'
-        opfstr += '      <reference href="cover.jpg" type="cover" title="Cover"/>\n'
-        opfstr += '   </guide>\n'
-    opfstr += '</package>\n'
+        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'
@@ -649,7 +673,6 @@ def usage():
 
 def main(argv):
     bookDir = ''
-
     if len(argv) == 0:
         argv = sys.argv
 
@@ -663,7 +686,7 @@ def main(argv):
 
     if len(opts) == 0 and len(args) == 0 :
         usage()
-        return 1 
+        return 1
 
     raw = 0
     fixedimage = True
index d962a029e69fc24286df87ccf19b359b03c337e5..828c6e2ef9da27aa6e1482f57a66337e9c705c3e 100644 (file)
@@ -5,19 +5,19 @@ from __future__ import with_statement
 # engine to remove drm from Kindle for Mac and Kindle for PC books
 # for personal use for archiving and converting your ebooks
 
-# PLEASE DO NOT PIRATE EBOOKS! 
+# PLEASE DO NOT PIRATE EBOOKS!
 
 # We want all authors and publishers, and eBook stores to live
-# long and prosperous lives but at the same time  we just want to 
-# be able to read OUR books on whatever device we want and to keep 
+# long and prosperous lives but at the same time  we just want to
+# be able to read OUR books on whatever device we want and to keep
 # readable for a long, long time
 
-#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
-#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
 #    and many many others
 
 
-__version__ = '3.9'
+__version__ = '4.0'
 
 class Unbuffered:
     def __init__(self, stream):
@@ -34,6 +34,8 @@ import string
 import re
 import traceback
 
+buildXML = False
+
 class DrmException(Exception):
     pass
 
@@ -50,7 +52,7 @@ else:
     import mobidedrm
     import topazextract
     import kgenpids
-        
+
 
 # cleanup bytestring filenames
 # borrowed from calibre from calibre/src/calibre/__init__.py
@@ -75,6 +77,8 @@ def cleanup_name(name):
     return one
 
 def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
+    global buildXML
+
     # handle the obvious cases at the beginning
     if not os.path.isfile(infile):
         print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
@@ -100,14 +104,14 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         outfilename = outfilename + "_" + filenametitle
     elif outfilename[:8] != filenametitle[:8]:
         outfilename = outfilename[:8] + "_" + filenametitle
-        
+
     # avoid excessively long file names
     if len(outfilename)>150:
         outfilename = outfilename[:150]
 
     # build pid list
     md1, md2 = mb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles)
 
     try:
         mb.processBook(pidlst)
@@ -128,9 +132,9 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
         else:
             outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
         mb.getMobiFile(outfile)
-        return 0            
+        return 0
 
-    # topaz: 
+    # topaz:
     print "   Creating NoDRM HTMLZ Archive"
     zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
     mb.getHTMLZip(zipname)
@@ -139,9 +143,10 @@ def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
     zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
     mb.getSVGZip(zipname)
 
-    print "   Creating XML ZIP Archive"
-    zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
-    mb.getXMLZip(zipname)
+    if buildXML:
+        print "   Creating XML ZIP Archive"
+        zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
+        mb.getXMLZip(zipname)
 
     # remove internal temporary directory of Topaz pieces
     mb.cleanup()
@@ -156,7 +161,7 @@ def usage(progname):
 
 #
 # Main
-#   
+#
 def main(argv=sys.argv):
     progname = os.path.basename(argv[0])
 
@@ -164,9 +169,9 @@ def main(argv=sys.argv):
     kInfoFiles = []
     serials = []
     pids = []
-    
+
     print ('K4MobiDeDrm v%(__version__)s '
-          'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+           'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
 
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
@@ -177,7 +182,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         sys.exit(2)
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -195,8 +200,8 @@ def main(argv=sys.argv):
     # try with built in Kindle Info files
     k4 = True
     if sys.platform.startswith('linux'):
-       k4 = False
-       kInfoFiles = None
+        k4 = False
+        kInfoFiles = None
     infile = args[0]
     outdir = args[1]
     return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
@@ -205,4 +210,3 @@ def main(argv=sys.argv):
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 7d5130cbf56a24828c2f542bcbaec1d982e60942..e66e9f3c76ec750d283d6dc12542011256a85db9 100644 (file)
@@ -5,7 +5,8 @@ from __future__ import with_statement
 import sys
 import os
 import os.path
-
+import re
+import copy
 import subprocess
 from struct import pack, unpack, unpack_from
 
@@ -24,6 +25,25 @@ def _load_crypto_libcrypto():
         raise DrmException('libcrypto not found')
     libcrypto = CDLL(libcrypto)
 
+    # From OpenSSL's crypto aes header
+    #
+    # AES_ENCRYPT     1
+    # AES_DECRYPT     0
+    # AES_MAXNR 14 (in bytes)
+    # AES_BLOCK_SIZE 16 (in bytes)
+    # 
+    # 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);
+    #
+    # note:  the ivec string, and output buffer are both mutable
+    # 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
     c_char_pp = POINTER(c_char_p)
     c_int_p = POINTER(c_int)
@@ -31,25 +51,31 @@ def _load_crypto_libcrypto():
     class AES_KEY(Structure):
         _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
     AES_KEY_p = POINTER(AES_KEY)
-    
+
     def F(restype, name, argtypes):
         func = getattr(libcrypto, name)
         func.restype = restype
         func.argtypes = argtypes
         return func
-    
+
     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])
 
-    PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', 
+    # From OpenSSL's Crypto evp/p5_crpt2.c
+    #
+    # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
+    #                        const unsigned char *salt, int saltlen, int iter,
+    #                        int keylen, unsigned char *out);
+
+    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])
-    
+
     class LibCrypto(object):
         def __init__(self):
             self._blocksize = 0
             self._keyctx = None
-            self.iv = 0
+            self._iv = 0
 
         def set_decrypt_key(self, userkey, iv):
             self._blocksize = len(userkey)
@@ -57,14 +83,17 @@ def _load_crypto_libcrypto():
                 raise DrmException('AES improper key used')
                 return
             keyctx = self._keyctx = AES_KEY()
-            self.iv = iv
+            self._iv = iv
+            self._userkey = userkey
             rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
             if rv < 0:
                 raise DrmException('Failed to initialize AES key')
 
         def decrypt(self, data):
             out = create_string_buffer(len(data))
-            rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
+            mutable_iv = create_string_buffer(self._iv, len(self._iv))
+            keyctx = self._keyctx
+            rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
             if rv == 0:
                 raise DrmException('AES decryption failed')
             return out.raw
@@ -111,13 +140,17 @@ def SHA256(message):
 
 # Various character maps used to decrypt books. Probably supposed to act as obfuscation
 charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM" 
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
 
-# For kinf approach of K4PC/K4Mac
+# For kinf approach of K4Mac 1.6.X or later
 # On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
 # For Mac they seem to re-use charMap2 here
 charMap5 = charMap2
 
+# new in K4M 1.9.X
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+
+
 def encode(data, map):
     result = ""
     for char in data:
@@ -144,7 +177,7 @@ def decode(data,map):
         result += pack("B",value)
     return result
 
-# For .kinf approach of K4PC and now K4Mac
+# For K4M 1.6.X and later
 # generate table of prime number less than or equal to int n
 def primes(n):
     if n==2: return [2]
@@ -271,7 +304,7 @@ def GetDiskPartitionUUID(diskpart):
     if not foundIt:
         uuidnum = ''
     return uuidnum
-    
+
 def GetMACAddressMunged():
     macnum = os.getenv('MYMACNUM')
     if macnum != None:
@@ -315,33 +348,11 @@ def GetMACAddressMunged():
     return macnum
 
 
-# uses unix env to get username instead of using sysctlbyname 
+# uses unix env to get username instead of using sysctlbyname
 def GetUserName():
     username = os.getenv('USER')
     return username
 
-
-# implements an Pseudo Mac Version of Windows built-in Crypto routine
-# used by Kindle for Mac versions < 1.6.0
-def CryptUnprotectData(encryptedData):
-    sernum = GetVolumeSerialNumber()
-    if sernum == '':
-        sernum = '9999999999'
-    sp = sernum + '!@#' + GetUserName()
-    passwdData = encode(SHA256(sp),charMap1)
-    salt = '16743'
-    iter = 0x3e8
-    keylen = 0x80
-    crp = LibCrypto()
-    key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
-    key = key_iv[0:32]
-    iv = key_iv[32:48]
-    crp.set_decrypt_key(key,iv)
-    cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext,charMap1)
-    return cleartext
-
-
 def isNewInstall():
     home = os.getenv('HOME')
     # soccer game fan anyone
@@ -350,7 +361,7 @@ def isNewInstall():
     if os.path.exists(dpath):
         return True
     return False
-    
+
 
 def GetIDString():
     # K4Mac now has an extensive set of ids strings it uses
@@ -359,13 +370,13 @@ def GetIDString():
 
     # BUT Amazon has now become nasty enough to detect when its app
     # is being run under a debugger and actually changes code paths
-    # including which one of these strings is chosen, all to try 
+    # including which one of these strings is chosen, all to try
     # to prevent reverse engineering
 
     # Sad really ... they will only hurt their own sales ...
     # true book lovers really want to keep their books forever
-    # and move them to their devices and DRM prevents that so they 
-    # will just buy from someplace else that they can remove 
+    # and move them to their devices and DRM prevents that so they
+    # will just buy from someplace else that they can remove
     # the DRM from
 
     # Amazon should know by now that true book lover's are not like
@@ -388,27 +399,91 @@ def GetIDString():
     return '9999999999'
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used by Kindle for Mac versions < 1.6.0
+class CryptUnprotectData(object):
+    def __init__(self):
+        sernum = GetVolumeSerialNumber()
+        if sernum == '':
+            sernum = '9999999999'
+        sp = sernum + '!@#' + GetUserName()
+        passwdData = encode(SHA256(sp),charMap1)
+        salt = '16743'
+        self.crp = LibCrypto()
+        iter = 0x3e8
+        keylen = 0x80
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext,charMap1)
+        return cleartext
+
+
 # implements an Pseudo Mac Version of Windows built-in Crypto routine
 # used for Kindle for Mac Versions >= 1.6.0
-def CryptUnprotectDataV2(encryptedData):
-    sp = GetUserName() + ':&%:' + GetIDString()
-    passwdData = encode(SHA256(sp),charMap5)
-    # salt generation as per the code
-    salt = 0x0512981d * 2 * 1 * 1
-    salt = str(salt) + GetUserName()
-    salt = encode(salt,charMap5)
+class CryptUnprotectDataV2(object):
+    def __init__(self):
+        sp = GetUserName() + ':&%:' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap5)
+        # salt generation as per the code
+        salt = 0x0512981d * 2 * 1 * 1
+        salt = str(salt) + GetUserName()
+        salt = encode(salt,charMap5)
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap5)
+        return cleartext
+
+
+# unprotect the new header blob in .kinf2011
+# used in Kindle for Mac Version >= 1.9.0
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
     crp = LibCrypto()
-    iter = 0x800
-    keylen = 0x400
     key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
     key = key_iv[0:32]
     iv = key_iv[32:48]
     crp.set_decrypt_key(key,iv)
     cleartext = crp.decrypt(encryptedData)
-    cleartext = decode(cleartext, charMap5)
     return cleartext
 
 
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.9.0
+class CryptUnprotectDataV3(object):
+    def __init__(self, entropy):
+        sp = GetUserName() + '+@#$%+' + GetIDString()
+        passwdData = encode(SHA256(sp),charMap2)
+        salt = entropy
+        self.crp = LibCrypto()
+        iter = 0x800
+        keylen = 0x400
+        key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+        self.key = key_iv[0:32]
+        self.iv = key_iv[32:48]
+        self.crp.set_decrypt_key(self.key, self.iv)
+
+    def decrypt(self, encryptedData):
+        cleartext = self.crp.decrypt(encryptedData)
+        cleartext = decode(cleartext, charMap2)
+        return cleartext
+
+
 # Locate the .kindle-info files
 def getKindleInfoFiles(kInfoFiles):
     # first search for current .kindle-info files
@@ -424,12 +499,22 @@ def getKindleInfoFiles(kInfoFiles):
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
             found = True
-    # add any .kinf files 
+    # add any .rainier*-kinf files
     cmdline = 'find "' + home + '/Library/Application Support" -name ".rainier*-kinf"'
     cmdline = cmdline.encode(sys.getfilesystemencoding())
     p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
     out1, out2 = p1.communicate()
     reslst = out1.split('\n')
+    for resline in reslst:
+        if os.path.isfile(resline):
+            kInfoFiles.append(resline)
+            found = True
+    # add any .kinf2011 files
+    cmdline = 'find "' + home + '/Library/Application Support" -name ".kinf2011"'
+    cmdline = cmdline.encode(sys.getfilesystemencoding())
+    p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+    out1, out2 = p1.communicate()
+    reslst = out1.split('\n')
     for resline in reslst:
         if os.path.isfile(resline):
             kInfoFiles.append(resline)
@@ -438,7 +523,7 @@ def getKindleInfoFiles(kInfoFiles):
         print('No kindle-info files have been found.')
     return kInfoFiles
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -449,7 +534,9 @@ def getDBfromFile(kInfoFile):
     data = infoReader.read()
 
     if data.find('[') != -1 :
+
         # older style kindle-info file
+        cud = CryptUnprotectData()
         items = data.split('[')
         for item in items:
             if item != '':
@@ -462,87 +549,175 @@ def getDBfromFile(kInfoFile):
                 if keyname == "unknown":
                     keyname = keyhash
                 encryptedValue = decode(rawdata,charMap2)
-                cleartext = CryptUnprotectData(encryptedValue)
+                cleartext = cud.decrypt(encryptedValue)
                 DB[keyname] = cleartext
                 cnt = cnt + 1
         if cnt == 0:
             DB = None
         return DB
 
-    # else newer style .kinf file used by K4Mac >= 1.6.0
-    # the .kinf file uses "/" to separate it into records
-    # so remove the trailing "/" to make it easy to use split
+    if hdr == '/':
+
+        # else newer style .kinf file used by K4Mac >= 1.6.0
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+        cud = CryptUnprotectDataV2()
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+            keyname = "unknown"
+
+            # the raw keyhash string is also used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            # "entropy" not used for K4Mac only K4PC
+            # entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents) - largest prime number less than or equal to int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            noffset = contlen - primes(int(contlen/3))[-1]
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using charMap5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            cleartext = cud.decrypt(encryptedValue)
+            DB[keyname] = cleartext
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # the latest .kinf2011 version for K4M 1.9.1
+    # put back the hdr char, it is needed
+    data = hdr + data
     data = data[:-1]
     items = data.split('/')
 
+    # the headerblob is the encrypted information needed to build the entropy string
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, charMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+
+    # now extract the pieces in the same way
+    # this version is different from K4PC it scales the build number by multipying by 735
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
+
+    cud = CryptUnprotectDataV3(entropy)
+
     # loop through the item records until all are processed
     while len(items) > 0:
-    
+
         # get the first item record
         item = items.pop(0)
-    
+
         # the first 32 chars of the first record of a group
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
         keyname = "unknown"
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        # "entropy" not used for K4Mac only K4PC
-        # entropy = SHA1(keyhash)
-    
-        # the remainder of the first record when decoded with charMap5 
+        # unlike K4PC the keyhash is not used in generating entropy
+        # entropy = SHA1(keyhash) + added_entropy
+        # entropy = added_entropy
+
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
         srcnt = decode(item[34:],charMap5)
         rcnt = int(srcnt)
-    
+
         # read and store in rcnt records of data
         # that make up the contents value
         edlst = []
         for i in xrange(rcnt):
             item = items.pop(0)
             edlst.append(item)
-    
+
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
         if keyname == "unknown":
             keyname = keyhash
-    
-        # the charMap5 encoded contents data has had a length 
+
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
-    
-        # The offset into the charMap5 encoded contents seems to be:
+
+        # The offset into the testMap8 encoded contents seems to be:
         # len(contents) - largest prime number less than or equal to int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
-    
-        # move first offsets chars to end to align for decode by charMap5
+
+        # move first offsets chars to end to align for decode by testMap8
         encdata = "".join(edlst)
         contlen = len(encdata)
 
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
+        # now properly split and recombine
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         noffset = contlen - primes(int(contlen/3))[-1]
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
-    
-        # decode using charMap5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        cleartext = CryptUnprotectDataV2(encryptedValue)
-        # Debugging
+
+        # decode using testMap8 to get the CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = cud.decrypt(encryptedValue)
         # print keyname
         # print cleartext
-        # print cleartext.encode('hex')
-        # print
         DB[keyname] = cleartext
         cnt = cnt + 1
 
index 6acdd5c65c6b224a48221d3033a061b1cc570b71..880b0f7323723edeca09b59d66ac0932016519ef 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-import sys, os
+import sys, os, re
 from struct import pack, unpack, unpack_from
 
 from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -11,9 +11,7 @@ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
     string_at, Structure, c_void_p, cast
 
 import _winreg as winreg
-
 MAX_PATH = 255
-
 kernel32 = windll.kernel32
 advapi32 = windll.advapi32
 crypt32 = windll.crypt32
@@ -33,9 +31,35 @@ def SHA1(message):
     ctx.update(message)
     return ctx.digest()
 
+def SHA256(message):
+    ctx = hashlib.sha256()
+    ctx.update(message)
+    return ctx.digest()
+
+# For K4PC 1.9.X
+# use routines in alfcrypto:
+#    AES_cbc_encrypt
+#    AES_set_decrypt_key
+#    PKCS5_PBKDF2_HMAC_SHA1
+
+from alfcrypto import AES_CBC, KeyIVGen
+
+def UnprotectHeaderData(encryptedData):
+    passwdData = 'header_key_data'
+    salt = 'HEADER.2011'
+    iter = 0x80
+    keylen = 0x100
+    key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
+    key = key_iv[0:32]
+    iv = key_iv[32:48]
+    aes=AES_CBC()
+    aes.set_decrypt_key(key, iv)
+    cleartext = aes.decrypt(encryptedData)
+    return cleartext
+
 
 # simple primes table (<= n) calculator
-def primes(n): 
+def primes(n):
     if n==2: return [2]
     elif n<2: return []
     s=range(3,n+1,2)
@@ -59,6 +83,10 @@ def primes(n):
 # Probably supposed to act as obfuscation
 charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
 charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# New maps in K4PC 1.9.0
+testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
 
 class DrmException(Exception):
     pass
@@ -73,7 +101,7 @@ def encode(data, 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)
@@ -165,7 +193,8 @@ def CryptUnprotectData():
         outdata = DataBlob()
         if not _CryptUnprotectData(byref(indata), None, byref(entropy),
                                    None, None, flags, byref(outdata)):
-            raise DrmException("Failed to Unprotect Data")
+            # raise DrmException("Failed to Unprotect Data")
+            return 'failed'
         return string_at(outdata.pbData, outdata.cbData)
     return CryptUnprotectData
 CryptUnprotectData = CryptUnprotectData()
@@ -198,10 +227,17 @@ def getKindleInfoFiles(kInfoFiles):
     else:
         kInfoFiles.append(kinfopath)
 
+    # now look for even newer (K4PC 1.9.0 and later) .kinf2011 file
+    kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
+    if not os.path.isfile(kinfopath):
+        print('No K4PC 1.9.X .kinf files have not been found.')
+    else:
+        kInfoFiles.append(kinfopath)
+
     return kInfoFiles
 
 
-# determine type of kindle info provided and return a 
+# determine type of kindle info provided and return a
 # database of keynames and values
 def getDBfromFile(kInfoFile):
     names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
@@ -232,12 +268,97 @@ def getDBfromFile(kInfoFile):
             DB = None
         return DB
 
-    # else newer style .kinf file
+    if hdr == '/':
+        # else rainier-2-1-1 .kinf file
+        # the .kinf file uses "/" to separate it into records
+        # so remove the trailing "/" to make it easy to use split
+        data = data[:-1]
+        items = data.split('/')
+
+        # loop through the item records until all are processed
+        while len(items) > 0:
+
+            # get the first item record
+            item = items.pop(0)
+
+            # the first 32 chars of the first record of a group
+            # is the MD5 hash of the key name encoded by charMap5
+            keyhash = item[0:32]
+
+            # the raw keyhash string is used to create entropy for the actual
+            # CryptProtectData Blob that represents that keys contents
+            entropy = SHA1(keyhash)
+
+            # the remainder of the first record when decoded with charMap5
+            # has the ':' split char followed by the string representation
+            # of the number of records that follow
+            # and make up the contents
+            srcnt = decode(item[34:],charMap5)
+            rcnt = int(srcnt)
+
+            # read and store in rcnt records of data
+            # that make up the contents value
+            edlst = []
+            for i in xrange(rcnt):
+                item = items.pop(0)
+                edlst.append(item)
+
+            keyname = "unknown"
+            for name in names:
+                if encodeHash(name,charMap5) == keyhash:
+                    keyname = name
+                    break
+            if keyname == "unknown":
+                keyname = keyhash
+            # the charMap5 encoded contents data has had a length
+            # of chars (always odd) cut off of the front and moved
+            # to the end to prevent decoding using charMap5 from
+            # working properly, and thereby preventing the ensuing
+            # CryptUnprotectData call from succeeding.
+
+            # The offset into the charMap5 encoded contents seems to be:
+            # len(contents)-largest prime number <=  int(len(content)/3)
+            # (in other words split "about" 2/3rds of the way through)
+
+            # move first offsets chars to end to align for decode by charMap5
+            encdata = "".join(edlst)
+            contlen = len(encdata)
+            noffset = contlen - primes(int(contlen/3))[-1]
+
+            # now properly split and recombine
+            # by moving noffset chars from the start of the
+            # string to the end of the string
+            pfx = encdata[0:noffset]
+            encdata = encdata[noffset:]
+            encdata = encdata + pfx
+
+            # decode using Map5 to get the CryptProtect Data
+            encryptedValue = decode(encdata,charMap5)
+            DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+            cnt = cnt + 1
+
+        if cnt == 0:
+            DB = None
+        return DB
+
+    # else newest .kinf2011 style .kinf file
     # the .kinf file uses "/" to separate it into records
     # so remove the trailing "/" to make it easy to use split
-    data = data[:-1]
+    # need to put back the first char read because it it part
+    # of the added entropy blob
+    data = hdr + data[:-1]
     items = data.split('/')
 
+    # starts with and encoded and encrypted header blob
+    headerblob = items.pop(0)
+    encryptedValue = decode(headerblob, testMap1)
+    cleartext = UnprotectHeaderData(encryptedValue)
+    # now extract the pieces that form the added entropy
+    pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+    for m in re.finditer(pattern, cleartext):
+        added_entropy = m.group(2) + m.group(4)
+
+
     # loop through the item records until all are processed
     while len(items) > 0:
 
@@ -248,11 +369,11 @@ def getDBfromFile(kInfoFile):
         # is the MD5 hash of the key name encoded by charMap5
         keyhash = item[0:32]
 
-        # the raw keyhash string is also used to create entropy for the actual
-        # CryptProtectData Blob that represents that keys contents
-        entropy = SHA1(keyhash)
+        # the sha1 of raw keyhash string is used to create entropy along
+        # with the added entropy provided above from the headerblob
+        entropy = SHA1(keyhash) + added_entropy
 
-        # the remainder of the first record when decoded with charMap5 
+        # the remainder of the first record when decoded with charMap5
         # has the ':' split char followed by the string representation
         # of the number of records that follow
         # and make up the contents
@@ -266,43 +387,39 @@ def getDBfromFile(kInfoFile):
             item = items.pop(0)
             edlst.append(item)
 
+        # key names now use the new testMap8 encoding
         keyname = "unknown"
         for name in names:
-            if encodeHash(name,charMap5) == keyhash:
+            if encodeHash(name,testMap8) == keyhash:
                 keyname = name
                 break
-        if keyname == "unknown":
-            keyname = keyhash
 
-        # the charMap5 encoded contents data has had a length 
+        # the testMap8 encoded contents data has had a length
         # of chars (always odd) cut off of the front and moved
-        # to the end to prevent decoding using charMap5 from 
-        # working properly, and thereby preventing the ensuing 
+        # to the end to prevent decoding using testMap8 from
+        # working properly, and thereby preventing the ensuing
         # CryptUnprotectData call from succeeding.
 
-        # The offset into the charMap5 encoded contents seems to be:
-        # len(contents) - largest prime number less than or equal to int(len(content)/3)
+        # The offset into the testMap8 encoded contents seems to be:
+        # len(contents)-largest prime number <=  int(len(content)/3)
         # (in other words split "about" 2/3rds of the way through)
 
-        # move first offsets chars to end to align for decode by charMap5
+        # move first offsets chars to end to align for decode by testMap8
+        # by moving noffset chars from the start of the
+        # string to the end of the string
         encdata = "".join(edlst)
         contlen = len(encdata)
         noffset = contlen - primes(int(contlen/3))[-1]
-
-        # now properly split and recombine 
-        # by moving noffset chars from the start of the 
-        # string to the end of the string 
         pfx = encdata[0:noffset]
         encdata = encdata[noffset:]
         encdata = encdata + pfx
 
-        # decode using Map5 to get the CryptProtect Data
-        encryptedValue = decode(encdata,charMap5)
-        DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+        # decode using new testMap8 to get the original CryptProtect Data
+        encryptedValue = decode(encdata,testMap8)
+        cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+        DB[keyname] = cleartext
         cnt = cnt + 1
 
     if cnt == 0:
         DB = None
     return DB
-
-
diff --git a/Other_Tools/KindleBooks/lib/libalfcrypto.dylib b/Other_Tools/KindleBooks/lib/libalfcrypto.dylib
new file mode 100644 (file)
index 0000000..01c348c
Binary files /dev/null and b/Other_Tools/KindleBooks/lib/libalfcrypto.dylib differ
index 4d978b377ae0cb64cb057212b5d82b314117176a..0a6b25ef4bc7dce592da7ff6d9128f10685ec6ff 100644 (file)
@@ -27,8 +27,8 @@
 #         files reveals that a confusion has arisen because trailing data entries
 #         are not encrypted, but it turns out that the multibyte entries
 #         in utf8 file are encrypted. (Although neither kind gets compressed.)
-#         This knowledge leads to a simplification of the test for the 
-#         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#         This knowledge leads to a simplification of the test for the
+#         trailing data byte flags - version 5 and higher AND header size >= 0xE4.
 #  0.15 - Now outputs 'heartbeat', and is also quicker for long files.
 #  0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
 #  0.17 - added modifications to support its use as an imported python module
@@ -42,7 +42,7 @@
 #  0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
 #  0.21 - Added support for multiple pids
 #  0.22 - revised structure to hold MobiBook as a class to allow an extended interface
-#  0.23 - fixed problem with older files with no EXTH section 
+#  0.23 - fixed problem with older files with no EXTH section
 #  0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
 #  0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
 #  0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
 #  0.30 - Modified interface slightly to work better with new calibre plugin style
 #  0.31 - The multibyte encrytion info is true for version 7 files too.
 #  0.32 - Added support for "Print Replica" Kindle ebooks
+#  0.33 - Performance improvements for large files (concatenation)
+#  0.34 - Performance improvements in decryption (libalfcrypto)
 
-__version__ = '0.32'
+__version__ = '0.34'
 
 import sys
 
@@ -72,6 +74,7 @@ sys.stdout=Unbuffered(sys.stdout)
 import os
 import struct
 import binascii
+from alfcrypto import Pukall_Cipher
 
 class DrmException(Exception):
     pass
@@ -83,36 +86,37 @@ class DrmException(Exception):
 
 # Implementation of Pukall Cipher 1
 def PC1(key, src, decryption=True):
-    sum1 = 0;
-    sum2 = 0;
-    keyXorVal = 0;
-    if len(key)!=16:
-        print "Bad key length!"
-        return None
-    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
+    return Pukall_Cipher().PC1(key,src,decryption)
+#     sum1 = 0;
+#     sum2 = 0;
+#     keyXorVal = 0;
+#     if len(key)!=16:
+#         print "Bad key length!"
+#         return None
+#     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
 
 def checksumPid(s):
     letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
@@ -236,7 +240,7 @@ class MobiBook:
             self.meta_array = {}
             pass
         self.print_replica = False
-            
+
     def getBookTitle(self):
         codec_map = {
             1252 : 'windows-1252',
@@ -319,7 +323,7 @@ class MobiBook:
 
     def getMobiFile(self, outpath):
         file(outpath,'wb').write(self.mobi_data)
-        
+
     def getPrintReplica(self):
         return self.print_replica
 
@@ -355,9 +359,9 @@ class MobiBook:
             if self.magic == 'TEXtREAd':
                 bookkey_data = self.sect[0x0E:0x0E+16]
             elif self.mobi_version < 0:
-                bookkey_data = self.sect[0x90:0x90+16] 
+                bookkey_data = self.sect[0x90:0x90+16]
             else:
-                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] 
+                bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
             pid = "00000000"
             found_key = PC1(t1_keyvec, bookkey_data)
         else :
@@ -372,7 +376,7 @@ class MobiBook:
             self.patchSection(0, "\0" * drm_size, drm_ptr)
             # kill the drm pointers
             self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
-            
+
         if pid=="00000000":
             print "File has default encryption, no specific PID."
         else:
@@ -383,7 +387,8 @@ class MobiBook:
 
         # decrypt sections
         print "Decrypting. Please wait . . .",
-        self.mobi_data = self.data_file[:self.sections[1][0]]
+        mobidataList = []
+        mobidataList.append(self.data_file[:self.sections[1][0]])
         for i in xrange(1, self.records+1):
             data = self.loadSection(i)
             extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
@@ -393,11 +398,12 @@ class MobiBook:
             decoded_data = PC1(found_key, data[0:len(data) - extra_size])
             if i==1:
                 self.print_replica = (decoded_data[0:4] == '%MOP')
-            self.mobi_data += decoded_data
+            mobidataList.append(decoded_data)
             if extra_size > 0:
-                self.mobi_data += data[-extra_size:]
+                mobidataList.append(data[-extra_size:])
         if self.num_sections > self.records+1:
-            self.mobi_data += self.data_file[self.sections[self.records+1][0]:]
+            mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
+        self.mobi_data = "".join(mobidataList)
         print "done"
         return
 
index e30abfaa0c1742adf3903016a8256cee7a43e54d..adbac4988187e1dc4eb4ce403eb931039069ff6b 100644 (file)
@@ -6,6 +6,7 @@ import csv
 import sys
 import os
 import getopt
+import re
 from struct import pack
 from struct import unpack
 
@@ -43,8 +44,8 @@ class DocParser(object):
         '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
@@ -59,10 +60,10 @@ class DocParser(object):
             item = docList[j]
             if item.find('=') >= 0:
                 (name, argres) = item.split('=',1)
-            else : 
+            else :
                 name = item
                 argres = ''
-            if name.endswith(tagpath) : 
+            if name.endswith(tagpath) :
                 result = argres
                 foundat = j
                 break
@@ -82,12 +83,19 @@ class DocParser(object):
         return startpos
 
     # returns a vector of integers for the tagpath
-    def getData(self, tagpath, pos, end):
+    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('|')
-            argres = [ int(strval) for strval in argList]
+            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):
@@ -112,7 +120,7 @@ class DocParser(object):
             (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 :
 
@@ -121,7 +129,7 @@ class DocParser(object):
                 if sclass != None:
                     sclass = sclass.replace(' ','-')
                     sclass = '.cl-' + sclass.lower()
-                else : 
+                else :
                     sclass = ''
 
                 # check for any "after class" specifiers
@@ -129,7 +137,7 @@ class DocParser(object):
                 if aftclass != None:
                     aftclass = aftclass.replace(' ','-')
                     aftclass = '.cl-' + aftclass.lower()
-                else : 
+                else :
                     aftclass = ''
 
                 cssargs = {}
@@ -140,7 +148,7 @@ class DocParser(object):
                     (pos2, val) = self.findinDoc('style.rule.value', start, end)
 
                     if attr == None : break
-                    
+
                     if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
                         # handle text based attributess
                         attr = attr + '-' + val
@@ -168,7 +176,7 @@ class DocParser(object):
                 if aftclass != "" : keep = False
 
                 if keep :
-                    # make sure line-space does not go below 100% or above 300% since 
+                    # 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]
@@ -178,7 +186,7 @@ class DocParser(object):
                         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]
@@ -211,7 +219,7 @@ class DocParser(object):
 
                     if sclass != '' :
                         classlst += sclass + '\n'
-                    
+
                     # handle special case of paragraph class used inside chapter heading
                     # and non-chapter headings
                     if sclass != '' :
@@ -232,7 +240,7 @@ class DocParser(object):
                     if cssline != ' { }':
                         csspage += self.stags[tag] + cssline + '\n'
 
-                
+
         return csspage, classlst
 
 
@@ -251,5 +259,5 @@ def convert2CSS(flatxml, fontsize, ph, pw):
 
 def getpageIDMap(flatxml):
     dp = DocParser(flatxml, 0, 0, 0)
-    pageidnumbers = dp.getData('info.original.pid', 0, -1)
+    pageidnumbers = dp.getData('info.original.pid', 0, -1, True)
     return pageidnumbers
index ed13aa1b7917bd7923efa09367d1ef2304b472e7..de084d303fcc72ce25e9213ab87f8f1d91bfdb03 100644 (file)
@@ -52,7 +52,7 @@ class Process(object):
             self.__stdout_thread = threading.Thread(
                 name="stdout-thread",
                 target=self.__reader, args=(self.__collected_outdata,
-                                           self.__process.stdout))
+                                            self.__process.stdout))
             self.__stdout_thread.setDaemon(True)
             self.__stdout_thread.start()
 
@@ -60,7 +60,7 @@ class Process(object):
             self.__stderr_thread = threading.Thread(
                 name="stderr-thread",
                 target=self.__reader, args=(self.__collected_errdata,
-                                           self.__process.stderr))
+                                            self.__process.stderr))
             self.__stderr_thread.setDaemon(True)
             self.__stderr_thread.start()
 
@@ -146,4 +146,3 @@ class Process(object):
         self.__quit = True
         self.__inputsem.release()
         self.__lock.release()
-
index eed53e263ce9c750de783efa533ed6024cd6192f..6afb7daf1c7af8d52f8a0839b9d31fdea3a87978 100644 (file)
@@ -16,15 +16,18 @@ if 'calibre' in sys.modules:
 else:
     inCalibre = False
 
+buildXML = False
+
 import os, csv, getopt
 import zlib, zipfile, tempfile, shutil
 from struct import pack
 from struct import unpack
+from alfcrypto import Topaz_Cipher
 
 class TpzDRMError(Exception):
     pass
 
-    
+
 # local support routines
 if inCalibre:
     from calibre_plugins.k4mobidedrm import kgenpids
@@ -58,22 +61,22 @@ def bookReadEncodedNumber(fo):
     flag = False
     data = ord(fo.read(1))
     if data == 0xFF:
-       flag = True
-       data = ord(fo.read(1))
+        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 
+        data = datax
     if flag:
-       data = -data
+        data = -data
     return data
-    
-# Get a length prefixed string from file 
+
+# Get a length prefixed string from file
 def bookReadString(fo):
     stringLength = bookReadEncodedNumber(fo)
-    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]  
+    return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
 
 #
 # crypto routines
@@ -81,25 +84,28 @@ def bookReadString(fo):
 
 # Context initialisation for the Topaz Crypto
 def topazCryptoInit(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]
-    
+    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):
-    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
+    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):
@@ -153,7 +159,7 @@ class TopazBook:
 
     def parseTopazHeaders(self):
         def bookReadHeaderRecordData():
-            # Read and return the data of one header record at the current book file position 
+            # Read and return the data of one header record at the current book file position
             # [[offset,decompressedLength,compressedLength],...]
             nbValues = bookReadEncodedNumber(self.fo)
             values = []
@@ -213,11 +219,11 @@ class TopazBook:
         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 
+        # Get a record in the book payload, given its name and index.
+        # decrypted and decompressed if necessary
         encrypted = False
         compressed = False
-        try: 
+        try:
             recordOffset = self.bookHeaderRecords[name][index][0]
         except:
             raise TpzDRMError("Parse Error : Invalid Record, record not found")
@@ -268,8 +274,8 @@ class TopazBook:
             rv = genbook.generateBook(self.outdir, raw, fixedimage)
             if rv == 0:
                 print "\nBook Successfully generated"
-            return rv            
-    
+            return rv
+
         # try each pid to decode the file
         bookKey = None
         for pid in pidlst:
@@ -297,7 +303,7 @@ class TopazBook:
         rv = genbook.generateBook(self.outdir, raw, fixedimage)
         if rv == 0:
             print "\nBook Successfully generated"
-        return rv            
+        return rv
 
     def createBookDirectory(self):
         outdir = self.outdir
@@ -361,7 +367,7 @@ class TopazBook:
         zipUpDir(svgzip, self.outdir, 'svg')
         zipUpDir(svgzip, self.outdir, 'img')
         svgzip.close()
-
+    
     def getXMLZip(self, zipname):
         xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
         targetdir = os.path.join(self.outdir,'xml')
@@ -371,23 +377,23 @@ class TopazBook:
 
     def cleanup(self):
         if os.path.isdir(self.outdir):
-            pass
-            # shutil.rmtree(self.outdir, True)
+            shutil.rmtree(self.outdir, True)
 
 def usage(progname):
     print "Removes DRM protection from Topaz ebooks and extract the contents"
     print "Usage:"
     print "    %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir>  " % progname
-    
+
 
 # Main
 def main(argv=sys.argv):
+    global buildXML
     progname = os.path.basename(argv[0])
     k4 = False
     pids = []
     serials = []
     kInfoFiles = []
-    
+
     try:
         opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
     except getopt.GetoptError, err:
@@ -397,7 +403,7 @@ def main(argv=sys.argv):
     if len(args)<2:
         usage(progname)
         return 1
-        
+
     for o, a in opts:
         if o == "-k":
             if a == None :
@@ -429,7 +435,7 @@ def main(argv=sys.argv):
     title = tb.getBookTitle()
     print "Processing Book: ", title
     keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
-    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles) 
+    pidlst = kgenpids.getPidList(keysRecord, keysRecordRecord, k4, pids, serials, kInfoFiles)
 
     try:
         print "Decrypting Book"
@@ -443,9 +449,10 @@ def main(argv=sys.argv):
         zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
         tb.getSVGZip(zipname)
 
-        print "   Creating XML ZIP Archive"
-        zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
-        tb.getXMLZip(zipname)
+        if buildXML:
+            print "   Creating XML ZIP Archive"
+            zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
+            tb.getXMLZip(zipname)
 
         # removing internal temporary directory of pieces
         tb.cleanup()
@@ -461,9 +468,8 @@ def main(argv=sys.argv):
         return 1
 
     return 0
-                
+
 
 if __name__ == '__main__':
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 69074488df6023332e8df4e97c73a6f3cc815f63..3f03182908a9526d100bcac9f226bd05b6c51ef7 100644 (file)
@@ -1,29 +1,28 @@
 Kindle for iPhone, iPod Touch, iPad
+------------------------------------
 
+The Kindle application for iOS (iPhone/iPod Touch/iPad) uses a PID derived from the serial number of the iPhone/iPod Touch/iPad. Kindlepid.pyw (see Other_Tools/Additional_Tools) is a python script that turns the serial number into the equivalent PID, which can then be used with the Calibre Plugins and DeDRM Applications.
 
-The Kindle application for iOS (iPhone/iPod Touch/iPad) uses a PID derived from the serial number of the iPhone/iPod Touch/iPad. Kindlepid.py is a python script that turns the serial number into the equivalent PID, which can then be used with the MobiDeDRM script.
-
-So, to remove the DRM from (Mobipocket) Kindle books downloaded to your iPhone/iPodTouch/iPad, you’ll need the latest toolsvx.x.zip archive and
+So, to remove the DRM from (Mobipocket) Kindle books downloaded to your iPhone/iPodTouch/iPad, you’ll need the latest tools_vX.X.zip archive and
 some way to extract the book files from the backup of your device on your computer. There are several free tools around to do this.
 
-Double-click on KindlePID.pyw to get your device’s PID, then use the extractor to get your book files, then double-click on MobiDeDRM.pyw with the PID and the files to get Drm-free versions of your books.
+Your first step is to download and install one of the free apps onto your iPod Touch/iPhone/iPad that will find your devices's UUID.  Alternatively, you can find your device's UDID in iTunes when your device is connected in the Summary page – click on the serial number and it changes to the (40 digit!) UDID.
 
-Kindlefix gives you another way to use the PID generated by kindlepid. Some ebook stores and libraries will accept the PIDs generated by kindlepid (some won’t), and you can then download ebooks from the store or library encrypted to work with your Kindle. Except they don’t. There’s a flag byte set in encrypted Kindle ebooks, and Kindles and the Kindle app won’t read encrypted mobipocket ebooks unless that flag is set. Kindlefix will set that flag for you. If your library have Mobipocket ebooks to lend and will accept your Kindle’s PID, you can now check out library ebooks, run kindlefix on them, and then read them on your Kindle, and when your loan period ends, they’ll automatically become unreadable.
+Once you have the UUID, double-click on KindlePID.pyw to get your device’s PID.
+You should get back a response something like:
+
+Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky
+iPhone serial number (UDID) detected
+Mobipocked PID for iPhone serial# FOURTYDIGITUDIDNUMBERGIVENHERE is TENDIGITPID
 
-To extract the files from your iPod Touch (not archived Kindle ebooks, but ones actually on your iPod Touch) it’s necessary to first do a back-up of the iPod Touch using iTunes. That creates a backup file on your Mac, and you can then extract the Mobipocket files from that using iPhone/ipod Touch Backup Extractor – free software from here: http://supercrazyawesome.com/
+You can then add this fixed PID to the DeDRM Applications or Calibre_Plugins or KindleBooks tools and be ready to go.
 
-Ok, so that will get your the .azw Kindle Mobipocket files.
+You next need to find an "iPhone Extractor" or "iPhone Explorer" program for your OS that will allow you to mount your iPhone/iPad/iPod Touch as a usb device on your machine and copy your Kindle ebooks from the device back to your home machine.
 
-Now you need the PID used to encrypt them. To get that you’ll need your iPod Touch UDID number – you can find it in iTunes when your iPod Touch is connected in the Summary page – click on the serial number and it changes to the (40 digit!) UDID. 
 
-And then you need to double-click the KindlePID.pyw script and enter your 40 digit UDID in the window and hit "Start".
+---
 
- and you should get back a response something like:
 
-Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky
-iPhone serial number (UDID) detected
-Mobipocked PID for iPhone serial# FOURTYDIGITUDIDNUMBERGIVENHERE is TENDIGITPID
+Kindlefix gives you another way to use the PID generated by kindlepid. Some ebook stores and libraries will accept the PIDs generated by kindlepid (some won’t), and you can then download ebooks from the store or library encrypted to work with your Kindle. Except they don’t. There’s a flag byte set in encrypted Kindle ebooks, and Kindles and the Kindle app won’t read encrypted mobipocket ebooks unless that flag is set. Kindlefix will set that flag for you. If your library have Mobipocket ebooks to lend and will accept your Kindle’s PID, you can now check out library ebooks, run kindlefix on them, and then read them on your Kindle, and when your loan period ends, they’ll automatically become unreadable.
 
-which gives you the PID to be used with MobiDeDRM to de-drm the files you extracted.
 
-All of these scripts are gui python programs.   Python 2.X (32 bit) is already installed in Mac OSX and Linux.  We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
\ No newline at end of file
index a1aafde23e57ee26d332d06ee6bd71dfc53d6ed8..523ef1a2109cb6d41dcaaec8175672484cccff9c 100644 (file)
@@ -30,8 +30,8 @@ class fixZip:
         self.inzip = zipfile.ZipFile(zinput,'r')
         self.outzip = zipfile.ZipFile(zoutput,'w')
         # open the input zip for reading only as a raw file
-       self.bzf = file(zinput,'rb')
-        
+        self.bzf = file(zinput,'rb')
+
     def getlocalname(self, zi):
         local_header_offset = zi.header_offset
         self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
@@ -86,7 +86,7 @@ class fixZip:
 
         return data
 
-        
+
 
     def fix(self):
         # get the zipinfo for each member of the input archive
@@ -103,7 +103,7 @@ class fixZip:
             if zinfo.filename != "mimetype" or self.ztype == '.zip':
                 data = None
                 nzinfo = zinfo
-                try: 
+                try:
                     data = self.inzip.read(zinfo.filename)
                 except zipfile.BadZipfile or zipfile.error:
                     local_name = self.getlocalname(zinfo)
@@ -126,7 +126,7 @@ def usage():
      inputzip is the source zipfile to fix
      outputzip is the fixed zip archive
     """
-    
+
 
 def repairBook(infile, outfile):
     if not os.path.exists(infile):
@@ -152,5 +152,3 @@ def main(argv=sys.argv):
 
 if __name__ == '__main__' :
     sys.exit(main())
-
-
index 7fefaf7125750b7c6ce448cafa0327727c07e035..8f958cd487a971e95ff6c6af979e618005b2a0ec 100644 (file)
@@ -16,7 +16,7 @@
 # Custom version 0.03 - no change to eReader support, only usability changes
 #   - start of pep-8 indentation (spaces not tab), fix trailing blanks
 #   - version variable, only one place to change
-#   - added main routine, now callable as a library/module, 
+#   - added main routine, now callable as a library/module,
 #     means tools can add optional support for ereader2html
 #   - outdir is no longer a mandatory parameter (defaults based on input name if missing)
 #   - time taken output to stdout
@@ -59,8 +59,8 @@
 #  0.18 - on Windows try PyCrypto first and OpenSSL next
 #  0.19 - Modify the interface to allow use of import
 #  0.20 - modify to allow use inside new interface for calibre plugins
-#  0.21 - Support eReader (drm) version 11. 
-#       - Don't reject dictionary format. 
+#  0.21 - Support eReader (drm) version 11.
+#       - Don't reject dictionary format.
 #       - Ignore sidebars for dictionaries (different format?)
 
 __version__='0.21'
@@ -178,7 +178,7 @@ def sanitizeFileName(s):
 def fixKey(key):
     def fixByte(b):
         return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
-    return     "".join([chr(fixByte(ord(a))) for a in key])
+    return      "".join([chr(fixByte(ord(a))) for a in key])
 
 def deXOR(text, sp, table):
     r=''
@@ -212,7 +212,7 @@ class EreaderProcessor(object):
             for i in xrange(len(data)):
                 j = (j + shuf) % len(data)
                 r[j] = data[i]
-            assert     len("".join(r)) == len(data)
+            assert      len("".join(r)) == len(data)
             return "".join(r)
         r = unshuff(input[0:-8], cookie_shuf)
 
@@ -314,7 +314,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(chaps, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             cv += '%d|%s\n' % (offset, name) 
+    #             cv += '%d|%s\n' % (offset, name)
     #     return cv
 
     # def getLinkNamePMLOffsetData(self):
@@ -326,7 +326,7 @@ class EreaderProcessor(object):
     #             offname = deXOR(links, j, self.xortable)
     #             offset = struct.unpack('>L', offname[0:4])[0]
     #             name = offname[4:].strip('\0')
-    #             lv += '%d|%s\n' % (offset, name) 
+    #             lv += '%d|%s\n' % (offset, name)
     #     return lv
 
     # def getExpandedTextSizesData(self):
@@ -354,7 +354,7 @@ class EreaderProcessor(object):
         for i in xrange(self.num_text_pages):
             logging.debug('get page %d', i)
             r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
-             
+
         # now handle footnotes pages
         if self.num_footnote_pages > 0:
             r += '\n'
@@ -399,12 +399,12 @@ class EreaderProcessor(object):
         return r
 
 def cleanPML(pml):
-       # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
-       pml2 = pml
-       for k in xrange(128,256):
-               badChar = chr(k)
-               pml2 = pml2.replace(badChar, '\\a%03d' % k)
-       return pml2
+        # Convert special characters to proper PML code.  High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
+    pml2 = pml
+    for k in xrange(128,256):
+        badChar = chr(k)
+        pml2 = pml2.replace(badChar, '\\a%03d' % k)
+    return pml2
 
 def convertEreaderToPml(infile, name, cc, outdir):
     if not os.path.exists(outdir):
@@ -435,7 +435,7 @@ def convertEreaderToPml(infile, name, cc, outdir):
     #     file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
 
 
-                
+
 def decryptBook(infile, outdir, name, cc, make_pmlz):
     if make_pmlz :
         # ignore specified outdir, use tempdir instead
@@ -468,7 +468,7 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
             shutil.rmtree(outdir, True)
             print 'output is %s' % zipname
         else :
-            print 'output in %s' % outdir 
+            print 'output in %s' % outdir
         print "done"
     except ValueError, e:
         print "Error: %s" % e
@@ -505,7 +505,7 @@ def main(argv=None):
             return 0
         elif o == "--make-pmlz":
             make_pmlz = True
-    
+
     print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
 
     if len(args)!=3 and len(args)!=4:
@@ -524,4 +524,3 @@ def main(argv=None):
 if __name__ == "__main__":
     sys.stdout=Unbuffered(sys.stdout)
     sys.exit(main())
-
index 8a044fa89a0641c156c5c5c18f6d4a9067bf5d9b..a4a40ca859417988ad76623ac65f8d3df2698fba 100644 (file)
@@ -18,7 +18,7 @@ def load_libcrypto():
         return None
 
     libcrypto = CDLL(libcrypto)
-    
+
     # typedef struct DES_ks
     #     {
     #     union
@@ -30,7 +30,7 @@ def load_libcrypto():
     #         } ks[16];
     #     } DES_key_schedule;
 
-    # just create a big enough place to hold everything 
+    # 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),
@@ -61,7 +61,7 @@ def load_libcrypto():
     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 :
@@ -87,4 +87,3 @@ def load_libcrypto():
             return ''.join(result)
 
     return DES
-
index 81502c80f564043dd03cc4811edfb78ffe413e9b..80d7d65064f42919e0b182680fafba5baeb0a5b3 100644 (file)
@@ -28,4 +28,3 @@ def load_pycrypto():
                 i += 8
             return ''.join(result)
     return DES
-
index 24d098c8e7b5e7a3881aabdd4e8a422fca7bed71..c6665c6dc72e3083a85432d554ed5af1fbe75f4a 100644 (file)
@@ -1,83 +1,95 @@
-Welcome to the tools!  
+Welcome to the tools!
+=====================
 
-The set includes tools to remove DRM from eReader PDB books, Barnes and Noble ePubs, Adobe ePubs, Adobe PDFs, and Kindle/Mobi ebooks (including Topaz).  
+This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. 
 
+The is archive includes tools to remove DRM from:
+ - eReader PDB books
+ - Barnes and Noble ePubs
+ - Adobe Digitial Editions ePubs
+ - Adobe Digitial Editions PDFs
+ - Kindle/Mobipocket ebooks (including Topaz, Print Replica and KF8).
 
-This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started.
+These tools do NOT work with Apple's iBooks FairPlay DRM.
 
+The only tool that removes Apple's iBooks Fairplay DRM that is Requiem by Brahms version 3.3 or later. Requiem is NOT included in this tools package. It is under active development because Apple constantly updates its DRM scheme to stop Requiem from working.
 
-Calibre Users (Mac OS X, Windows)
--------------
-If you are a calibre user, the quickest and easiest way to remove DRM from your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins README file.
+Requiem has a Tor website: http://tag3ulp55xczs3pn.onion. To reach the site using Tor, you will need to install Tor (http://www.torproject.org). If you're willing to sacrifice your anonymity, you can use the regular web with tor2web. Just go to http://tag3ulp55xczs3pn.tor2web.com.
 
-Once installed and configured, you can simply import a DRM book into Calibre and end up with the DeDRM version in the Calibre database.
 
-These plugins work for Windows and  Mac OS X
 
+Calibre Users (Mac OS X, Windows, and Linux)
+--------------------------------------------
+If you are a calibre user, the quickest and easiest way to remove DRM from your ebooks is to open the Calibre_Plugins folder and install each of the plugins following the instructions and configuration directions provided in each plugins' README file.
 
+Once installed and configured, you can simply add a DRM book to calibre and the DeDRM version will be imported into the calibre database. Note that DRM removal ONLY occurs on import. If you have already imported DRM books you'll need to remove them from calibre and re-import them.
 
-Mac OS X Users (Mac OS X 10.5, 10.6, and 10.7)
---------------
-From the DeDRM_for_Mac_and_Win folder, drag the DeDRM_X.X.app.zip droplet to your Desktop.  Double-click on it once to unzip it to create the DeDRM X.X.app droplet.   Double-click on the droplet once and it will guide you through collecting the data it needs to remove the DRM.
+These plugins work for Windows and Mac OS X. All plugins (including the K4MobiDeDRM plugin when used on books from Kindle for PC under wine) should also work on Linux. Linux users should read the section at the end of this ReadMe.
 
-To use it simply drag a book onto the droplet and it will process the book.  This tools supports dragging and dropping of folders of ebooks as well.
 
 
+DeDRM Application for Mac OS X Users: (Mac OS X 10.5, 10.6, and 10.7)
+----------------------------------------------------------------------
+From the DeDRM_Applications folder, drag the DeDRM_X.X.app.zip droplet to your Desktop. Double-click on it once to unzip it to create the DeDRM X.X.app droplet. Double-click on the droplet once and it will guide you through collecting the data it needs to remove the DRM.
 
-Windows Users (Xp through Windows 7)
---------------
-From the DeDRM_for_Mac_and_Win folder, fully extract the DeDRM_WinApp_vX.X.zip.  Drag the resulting DeDRM_WinApp_vx.x folder to someplace out of the way on your machine.  Open the folder and make a short-cut from the DeDRM_Drop_Target onto your Desktop. Double-click on the short-cut and DeDRM will launch it will guide you through collecting the data it needs to remove the DRM.  
+To use it simply drag ebooks or folders containing ebooks onto the DeDRM X.X.app droplet and it will process the ebooks.
 
-***This program requires that Python and PyCrypto be properly installed***.  See below for details on which versions are best.
 
-To use it simply drag ebooks or folders onto the DeDRM_Drop_Target short-cut, and it will process the ebooks.
 
+DeDRM Application for Windows Users: (Windows XP through Windows 7)
+------------------------------------------------------------------
+***This program requires that Python and PyCrypto be properly installed***.
+See below for details on which versions are recommended.
 
+From the DeDRM_Applications folder, fully extract the DeDRM_vX.X_WinApp.zip. Drag the resulting DeDRM_vx.x_WinApp folder to someplace out of the way on your machine. Open the folder and make a short-cut from the DeDRM_Drop_Target onto your Desktop. Double-click on the short-cut and DeDRM will launch and it will guide you through collecting the data it needs to remove the DRM. 
 
-Not a Calibre or a DeDRM User?
-------------------------------
-There are a number of python based tools that have graphical user interfaces to make them easy to use.  To use any of these tools, you need to have Python 2.5, 2.6, or 2.7  for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools.
+To use it simply drag ebooks or folders containing ebooks onto the DeDRM_Drop_Target short-cut, and it will process the ebooks.
 
-On Mac OS X (10.5, 10.6 and 10.7), your systems already have the proper Python and OpenSSL installed.  So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts.
 
-Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.X) for OS X 10.3 and later from  http://www.python.org/download/releases/2.7.1/
 
-On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL.   We ***strongly*** recommend teh free ActiveState's Active Python version.  See the end of this document for details.
+Other_Tools
+-----------
+There are a number of other python based tools that have graphical user interfaces to make them easy to use. To use any of these tools, you need to have Python 2.5, 2.6, or 2.7 for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools.
 
-The scripts are organized by type of ebook you need to remove the DRM from.  Choose from among:
+On Mac OS X (10.5, 10.6 and 10.7), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts.
 
-     "Adobe_ePub_Tools"
-     "Adobe_PDF_Tools"
-     "Barnes_and_Noble_ePub_Tools"
-     "eReader_PDB_Tools"
-     "KindleBooks"
-     "Kindle_for_Android_Patch"
+Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.X) for OS X 10.3 and later from http://www.python.org/download/releases/2.7.1/
 
-by simply opening that folder.
+On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. We ***strongly*** recommend the free community edition of ActiveState's Active Python version. See the end of this document for details.
 
+Linux users should have python 2.7, and openssl installed. but may need to run some of these tools under recent versions of Wine. See the Linux_Users section below:
 
-Look for a README inside of the relevant folder to get you started. 
+The scripts in the Other_Tools folder are organized by type of ebook you need to remove the DRM from. Choose from among:
+
+  "Adobe_ePub_Tools"
+  "Adobe_PDF_Tools"
+  "Barnes_and_Noble_ePub_Tools"
+  "eReader_PDB_Tools"
+  "KindleBooks"
+  "Kindle_for_Android_Patch"
+  "ePub_Fixer" (for fixing incorrectly made Adobe and Barnes and Noble ePubs)
 
+by simply opening that folder.
 
+Look for a README inside of the relevant folder to get you started. 
 
-Additional Tools
-----------------------
-Some additional tools are also provided in the "Mobi_Additional_Tools" folder. There are tools for working with "Kindle for iPhone/iPod_Touch/iPad", finding Topaz ebooks, unpacking Mobi ebooks (without DRM) to get to the Mobi markup language inside, and etc.
 
-There is also an "ePub_Fixer" folder that can be used to fix broken DRM epubs that sometimes re provided by Adobe and Barnes and Noble that actually violate the zip/epub standard.
 
-Check out their readmes for more info.
+Additional Tools
+----------------
+Some additional useful tools **unrelated to DRM** are also provided in the "Additional_Tools" folder. There are tools for working with finding Topaz ebooks, unpacking Kindle/Mobipocket ebooks (without DRM) to get to the Mobipocket markup language inside, tools to strip source archive from Kindlegen generated mobis, tools to work with Kindle for iPhone/iPad, etc, and tools to dump the contents of mobi headers to see all EXTH (metadata) and related values.
 
 
 
-Windows and Python Tools
-------------------------
-We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits.  This can be downloaded for free from:
+Windows and Python
+------------------
+We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from:
 
        http://www.activestate.com/activepython/downloads
 
 We do **NOT** recommend the version of Python from python.org.
-The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for guis) unless you select it as an option in the installer, and does not properly update the system PATH environment variable.  Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users.
+
+The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for graphical user interfaces) unless you select it as an option in the installer, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users.
 
 In addition, Windows Users need one of PyCrypto OR OpenSSL.
 
@@ -94,7 +106,7 @@ For OpenSSL:
 
 For PyCrypto:
 
-       There are many places to get PyCrypto installers for Windows.  One such place is:
+       There are many places to get PyCrypto installers for Windows. One such place is:
 
                http://www.voidspace.org.uk/python/modules.shtml
 
@@ -104,7 +116,121 @@ Once Windows users have installed Python 2.X for 32 bits, and the matching OpenS
 
 
 
-Linux Users
------------
 
-Please see the ReadMe_Linux_Users.txt
+
+Linux Users Only
+================
+
+Since Kindle for PC and Adobe Digital Editions do not offer native Linux versions, here are instructions for using Windows versions under Wine as well as related instructions for the special way to handle some of these tools:
+
+
+Linux and Kindle for PC (Other_Tools/KindleBooks/)
+--------------------------------------------------
+
+Here are the instructions for using Kindle for PC and KindleBooks.pyw on Linux under Wine. (Thank you Eyeless and Pete)
+
+1. upgrade to very recent versions of Wine; This has been tested with Wine 1.3.15 – 1.3.2X. It may work with earlier versions but no promises. It does not work with wine 1.2.X versions.
+
+If you have not already installed Kindle for PC under wine, follow steps 2 and 3 otherwise jump to step 4
+
+2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer).
+cd ~
+cd .wine
+cd drive_c
+echo deadbeef > .windows-serial 
+
+Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it
+
+3. Only ***after*** setting the volume serial number properly – download and install under wine K4PC version for Windows. Register it and download from your Archive one of your Kindle ebooks. Versions known to work are K4PC 1.7.1 and earlier. Later version may work but no promises.
+
+4. Download and install under wine ActiveState Active Python 2.7 for Windows 32bit
+
+5. Download and unzip tools_vX.X.zip
+
+6. Now make sure the executable bit is NOT set for KindleBooks.pyw as Linux will actually keep trying to ignore wine and launch it under Linux python which will cause it to fail.
+
+cd tools_vX.X/KindleBooks/
+chmod ugo-x KindleBooks.pyw
+
+7. Then run KindleBook.pyw ***under python running on wine*** using the Linux shell as follows:
+
+wine python KindleBooks.pyw
+
+Select the ebook file directly from your “My Kindle Content” folder, select a new/unused directory for the output. You should not need to enter any PID or Serial Number for Kindle for PC.
+
+
+
+
+Linux and Adobe Digital Editions ePubs
+--------------------------------------
+
+Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!)
+
+
+1. download the most recent version of wine from winehq.org (1.3.29 in my case)
+
+For debian users: 
+
+to get a recent version of wine I decited to use aptosid (2011-02, xfce)
+(because I’m used to debian)
+install aptosid and upgrade it (see aptosid site for detaild instructions)
+
+
+2. properly install Wine (see the Wine site for details)
+
+For debian users:
+
+cd to this dir and install the packages as root:
+‘dpkg -i *.deb’ 
+you will get some error messages, which can be ignored.
+again as root use
+‘apt-get -f install’ to correct this errors
+
+3. python 2.7 should already be installed on your system but you may need the following additional python package
+
+'apt-get install python-tk’
+
+4. all programms need to be installed as normal user. All these programm are installed the same way:
+‘wine ‘
+we need:
+a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
+(there is a “can’t install ADE” site, where the setup.exe hides)
+
+b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads)
+
+c) Win32OpenSSL_Light-0_9_8r.exe (from: http://www.slproweb.com/)
+
+d) pycrypto-2.3.win32-py2.7.msi (from: http://www.voidspace.org.uk/python/modules.shtml)
+
+5. now get and unpack the very latest tools_vX.X (from Apprentice Alf) in the users drive_c of wine
+(~/.wine/drive_c/)
+
+6. start ADE with:
+‘wine digitaleditions.exe’ or from the start menue wine-adobe-digital..
+
+7. register this instance of ADE with your adobeID and close it
+ change to the tools_vX.X dir:
+cd ~/.wine/drive_c/tools_vX.X/Other_Tools/Adobe_ePub_Tools
+
+8. create the adeptkey.der with:
+‘wine python ineptkey_v5.4.pyw’ (only need once!)
+(key will be here: ~/.wine/drive_c/tools_v4.X/Other_Tools/Adobe_ePub_Tools/adeptkey.der)
+
+9. Use ADE running under Wine to dowload all of your purchased ePub ebooks
+
+10. for each book you have downloaded via Adobe Digital Editions
+There is no need to use Wine for this step!
+
+'python ineptpub_v5.6.pyw’
+this will launch a window with 3 lines
+1. key: (allready filled in, otherwise it’s in the path where you did step 8.
+2. input file: drmbook.epub
+3. output file: name-ypu-want_for_free_book.epub
+
+Also… once you successfully generate your adept.der keyfile using WINE, you can use the regular ineptepub plugin with the standard Linux calibre. Just put the *.der file(s) in your calibre configuration directory.
+so if you want you can use calibre in Linux:
+
+11. install the plugins from the tools as discribed in the readmes for win
+
+12. copy the adeptkey.der into the config dir of calibre (~/.config/calibre in debian). Every book imported to calibre will automaticly freed from DRM.
+