]> xmof Git - DeDRM.git/commitdiff
tools v5.6
authorApprentice Alf <apprenticealf@gmail.com>
Sat, 19 Jan 2013 14:50:57 +0000 (14:50 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Sat, 7 Mar 2015 14:21:18 +0000 (14:21 +0000)
51 files changed:
Calibre_Plugins/Ignobleepub_ReadMe.txt [moved from Calibre_Plugins/Ignobleepub ReadMe.txt with 96% similarity]
Calibre_Plugins/Ineptepub_ReadMe.txt [moved from Calibre_Plugins/Ineptepub ReadMe.txt with 94% similarity]
Calibre_Plugins/Ineptpdf_ReadMe.txt [moved from Calibre_Plugins/Ineptpdf ReadMe.txt with 95% similarity]
Calibre_Plugins/K4MobiDeDRM_ReadMe.txt [moved from Calibre_Plugins/K4MobiDeDRM ReadMe.txt with 98% similarity]
Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py
Calibre_Plugins/K4MobiDeDRM_plugin/convert2xml.py
Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py
Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py
Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm.py
Calibre_Plugins/eReaderPDB2PML_ReadMe.txt [moved from Calibre_Plugins/eReaderPDB2PML ReadMe.txt with 100% similarity]
Calibre_Plugins/ignobleepub_plugin.zip
Calibre_Plugins/ignobleepub_plugin/Ignoble Epub DeDRM_Help.htm
Calibre_Plugins/ignobleepub_plugin/__init__.py
Calibre_Plugins/ignobleepub_plugin/ignobleepub.py
Calibre_Plugins/ignobleepub_plugin/ignoblekeygen.py
Calibre_Plugins/ignobleepub_plugin/plugin-import-name-ignobleepub.txt
Calibre_Plugins/ignobleepub_plugin/utilities.py
Calibre_Plugins/ignobleepub_plugin/zipfilerugged.py
Calibre_Plugins/ignobleepub_plugin/zipfix.py
Calibre_Plugins/ineptepub_plugin.zip
Calibre_Plugins/ineptepub_plugin/__init__.py
Calibre_Plugins/ineptepub_plugin/ineptepub.py
Calibre_Plugins/k4mobidedrm_plugin.zip
DeDRM_Macintosh_Application/DeDRM ReadMe.rtf
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/alfcrypto.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.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/ineptepub.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptepub.py [deleted file]
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py [deleted file]
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdf.py [deleted file]
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/flatxml2html.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/genbook.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py [new file with mode: 0644]
DeDRM_Windows_Application/DeDRM_ReadMe.txt
Other_Tools/B&N_Download_Helper/BN-Dload.user_ReadMe.txt
Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51.pyw [new file with mode: 0644]
Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51_ReadMe.txt [new file with mode: 0644]
ReadMe_First.txt

similarity index 96%
rename from Calibre_Plugins/Ignobleepub ReadMe.txt
rename to Calibre_Plugins/Ignobleepub_ReadMe.txt
index dd6a41d3cb71e392f4bda7ef5b62e8b5ed94ccec..5cbe64800015e34782afe07fcb0dc9a5d51f6a21 100644 (file)
@@ -1,4 +1,4 @@
-Ignoble Epub DeDRM - ignobleepub_v02.5_plugin.zip
+Ignoble Epub DeDRM - ignobleepub_v02.6_plugin.zip
 =================================================
 
 All credit given to i♥cabbages for the original standalone scripts. I had the much easier job of converting them to a calibre plugin.
@@ -9,7 +9,7 @@ This plugin is meant to decrypt Barnes & Noble Epubs that are protected with Ado
 Installation
 ------------
 
-Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.5_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.6_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
 
 
 Customization
@@ -30,7 +30,7 @@ Creating New Keys:
 On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.
 
 * Unique Key Name: this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
-* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
+* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences. For some B&N accounts, the name to use is the name used in the default shipping address. For some B&N accounts, the name to use is the name used for the default Credit Card.
 * Credit Card number: this is the credit card number that was set as default with Barnes & Noble at the time of download. Nothing fancy here; no dashes or spaces ... just the 16 (15?) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
 Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.
 
similarity index 94%
rename from Calibre_Plugins/Ineptepub ReadMe.txt
rename to Calibre_Plugins/Ineptepub_ReadMe.txt
index 0620c5fa595afb16d4ed33a35518d089a6837247..dabcb280fc550873c14e05a000f83d2d73cdd5fd 100644 (file)
@@ -1,4 +1,4 @@
-Inept Epub DeDRM - ineptepub_v02.0_plugin.zip
+Inept Epub DeDRM - ineptepub_v02.1_plugin.zip
 =============================================
 
 All credit given to i♥cabbages for the original standalone scripts. I had the much easier job of converting them to a Calibre plugin.
@@ -9,7 +9,7 @@ This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected w
 Installation
 ------------
 
-Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_v02.0_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_v02.1_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
 
 
 
@@ -55,7 +55,7 @@ Paste the information into a comment at my blog, http://apprenticealf.wordpress.
 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!)
+Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien and Fadel!)
 
 
 1. download the most recent version of wine from winehq.org (1.3.29 in my case)
@@ -81,8 +81,7 @@ again as root use
 
 'apt-get install python-tk’
 
-4. all programms need to be installed as normal user. All these programm are installed the same way:
-‘wine ‘
+4. all programms need to be installed as normal user. The .exe files are installed using ‘wine <filename>’ but .msi files must be installed using ‘wine start <filename>’
 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)
similarity index 95%
rename from Calibre_Plugins/Ineptpdf ReadMe.txt
rename to Calibre_Plugins/Ineptpdf_ReadMe.txt
index 180068cddfa4963c8a7b689f0f5f149bd8153593..c82f6a7b95fc6fb8732e3d13bb77611209a68c76 100644 (file)
@@ -54,7 +54,7 @@ Paste the information into a comment at my blog, http://apprenticealf.wordpress.
 Linux and Adobe Digital Editions PDFs
 --------------------------------------
 
-Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!)
+Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien and Fadel!)
 
 
 1. download the most recent version of wine from winehq.org (1.3.29 in my case)
@@ -80,10 +80,9 @@ again as root use
 
 'apt-get install python-tk’
 
-4. all programms need to be installed as normal user. All these programm are installed the same way:
-‘wine ‘
+4. all programms need to be installed as normal user. The .exe files are installed using ‘wine <filename>’ but .msi files must be installed using ‘wine start <filename>’
 we need:
-a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
+a) Adobe Digital Editions 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)
similarity index 98%
rename from Calibre_Plugins/K4MobiDeDRM ReadMe.txt
rename to Calibre_Plugins/K4MobiDeDRM_ReadMe.txt
index f083b9ffecbe2fe8d19b71f79e1d900ff6f8e172..bacc9b5ec0e26b26e052c9e37c45dedffe0929a0 100644 (file)
@@ -1,4 +1,4 @@
-Kindle and Mobipocket Plugin - K4MobiDeDRM_v04.10_plugin.zip
+Kindle and Mobipocket Plugin - K4MobiDeDRM_v04.18_plugin.zip
 ============================================================
 
 Credit given to The Dark Reverser for the original standalone script. Credit also to the many people who have updated and expanded that script since then.
@@ -13,7 +13,7 @@ This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .az
 Installation
 ------------
 
-Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.10_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page.  Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.18_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
 
 Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one.
 
index 46b57c94b2ab029fa97b85580581638a92dea316..6b3fe2f8295a89a2c14ed8dd8c9b38f7a1c30c5e 100644 (file)
@@ -22,13 +22,18 @@ __docformat__ = 'restructuredtext en'
 #   0.4.11 - Fixed Linux support of K4PC
 #   0.4.12 - More Linux Wine fixes
 #   0.4.13 - Ancient Mobipocket files fix
+#   0.4.14 - Error on invalid character in book names fix
+#   0.4.15 - Another Topaz fix
+#   0.4.16 - Yet another Topaz fix
+#   0.4.17 - Manage to include the actual fix.
+#   0.4.18 - More Topaz fixes
 
 """
 Decrypt Amazon Kindle and Mobipocket encrypted ebooks.
 """
 
 PLUGIN_NAME = u"Kindle and Mobipocket DeDRM"
-PLUGIN_VERSION_TUPLE = (0, 4, 13)
+PLUGIN_VERSION_TUPLE = (0, 4, 18)
 PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
 
 import sys, os, re
@@ -170,7 +175,7 @@ class K4DeDRM(FileTypePlugin):
 
         print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-starttime)
 
-        of = self.temporary_file(k4mobidedrm.cleanup_name(k4mobidedrm.unescape(book.getBookTitle()))+book.getBookExtension())
+        of = self.temporary_file(u"decrypted_ebook.{0}".format(book.getBookExtension()))
         book.getFile(of.name)
         book.cleanup()
         return of.name
index 6c8fa83cd5cdeb1fbb00b44a746d4ed085e9ade9..c4e23b76475a1fb6579414399f8d3e573c498988 100644 (file)
@@ -255,13 +255,15 @@ class PageParser(object):
 
         'empty_text_region' : (1, 'snippets', 1, 0),
 
-        'img'           : (1, 'snippets', 1, 0),
-        'img.x'         : (1, 'scalar_number', 0, 0),
-        'img.y'         : (1, 'scalar_number', 0, 0),
-        'img.h'         : (1, 'scalar_number', 0, 0),
-        'img.w'         : (1, 'scalar_number', 0, 0),
-        'img.src'       : (1, 'scalar_number', 0, 0),
-        'img.color_src' : (1, 'scalar_number', 0, 0),
+        'img'                   : (1, 'snippets', 1, 0),
+        'img.x'                 : (1, 'scalar_number', 0, 0),
+        'img.y'                 : (1, 'scalar_number', 0, 0),
+        'img.h'                 : (1, 'scalar_number', 0, 0),
+        'img.w'                 : (1, 'scalar_number', 0, 0),
+        'img.src'               : (1, 'scalar_number', 0, 0),
+        'img.color_src'         : (1, 'scalar_number', 0, 0),
+        'img.gridBeginCenter'   : (1, 'scalar_number', 0, 0),
+        'img.gridEndCenter'     : (1, 'scalar_number', 0, 0),
 
         'paragraph'           : (1, 'snippets', 1, 0),
         'paragraph.class'     : (1, 'scalar_text', 0, 0),
@@ -307,6 +309,7 @@ class PageParser(object):
         'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
 
         'extratokens'            : (1, 'snippets', 1, 0),
+        'extratokens.class'      : (1, 'scalar_text', 0, 0),
         'extratokens.type'       : (1, 'scalar_text', 0, 0),
         'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
         'extratokens.lastGlyph'  : (1, 'scalar_number', 0, 0),
index e5647f4bc3842abb1546088d41b2c899fa88c36a..4d83368c15f990b1d065c81a2aff1a6c00f653f6 100644 (file)
@@ -387,10 +387,14 @@ 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'
+                # we only handle spaceafter word class
+                try:
+                    (cname, space) = argres.split('-',1)
+                    if space == '' : space = '0'
+                    if (cname == 'spaceafter') and (int(space) > 0) :
+                        word_class = 'sa'
+                except:
+                    pass
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
index 97338872be00259f25164d5efa82a6970f24758b..746178f4504278cdd2b620929cf5bfeaf46e41d2 100644 (file)
@@ -117,7 +117,7 @@ class Dictionary(object):
             self.pos = val
             return self.stable[self.pos]
         else:
-            print "Error - %d outside of string table limits" % val
+            print "Error: %d outside of string table limits" % val
             raise TpzDRMError('outside or string table limits')
             # sys.exit(-1)
     def getSize(self):
index 8adb107193e41ad45b85b1833089cf9ef992c118..ca8fdccd794020b93dd8ba153e39a7d71b83662c 100644 (file)
@@ -50,8 +50,9 @@ from __future__ import with_statement
 #  4.7 - Added timing reports, and changed search for Mac key files
 #  4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
 #      - Moved back into plugin, __init__ in plugin now only contains plugin code.
+#  4.9 - Missed some invalid characters in cleanup_name
 
-__version__ = '4.8'
+__version__ = '4.9'
 
 
 import sys, os, re
@@ -144,7 +145,7 @@ def unicode_argv():
 # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
 def cleanup_name(name):
     # substitute filename unfriendly characters
-    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
+    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
     # delete control characters
     name = u"".join(char for char in name if ord(char)>=32)
     # white space to single space, delete leading and trailing while space
@@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
         book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
+        traceback.print_exc()
         return 1
 
     # if we're saving to the same folder as the original, use file name_
index 58086686e46768f1e64df5e3c68f95ff2e68d797..1cbdaa1c4ff3b912e59ce530651550685152db19 100644 (file)
Binary files a/Calibre_Plugins/ignobleepub_plugin.zip and b/Calibre_Plugins/ignobleepub_plugin.zip differ
index 81c2597ac288af39201850c632215f6230091823..2c719d77dbbf396718bda3bf4774241f5829c6ec 100644 (file)
Binary files a/Calibre_Plugins/ignobleepub_plugin/Ignoble Epub DeDRM_Help.htm and b/Calibre_Plugins/ignobleepub_plugin/Ignoble Epub DeDRM_Help.htm differ
index a967d619c877db24e5bd13cbec7a5dce04579706..9d17c92c6f607f6a5be1e7487811a6daa34a8b32 100644 (file)
@@ -44,13 +44,14 @@ __docformat__ = 'restructuredtext en'
 #         - added ability to rename existing keys.
 #   0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and
 #         - ignoblekeygen 2.4 and later.
+#   0.2.6 - Tweaked to eliminate issue with both ignoble and inept calibre plugins installed/enabled at once
 
 """
 Decrypt Barnes & Noble ADEPT encrypted EPUB books.
 """
 
 PLUGIN_NAME = u"Ignoble Epub DeDRM"
-PLUGIN_VERSION_TUPLE = (0, 2, 5)
+PLUGIN_VERSION_TUPLE = (0, 2, 6)
 PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
 # Include an html helpfile in the plugin's zipfile with the following name.
 RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@@ -138,10 +139,7 @@ class IgnobleDeDRM(FileTypePlugin):
         #check the book
         from calibre_plugins.ignobleepub import ignobleepub
         if not ignobleepub.ignobleBook(inf.name):
-            print u"{0} v{1}: {2} is not a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
-            # return the original file, so that no error message is generated in the GUI
-            return path_to_ebook
-
+            raise IGNOBLEError(u"{0} v{1}: {2} is not a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
 
         # Attempt to decrypt epub with each encryption key (generated or provided).
         for keyname, userkey in cfg.prefs['keys'].items():
@@ -152,30 +150,21 @@ class IgnobleDeDRM(FileTypePlugin):
             # Give the user key, ebook and TemporaryPersistent file to the decryption function.
             result = ignobleepub.decryptBook(userkey, inf.name, of.name)
 
-            # Ebook is not a B&N epub... do nothing and pass it on.
-            # This allows a non-encrypted epub to be imported without error messages.
-            if  result[0] == 1:
-                print u"{0} v{1}: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, result[1])
-                of.close()
-                return path_to_ebook
-                break
+            of.close()
 
             # Decryption was successful return the modified PersistentTemporary
             # file to Calibre's import process.
-            if  result[0] == 0:
+            if  result == 0:
                 print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
-                of.close()
                 return of.name
                 break
 
-            print u"{0} v{1}: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, result[1])
-            of.close()
-
+            print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION)
 
         # Something went wrong with decryption.
         # Import the original unmolested epub.
-        print(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION))
-        return path_to_ebook
+        raise IGNOBLEError(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION))
+        return
 
     def is_customizable(self):
         # return true to allow customization via the Plugin->Preferences.
index 2e0bd06d7dfa37b0bb568e648ed244140e2d6d15..e58bf1a7cfce54ffcfbe00450b1432fe3c7287b9 100644 (file)
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 
-from __future__ import with_statement
+<html xmlns="http://www.w3.org/1999/xhtml">
 
-# ignobleepub.pyw, version 3.6
-# Copyright © 2009-2010 by i♥cabbages
+<head>
+<title>Ignoble Epub DeDRM Plugin Configuration</title>
+</head>
 
-# Released under the terms of the GNU General Public Licence, version 3
-# <http://www.gnu.org/licenses/>
+<body>
 
-# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
+<h1>Ignoble Epub DeDRM Plugin</h1>
+<h3>(version 0.2.6)</h3>
+<h3> For additional help read the <a href="http://apprenticealf.wordpress.com/2011/01/17/frequently-asked-questions-about-the-drm-removal-tools/" target="_blank">FAQ</a> on <a href="http://apprenticealf.wordpress.com" target="_blank">Apprentice Alf's Blog</a> and ask questions in the comments section of the <a href="http://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/" target="_blank">first post</a>.</h3>
 
-# Windows users: Before running this program, you must first install Python 2.6
-#   from <http://www.python.org/download/> and 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
-#   ineptepub.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ineptepub.pyw.  You can run this
-#   program from the command line (pythonw ineptepub.pyw) or by double-clicking
-#   it when it has been associated with PythonLauncher.
+<p>All credit given to I ♥ Cabbages for the original standalone scripts (I had the much easier job of converting them to a calibre plugin).</p>
 
-# Revision history:
-#   1 - Initial release
-#   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.3 - On Windows try PyCrypto first and OpenSSL next
-#   3.4 - Modify interace to allow use with import
-#   3.5 - Fix for potential problem with PyCrypto
-#   3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
+<p>This plugin is meant to decrypt Barnes & Noble ePubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.</p>
 
-"""
-Decrypt Barnes & Noble encrypted ePub books.
-"""
+<p>This help file is always available from within the plugin's customization dialog in calibre (when installed, of course). The "Plugin Help" link can be found in the upper-right portion of the customization dialog.</p> 
 
-__license__ = 'GPL v3'
-__version__ = "3.6"
+<h3>Installation:</h3>
 
-import sys
-import os
-import traceback
-import zlib
-import zipfile
-from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
-from contextlib import closing
-import xml.etree.ElementTree as etree
+<p>Go to calibre's Preferences page.  Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file  (ignobleepub_v02.3_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. <b><u>Now restart calibre</u></b>.</p>
 
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
-    def __init__(self, stream):
-        self.stream = stream
-        self.encoding = stream.encoding
-        if self.encoding == None:
-            self.encoding = "utf-8"
-    def write(self, data):
-        if isinstance(data,unicode):
-            data = data.encode(self.encoding,"replace")
-        self.stream.write(data)
-        self.stream.flush()
-    def __getattr__(self, attr):
-        return getattr(self.stream, attr)
 
-try:
-    from calibre.constants import iswindows, isosx
-except:
-    iswindows = sys.platform.startswith('win')
-    isosx = sys.platform.startswith('darwin')
+<h3>Configuration:</h3>
 
-def unicode_argv():
-    if iswindows:
-        # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
-        # strings.
+<p>Upon first installing the plugin (or upgrading from a version earlier than 0.2.0), the plugin will be unconfigured. Until you create at least one B&amp;N key&mdash;or migrate your existing key(s)/data from an earlier version of the plugin&mdash;the plugin will not function. When unconfigured (no saved keys)... an error message will occur whenever ePubs are imported to calibre. To eliminate the error message, open the plugin's customization dialog and create/import/migrate a key (or disable/uninstall the plugin). You can get to the plugin's customization dialog by opening calibre's Preferences dialog, and clicking Plugins (under the Advanced section). Once in the Plugin Preferences, expand the "File type plugins" section and look for the "Ignoble Epub DeDRM" plugin. Highlight that plugin and click the "Customize plugin" button.</p>
 
-        # Versions 2.x of Python don't support Unicode in sys.argv on
-        # Windows, with the underlying Windows API instead replacing multi-byte
-        # characters with '?'.
+<p>If you are upgrading from an earlier version of this plugin and have provided your name(s) and credit card number(s) as part of the old plugin's customization string, you will be prompted to migrate this data to the plugin's new, more secure, key storage method when you open the customization dialog for the first time. If you choose NOT to migrate that data, you will be prompted to save that data as a text file in a location of your choosing. Either way, this plugin will no longer be storing names and credit card numbers in plain sight (or anywhere for that matter) on your computer or in calibre. If you don't choose to migrate OR save the data, that data will be lost. You have been warned!!</p>
 
+<p>Upon configuring for the first time, you may also be asked if you wish to import your existing *.b64 keyfiles (if you use them) to the plugin's new key storage method. The new plugin no longer looks for keyfiles in calibre's configuration directory, so it's highly recommended that you import any existing keyfiles when prompted ... but you <i>always</i> have the ability to import existing keyfiles anytime you might need/want to.</p>
 
-        from ctypes import POINTER, byref, cdll, c_int, windll
-        from ctypes.wintypes import LPCWSTR, LPWSTR
+<p>If you have upgraded from an earlier version of the plugin, the above instructions may be all you need to do to get the new plugin up and running. Continue reading for new-key generation and existing-key management instructions.</p>
 
-        GetCommandLineW = cdll.kernel32.GetCommandLineW
-        GetCommandLineW.argtypes = []
-        GetCommandLineW.restype = LPCWSTR
+<h4 style="margin-left: 1.0em;"><u>Creating New Keys:</u></h4>
 
-        CommandLineToArgvW = windll.shell32.CommandLineToArgvW
-        CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
-        CommandLineToArgvW.restype = POINTER(LPWSTR)
+<p style="margin-left: 1.0em">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
+<ul style="margin-left: 2.0em;">
+<li><b>Unique Key Name:</b> this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.</i>
+<li style="margin-top: 0.5em;"><b>Your Name:</b> Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.</i>
+<li style="margin-top: 0.5em;"><b>Credit Card#:</b> this is the default credit card number that was on file with Barnes & Noble at the time of download of the ebook to be de-DRMed. Nothing fancy here; no dashes or spaces ... just the 16 (15 for American Express) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.</i> 
+</ul> 
 
-        cmd = GetCommandLineW()
-        argc = c_int(0)
-        argv = CommandLineToArgvW(cmd, byref(argc))
-        if argc.value > 0:
-            # Remove Python executable and commands if present
-            start = argc.value - len(sys.argv)
-            return [argv[i] for i in
-                    xrange(start, argc.value)]
-        return [u"ineptepub.py"]
-    else:
-        argvencoding = sys.stdin.encoding
-        if argvencoding == None:
-            argvencoding = "utf-8"
-        return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+<p style="margin-left: 1.0em;">Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.</p>
 
+<h4 style="margin-left: 1.0em;"><u>Deleting Keys:</u></h4>
 
-class IGNOBLEError(Exception):
-    pass
+<p style="margin-left: 1.0em;">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that's what you truly mean to do. Once gone, it's permanently gone.</p>
 
-def _load_crypto_libcrypto():
-    from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
-        Structure, c_ulong, create_string_buffer, cast
-    from ctypes.util import find_library
+<h4 style="margin-left: 1.0em;"><u>Exporting Keys:</u></h4>
 
-    if iswindows:
-        libcrypto = find_library('libeay32')
-    else:
-        libcrypto = find_library('crypto')
+<p style="margin-left: 1.0em;">On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a computer's hard-drive. Use this button to export the highlighted key to a file (*.b64). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
 
-    if libcrypto is None:
-        raise IGNOBLEError('libcrypto not found')
-    libcrypto = CDLL(libcrypto)
+<h4 style="margin-left: 1.0em;"><u>Importing Existing Keyfiles:</u></h4>
 
-    AES_MAXNR = 14
+<p style="margin-left: 1.0em;">At the bottom-left of the plugin's customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing *.b64 keyfiles. Used for migrating keyfiles from older versions of the plugin (or keys generated with the original I &lt;3 Cabbages script), or moving keyfiles from computer to computer, or restoring a backup. Some very basic validation is done to try to avoid overwriting already configured keys with incoming, imported keyfiles with the same base file name, but I'm sure that could be broken if someone tried hard. Just take care when importing.</p>
 
-    c_char_pp = POINTER(c_char_p)
-    c_int_p = POINTER(c_int)
+<p>Once done creating/importing/exporting/deleting decryption keys; click "OK" to exit the customization dialogue (the cancel button will actually work the same way here ... at this point all data/changes are committed already, so take your pick).</p>
 
-    class AES_KEY(Structure):
-        _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
-                    ('rounds', c_int)]
-    AES_KEY_p = POINTER(AES_KEY)
+<h3>Troubleshooting:</h3>
 
-    def F(restype, name, argtypes):
-        func = getattr(libcrypto, name)
-        func.restype = restype
-        func.argtypes = argtypes
-        return func
+<p style="margin-top: 0.5em;">If you find that it's not working for you (imported Barnes & Noble epubs still have DRM), you can save a lot of time and trouble by trying to add the epub to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)</p>
 
-    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',
-                        [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
-                         c_int])
+<p>Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.epub" **. Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.</p>
 
-    class AES(object):
-        def __init__(self, userkey):
-            self._blocksize = len(userkey)
-            if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
-                raise IGNOBLEError('AES improper key used')
-                return
-            key = self._key = AES_KEY()
-            rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
-            if rv < 0:
-                raise IGNOBLEError('Failed to initialize AES key')
+<p>Another way to debug (perhaps easier if you're not all that comfortable with command-line stuff) is to launch calibre in debug mode. Open a command prompt (terminal) and type "calibre-debug -g" (again without the quotes). Calibre will launch, and you can can add the problem book(s) using the normal gui method. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into any online help request you make.</p>
+<p>&nbsp;</p>
+<p>** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.</p>
 
-        def decrypt(self, data):
-            out = create_string_buffer(len(data))
-            iv = ("\x00" * self._blocksize)
-            rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
-            if rv == 0:
-                raise IGNOBLEError('AES decryption failed')
-            return out.raw
+<p>&nbsp;</p>
+<h4>Revision history:</h4>
+<pre>
+   0.1.0 - Initial release
+   0.1.1 - Allow Windows users to make use of openssl if they have it installed.
+          - Incorporated SomeUpdates zipfix routine.
+   0.1.2 - bug fix for non-ascii file names in encryption.xml
+   0.1.3 - Try PyCrypto on Windows first
+   0.1.4 - update zipfix to deal with mimetype not in correct place
+   0.1.5 - update zipfix to deal with completely missing mimetype files
+   0.1.6 - update to the new calibre plugin interface
+   0.1.7 - Fix for potential problem with PyCrypto
+   0.1.8 - an updated/modified zipfix.py and included zipfilerugged.py
+   0.2.0 - Completely overhauled plugin configuration dialog and key management/storage
+   0.2.1 - an updated/modified zipfix.py and included zipfilerugged.py
+   0.2.2 - added in potential fixes from 0.1.7 that had been missed.
+   0.2.3 - fixed possible output/unicode problem
+   0.2.4 - ditched nearly hopeless caselessStrCmp method in favor of uStrCmp.
+         - added ability to rename existing keys.
+   0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and
+         - ignoblekeygen 2.4 and later.
+   0.2.6 - Modified to alleviate the issue with having both the ignoble and inept epub plugins installed/enabled
+</pre>
+</body>
 
-    return AES
-
-def _load_crypto_pycrypto():
-    from Crypto.Cipher import AES as _AES
-
-    class AES(object):
-        def __init__(self, key):
-            self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
-
-        def decrypt(self, data):
-            return self._aes.decrypt(data)
-
-    return AES
-
-def _load_crypto():
-    AES = None
-    cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
-    if sys.platform.startswith('win'):
-        cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
-    for loader in cryptolist:
-        try:
-            AES = loader()
-            break
-        except (ImportError, IGNOBLEError):
-            pass
-    return AES
-
-AES = _load_crypto()
-
-META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
-NSMAP = {'adept': 'http://ns.adobe.com/adept',
-         'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-
-class ZipInfo(zipfile.ZipInfo):
-    def __init__(self, *args, **kwargs):
-        if 'compress_type' in kwargs:
-            compress_type = kwargs.pop('compress_type')
-        super(ZipInfo, self).__init__(*args, **kwargs)
-        self.compress_type = compress_type
-
-class Decryptor(object):
-    def __init__(self, bookkey, encryption):
-        enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
-        self._aes = AES(bookkey)
-        encryption = etree.fromstring(encryption)
-        self._encrypted = encrypted = set()
-        expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
-                               enc('CipherReference'))
-        for elem in encryption.findall(expr):
-            path = elem.get('URI', None)
-            if path is not None:
-                path = path.encode('utf-8')
-                encrypted.add(path)
-
-    def decompress(self, bytes):
-        dc = zlib.decompressobj(-15)
-        bytes = dc.decompress(bytes)
-        ex = dc.decompress('Z') + dc.flush()
-        if ex:
-            bytes = bytes + ex
-        return bytes
-
-    def decrypt(self, path, data):
-        if path in self._encrypted:
-            data = self._aes.decrypt(data)[16:]
-            data = data[:-ord(data[-1])]
-            data = self.decompress(data)
-        return data
-
-# check file to make check whether it's probably an Adobe Adept encrypted ePub
-def ignobleBook(inpath):
-    with closing(ZipFile(open(inpath, 'rb'))) as inf:
-        namelist = set(inf.namelist())
-        if 'META-INF/rights.xml' not in namelist or \
-           'META-INF/encryption.xml' not in namelist:
-            return False
-        try:
-            rights = etree.fromstring(inf.read('META-INF/rights.xml'))
-            adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
-            expr = './/%s' % (adept('encryptedKey'),)
-            bookkey = ''.join(rights.findtext(expr))
-            if len(bookkey) == 64:
-                return True
-        except:
-            # if we couldn't check, assume it is
-            return True
-    return False
-
-# return error code and error message duple
-def decryptBook(keyb64, inpath, outpath):
-    if AES is None:
-        # 1 means don't try again
-        return (1, u"PyCrypto or OpenSSL must be installed.")
-    key = keyb64.decode('base64')[:16]
-    aes = AES(key)
-    with closing(ZipFile(open(inpath, 'rb'))) as inf:
-        namelist = set(inf.namelist())
-        if 'META-INF/rights.xml' not in namelist or \
-           'META-INF/encryption.xml' not in namelist:
-            return (1, u"Not a secure Barnes & Noble ePub.")
-        for name in META_NAMES:
-            namelist.remove(name)
-        try:
-            rights = etree.fromstring(inf.read('META-INF/rights.xml'))
-            adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
-            expr = './/%s' % (adept('encryptedKey'),)
-            bookkey = ''.join(rights.findtext(expr))
-            if len(bookkey) != 64:
-                return (1, u"Not a secure Barnes & Noble ePub.")
-            bookkey = aes.decrypt(bookkey.decode('base64'))
-            bookkey = bookkey[:-ord(bookkey[-1])]
-            encryption = inf.read('META-INF/encryption.xml')
-            decryptor = Decryptor(bookkey[-16:], encryption)
-            kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
-            with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
-                zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
-                outf.writestr(zi, inf.read('mimetype'))
-                for path in namelist:
-                    data = inf.read(path)
-                    outf.writestr(path, decryptor.decrypt(path, data))
-        except Exception, e:
-            return (2, u"{0}.".format(e.args[0]))
-    return (0, u"Success")
-
-
-def cli_main(argv=unicode_argv()):
-    progname = os.path.basename(argv[0])
-    if len(argv) != 4:
-        print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
-        return 1
-    keypath, inpath, outpath = argv[1:]
-    userkey = open(keypath,'rb').read()
-    result = decryptBook(userkey, inpath, outpath)
-    print result[1]
-    return result[0]
-
-def gui_main():
-    import Tkinter
-    import Tkconstants
-    import tkFileDialog
-    import traceback
-
-    class DecryptionDialog(Tkinter.Frame):
-        def __init__(self, root):
-            Tkinter.Frame.__init__(self, root, border=5)
-            self.status = Tkinter.Label(self, text=u"Select files for decryption")
-            self.status.pack(fill=Tkconstants.X, expand=1)
-            body = Tkinter.Frame(self)
-            body.pack(fill=Tkconstants.X, expand=1)
-            sticky = Tkconstants.E + Tkconstants.W
-            body.grid_columnconfigure(1, weight=2)
-            Tkinter.Label(body, text=u"Key file").grid(row=0)
-            self.keypath = Tkinter.Entry(body, width=30)
-            self.keypath.grid(row=0, column=1, sticky=sticky)
-            if os.path.exists(u"bnepubkey.b64"):
-                self.keypath.insert(0, u"bnepubkey.b64")
-            button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
-            button.grid(row=0, column=2)
-            Tkinter.Label(body, text=u"Input file").grid(row=1)
-            self.inpath = Tkinter.Entry(body, width=30)
-            self.inpath.grid(row=1, column=1, sticky=sticky)
-            button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
-            button.grid(row=1, column=2)
-            Tkinter.Label(body, text=u"Output file").grid(row=2)
-            self.outpath = Tkinter.Entry(body, width=30)
-            self.outpath.grid(row=2, column=1, sticky=sticky)
-            button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
-            button.grid(row=2, column=2)
-            buttons = Tkinter.Frame(self)
-            buttons.pack()
-            botton = Tkinter.Button(
-                buttons, text=u"Decrypt", width=10, command=self.decrypt)
-            botton.pack(side=Tkconstants.LEFT)
-            Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
-            button = Tkinter.Button(
-                buttons, text=u"Quit", width=10, command=self.quit)
-            button.pack(side=Tkconstants.RIGHT)
-
-        def get_keypath(self):
-            keypath = tkFileDialog.askopenfilename(
-                parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
-                defaultextension=u".b64",
-                filetypes=[('base64-encoded files', '.b64'),
-                           ('All Files', '.*')])
-            if keypath:
-                keypath = os.path.normpath(keypath)
-                self.keypath.delete(0, Tkconstants.END)
-                self.keypath.insert(0, keypath)
-            return
-
-        def get_inpath(self):
-            inpath = tkFileDialog.askopenfilename(
-                parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
-                defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
-            if inpath:
-                inpath = os.path.normpath(inpath)
-                self.inpath.delete(0, Tkconstants.END)
-                self.inpath.insert(0, inpath)
-            return
-
-        def get_outpath(self):
-            outpath = tkFileDialog.asksaveasfilename(
-                parent=None, title=u"Select unencrypted ePub file to produce",
-                defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
-            if outpath:
-                outpath = os.path.normpath(outpath)
-                self.outpath.delete(0, Tkconstants.END)
-                self.outpath.insert(0, outpath)
-            return
-
-        def decrypt(self):
-            keypath = self.keypath.get()
-            inpath = self.inpath.get()
-            outpath = self.outpath.get()
-            if not keypath or not os.path.exists(keypath):
-                self.status['text'] = u"Specified key file does not exist"
-                return
-            if not inpath or not os.path.exists(inpath):
-                self.status['text'] = u"Specified input file does not exist"
-                return
-            if not outpath:
-                self.status['text'] = u"Output file not specified"
-                return
-            if inpath == outpath:
-                self.status['text'] = u"Must have different input and output files"
-                return
-            userkey = open(keypath,'rb').read()
-            self.status['text'] = u"Decrypting..."
-            try:
-                decrypt_status = decryptBook(userkey, inpath, outpath)
-            except Exception, e:
-                self.status['text'] = u"Error: {0}".format(e.args[0])
-                return
-            if decrypt_status[0] == 0:
-                self.status['text'] = u"File successfully decrypted"
-            else:
-                self.status['text'] = decrypt_status[1]
-
-    root = Tkinter.Tk()
-    root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
-    root.resizable(True, False)
-    root.minsize(300, 0)
-    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
-    root.mainloop()
-    return 0
-
-if __name__ == '__main__':
-    if len(sys.argv) > 1:
-        sys.stdout=SafeUnbuffered(sys.stdout)
-        sys.stderr=SafeUnbuffered(sys.stderr)
-        sys.exit(cli_main())
-    sys.exit(gui_main())
+</html>
index f25359c92a2f47148adf213adbe16c82dc0d8262..b7cbdc5501a9313412b82052dc4d9c96484ce867 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-# ignoblekeygen.pyw, version 2.5
+# ignobleepub.pyw, version 3.7
 # Copyright © 2009-2010 by i♥cabbages
 
 # Released under the terms of the GNU General Public Licence, version 3
@@ -15,31 +15,39 @@ from __future__ import with_statement
 #   from <http://www.python.org/download/> and 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
-#   ignoblekeygen.pyw and double-click on it to run it.
+#   ineptepub.pyw and double-click on it to run it.
 #
-# Mac OS X users: Save this script file as ignoblekeygen.pyw.  You can run this
-#   program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking
+# Mac OS X users: Save this script file as ineptepub.pyw.  You can run this
+#   program from the command line (pythonw ineptepub.pyw) or by double-clicking
 #   it when it has been associated with PythonLauncher.
 
 # Revision history:
 #   1 - Initial release
-#   2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
-#   2.1 - Allow Windows versions of libcrypto to be found
-#   2.2 - On Windows try PyCrypto first and then OpenSSL next
-#   2.3 - Modify interface to allow use of import
-#   2.4 - Improvements to UI and now works in plugins
-#   2.5 - Additional improvement for unicode and plugin support
+#   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 decrypt from encryption.xml
+#   3.3 - On Windows try PyCrypto first, OpenSSL next
+#   3.4 - Modify interface to allow use with import
+#   3.5 - Fix for potential problem with PyCrypto
+#   3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
+#   3.7 - Tweaked to match ineptepub more closely
 
 """
-Generate Barnes & Noble EPUB user key from name and credit card number.
+Decrypt Barnes & Noble encrypted ePub books.
 """
 
 __license__ = 'GPL v3'
-__version__ = "2.5"
+__version__ = "3.7"
 
 import sys
 import os
-import hashlib
+import traceback
+import zlib
+import zipfile
+from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
+from contextlib import closing
+import xml.etree.ElementTree as etree
 
 # Wrap a stream so that output gets flushed immediately
 # and also make sure that any unicode strings get
@@ -58,8 +66,11 @@ class SafeUnbuffered:
     def __getattr__(self, attr):
         return getattr(self.stream, attr)
 
-iswindows = sys.platform.startswith('win')
-isosx = sys.platform.startswith('darwin')
+try:
+    from calibre.constants import iswindows, isosx
+except:
+    iswindows = sys.platform.startswith('win')
+    isosx = sys.platform.startswith('darwin')
 
 def unicode_argv():
     if iswindows:
@@ -90,9 +101,7 @@ def unicode_argv():
             start = argc.value - len(sys.argv)
             return [argv[i] for i in
                     xrange(start, argc.value)]
-        # if we don't have any arguments at all, just pass back script name
-        # this should never happen
-        return [u"ignoblekeygen.py"]
+        return [u"ineptepub.py"]
     else:
         argvencoding = sys.stdin.encoding
         if argvencoding == None:
@@ -133,26 +142,29 @@ def _load_crypto_libcrypto():
         func.argtypes = argtypes
         return func
 
-    AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
+    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',
                         [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):
             self._blocksize = len(userkey)
-            self._iv = iv
+            if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+                raise IGNOBLEError('AES improper key used')
+                return
             key = self._key = AES_KEY()
-            rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
+            rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
             if rv < 0:
-                raise IGNOBLEError('Failed to initialize AES Encrypt key')
+                raise IGNOBLEError('Failed to initialize AES key')
 
-        def encrypt(self, data):
+        def decrypt(self, data):
             out = create_string_buffer(len(data))
-            rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
+            iv = ("\x00" * self._blocksize)
+            rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
             if rv == 0:
-                raise IGNOBLEError('AES encryption failed')
+                raise IGNOBLEError('AES decryption failed')
             return out.raw
 
     return AES
@@ -161,11 +173,11 @@ def _load_crypto_pycrypto():
     from Crypto.Cipher import AES as _AES
 
     class AES(object):
-        def __init__(self, key, iv):
-            self._aes = _AES.new(key, _AES.MODE_CBC, iv)
+        def __init__(self, key):
+            self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
 
-        def encrypt(self, data):
-            return self._aes.encrypt(data)
+        def decrypt(self, data):
+            return self._aes.decrypt(data)
 
     return AES
 
@@ -184,78 +196,151 @@ def _load_crypto():
 
 AES = _load_crypto()
 
-def normalize_name(name):
-    return ''.join(x for x in name.lower() if x != ' ')
-
-
-def generate_key(name, ccn):
-    # remove spaces and case from name and CC numbers.
-    if type(name)==unicode:
-        name = name.encode('utf-8')
-    if type(ccn)==unicode:
-        ccn = ccn.encode('utf-8')
-
-    name = normalize_name(name) + '\x00'
-    ccn = normalize_name(ccn) + '\x00'
-
-    name_sha = hashlib.sha1(name).digest()[:16]
-    ccn_sha = hashlib.sha1(ccn).digest()[:16]
-    both_sha = hashlib.sha1(name + ccn).digest()
-    aes = AES(ccn_sha, name_sha)
-    crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
-    userkey = hashlib.sha1(crypt).digest()
-    return userkey.encode('base64')
-
-
+META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
+NSMAP = {'adept': 'http://ns.adobe.com/adept',
+         'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+class ZipInfo(zipfile.ZipInfo):
+    def __init__(self, *args, **kwargs):
+        if 'compress_type' in kwargs:
+            compress_type = kwargs.pop('compress_type')
+        super(ZipInfo, self).__init__(*args, **kwargs)
+        self.compress_type = compress_type
+
+class Decryptor(object):
+    def __init__(self, bookkey, encryption):
+        enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
+        self._aes = AES(bookkey)
+        encryption = etree.fromstring(encryption)
+        self._encrypted = encrypted = set()
+        expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
+                               enc('CipherReference'))
+        for elem in encryption.findall(expr):
+            path = elem.get('URI', None)
+            if path is not None:
+                path = path.encode('utf-8')
+                encrypted.add(path)
+
+    def decompress(self, bytes):
+        dc = zlib.decompressobj(-15)
+        bytes = dc.decompress(bytes)
+        ex = dc.decompress('Z') + dc.flush()
+        if ex:
+            bytes = bytes + ex
+        return bytes
+
+    def decrypt(self, path, data):
+        if path in self._encrypted:
+            data = self._aes.decrypt(data)[16:]
+            data = data[:-ord(data[-1])]
+            data = self.decompress(data)
+        return data
+
+# check file to make check whether it's probably an Adobe Adept encrypted ePub
+def ignobleBook(inpath):
+    with closing(ZipFile(open(inpath, 'rb'))) as inf:
+        namelist = set(inf.namelist())
+        if 'META-INF/rights.xml' not in namelist or \
+           'META-INF/encryption.xml' not in namelist:
+            return False
+        try:
+            rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+            adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+            expr = './/%s' % (adept('encryptedKey'),)
+            bookkey = ''.join(rights.findtext(expr))
+            if len(bookkey) == 64:
+                return True
+        except:
+            # if we couldn't check, assume it is
+            return True
+    return False
+
+def decryptBook(keyb64, inpath, outpath):
+    if AES is None:
+        raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
+    key = keyb64.decode('base64')[:16]
+    aes = AES(key)
+    with closing(ZipFile(open(inpath, 'rb'))) as inf:
+        namelist = set(inf.namelist())
+        if 'META-INF/rights.xml' not in namelist or \
+           'META-INF/encryption.xml' not in namelist:
+            print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
+            return 1
+        for name in META_NAMES:
+            namelist.remove(name)
+        try:
+            rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+            adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+            expr = './/%s' % (adept('encryptedKey'),)
+            bookkey = ''.join(rights.findtext(expr))
+            if len(bookkey) != 64:
+                print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
+                return 1
+            bookkey = aes.decrypt(bookkey.decode('base64'))
+            bookkey = bookkey[:-ord(bookkey[-1])]
+            encryption = inf.read('META-INF/encryption.xml')
+            decryptor = Decryptor(bookkey[-16:], encryption)
+            kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+            with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+                zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
+                outf.writestr(zi, inf.read('mimetype'))
+                for path in namelist:
+                    data = inf.read(path)
+                    outf.writestr(path, decryptor.decrypt(path, data))
+        except:
+            print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
+            return 2
+    return 0
 
 
 def cli_main(argv=unicode_argv()):
     progname = os.path.basename(argv[0])
-    if AES is None:
-        print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
-              "separately.  Read the top-of-script comment for details." % \
-              (progname,)
-        return 1
     if len(argv) != 4:
-        print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
+        print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
         return 1
-    name, ccn, keypath = argv[1:]
-    userkey = generate_key(name, ccn)
-    open(keypath,'wb').write(userkey)
-    return 0
-
+    keypath, inpath, outpath = argv[1:]
+    userkey = open(keypath,'rb').read()
+    result = decryptBook(userkey, inpath, outpath)
+    if result == 0:
+        print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
+    return result
 
 def gui_main():
     import Tkinter
     import Tkconstants
     import tkFileDialog
-    import tkMessageBox
+    import traceback
 
     class DecryptionDialog(Tkinter.Frame):
         def __init__(self, root):
             Tkinter.Frame.__init__(self, root, border=5)
-            self.status = Tkinter.Label(self, text=u"Enter parameters")
+            self.status = Tkinter.Label(self, text=u"Select files for decryption")
             self.status.pack(fill=Tkconstants.X, expand=1)
             body = Tkinter.Frame(self)
             body.pack(fill=Tkconstants.X, expand=1)
             sticky = Tkconstants.E + Tkconstants.W
             body.grid_columnconfigure(1, weight=2)
-            Tkinter.Label(body, text=u"Account Name").grid(row=0)
-            self.name = Tkinter.Entry(body, width=40)
-            self.name.grid(row=0, column=1, sticky=sticky)
-            Tkinter.Label(body, text=u"CC#").grid(row=1)
-            self.ccn = Tkinter.Entry(body, width=40)
-            self.ccn.grid(row=1, column=1, sticky=sticky)
-            Tkinter.Label(body, text=u"Output file").grid(row=2)
-            self.keypath = Tkinter.Entry(body, width=40)
-            self.keypath.grid(row=2, column=1, sticky=sticky)
-            self.keypath.insert(2, u"bnepubkey.b64")
+            Tkinter.Label(body, text=u"Key file").grid(row=0)
+            self.keypath = Tkinter.Entry(body, width=30)
+            self.keypath.grid(row=0, column=1, sticky=sticky)
+            if os.path.exists(u"bnepubkey.b64"):
+                self.keypath.insert(0, u"bnepubkey.b64")
             button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
+            button.grid(row=0, column=2)
+            Tkinter.Label(body, text=u"Input file").grid(row=1)
+            self.inpath = Tkinter.Entry(body, width=30)
+            self.inpath.grid(row=1, column=1, sticky=sticky)
+            button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
+            button.grid(row=1, column=2)
+            Tkinter.Label(body, text=u"Output file").grid(row=2)
+            self.outpath = Tkinter.Entry(body, width=30)
+            self.outpath.grid(row=2, column=1, sticky=sticky)
+            button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
             button.grid(row=2, column=2)
             buttons = Tkinter.Frame(self)
             buttons.pack()
             botton = Tkinter.Button(
-                buttons, text=u"Generate", width=10, command=self.generate)
+                buttons, text=u"Decrypt", width=10, command=self.decrypt)
             botton.pack(side=Tkconstants.LEFT)
             Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
             button = Tkinter.Button(
@@ -263,8 +348,8 @@ def gui_main():
             button.pack(side=Tkconstants.RIGHT)
 
         def get_keypath(self):
-            keypath = tkFileDialog.asksaveasfilename(
-                parent=None, title=u"Select B&N ePub key file to produce",
+            keypath = tkFileDialog.askopenfilename(
+                parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
                 defaultextension=u".b64",
                 filetypes=[('base64-encoded files', '.b64'),
                            ('All Files', '.*')])
@@ -274,37 +359,56 @@ def gui_main():
                 self.keypath.insert(0, keypath)
             return
 
-        def generate(self):
-            name = self.name.get()
-            ccn = self.ccn.get()
+        def get_inpath(self):
+            inpath = tkFileDialog.askopenfilename(
+                parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
+                defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+            if inpath:
+                inpath = os.path.normpath(inpath)
+                self.inpath.delete(0, Tkconstants.END)
+                self.inpath.insert(0, inpath)
+            return
+
+        def get_outpath(self):
+            outpath = tkFileDialog.asksaveasfilename(
+                parent=None, title=u"Select unencrypted ePub file to produce",
+                defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+            if outpath:
+                outpath = os.path.normpath(outpath)
+                self.outpath.delete(0, Tkconstants.END)
+                self.outpath.insert(0, outpath)
+            return
+
+        def decrypt(self):
             keypath = self.keypath.get()
-            if not name:
-                self.status['text'] = u"Name not specified"
+            inpath = self.inpath.get()
+            outpath = self.outpath.get()
+            if not keypath or not os.path.exists(keypath):
+                self.status['text'] = u"Specified key file does not exist"
                 return
-            if not ccn:
-                self.status['text'] = u"Credit card number not specified"
+            if not inpath or not os.path.exists(inpath):
+                self.status['text'] = u"Specified input file does not exist"
                 return
-            if not keypath:
-                self.status['text'] = u"Output keyfile path not specified"
+            if not outpath:
+                self.status['text'] = u"Output file not specified"
                 return
-            self.status['text'] = u"Generating..."
+            if inpath == outpath:
+                self.status['text'] = u"Must have different input and output files"
+                return
+            userkey = open(keypath,'rb').read()
+            self.status['text'] = u"Decrypting..."
             try:
-                userkey = generate_key(name, ccn)
+                decrypt_status = decryptBook(userkey, inpath, outpath)
             except Exception, e:
-                self.status['text'] = u"Error: (0}".format(e.args[0])
+                self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
-            open(keypath,'wb').write(userkey)
-            self.status['text'] = u"Keyfile successfully generated"
+            if decrypt_status == 0:
+                self.status['text'] = u"File successfully decrypted"
+            else:
+                self.status['text'] = u"The was an error decrypting the file."
 
     root = Tkinter.Tk()
-    if AES is None:
-        root.withdraw()
-        tkMessageBox.showerror(
-            "Ignoble EPUB Keyfile Generator",
-            "This script requires OpenSSL or PyCrypto, which must be installed "
-            "separately.  Read the top-of-script comment for details.")
-        return 1
-    root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
+    root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
     root.resizable(True, False)
     root.minsize(300, 0)
     DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f25359c92a2f47148adf213adbe16c82dc0d8262 100644 (file)
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+# ignoblekeygen.pyw, version 2.5
+# Copyright © 2009-2010 by i♥cabbages
+
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
+
+# Windows users: Before running this program, you must first install Python 2.6
+#   from <http://www.python.org/download/> and 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
+#   ignoblekeygen.pyw and double-click on it to run it.
+#
+# Mac OS X users: Save this script file as ignoblekeygen.pyw.  You can run this
+#   program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking
+#   it when it has been associated with PythonLauncher.
+
+# Revision history:
+#   1 - Initial release
+#   2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
+#   2.1 - Allow Windows versions of libcrypto to be found
+#   2.2 - On Windows try PyCrypto first and then OpenSSL next
+#   2.3 - Modify interface to allow use of import
+#   2.4 - Improvements to UI and now works in plugins
+#   2.5 - Additional improvement for unicode and plugin support
+
+"""
+Generate Barnes & Noble EPUB user key from name and credit card number.
+"""
+
+__license__ = 'GPL v3'
+__version__ = "2.5"
+
+import sys
+import os
+import hashlib
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+    def __init__(self, stream):
+        self.stream = stream
+        self.encoding = stream.encoding
+        if self.encoding == None:
+            self.encoding = "utf-8"
+    def write(self, data):
+        if isinstance(data,unicode):
+            data = data.encode(self.encoding,"replace")
+        self.stream.write(data)
+        self.stream.flush()
+    def __getattr__(self, attr):
+        return getattr(self.stream, attr)
+
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+    if iswindows:
+        # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+        # strings.
+
+        # Versions 2.x of Python don't support Unicode in sys.argv on
+        # Windows, with the underlying Windows API instead replacing multi-byte
+        # characters with '?'.
+
+
+        from ctypes import POINTER, byref, cdll, c_int, windll
+        from ctypes.wintypes import LPCWSTR, LPWSTR
+
+        GetCommandLineW = cdll.kernel32.GetCommandLineW
+        GetCommandLineW.argtypes = []
+        GetCommandLineW.restype = LPCWSTR
+
+        CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+        CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+        CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+        cmd = GetCommandLineW()
+        argc = c_int(0)
+        argv = CommandLineToArgvW(cmd, byref(argc))
+        if argc.value > 0:
+            # Remove Python executable and commands if present
+            start = argc.value - len(sys.argv)
+            return [argv[i] for i in
+                    xrange(start, argc.value)]
+        # if we don't have any arguments at all, just pass back script name
+        # this should never happen
+        return [u"ignoblekeygen.py"]
+    else:
+        argvencoding = sys.stdin.encoding
+        if argvencoding == None:
+            argvencoding = "utf-8"
+        return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
+
+class IGNOBLEError(Exception):
+    pass
+
+def _load_crypto_libcrypto():
+    from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+        Structure, c_ulong, create_string_buffer, cast
+    from ctypes.util import find_library
+
+    if iswindows:
+        libcrypto = find_library('libeay32')
+    else:
+        libcrypto = find_library('crypto')
+
+    if libcrypto is None:
+        raise IGNOBLEError('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_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):
+            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):
+            out = create_string_buffer(len(data))
+            rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
+            if rv == 0:
+                raise IGNOBLEError('AES encryption failed')
+            return out.raw
+
+    return AES
+
+def _load_crypto_pycrypto():
+    from Crypto.Cipher import AES as _AES
+
+    class AES(object):
+        def __init__(self, key, iv):
+            self._aes = _AES.new(key, _AES.MODE_CBC, iv)
+
+        def encrypt(self, data):
+            return self._aes.encrypt(data)
+
+    return AES
+
+def _load_crypto():
+    AES = None
+    cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
+    if sys.platform.startswith('win'):
+        cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+    for loader in cryptolist:
+        try:
+            AES = loader()
+            break
+        except (ImportError, IGNOBLEError):
+            pass
+    return AES
+
+AES = _load_crypto()
+
+def normalize_name(name):
+    return ''.join(x for x in name.lower() if x != ' ')
+
+
+def generate_key(name, ccn):
+    # remove spaces and case from name and CC numbers.
+    if type(name)==unicode:
+        name = name.encode('utf-8')
+    if type(ccn)==unicode:
+        ccn = ccn.encode('utf-8')
+
+    name = normalize_name(name) + '\x00'
+    ccn = normalize_name(ccn) + '\x00'
+
+    name_sha = hashlib.sha1(name).digest()[:16]
+    ccn_sha = hashlib.sha1(ccn).digest()[:16]
+    both_sha = hashlib.sha1(name + ccn).digest()
+    aes = AES(ccn_sha, name_sha)
+    crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
+    userkey = hashlib.sha1(crypt).digest()
+    return userkey.encode('base64')
+
+
+
+
+def cli_main(argv=unicode_argv()):
+    progname = os.path.basename(argv[0])
+    if AES is None:
+        print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
+              "separately.  Read the top-of-script comment for details." % \
+              (progname,)
+        return 1
+    if len(argv) != 4:
+        print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
+        return 1
+    name, ccn, keypath = argv[1:]
+    userkey = generate_key(name, ccn)
+    open(keypath,'wb').write(userkey)
+    return 0
+
+
+def gui_main():
+    import Tkinter
+    import Tkconstants
+    import tkFileDialog
+    import tkMessageBox
+
+    class DecryptionDialog(Tkinter.Frame):
+        def __init__(self, root):
+            Tkinter.Frame.__init__(self, root, border=5)
+            self.status = Tkinter.Label(self, text=u"Enter parameters")
+            self.status.pack(fill=Tkconstants.X, expand=1)
+            body = Tkinter.Frame(self)
+            body.pack(fill=Tkconstants.X, expand=1)
+            sticky = Tkconstants.E + Tkconstants.W
+            body.grid_columnconfigure(1, weight=2)
+            Tkinter.Label(body, text=u"Account Name").grid(row=0)
+            self.name = Tkinter.Entry(body, width=40)
+            self.name.grid(row=0, column=1, sticky=sticky)
+            Tkinter.Label(body, text=u"CC#").grid(row=1)
+            self.ccn = Tkinter.Entry(body, width=40)
+            self.ccn.grid(row=1, column=1, sticky=sticky)
+            Tkinter.Label(body, text=u"Output file").grid(row=2)
+            self.keypath = Tkinter.Entry(body, width=40)
+            self.keypath.grid(row=2, column=1, sticky=sticky)
+            self.keypath.insert(2, u"bnepubkey.b64")
+            button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
+            button.grid(row=2, column=2)
+            buttons = Tkinter.Frame(self)
+            buttons.pack()
+            botton = Tkinter.Button(
+                buttons, text=u"Generate", width=10, command=self.generate)
+            botton.pack(side=Tkconstants.LEFT)
+            Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+            button = Tkinter.Button(
+                buttons, text=u"Quit", width=10, command=self.quit)
+            button.pack(side=Tkconstants.RIGHT)
+
+        def get_keypath(self):
+            keypath = tkFileDialog.asksaveasfilename(
+                parent=None, title=u"Select B&N ePub key file to produce",
+                defaultextension=u".b64",
+                filetypes=[('base64-encoded files', '.b64'),
+                           ('All Files', '.*')])
+            if keypath:
+                keypath = os.path.normpath(keypath)
+                self.keypath.delete(0, Tkconstants.END)
+                self.keypath.insert(0, keypath)
+            return
+
+        def generate(self):
+            name = self.name.get()
+            ccn = self.ccn.get()
+            keypath = self.keypath.get()
+            if not name:
+                self.status['text'] = u"Name not specified"
+                return
+            if not ccn:
+                self.status['text'] = u"Credit card number not specified"
+                return
+            if not keypath:
+                self.status['text'] = u"Output keyfile path not specified"
+                return
+            self.status['text'] = u"Generating..."
+            try:
+                userkey = generate_key(name, ccn)
+            except Exception, e:
+                self.status['text'] = u"Error: (0}".format(e.args[0])
+                return
+            open(keypath,'wb').write(userkey)
+            self.status['text'] = u"Keyfile successfully generated"
+
+    root = Tkinter.Tk()
+    if AES is None:
+        root.withdraw()
+        tkMessageBox.showerror(
+            "Ignoble EPUB Keyfile Generator",
+            "This script requires OpenSSL or PyCrypto, which must be installed "
+            "separately.  Read the top-of-script comment for details.")
+        return 1
+    root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
+    root.resizable(True, False)
+    root.minsize(300, 0)
+    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+    root.mainloop()
+    return 0
+
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        sys.stdout=SafeUnbuffered(sys.stdout)
+        sys.stderr=SafeUnbuffered(sys.stderr)
+        sys.exit(cli_main())
+    sys.exit(gui_main())
index c730607131a9f4a51eac4a9a6a330da8cd823334..6555fed3e44628a21c76c99b2f02d6d9bc35a44c 100644 (file)
Binary files a/Calibre_Plugins/ignobleepub_plugin/utilities.py and b/Calibre_Plugins/ignobleepub_plugin/utilities.py differ
index adf3c53996aa063764978a757d750d876fd095c9..462cd9410c68aa6f4d21eae274c8c00a4be3ee13 100644 (file)
Binary files a/Calibre_Plugins/ignobleepub_plugin/zipfilerugged.py and b/Calibre_Plugins/ignobleepub_plugin/zipfilerugged.py differ
index eaee20d1b257166ac20645cb11f3f57c46822d43..c730607131a9f4a51eac4a9a6a330da8cd823334 100644 (file)
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-import sys
-import zlib
-import zipfilerugged
-import os
-import os.path
-import getopt
-from struct import unpack
-
-
-_FILENAME_LEN_OFFSET = 26
-_EXTRA_LEN_OFFSET = 28
-_FILENAME_OFFSET = 30
-_MAX_SIZE = 64 * 1024
-_MIMETYPE = 'application/epub+zip'
-
-class ZipInfo(zipfilerugged.ZipInfo):
-    def __init__(self, *args, **kwargs):
-        if 'compress_type' in kwargs:
-            compress_type = kwargs.pop('compress_type')
-        super(ZipInfo, self).__init__(*args, **kwargs)
-        self.compress_type = compress_type
-
-class fixZip:
-    def __init__(self, zinput, zoutput):
-        self.ztype = 'zip'
-        if zinput.lower().find('.epub') >= 0 :
-            self.ztype = 'epub'
-        self.inzip = zipfilerugged.ZipFile(zinput,'r')
-        self.outzip = zipfilerugged.ZipFile(zoutput,'w')
-        # open the input zip for reading only as a raw file
-        self.bzf = file(zinput,'rb')
-
-    def getlocalname(self, zi):
-        local_header_offset = zi.header_offset
-        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
-        leninfo = self.bzf.read(2)
-        local_name_length, = unpack('<H', leninfo)
-        self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
-        local_name = self.bzf.read(local_name_length)
-        return local_name
-
-    def uncompress(self, cmpdata):
-        dc = zlib.decompressobj(-15)
-        data = ''
-        while len(cmpdata) > 0:
-            if len(cmpdata) > _MAX_SIZE :
-                newdata = cmpdata[0:_MAX_SIZE]
-                cmpdata = cmpdata[_MAX_SIZE:]
-            else:
-                newdata = cmpdata
-                cmpdata = ''
-            newdata = dc.decompress(newdata)
-            unprocessed = dc.unconsumed_tail
-            if len(unprocessed) == 0:
-                newdata += dc.flush()
-            data += newdata
-            cmpdata += unprocessed
-            unprocessed = ''
-        return data
-
-    def getfiledata(self, zi):
-        # get file name length and exta data length to find start of file data
-        local_header_offset = zi.header_offset
-
-        self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
-        leninfo = self.bzf.read(2)
-        local_name_length, = unpack('<H', leninfo)
-
-        self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
-        exinfo = self.bzf.read(2)
-        extra_field_length, = unpack('<H', exinfo)
-
-        self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
-        data = None
-
-        # if not compressed we are good to go
-        if zi.compress_type == zipfilerugged.ZIP_STORED:
-            data = self.bzf.read(zi.file_size)
-
-        # if compressed we must decompress it using zlib
-        if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
-            cmpdata = self.bzf.read(zi.compress_size)
-            data = self.uncompress(cmpdata)
-
-        return data
-
-
-
-    def fix(self):
-        # get the zipinfo for each member of the input archive
-        # and copy member over to output archive
-        # if problems exist with local vs central filename, fix them
-
-        # if epub write mimetype file first, with no compression
-        if self.ztype == 'epub':
-            nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED)
-            self.outzip.writestr(nzinfo, _MIMETYPE)
-
-        # write the rest of the files
-        for zinfo in self.inzip.infolist():
-            if zinfo.filename != "mimetype" or self.ztype == '.zip':
-                data = None
-                nzinfo = zinfo
-                try:
-                    data = self.inzip.read(zinfo.filename)
-                except zipfilerugged.BadZipfile or zipfilerugged.error:
-                    local_name = self.getlocalname(zinfo)
-                    data = self.getfiledata(zinfo)
-                    nzinfo.filename = local_name
-
-                nzinfo.date_time = zinfo.date_time
-                nzinfo.compress_type = zinfo.compress_type
-                nzinfo.flag_bits = 0
-                nzinfo.internal_attr = 0
-                self.outzip.writestr(nzinfo,data)
-
-        self.bzf.close()
-        self.inzip.close()
-        self.outzip.close()
-
-
-def usage():
-    print """usage: zipfix.py inputzip outputzip
-     inputzip is the source zipfile to fix
-     outputzip is the fixed zip archive
-    """
-
-
-def repairBook(infile, outfile):
-    if not os.path.exists(infile):
-        print "Error: Input Zip File does not exist"
-        return 1
-    try:
-        fr = fixZip(infile, outfile)
-        fr.fix()
-        return 0
-    except Exception, e:
-        print "Error Occurred ", e
-        return 2
-
-
-def main(argv=sys.argv):
-    if len(argv)!=3:
-        usage()
-        return 1
-    infile = argv[1]
-    outfile = argv[2]
-    return repairBook(infile, outfile)
-
-
-if __name__ == '__main__' :
-    sys.exit(main())
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+DETAILED_MESSAGE = \
+'You have personal information stored in this plugin\'s customization '+ \
+'string from a previous version of this plugin.\n\n'+ \
+'This new version of the plugin can convert that info '+ \
+'into key data that the new plugin can then use (which doesn\'t '+ \
+'require personal information to be stored/displayed in an insecure '+ \
+'manner like the old plugin did).\n\nIf you choose NOT to migrate this data at this time '+ \
+'you will be prompted to save that personal data to a file elsewhere; and you\'ll have '+ \
+'to manually re-configure this plugin with your information.\n\nEither way... ' + \
+'this new version of the plugin will not be responsible for storing that personal '+ \
+'info in plain sight any longer.'
+
+def uStrCmp (s1, s2, caseless=False):
+    import unicodedata as ud
+    str1 = s1 if isinstance(s1, unicode) else unicode(s1)
+    str2 = s2 if isinstance(s2, unicode) else unicode(s2)
+    if caseless:
+        return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
+    else:
+        return ud.normalize('NFC', str1) == ud.normalize('NFC', str2)
+
+def parseCustString(keystuff):
+    userkeys = []
+    ar = keystuff.split(':')
+    for i in ar:
+        try:
+            name, ccn = i.split(',')
+            # Generate Barnes & Noble EPUB user key from name and credit card number.
+            userkeys.append(generate_key(name, ccn))
+        except:
+            pass
+    return userkeys
index b1a002b26366eb2437e40a96c0cfbc5d053e928d..93bb9a8eabc73c7cda5840bbb7fcbb7c5df3218e 100644 (file)
Binary files a/Calibre_Plugins/ineptepub_plugin.zip and b/Calibre_Plugins/ineptepub_plugin.zip differ
index 2d20be14aea5dc013c854b4b8cb48c9b4854595c..e37c05179025526eae8e2d7be372bbd5be7d54ce 100644 (file)
@@ -6,8 +6,8 @@ __license__   = 'GPL v3'
 __docformat__ = 'restructuredtext en'
 
 
-# Released under the terms of the GNU General Public Licence, version 3 or
-# later.  <http://www.gnu.org/licenses/>
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
 #
 # Requires Calibre version 0.7.55 or higher.
 #
@@ -58,10 +58,11 @@ __docformat__ = 'restructuredtext en'
 #   0.1.8 - Fix for potential problem with PyCrypto
 #   0.1.9 - Fix for potential problem with ADE keys and fix possible output/unicode problem
 #   0.2.0 - Major code change to use unaltered ineptepub.py file 5.8 or later.
+#   0.2.1 - Tweaked to eliminate issue with both ignoble and inept calibre plugins installed/enabled at once
 
 
 PLUGIN_NAME = u"Inept Epub DeDRM"
-PLUGIN_VERSION_TUPLE = (0, 2, 0)
+PLUGIN_VERSION_TUPLE = (0, 2, 1)
 PLUGIN_VERSION = u'.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
 
 import sys, os, re
@@ -118,16 +119,14 @@ class IneptDeDRM(FileTypePlugin):
             fr = zipfix.fixZip(path_to_ebook, inf.name)
             fr.fix()
         except Exception, e:
-            print u"{0} v{1}: Error when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION)
+            print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
             raise Exception(e)
             return
 
         #check the book
         from calibre_plugins.ineptepub import ineptepub
         if not ineptepub.adeptBook(inf.name):
-            print u"{0} v{1}: {2} is not a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
-            # return the original file, so that no error message is generated in the GUI
-            return path_to_ebook
+            raise ADEPTError(u"{0} v{1}: {2} is not a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
 
         # Load any keyfiles (*.der) included Calibre's config directory.
         userkeys = []
@@ -181,30 +180,23 @@ class IneptDeDRM(FileTypePlugin):
 
         # Attempt to decrypt epub with each encryption key found.
         for userkeyinfo in userkeys:
-            print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, userkeyinfo[1])
+            userkey,keyname = userkeyinfo
+            print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
             of = self.temporary_file(u".epub")
 
             # Give the user key, ebook and TemporaryPersistent file to the decryption function.
-            result = ineptepub.decryptBook(userkeyinfo[0], inf.name, of.name)
-
-            # Ebook is not an Adobe Adept epub... do nothing and pass it on.
-            # This allows a non-encrypted epub to be imported without error messages.
-            if  result == 1:
-                print u"{0} v{1}: {2} is not a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION,os.path.basename(path_to_ebook))
-                of.close()
-                return path_to_ebook
-                break
+            result = ineptepub.decryptBook(userkey, inf.name, of.name)
+
+            of.close()
 
             # Decryption was successful return the modified PersistentTemporary
             # file to Calibre's import process.
             if  result == 0:
                 print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
-                of.close()
                 return of.name
                 break
 
             print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION)
-            of.close
 
         # Something went wrong with decryption.
         # Import the original unmolested epub.
index 4b5a29611a15d9e0dc0fad5b18eec78fb2db18ca..48b7727cfd14903fd0c75e99f177954dba7fa25f 100644 (file)
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 from __future__ import with_statement
@@ -542,7 +542,7 @@ def gui_main():
             try:
                 decrypt_status = decryptBook(userkey, inpath, outpath)
             except Exception, e:
-                self.status['text'] = u"Error; {0}".format(e)
+                self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
             if decrypt_status == 0:
                 self.status['text'] = u"File successfully decrypted"
index 37436f659be8f7074febd5354355d436f7191492..702253e5371ef08076319367590eadc561f8b716 100644 (file)
Binary files a/Calibre_Plugins/k4mobidedrm_plugin.zip and b/Calibre_Plugins/k4mobidedrm_plugin.zip differ
index 9293969dab322cd0cebfde82818895b3b13d73ae..b6f524438fe644d0f91106cd4c7739854ec79f63 100644 (file)
@@ -41,7 +41,7 @@ Mac OS X 10.5 and above: You do
 \i not
 \i0  need to install Python.\
 \
-Drag the DeDRM application from from tools_v5.5.3\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
+Drag the DeDRM application from from tools_v5.6\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
 \
 \
 
index 64cc357d5551795b31a29d58bcf435fd6d9205c2..eee088fa33e51cebfeb00951ff34cd59ead776b0 100644 (file)
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM 5.5.3. AppleScript written 2010–2012 by Apprentice Alf and others.</string>
+       <string>DeDRM 5.6. AppleScript written 2010–2013 by Apprentice Alf and others.</string>
        <key>CFBundleIconFile</key>
        <string>DeDRM</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleName</key>
-       <string>DeDRM 5.5.3</string>
+       <string>DeDRM 5.6</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>5.5.3</string>
+       <string>5.6</string>
        <key>CFBundleSignature</key>
        <string>dplt</string>
        <key>LSRequiresCarbon</key>
index 1504336a69833e85e9960d9f9354b4ce03f3615f..2108e076d5a207a67e0a765de334f705e3e4d44b 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
index b1b06068bea2b1b4103b83323764909b7ff95c05..036ba10ea359017c7cbf07f197c0678531af5e83 100644 (file)
@@ -34,10 +34,14 @@ def _load_libalfcrypto():
         else:
             name_of_lib = 'libalfcrypto64.so'
 
+    # hard code to local location for libalfcrypto
     libalfcrypto = os.path.join(sys.path[0],name_of_lib)
-
     if not os.path.isfile(libalfcrypto):
-        raise Exception('libalfcrypto not found')
+        libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
+    if not os.path.isfile(libalfcrypto):
+        libalfcrypto = os.path.join('.',name_of_lib)
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found at %s' % libalfcrypto)
 
     libalfcrypto = CDLL(libalfcrypto)
 
index 6c8fa83cd5cdeb1fbb00b44a746d4ed085e9ade9..c4e23b76475a1fb6579414399f8d3e573c498988 100644 (file)
@@ -255,13 +255,15 @@ class PageParser(object):
 
         'empty_text_region' : (1, 'snippets', 1, 0),
 
-        'img'           : (1, 'snippets', 1, 0),
-        'img.x'         : (1, 'scalar_number', 0, 0),
-        'img.y'         : (1, 'scalar_number', 0, 0),
-        'img.h'         : (1, 'scalar_number', 0, 0),
-        'img.w'         : (1, 'scalar_number', 0, 0),
-        'img.src'       : (1, 'scalar_number', 0, 0),
-        'img.color_src' : (1, 'scalar_number', 0, 0),
+        'img'                   : (1, 'snippets', 1, 0),
+        'img.x'                 : (1, 'scalar_number', 0, 0),
+        'img.y'                 : (1, 'scalar_number', 0, 0),
+        'img.h'                 : (1, 'scalar_number', 0, 0),
+        'img.w'                 : (1, 'scalar_number', 0, 0),
+        'img.src'               : (1, 'scalar_number', 0, 0),
+        'img.color_src'         : (1, 'scalar_number', 0, 0),
+        'img.gridBeginCenter'   : (1, 'scalar_number', 0, 0),
+        'img.gridEndCenter'     : (1, 'scalar_number', 0, 0),
 
         'paragraph'           : (1, 'snippets', 1, 0),
         'paragraph.class'     : (1, 'scalar_text', 0, 0),
@@ -307,6 +309,7 @@ class PageParser(object):
         'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
 
         'extratokens'            : (1, 'snippets', 1, 0),
+        'extratokens.class'      : (1, 'scalar_text', 0, 0),
         'extratokens.type'       : (1, 'scalar_text', 0, 0),
         'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
         'extratokens.lastGlyph'  : (1, 'scalar_number', 0, 0),
index e5647f4bc3842abb1546088d41b2c899fa88c36a..4d83368c15f990b1d065c81a2aff1a6c00f653f6 100644 (file)
@@ -387,10 +387,14 @@ 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'
+                # we only handle spaceafter word class
+                try:
+                    (cname, space) = argres.split('-',1)
+                    if space == '' : space = '0'
+                    if (cname == 'spaceafter') and (int(space) > 0) :
+                        word_class = 'sa'
+                except:
+                    pass
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
index 97338872be00259f25164d5efa82a6970f24758b..746178f4504278cdd2b620929cf5bfeaf46e41d2 100644 (file)
@@ -117,7 +117,7 @@ class Dictionary(object):
             self.pos = val
             return self.stable[self.pos]
         else:
-            print "Error - %d outside of string table limits" % val
+            print "Error: %d outside of string table limits" % val
             raise TpzDRMError('outside or string table limits')
             # sys.exit(-1)
     def getSize(self):
index 2e0bd06d7dfa37b0bb568e648ed244140e2d6d15..b7cbdc5501a9313412b82052dc4d9c96484ce867 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-# ignobleepub.pyw, version 3.6
+# ignobleepub.pyw, version 3.7
 # Copyright © 2009-2010 by i♥cabbages
 
 # Released under the terms of the GNU General Public Licence, version 3
@@ -26,18 +26,19 @@ 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.3 - On Windows try PyCrypto first and OpenSSL next
-#   3.4 - Modify interace to allow use with import
+#   3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
+#   3.3 - On Windows try PyCrypto first, OpenSSL next
+#   3.4 - Modify interface to allow use with import
 #   3.5 - Fix for potential problem with PyCrypto
 #   3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
+#   3.7 - Tweaked to match ineptepub more closely
 
 """
 Decrypt Barnes & Noble encrypted ePub books.
 """
 
 __license__ = 'GPL v3'
-__version__ = "3.6"
+__version__ = "3.7"
 
 import sys
 import os
@@ -254,18 +255,17 @@ def ignobleBook(inpath):
             return True
     return False
 
-# return error code and error message duple
 def decryptBook(keyb64, inpath, outpath):
     if AES is None:
-        # 1 means don't try again
-        return (1, u"PyCrypto or OpenSSL must be installed.")
+        raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
     key = keyb64.decode('base64')[:16]
     aes = AES(key)
     with closing(ZipFile(open(inpath, 'rb'))) as inf:
         namelist = set(inf.namelist())
         if 'META-INF/rights.xml' not in namelist or \
            'META-INF/encryption.xml' not in namelist:
-            return (1, u"Not a secure Barnes & Noble ePub.")
+            print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
+            return 1
         for name in META_NAMES:
             namelist.remove(name)
         try:
@@ -274,7 +274,8 @@ def decryptBook(keyb64, inpath, outpath):
             expr = './/%s' % (adept('encryptedKey'),)
             bookkey = ''.join(rights.findtext(expr))
             if len(bookkey) != 64:
-                return (1, u"Not a secure Barnes & Noble ePub.")
+                print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
+                return 1
             bookkey = aes.decrypt(bookkey.decode('base64'))
             bookkey = bookkey[:-ord(bookkey[-1])]
             encryption = inf.read('META-INF/encryption.xml')
@@ -286,21 +287,23 @@ def decryptBook(keyb64, inpath, outpath):
                 for path in namelist:
                     data = inf.read(path)
                     outf.writestr(path, decryptor.decrypt(path, data))
-        except Exception, e:
-            return (2, u"{0}.".format(e.args[0]))
-    return (0, u"Success")
+        except:
+            print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
+            return 2
+    return 0
 
 
 def cli_main(argv=unicode_argv()):
     progname = os.path.basename(argv[0])
     if len(argv) != 4:
-        print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
+        print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
         return 1
     keypath, inpath, outpath = argv[1:]
     userkey = open(keypath,'rb').read()
     result = decryptBook(userkey, inpath, outpath)
-    print result[1]
-    return result[0]
+    if result == 0:
+        print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
+    return result
 
 def gui_main():
     import Tkinter
@@ -399,10 +402,10 @@ def gui_main():
             except Exception, e:
                 self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
-            if decrypt_status[0] == 0:
+            if decrypt_status == 0:
                 self.status['text'] = u"File successfully decrypted"
             else:
-                self.status['text'] = decrypt_status[1]
+                self.status['text'] = u"The was an error decrypting the file."
 
     root = Tkinter.Tk()
     root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
index 4b5a29611a15d9e0dc0fad5b18eec78fb2db18ca..48b7727cfd14903fd0c75e99f177954dba7fa25f 100644 (file)
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 from __future__ import with_statement
@@ -542,7 +542,7 @@ def gui_main():
             try:
                 decrypt_status = decryptBook(userkey, inpath, outpath)
             except Exception, e:
-                self.status['text'] = u"Error; {0}".format(e)
+                self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
             if decrypt_status == 0:
                 self.status['text'] = u"File successfully decrypted"
index 8adb107193e41ad45b85b1833089cf9ef992c118..70ed8985d005c81beb10d802fb098504f5ceb905 100644 (file)
@@ -50,8 +50,9 @@ from __future__ import with_statement
 #  4.7 - Added timing reports, and changed search for Mac key files
 #  4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
 #      - Moved back into plugin, __init__ in plugin now only contains plugin code.
+#  4.9 - Missed some invalid characters in cleanup_name
 
-__version__ = '4.8'
+__version__ = '4.9'
 
 
 import sys, os, re
@@ -144,7 +145,7 @@ def unicode_argv():
 # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
 def cleanup_name(name):
     # substitute filename unfriendly characters
-    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
+    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
     # delete control characters
     name = u"".join(char for char in name if ord(char)>=32)
     # white space to single space, delete leading and trailing while space
@@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
         book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
+        traceback.print_exc()
         return 1
 
     # if we're saving to the same folder as the original, use file name_
@@ -246,6 +248,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
 
     # remove internal temporary directory of Topaz pieces
     book.cleanup()
+    return 0
 
 
 def usage(progname):
index 23cc30a1ed331b136c52b340a763948740d50091..8e9290e37ff2e828cf16ed045ae765318f61a9f5 100644 (file)
@@ -1,13 +1,19 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# DeDRM.pyw, version 5.5.3
+# DeDRM.pyw, version 5.6
 # By some_updates and Apprentice Alf
 
 import sys
 import os, os.path
 sys.path.append(os.path.join(sys.path[0],"lib"))
-os.environ['PYTHONIOENCODING'] = "utf-8"
+import sys, os
+import codecs
+
+from argv_utils import add_cp65001_codec, set_utf8_default_encoding, utf8_argv
+add_cp65001_codec()
+set_utf8_default_encoding()
+
 
 import shutil
 import Tkinter
@@ -16,15 +22,35 @@ import Tkconstants
 import tkFileDialog
 from scrolltextwidget import ScrolledText
 from activitybar import ActivityBar
-import subprocess
-from subprocess import Popen, PIPE, STDOUT
-import subasyncio
-from subasyncio import Process
 import re
 import simpleprefs
 
-
-__version__ = '5.5.3'
+from Queue import Full
+from Queue import Empty
+from multiprocessing import Process, Queue
+
+from scriptinterface import decryptepub, decryptpdb, decryptpdf, decryptk4mobi
+
+
+# Wrap a stream so that output gets flushed immediately
+# and appended to shared queue
+class QueuedStream:
+    def __init__(self, stream, q):
+        self.stream = stream
+        self.encoding = stream.encoding
+        self.q = q
+        if self.encoding == None:
+            self.encoding = "utf-8"
+    def write(self, data):
+        if isinstance(data,unicode):
+            data = data.encode(self.encoding,"replace")
+        self.q.put(data)
+        # self.stream.write(data)
+        # self.stream.flush()
+    def __getattr__(self, attr):
+        return getattr(self.stream, attr)
+
+__version__ = '5.6'
 
 class DrmException(Exception):
     pass
@@ -35,6 +61,7 @@ class MainApp(Tk):
         self.withdraw()
         self.dnd = dnd
         self.apphome = apphome
+
         # preference settings
         # [dictionary key, file in preferences directory where info is stored]
         description = [ ['pids'   , 'pidlist.txt'   ],
@@ -152,7 +179,7 @@ class PrefsDialog(Toplevel):
             self.pidnums.set(self.prefs_array['pids'])
         self.pidinfo.grid(row=3, column=1, sticky=sticky)
 
-        Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, first character B, comma separated)').grid(row=4, sticky=Tkconstants.E)
+        Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=4, sticky=Tkconstants.E)
         self.sernums = Tkinter.StringVar()
         self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums)
         if 'serials' in self.prefs_array:
@@ -327,10 +354,11 @@ class ConvDialog(Toplevel):
         self.filenames = filenames
         self.interval = 50
         self.p2 = None
+        self.q = Queue()
         self.running = 'inactive'
         self.numgood = 0
         self.numbad = 0
-        self.log = u""
+        self.log = ''
         self.status = Tkinter.Label(self, text='DeDRM processing...')
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
@@ -378,16 +406,18 @@ class ConvDialog(Toplevel):
             if len(self.filenames) > 0:
                 filename = self.filenames.pop(0)
             if filename == None:
-                msg = u"\nComplete:   Successes: {0}, Failures: {1}\n".format(self.numgood,self.numbad)
+                msg = '\nComplete:  '
+                msg += 'Successes: %d, ' % self.numgood
+                msg += 'Failures: %d\n' % self.numbad
                 self.showCmdOutput(msg)
                 if self.numbad == 0:
                     self.after(2000,self.conversion_done())
                 logfile = os.path.join(rscpath,'dedrm.log')
-                file(logfile,'w').write(self.log.encode('utf8'))
+                file(logfile,'wb').write(self.log)
                 return
             infile = filename
             bname = os.path.basename(infile)
-            msg = u"Processing: {0} ... ".format(bname)
+            msg = 'Processing: ' + bname + ' ... '
             self.log += msg
             self.showCmdOutput(msg)
             outdir = os.path.dirname(filename)
@@ -399,9 +429,9 @@ class ConvDialog(Toplevel):
             if rv == 0:
                 self.bar.start()
                 self.running = 'active'
-                self.processPipe()
+                self.processQueue()
             else:
-                msg = u"Unknown File: {0}\n".format(bname)
+                msg = 'Unknown File: ' + bname + '\n'
                 self.log += msg
                 self.showCmdOutput(msg)
                 self.numbad += 1
@@ -410,7 +440,7 @@ class ConvDialog(Toplevel):
         # kill any still running subprocess
         self.running = 'stopped'
         if self.p2 != None:
-            if (self.p2.wait('nowait') == None):
+            if (self.p2.exitcode == None):
                 self.p2.terminate()
         self.conversion_done()
 
@@ -426,130 +456,127 @@ class ConvDialog(Toplevel):
     # read from subprocess pipe without blocking
     # invoked every interval via the widget "after"
     # option being used, so need to reset it for the next time
-    def processPipe(self):
+    def processQueue(self):
         if self.p2 == None:
             # nothing to wait for so just return
             return
-        poll = self.p2.wait('nowait')
+        poll = self.p2.exitcode
         if poll != None:
             self.bar.stop()
             if poll == 0:
-                msg = u"\nSuccess\n"
+                msg = 'Success\n'
                 self.numgood += 1
-                text = self.p2.read().decode('utf8')
-                text += self.p2.readerr().decode('utf8')
+                done = False
+                text = ''
+                while not done:
+                    try:
+                        data = self.q.get_nowait()
+                        text += data
+                    except Empty:
+                        done = True
+                        pass
                 self.log += text
                 self.log += msg
-            else:
-                msg = u"\nFailed\n"
-                text = self.p2.read().decode('utf8')
-                text += self.p2.readerr().decode('utf8')
-                msg += text
-                self.numbad += 1
+            if poll != 0:
+                msg = 'Failed\n'
+                done = False
+                text = ''
+                while not done:
+                    try:
+                        data = self.q.get_nowait()
+                        text += data
+                    except Empty:
+                        done = True
+                        pass
+                msg += '\n'
+                self.log += text
                 self.log += msg
+                self.numbad += 1
+            self.p2.join()
             self.showCmdOutput(msg)
             self.p2 = None
             self.running = 'inactive'
             self.after(50,self.processBooks)
             return
+        try:
+            text = self.q.get_nowait()
+        except Empty:
+            text = ''
+            pass
+        if text != '':
+            self.log += text
         # make sure we get invoked again by event loop after interval
-        self.stext.after(self.interval,self.processPipe)
+        self.stext.after(self.interval,self.processQueue)
         return
 
     def decrypt_ebook(self, infile, outdir, rscpath):
-        apphome = self.apphome
+        q = self.q
         rv = 1
         name, ext = os.path.splitext(os.path.basename(infile))
         ext = ext.lower()
         if ext == '.epub':
-            self.p2 = processEPUB(apphome, infile, outdir, rscpath)
+            self.p2 = Process(target=processEPUB, args=(q, infile, outdir, rscpath))
+            self.p2.start()
             return 0
         if ext == '.pdb':
-            self.p2 = processPDB(apphome, infile, outdir, rscpath)
+            self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath))
+            self.p2.start()
             return 0
         if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']:
-            self.p2 = processK4MOBI(apphome, infile, outdir, rscpath)
+            self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath))
+            self.p2.start()
             return 0
         if ext == '.pdf':
-            self.p2 = processPDF(apphome, infile, outdir, rscpath)
+            self.p2 = Process(target=processPDF, args=(q, infile, outdir, rscpath))
+            self.p2.start()
             return 0
         return rv
 
 
-# run as a subprocess via pipes and collect stdout, stderr, and return value
-def runit(apphome, ncmd, nparms):
-    pengine = sys.executable
-    if pengine is None or pengine == '':
-        pengine = 'python'
-    pengine = os.path.normpath(pengine)
-    cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
-    # if sys.platform.startswith('win'):
-    #     search_path = os.environ['PATH']
-    #     search_path = search_path.lower()
-    #     if search_path.find('python') < 0:
-    #        # if no python hope that win registry finds what is associated with py extension
-    #        cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
-    cmdline += nparms
-    cmdline = cmdline.encode(sys.getfilesystemencoding())
-    p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False, env = os.environ)
-    return p2
-
-def processK4MOBI(apphome, infile, outdir, rscpath):
-    cmd = os.path.join('lib','k4mobidedrm.py')
-    parms = ''
-    pidnums = ''
-    pidspath = os.path.join(rscpath,'pidlist.txt')
-    if os.path.exists(pidspath):
-        pidnums = file(pidspath,'r').read()
-        pidnums = pidnums.rstrip(os.linesep)
-    if pidnums != '':
-        parms += '-p "' + pidnums + '" '
-    serialnums = ''
-    serialnumspath = os.path.join(rscpath,'seriallist.txt')
-    if os.path.exists(serialnumspath):
-        serialnums = file(serialnumspath,'r').read()
-        serialnums = serialnums.rstrip(os.linesep)
-    if serialnums != '':
-        parms += '-s "' + serialnums + '" '
-
-    files = os.listdir(rscpath)
-    filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
-    files = filter(filefilter.search, files)
-    if files:
-        for filename in files:
-            dpath = os.path.join(rscpath,filename)
-            parms += '-k "' + dpath + '" '
-    parms += '"' + infile +'" "' + outdir + '"'
-    p2 = runit(apphome, cmd, parms)
-    return p2
-
-def processPDF(apphome, infile, outdir, rscpath):
-    cmd = os.path.join('lib','decryptpdf.py')
-    parms =  '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
-    p2 = runit(apphome, cmd, parms)
-    return p2
-
-def processEPUB(apphome, infile, outdir, rscpath):
-    # invoke routine to check both Adept and Barnes and Noble
-    cmd = os.path.join('lib','decryptepub.py')
-    parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
-    p2 = runit(apphome, cmd, parms)
-    return p2
-
-def processPDB(apphome, infile, outdir, rscpath):
-    cmd = os.path.join('lib','decryptpdb.py')
-    parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
-    p2 = runit(apphome, cmd, parms)
-    return p2
-
-
-def main(argv=sys.argv):
-    apphome = os.path.dirname(sys.argv[0])
+# child process starts here
+def processK4MOBI(q, infile, outdir, rscpath):
+    add_cp65001_codec()
+    set_utf8_default_encoding()
+    sys.stdout = QueuedStream(sys.stdout, q)
+    sys.stderr = QueuedStream(sys.stderr, q)
+    rv = decryptk4mobi(infile, outdir, rscpath)
+    sys.exit(rv)
+
+# child process starts here
+def processPDF(q, infile, outdir, rscpath):
+    add_cp65001_codec()
+    set_utf8_default_encoding()
+    sys.stdout = QueuedStream(sys.stdout, q)
+    sys.stderr = QueuedStream(sys.stderr, q)
+    rv = decryptpdf(infile, outdir, rscpath)
+    sys.exit(rv)
+
+# child process starts here
+def processEPUB(q, infile, outdir, rscpath):
+    add_cp65001_codec()
+    set_utf8_default_encoding()
+    sys.stdout = QueuedStream(sys.stdout, q)
+    sys.stderr = QueuedStream(sys.stderr, q)
+    rv = decryptepub(infile, outdir, rscpath)
+    sys.exit(rv)
+
+# child process starts here
+def processPDB(q, infile, outdir, rscpath):
+    add_cp65001_codec()
+    set_utf8_default_encoding()
+    sys.stdout = QueuedStream(sys.stdout, q)
+    sys.stderr = QueuedStream(sys.stderr, q)
+    rv = decryptpdb(infile, outdir, rscpath)
+    sys.exit(rv)
+
+
+def main(argv=utf8_argv()):
+    apphome = os.path.dirname(argv[0])
     apphome = os.path.abspath(apphome)
 
     # windows may pass a spurious quoted null string as argv[1] from bat file
     # simply work around this until we can figure out a better way to handle things
-    if len(argv) == 2:
+    if sys.platform.startswith('win') and len(argv) == 2:
         temp = argv[1]
         temp = temp.strip('"')
         temp = temp.strip()
@@ -563,11 +590,10 @@ def main(argv=sys.argv):
     else : # processing books via drag and drop
         dnd = True
         # build a list of the files to be processed
+        # note all filenames and paths have been utf-8 encoded
         infilelst = argv[1:]
         filenames = []
         for infile in infilelst:
-            infile = infile.decode(sys.getfilesystemencoding())
-            print infile
             infile = infile.replace('"','')
             infile = os.path.abspath(infile)
             if os.path.isdir(infile):
index b1b06068bea2b1b4103b83323764909b7ff95c05..036ba10ea359017c7cbf07f197c0678531af5e83 100644 (file)
@@ -34,10 +34,14 @@ def _load_libalfcrypto():
         else:
             name_of_lib = 'libalfcrypto64.so'
 
+    # hard code to local location for libalfcrypto
     libalfcrypto = os.path.join(sys.path[0],name_of_lib)
-
     if not os.path.isfile(libalfcrypto):
-        raise Exception('libalfcrypto not found')
+        libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
+    if not os.path.isfile(libalfcrypto):
+        libalfcrypto = os.path.join('.',name_of_lib)
+    if not os.path.isfile(libalfcrypto):
+        raise Exception('libalfcrypto not found at %s' % libalfcrypto)
 
     libalfcrypto = CDLL(libalfcrypto)
 
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/argv_utils.py
new file mode 100644 (file)
index 0000000..717387a
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys, os
+import locale
+import codecs
+
+# get sys.argv arguments and encode them into utf-8
+def utf8_argv():
+    if sys.platform.startswith('win'):
+        # Versions 2.x of Python don't support Unicode in sys.argv on
+        # Windows, with the underlying Windows API instead replacing multi-byte
+        # characters with '?'.  So use shell32.GetCommandLineArgvW to get sys.argv 
+        # as a list of Unicode strings and encode them as utf-8
+
+        from ctypes import POINTER, byref, cdll, c_int, windll
+        from ctypes.wintypes import LPCWSTR, LPWSTR
+
+        GetCommandLineW = cdll.kernel32.GetCommandLineW
+        GetCommandLineW.argtypes = []
+        GetCommandLineW.restype = LPCWSTR
+
+        CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+        CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+        CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+        cmd = GetCommandLineW()
+        argc = c_int(0)
+        argv = CommandLineToArgvW(cmd, byref(argc))
+        if argc.value > 0:
+            # Remove Python executable and commands if present
+            start = argc.value - len(sys.argv)
+            return [argv[i].encode('utf-8') for i in
+                    xrange(start, argc.value)]
+        # this should never happen
+        return None
+    else:
+        argv = []
+        argvencoding = sys.stdin.encoding
+        if argvencoding == None:
+            argvencoding = sys.getfilesystemencoding()
+        if argvencoding == None:
+            argvencoding = 'utf-8'
+        for arg in sys.argv:
+            if type(arg) == unicode:
+                argv.append(arg.encode('utf-8'))
+            else:
+                argv.append(arg.decode(argvencoding).encode('utf-8'))
+        return argv
+
+
+def add_cp65001_codec():
+  try:
+    codecs.lookup('cp65001')
+  except LookupError:
+    codecs.register(
+        lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
+  return
+
+
+def set_utf8_default_encoding():
+  if sys.getdefaultencoding() == 'utf-8':
+    return
+
+  # Regenerate setdefaultencoding.
+  reload(sys)
+  sys.setdefaultencoding('utf-8')
+
+  for attr in dir(locale):
+    if attr[0:3] != 'LC_':
+      continue
+    aref = getattr(locale, attr)
+    try:
+      locale.setlocale(aref, '')
+    except locale.Error:
+      continue
+    try:
+      lang = locale.getlocale(aref)[0]
+    except (TypeError, ValueError):
+      continue
+    if lang:
+      try:
+        locale.setlocale(aref, (lang, 'UTF-8'))
+      except locale.Error:
+        os.environ[attr] = lang + '.UTF-8'
+  try:
+    locale.setlocale(locale.LC_ALL, '')
+  except locale.Error:
+    pass
+  return
+
+
index 6c8fa83cd5cdeb1fbb00b44a746d4ed085e9ade9..c4e23b76475a1fb6579414399f8d3e573c498988 100644 (file)
@@ -255,13 +255,15 @@ class PageParser(object):
 
         'empty_text_region' : (1, 'snippets', 1, 0),
 
-        'img'           : (1, 'snippets', 1, 0),
-        'img.x'         : (1, 'scalar_number', 0, 0),
-        'img.y'         : (1, 'scalar_number', 0, 0),
-        'img.h'         : (1, 'scalar_number', 0, 0),
-        'img.w'         : (1, 'scalar_number', 0, 0),
-        'img.src'       : (1, 'scalar_number', 0, 0),
-        'img.color_src' : (1, 'scalar_number', 0, 0),
+        'img'                   : (1, 'snippets', 1, 0),
+        'img.x'                 : (1, 'scalar_number', 0, 0),
+        'img.y'                 : (1, 'scalar_number', 0, 0),
+        'img.h'                 : (1, 'scalar_number', 0, 0),
+        'img.w'                 : (1, 'scalar_number', 0, 0),
+        'img.src'               : (1, 'scalar_number', 0, 0),
+        'img.color_src'         : (1, 'scalar_number', 0, 0),
+        'img.gridBeginCenter'   : (1, 'scalar_number', 0, 0),
+        'img.gridEndCenter'     : (1, 'scalar_number', 0, 0),
 
         'paragraph'           : (1, 'snippets', 1, 0),
         'paragraph.class'     : (1, 'scalar_text', 0, 0),
@@ -307,6 +309,7 @@ class PageParser(object):
         'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
 
         'extratokens'            : (1, 'snippets', 1, 0),
+        'extratokens.class'      : (1, 'scalar_text', 0, 0),
         'extratokens.type'       : (1, 'scalar_text', 0, 0),
         'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
         'extratokens.lastGlyph'  : (1, 'scalar_number', 0, 0),
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptepub.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptepub.py
deleted file mode 100644 (file)
index e64c860..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-class Unbuffered:
-    def __init__(self, stream):
-        self.stream = stream
-    def write(self, data):
-        self.stream.write(data)
-        self.stream.flush()
-    def __getattr__(self, attr):
-        return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-import os
-
-import ineptepub
-import ignobleepub
-import zipfix
-import re
-
-def main(argv=sys.argv):
-    args = argv[1:]
-    if len(args) != 3:
-        return -1
-    infile = args[0]
-    outdir = args[1]
-    rscpath = args[2]
-    errlog = ''
-
-    # first fix the epub to make sure we do not get errors
-    name, ext = os.path.splitext(os.path.basename(infile))
-    bpath = os.path.dirname(infile)
-    zippath = os.path.join(bpath,name + '_temp.zip')
-    rv = zipfix.repairBook(infile, zippath)
-    if rv != 0:
-        print "Error while trying to fix epub"
-        return rv
-
-    # determine a good name for the output file
-    outfile = os.path.join(outdir, name + '_nodrm.epub')
-
-    rv = 1
-    # first try with the Adobe adept epub
-    # try with any keyfiles (*.der) in the rscpath
-    files = os.listdir(rscpath)
-    filefilter = re.compile("\.der$", re.IGNORECASE)
-    files = filter(filefilter.search, files)
-    if files:
-        for filename in files:
-            keypath = os.path.join(rscpath, filename)
-            try:
-                rv = ineptepub.decryptBook(keypath, zippath, outfile)
-                if rv == 0:
-                    break
-            except Exception, e:
-                errlog += str(e)
-                rv = 1
-                pass
-    if rv == 0:
-        os.remove(zippath)
-        return 0
-
-    # still no luck
-    # now try with ignoble epub
-    # try with any keyfiles (*.b64) in the rscpath
-    files = os.listdir(rscpath)
-    filefilter = re.compile("\.b64$", re.IGNORECASE)
-    files = filter(filefilter.search, files)
-    if files:
-        for filename in files:
-            keypath = os.path.join(rscpath, filename)
-            try:
-                rv = ignobleepub.decryptBook(keypath, zippath, outfile)
-                if rv == 0:
-                    break
-            except Exception, e:
-                errlog += str(e)
-                rv = 1
-                pass
-    os.remove(zippath)
-    if rv != 0:
-        print errlog
-    return rv
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py
deleted file mode 100644 (file)
index f0775c1..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-class Unbuffered:
-    def __init__(self, stream):
-        self.stream = stream
-    def write(self, data):
-        self.stream.write(data)
-        self.stream.flush()
-    def __getattr__(self, attr):
-        return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-import os
-
-import erdr2pml
-
-def main(argv=sys.argv):
-    args = argv[1:]
-    if len(args) != 3:
-        return -1
-    infile = args[0]
-    outdir = args[1]
-    rscpath = args[2]
-    rv = 1
-    socialpath = os.path.join(rscpath,'sdrmlist.txt')
-    if os.path.exists(socialpath):
-        keydata = file(socialpath,'r').read()
-        keydata = keydata.rstrip(os.linesep)
-        ar = keydata.split(',')
-        for i in ar:
-            try:
-                name, cc8 = i.split(':')
-            except ValueError:
-                print '   Error parsing user supplied social drm data.'
-                return 1
-            rv = erdr2pml.decryptBook(infile, outdir, True, erdr2pml.getuser_key(name, cc8) )
-            if rv == 0:
-                break
-    return rv
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdf.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdf.py
deleted file mode 100644 (file)
index ddaeacd..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-class Unbuffered:
-    def __init__(self, stream):
-        self.stream = stream
-    def write(self, data):
-        self.stream.write(data)
-        self.stream.flush()
-    def __getattr__(self, attr):
-        return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-import os
-import re
-import ineptpdf
-
-def main(argv=sys.argv):
-    args = argv[1:]
-    if len(args) != 3:
-        return -1
-    infile = args[0]
-    outdir = args[1]
-    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)
-    files = filter(filefilter.search, files)
-    if files:
-        for filename in files:
-            keypath = os.path.join(rscpath, filename)
-            try:
-                rv = ineptpdf.decryptBook(keypath, infile, outfile)
-                if rv == 0:
-                    break
-            except Exception, e:
-                errlog += str(e)
-                rv = 1
-                pass
-    if rv != 0:
-        print errlog
-    return rv
-
-
-if __name__ == "__main__":
-    sys.exit(main())
index e5647f4bc3842abb1546088d41b2c899fa88c36a..4d83368c15f990b1d065c81a2aff1a6c00f653f6 100644 (file)
@@ -387,10 +387,14 @@ 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'
+                # we only handle spaceafter word class
+                try:
+                    (cname, space) = argres.split('-',1)
+                    if space == '' : space = '0'
+                    if (cname == 'spaceafter') and (int(space) > 0) :
+                        word_class = 'sa'
+                except:
+                    pass
 
             elif name.endswith('word.img.src'):
                 result.append(('img' + word_class, int(argres)))
index 97338872be00259f25164d5efa82a6970f24758b..746178f4504278cdd2b620929cf5bfeaf46e41d2 100644 (file)
@@ -117,7 +117,7 @@ class Dictionary(object):
             self.pos = val
             return self.stable[self.pos]
         else:
-            print "Error - %d outside of string table limits" % val
+            print "Error: %d outside of string table limits" % val
             raise TpzDRMError('outside or string table limits')
             # sys.exit(-1)
     def getSize(self):
index 2e0bd06d7dfa37b0bb568e648ed244140e2d6d15..b7cbdc5501a9313412b82052dc4d9c96484ce867 100644 (file)
@@ -3,7 +3,7 @@
 
 from __future__ import with_statement
 
-# ignobleepub.pyw, version 3.6
+# ignobleepub.pyw, version 3.7
 # Copyright © 2009-2010 by i♥cabbages
 
 # Released under the terms of the GNU General Public Licence, version 3
@@ -26,18 +26,19 @@ 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.3 - On Windows try PyCrypto first and OpenSSL next
-#   3.4 - Modify interace to allow use with import
+#   3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
+#   3.3 - On Windows try PyCrypto first, OpenSSL next
+#   3.4 - Modify interface to allow use with import
 #   3.5 - Fix for potential problem with PyCrypto
 #   3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
+#   3.7 - Tweaked to match ineptepub more closely
 
 """
 Decrypt Barnes & Noble encrypted ePub books.
 """
 
 __license__ = 'GPL v3'
-__version__ = "3.6"
+__version__ = "3.7"
 
 import sys
 import os
@@ -254,18 +255,17 @@ def ignobleBook(inpath):
             return True
     return False
 
-# return error code and error message duple
 def decryptBook(keyb64, inpath, outpath):
     if AES is None:
-        # 1 means don't try again
-        return (1, u"PyCrypto or OpenSSL must be installed.")
+        raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
     key = keyb64.decode('base64')[:16]
     aes = AES(key)
     with closing(ZipFile(open(inpath, 'rb'))) as inf:
         namelist = set(inf.namelist())
         if 'META-INF/rights.xml' not in namelist or \
            'META-INF/encryption.xml' not in namelist:
-            return (1, u"Not a secure Barnes & Noble ePub.")
+            print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
+            return 1
         for name in META_NAMES:
             namelist.remove(name)
         try:
@@ -274,7 +274,8 @@ def decryptBook(keyb64, inpath, outpath):
             expr = './/%s' % (adept('encryptedKey'),)
             bookkey = ''.join(rights.findtext(expr))
             if len(bookkey) != 64:
-                return (1, u"Not a secure Barnes & Noble ePub.")
+                print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
+                return 1
             bookkey = aes.decrypt(bookkey.decode('base64'))
             bookkey = bookkey[:-ord(bookkey[-1])]
             encryption = inf.read('META-INF/encryption.xml')
@@ -286,21 +287,23 @@ def decryptBook(keyb64, inpath, outpath):
                 for path in namelist:
                     data = inf.read(path)
                     outf.writestr(path, decryptor.decrypt(path, data))
-        except Exception, e:
-            return (2, u"{0}.".format(e.args[0]))
-    return (0, u"Success")
+        except:
+            print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
+            return 2
+    return 0
 
 
 def cli_main(argv=unicode_argv()):
     progname = os.path.basename(argv[0])
     if len(argv) != 4:
-        print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
+        print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
         return 1
     keypath, inpath, outpath = argv[1:]
     userkey = open(keypath,'rb').read()
     result = decryptBook(userkey, inpath, outpath)
-    print result[1]
-    return result[0]
+    if result == 0:
+        print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
+    return result
 
 def gui_main():
     import Tkinter
@@ -399,10 +402,10 @@ def gui_main():
             except Exception, e:
                 self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
-            if decrypt_status[0] == 0:
+            if decrypt_status == 0:
                 self.status['text'] = u"File successfully decrypted"
             else:
-                self.status['text'] = decrypt_status[1]
+                self.status['text'] = u"The was an error decrypting the file."
 
     root = Tkinter.Tk()
     root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
index 4b5a29611a15d9e0dc0fad5b18eec78fb2db18ca..48b7727cfd14903fd0c75e99f177954dba7fa25f 100644 (file)
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 from __future__ import with_statement
@@ -542,7 +542,7 @@ def gui_main():
             try:
                 decrypt_status = decryptBook(userkey, inpath, outpath)
             except Exception, e:
-                self.status['text'] = u"Error; {0}".format(e)
+                self.status['text'] = u"Error: {0}".format(e.args[0])
                 return
             if decrypt_status == 0:
                 self.status['text'] = u"File successfully decrypted"
index 8adb107193e41ad45b85b1833089cf9ef992c118..70ed8985d005c81beb10d802fb098504f5ceb905 100644 (file)
@@ -50,8 +50,9 @@ from __future__ import with_statement
 #  4.7 - Added timing reports, and changed search for Mac key files
 #  4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
 #      - Moved back into plugin, __init__ in plugin now only contains plugin code.
+#  4.9 - Missed some invalid characters in cleanup_name
 
-__version__ = '4.8'
+__version__ = '4.9'
 
 
 import sys, os, re
@@ -144,7 +145,7 @@ def unicode_argv():
 # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
 def cleanup_name(name):
     # substitute filename unfriendly characters
-    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
+    name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
     # delete control characters
     name = u"".join(char for char in name if ord(char)>=32)
     # white space to single space, delete leading and trailing while space
@@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
         book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
     except Exception, e:
         print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
+        traceback.print_exc()
         return 1
 
     # if we're saving to the same folder as the original, use file name_
@@ -246,6 +248,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
 
     # remove internal temporary directory of Topaz pieces
     book.cleanup()
+    return 0
 
 
 def usage(progname):
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/scriptinterface.py
new file mode 100644 (file)
index 0000000..b8f1cff
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import sys
+import os
+import re
+import ineptepub
+import ignobleepub
+import zipfix
+import ineptpdf
+import erdr2pml
+import k4mobidedrm
+
+def decryptepub(infile, outdir, rscpath):
+    errlog = ''
+
+    # first fix the epub to make sure we do not get errors
+    name, ext = os.path.splitext(os.path.basename(infile))
+    bpath = os.path.dirname(infile)
+    zippath = os.path.join(bpath,name + '_temp.zip')
+    rv = zipfix.repairBook(infile, zippath)
+    if rv != 0:
+        print "Error while trying to fix epub"
+        return rv
+
+    # determine a good name for the output file
+    outfile = os.path.join(outdir, name + '_nodrm.epub')
+
+    rv = 1
+    # first try with the Adobe adept epub
+    # try with any keyfiles (*.der) in the rscpath
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.der$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            keypath = os.path.join(rscpath, filename)
+            userkey = open(keypath,'rb').read()
+            try:
+                rv = ineptepub.decryptBook(userkey, zippath, outfile)
+                if rv == 0:
+                    break
+            except Exception, e:
+                errlog += str(e)
+                rv = 1
+                pass
+    if rv == 0:
+        os.remove(zippath)
+        return 0
+
+    # still no luck
+    # now try with ignoble epub
+    # try with any keyfiles (*.b64) in the rscpath
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.b64$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            keypath = os.path.join(rscpath, filename)
+            userkey = open(keypath,'rb').read()
+            try:
+                rv = ignobleepub.decryptBook(userkey, zippath, outfile)
+                if rv == 0:
+                    break
+            except Exception, e:
+                errlog += str(e)
+                rv = 1
+                pass
+    os.remove(zippath)
+    if rv != 0:
+        print errlog
+    return rv
+
+
+def decryptpdf(infile, outdir, rscpath):
+    errlog = ''
+    rv = 1
+
+    # determine a good name for the output file
+    name, ext = os.path.splitext(os.path.basename(infile))
+    outfile = os.path.join(outdir, name + '_nodrm.pdf')
+
+    # try with any keyfiles (*.der) in the rscpath
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.der$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            keypath = os.path.join(rscpath, filename)
+            userkey = open(keypath,'rb').read()
+            try:
+                rv = ineptpdf.decryptBook(userkey, infile, outfile)
+                if rv == 0:
+                    break
+            except Exception, e:
+                errlog += str(e)
+                rv = 1
+                pass
+    if rv != 0:
+        print errlog
+    return rv
+
+
+def decryptpdb(infile, outdir, rscpath):
+    outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
+    outpath = os.path.join(outdir, outname)
+    rv = 1
+    socialpath = os.path.join(rscpath,'sdrmlist.txt')
+    if os.path.exists(socialpath):
+        keydata = file(socialpath,'r').read()
+        keydata = keydata.rstrip(os.linesep)
+        ar = keydata.split(',')
+        for i in ar:
+            try:
+                name, cc8 = i.split(':')
+            except ValueError:
+                print '   Error parsing user supplied social drm data.'
+                return 1
+            rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
+            if rv == 0:
+                break
+    return rv
+
+
+def decryptk4mobi(infile, outdir, rscpath):
+    rv = 1
+    pidnums = []
+    pidspath = os.path.join(rscpath,'pidlist.txt')
+    if os.path.exists(pidspath):
+        pidstr = file(pidspath,'r').read()
+        pidstr = pidstr.rstrip(os.linesep)
+        pidstr = pidstr.strip()
+        if pidstr != '':
+            pidnums = pidstr.split(',')
+    serialnums = []
+    serialnumspath = os.path.join(rscpath,'seriallist.txt')
+    if os.path.exists(serialnumspath):
+        serialstr = file(serialnumspath,'r').read()
+        serialstr = serialstr.rstrip(os.linesep)
+        serialstr = serialstr.strip()
+        if serialstr != '':
+            serialnums = serialstr.split(',')
+    kInfoFiles = []
+    files = os.listdir(rscpath)
+    filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    if files:
+        for filename in files:
+            dpath = os.path.join(rscpath,filename)
+            kInfoFiles.append(dpath)
+    rv = k4mobidedrm.decryptBook(infile, outdir, kInfoFiles, serialnums, pidnums)
+    return rv
index ad52d33098f04f4724cd2ca8d3b1fcdef570e9e6..2650a19080cb7a5e18412798856865be504fd1f1 100644 (file)
@@ -1,7 +1,7 @@
-ReadMe_DeDRM_v5.5.3_WinApp
+ReadMe_DeDRM_v5.6_WinApp
 ========================
 
-DeDRM_v5.5.3_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 all the "tools" python software in one easy to use program that remembers preferences and settings.
+DeDRM_v5.6_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 all the "tools" python software in one easy to use program that remembers preferences and settings.
 
 It will work without manual configuration for Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
 
@@ -23,9 +23,9 @@ Installation
 
 0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing.
 
-1. Drag the DeDRM_5.5.3 folder from tools_v5.5.3/DeDRM_Applications/Windows to your "My Documents" folder.
+1. Drag the DeDRM_5.6 folder from tools_v5.6/DeDRM_Applications/Windows to your "My Documents" folder.
 
-2. Open the DeDRM_5.5.3 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
+2. Open the DeDRM_5.6 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
 
 3. To set the preferences simply double-click on your just created short-cut.
 
index 6c41ed528d45ac0d608b3f655a3a770f955c4166..bf0390be0f4e35eee1304d7c9a894175e15f04b1 100644 (file)
@@ -9,7 +9,7 @@ If the downloaded file is encrypted, install and configure the ignoble plugin in
 DOWNLOAD HIDDEN FILES FROM B&N
 ------------------------------
 
-Some content is not downloadable from the B&N website, notably magazines. The Greasemonkey script included in the tools modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
+Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (link below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
 
 Prerequisites
 -------------
diff --git a/Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51.pyw b/Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51.pyw
new file mode 100644 (file)
index 0000000..6277c50
--- /dev/null
@@ -0,0 +1,3160 @@
+#! /usr/bin/python
+
+# ineptpdf8.4.51.pyw
+# ineptpdf, version 8.4.51
+
+# To run this program install Python 2.7 from http://www.python.org/download/
+#
+# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
+#
+# and PyWin Extension (Win32API module) from
+# http://sourceforge.net/projects/pywin32/files/
+#
+# Make sure to install the dedicated versions for Python 2.7.
+#
+# It's recommended to use the 32-Bit Python Windows versions (even with a 64-bit
+# Windows system).
+#
+# Save this script file as
+# ineptpdf8.4.51.pyw and double-click on it to run it.
+
+# Revision history:
+#   1 - Initial release
+#   2 - Improved determination of key-generation algorithm
+#   3 - Correctly handle PDF >=1.5 cross-reference streams
+#   4 - Removal of ciando's personal ID (anon)
+#   5 - removing small bug with V3 ebooks (anon)
+#   6 - changed to adeptkey4.der format for 1.7.2 support (anon)
+#   6.1 - backward compatibility for 1.7.1 and old adeptkey.der (anon)
+#   7 - Get cross reference streams and object streams working for input.
+#       Not yet supported on output but this only effects file size,
+#       not functionality. (anon2)
+#   7.1 - Correct a problem when an old trailer is not followed by startxref (anon2)
+#   7.2 - Correct malformed Mac OS resource forks for Stanza
+#       - Support for cross ref streams on output (decreases file size) (anon2)
+#   7.3 - Correct bug in trailer with cross ref stream that caused the error (anon2)
+#         "The root object is missing or invalid" in Adobe Reader.
+#   7.4 - Force all generation numbers in output file to be 0, like in v6.
+#         Fallback code for wrong xref improved (search till last trailer
+#         instead of first) (anon2)
+#   8 - fileopen user machine identifier support (Tetrachroma)
+#   8.1 - fileopen user cookies support (Tetrachroma)
+#   8.2 - fileopen user name/password support (Tetrachroma)
+#   8.3 - fileopen session cookie support (Tetrachroma)
+#   8.3.1 - fix for the "specified key file does not exist" error (Tetrachroma)
+#   8.3.2 - improved server result parsing (Tetrachroma)
+#   8.4 - Ident4D and encrypted Uuid support (Tetrachroma)
+#   8.4.1 - improved MAC address processing (Tetrachroma)
+#   8.4.2 - FowP3Uuid fallback file processing (Tetrachroma)
+#   8.4.3 - improved user/password pdf file detection (Tetrachroma)
+#   8.4.4 - small bugfix (Tetrachroma)
+#   8.4.5 - improved cookie host searching (Tetrachroma)
+#   8.4.6 - STRICT parsing disabled (non-standard pdf processing) (Tetrachroma)
+#   8.4.7 - UTF-8 input file conversion (Tetrachroma)
+#   8.4.8 - fix for more rare utf8 problems (Tetrachroma)
+#   8.4.9 - solution for utf8 in comination with
+#           ident4id method (Tetrachroma)
+#   8.4.10 - line feed processing, non c system drive patch, nrbook support (Tetrachroma)
+#   8.4.11 - alternative ident4id calculation (Tetrachroma)
+#   8.4.12 - fix for capital username characters and
+#            other unusual user login names (Tetrachroma & ZeroPoint)
+#   8.4.13 - small bug fixes (Tetrachroma)
+#   8.4.14 - fix for non-standard-conform fileopen pdfs (Tetrachroma)
+#   8.4.15 - 'bad file descriptor'-fix (Tetrachroma)
+#   8.4.16 - improves user/pass detection (Tetrachroma)
+#   8.4.17 - fix for several '=' chars in a DPRM entity (Tetrachroma)
+#   8.4.18 - follow up bug fix for the DPRM problem,
+#            more readable error messages (Tetrachroma)
+#   8.4.19 - 2nd fix for 'bad file descriptor' problem (Tetrachroma)
+#   8.4.20 - follow up patch (Tetrachroma)
+#   8.4.21 - 3rd patch for 'bad file descriptor' (Tetrachroma)
+#   8.4.22 - disable prints for exception prevention (Tetrachroma)
+#   8.4.23 - check for additional security attributes (Tetrachroma)
+#   8.4.24 - improved cookie session support (Tetrachroma)
+#   8.4.25 - more compatibility with unicode files (Tetrachroma)
+#   8.4.26 - automated session/user cookie request function (works
+#            only with Firefox 3.x+) (Tetrachroma)
+#   8.4.27 - user/password fallback
+#   8.4.28 - AES decryption, improved misconfigured pdf handling,
+#            limited experimental APS support (Tetrachroma & Neisklar)
+#   8.4.29 - backport for bad formatted rc4 encrypted pdfs (Tetrachroma)
+#   8.4.30 - extended authorization attributes support (Tetrachroma)
+#   8.4.31 - improved session cookie and better server response error
+#            handling (Tetrachroma)
+#   8.4.33 - small cookie optimizations (Tetrachroma)
+#   8.4.33 - debug output option (Tetrachroma)
+#   8.4.34 - better user/password management
+#            handles the 'AskUnp' response) (Tetrachroma)
+#   8.4.35 - special handling for non-standard systems (Tetrachroma)
+#   8.4.36 - previous machine/disk handling [PrevMach/PrevDisk] (Tetrachroma)
+#   8.4.36 - FOPN_flock support (Tetrachroma)
+#   8.4.37 - patch for unicode paths/filenames (Tetrachroma)
+#   8.4.38 - small fix for user/password dialog (Tetrachroma)
+#   8.4.39 - sophisticated request mode differentiation, forced
+#            uuid calculation (Tetrachroma)
+#   8.4.40 - fix for non standard server responses (Tetrachroma)
+#   8.4.41 - improved user/password request windows,
+#            better server response tolerance (Tetrachroma)
+#   8.4.42 - improved nl/cr server response parsing (Tetrachroma)
+#   8.4.43 - fix for user names longer than 13 characters and special
+#            uuid encryption (Tetrachroma)
+#   8.4.44 - another fix for ident4d problem (Tetrachroma)
+#   8.4.45 - 2nd fix for ident4d problem (Tetrachroma)
+#   8.4.46 - script cleanup and optimizations (Tetrachroma)
+#   8.4.47 - script identification change to Adobe Reader (Tetrachroma)
+#   8.4.48 - improved tolerance for false file/registry entries (Tetrachroma)
+#   8.4.49 - improved username encryption (Tetrachroma)
+#   8.4.50 - improved (experimental) APS support (Tetrachroma & Neisklar)
+#   8.4.51 - automatic APS offline key retrieval (works only for
+#            Onleihe right now) (80ka80 & Tetrachroma)
+
+"""
+Decrypts Adobe ADEPT-encrypted and Fileopen PDF files.
+"""
+
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import re
+import zlib
+import struct
+import hashlib
+from itertools import chain, islice
+import xml.etree.ElementTree as etree
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+# added for fileopen support
+import urllib
+import urlparse
+import time
+import socket
+import string
+import uuid
+import subprocess
+import time
+import getpass
+from ctypes import *
+import traceback
+import inspect
+import tempfile
+import sqlite3
+import httplib
+try:
+    from Crypto.Cipher import ARC4
+    # needed for newer pdfs
+    from Crypto.Cipher import AES
+    from Crypto.Hash import SHA256
+    from Crypto.PublicKey import RSA
+
+except ImportError:
+    ARC4 = None
+    RSA = None
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+class ADEPTError(Exception):
+    pass
+
+# global variable (needed for fileopen and password decryption)
+INPUTFILEPATH = ''
+KEYFILEPATH = ''
+PASSWORD = ''
+DEBUG_MODE = False
+IVERSION = '8.4.51'
+
+# Do we generate cross reference streams on output?
+# 0 = never
+# 1 = only if present in input
+# 2 = always
+
+GEN_XREF_STM = 1
+
+# This is the value for the current document
+gen_xref_stm = False # will be set in PDFSerializer
+
+###
+### ASN.1 parsing code from tlslite
+
+def bytesToNumber(bytes):
+    total = 0L
+    for byte in bytes:
+        total = (total << 8) + byte
+    return total
+
+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")
+            x = 0
+            for count in range(length):
+                x <<= 8
+                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:
+                raise ASN1Error("Error decoding ASN.1")
+            lengthList = int(lengthList/length)
+            l = [0] * lengthList
+            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
+            elif (self.index - self.indexCheck) == self.lengthCheck:
+                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):
+            markIndex = p.index
+            p.get(1)
+            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:
+            return firstLength
+        else:
+            lengthLength = firstLength & 0x7F
+            return p.get(lengthLength)
+
+###
+### PDF parsing routines from pdfminer, with changes for EBX_HANDLER
+
+##  Utilities
+##
+def choplist(n, seq):
+    '''Groups every n elements of the list.'''
+    r = []
+    for x in seq:
+        r.append(x)
+        if len(r) == n:
+            yield tuple(r)
+            r = []
+    return
+
+def nunpack(s, default=0):
+    '''Unpacks up to 4 bytes big endian.'''
+    l = len(s)
+    if not l:
+        return default
+    elif l == 1:
+        return ord(s)
+    elif l == 2:
+        return struct.unpack('>H', s)[0]
+    elif l == 3:
+        return struct.unpack('>L', '\x00'+s)[0]
+    elif l == 4:
+        return struct.unpack('>L', s)[0]
+    else:
+        return TypeError('invalid length: %d' % l)
+
+
+STRICT = 0
+
+
+##  PS Exceptions
+##
+class PSException(Exception): pass
+class PSEOF(PSException): pass
+class PSSyntaxError(PSException): pass
+class PSTypeError(PSException): pass
+class PSValueError(PSException): pass
+
+
+##  Basic PostScript Types
+##
+
+# PSLiteral
+class PSObject(object): pass
+
+class PSLiteral(PSObject):
+    '''
+    PS literals (e.g. "/Name").
+    Caution: Never create these objects directly.
+    Use PSLiteralTable.intern() instead.
+    '''
+    def __init__(self, name):
+        self.name = name
+        return
+
+    def __repr__(self):
+        name = []
+        for char in self.name:
+            if not char.isalnum():
+                char = '#%02x' % ord(char)
+            name.append(char)
+        return '/%s' % ''.join(name)
+
+# PSKeyword
+class PSKeyword(PSObject):
+    '''
+    PS keywords (e.g. "showpage").
+    Caution: Never create these objects directly.
+    Use PSKeywordTable.intern() instead.
+    '''
+    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]
+        else:
+            lit = self.classe(name)
+            self.dic[name] = lit
+        return lit
+
+PSLiteralTable = PSSymbolTable(PSLiteral)
+PSKeywordTable = PSSymbolTable(PSKeyword)
+LIT = PSLiteralTable.intern
+KWD = PSKeywordTable.intern
+KEYWORD_BRACE_BEGIN = KWD('{')
+KEYWORD_BRACE_END = KWD('}')
+KEYWORD_ARRAY_BEGIN = KWD('[')
+KEYWORD_ARRAY_END = KWD(']')
+KEYWORD_DICT_BEGIN = KWD('<<')
+KEYWORD_DICT_END = KWD('>>')
+
+
+def literal_name(x):
+    if not isinstance(x, PSLiteral):
+        if STRICT:
+            raise PSTypeError('Literal required: %r' % x)
+        else:
+            return str(x)
+    return x.name
+
+def keyword_name(x):
+    if not isinstance(x, PSKeyword):
+        if STRICT:
+            raise PSTypeError('Keyword required: %r' % x)
+        else:
+            return str(x)
+    return x.name
+
+
+##  PSBaseParser
+##
+EOL = re.compile(r'[\r\n]')
+SPC = re.compile(r'\s')
+NONSPC = re.compile(r'\S')
+HEX = re.compile(r'[0-9a-fA-F]')
+END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
+END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
+HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
+END_NUMBER = re.compile(r'[^0-9]')
+END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
+END_STRING = re.compile(r'[()\134]')
+OCT_STRING = re.compile(r'[0-7]')
+ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 }
+
+class PSBaseParser(object):
+
+    '''
+    Most basic PostScript parser that performs only basic tokenization.
+    '''
+    BUFSIZ = 4096
+
+    def __init__(self, fp):
+        self.fp = fp
+        self.seek(0)
+        return
+
+    def __repr__(self):
+        return '<PSBaseParser: %r, bufpos=%d>' % (self.fp, self.bufpos)
+
+    def flush(self):
+        return
+
+    def close(self):
+        self.flush()
+        return
+
+    def tell(self):
+        return self.bufpos+self.charpos
+
+    def poll(self, pos=None, n=80):
+        pos0 = self.fp.tell()
+        if not pos:
+            pos = self.bufpos+self.charpos
+        self.fp.seek(pos)
+        ##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
+        self.fp.seek(pos0)
+        return
+
+    def seek(self, pos):
+        '''
+        Seeks the parser to the given position.
+        '''
+        self.fp.seek(pos)
+        # reset the status for nextline()
+        self.bufpos = pos
+        self.buf = ''
+        self.charpos = 0
+        # reset the status for nexttoken()
+        self.parse1 = self.parse_main
+        self.tokens = []
+        return
+
+    def fillbuf(self):
+        if self.charpos < len(self.buf): return
+        # fetch next chunk.
+        self.bufpos = self.fp.tell()
+        self.buf = self.fp.read(self.BUFSIZ)
+        if not self.buf:
+            raise PSEOF('Unexpected EOF')
+        self.charpos = 0
+        return
+
+    def parse_main(self, s, i):
+        m = NONSPC.search(s, i)
+        if not m:
+            return (self.parse_main, len(s))
+        j = m.start(0)
+        c = s[j]
+        self.tokenstart = self.bufpos+j
+        if c == '%':
+            self.token = '%'
+            return (self.parse_comment, j+1)
+        if c == '/':
+            self.token = ''
+            return (self.parse_literal, j+1)
+        if c in '-+' or c.isdigit():
+            self.token = c
+            return (self.parse_number, j+1)
+        if c == '.':
+            self.token = c
+            return (self.parse_float, j+1)
+        if c.isalpha():
+            self.token = c
+            return (self.parse_keyword, j+1)
+        if c == '(':
+            self.token = ''
+            self.paren = 1
+            return (self.parse_string, j+1)
+        if c == '<':
+            self.token = ''
+            return (self.parse_wopen, j+1)
+        if c == '>':
+            self.token = ''
+            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:
+            self.token += s[i:]
+            return (self.parse_comment, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        # 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:
+            self.token += s[i:]
+            return (self.parse_literal, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        c = s[j]
+        if c == '#':
+            self.hex = ''
+            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:
+            self.hex += c
+            return (self.parse_literal_hex, i+1)
+        if self.hex:
+            self.token += chr(int(self.hex, 16))
+        return (self.parse_literal, i)
+
+    def parse_number(self, s, i):
+        m = END_NUMBER.search(s, i)
+        if not m:
+            self.token += s[i:]
+            return (self.parse_number, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        c = s[j]
+        if c == '.':
+            self.token += c
+            return (self.parse_float, j+1)
+        try:
+            self.add_token(int(self.token))
+        except ValueError:
+            pass
+        return (self.parse_main, j)
+    def parse_float(self, s, i):
+        m = END_NUMBER.search(s, i)
+        if not m:
+            self.token += s[i:]
+            return (self.parse_float, len(s))
+        j = m.start(0)
+        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:
+            self.token += s[i:]
+            return (self.parse_keyword, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        if self.token == 'true':
+            token = True
+        elif self.token == 'false':
+            token = False
+        else:
+            token = KWD(self.token)
+        self.add_token(token)
+        return (self.parse_main, j)
+
+    def parse_string(self, s, i):
+        m = END_STRING.search(s, i)
+        if not m:
+            self.token += s[i:]
+            return (self.parse_string, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        c = s[j]
+        if c == '\\':
+            self.oct = ''
+            return (self.parse_string_1, j+1)
+        if c == '(':
+            self.paren += 1
+            self.token += c
+            return (self.parse_string, j+1)
+        if c == ')':
+            self.paren -= 1
+            if self.paren:
+                self.token += c
+                return (self.parse_string, j+1)
+        self.add_token(self.token)
+        return (self.parse_main, j+1)
+    def parse_string_1(self, s, i):
+        c = s[i]
+        if OCT_STRING.match(c) and len(self.oct) < 3:
+            self.oct += c
+            return (self.parse_string_1, i+1)
+        if self.oct:
+            self.token += chr(int(self.oct, 8))
+            return (self.parse_string, i)
+        if c in ESC_STRING:
+            self.token += chr(ESC_STRING[c])
+        return (self.parse_string, i+1)
+
+    def parse_wopen(self, s, i):
+        c = s[i]
+        if c.isspace() or HEX.match(c):
+            return (self.parse_hexstring, i)
+        if c == '<':
+            self.add_token(KEYWORD_DICT_BEGIN)
+            i += 1
+        return (self.parse_main, i)
+
+    def parse_wclose(self, s, i):
+        c = s[i]
+        if c == '>':
+            self.add_token(KEYWORD_DICT_END)
+            i += 1
+        return (self.parse_main, i)
+
+    def parse_hexstring(self, s, i):
+        m = END_HEX_STRING.search(s, i)
+        if not m:
+            self.token += s[i:]
+            return (self.parse_hexstring, len(s))
+        j = m.start(0)
+        self.token += s[i:j]
+        token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
+                                                 SPC.sub('', self.token))
+        self.add_token(token)
+        return (self.parse_main, j)
+
+    def nexttoken(self):
+        while not self.tokens:
+            self.fillbuf()
+            (self.parse1, self.charpos) = self.parse1(self.buf, self.charpos)
+        token = self.tokens.pop(0)
+        return token
+
+    def nextline(self):
+        '''
+        Fetches a next line that ends either with \\r or \\n.
+        '''
+        linebuf = ''
+        linepos = self.bufpos + self.charpos
+        eol = False
+        while 1:
+            self.fillbuf()
+            if eol:
+                c = self.buf[self.charpos]
+                # handle '\r\n'
+                if c == '\n':
+                    linebuf += c
+                    self.charpos += 1
+                break
+            m = EOL.search(self.buf, self.charpos)
+            if m:
+                linebuf += self.buf[self.charpos:m.end(0)]
+                self.charpos = m.end(0)
+                if linebuf[-1] == '\r':
+                    eol = True
+                else:
+                    break
+            else:
+                linebuf += self.buf[self.charpos:]
+                self.charpos = len(self.buf)
+        return (linepos, linebuf)
+
+    def revreadlines(self):
+        '''
+        Fetches a next line backword. This is used to locate
+        the trailers at the end of a file.
+        '''
+        self.fp.seek(0, 2)
+        pos = self.fp.tell()
+        buf = ''
+        while 0 < pos:
+            prevpos = pos
+            pos = max(0, pos-self.BUFSIZ)
+            self.fp.seek(pos)
+            s = self.fp.read(prevpos-pos)
+            if not s: break
+            while 1:
+                n = max(s.rfind('\r'), s.rfind('\n'))
+                if n == -1:
+                    buf = s + buf
+                    break
+                yield s[n:]+buf
+                s = s[:n]
+                buf = ''
+        return
+
+
+##  PSStackParser
+##
+class PSStackParser(PSBaseParser):
+
+    def __init__(self, fp):
+        PSBaseParser.__init__(self, fp)
+        self.reset()
+        return
+
+    def reset(self):
+        self.context = []
+        self.curtype = None
+        self.curstack = []
+        self.results = []
+        return
+
+    def seek(self, pos):
+        PSBaseParser.seek(self, pos)
+        self.reset()
+        return
+
+    def push(self, *objs):
+        self.curstack.extend(objs)
+        return
+    def pop(self, n):
+        objs = self.curstack[-n:]
+        self.curstack[-n:] = []
+        return objs
+    def popall(self):
+        objs = self.curstack
+        self.curstack = []
+        return objs
+    def add_results(self, *objs):
+        self.results.extend(objs)
+        return
+
+    def start_type(self, pos, type):
+        self.context.append((pos, self.curtype, self.curstack))
+        (self.curtype, self.curstack) = (type, [])
+        return
+    def end_type(self, type):
+        if self.curtype != type:
+            raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
+        objs = [ obj for (_,obj) in self.curstack ]
+        (pos, self.curtype, self.curstack) = self.context.pop()
+        return (pos, objs)
+
+    def do_keyword(self, pos, token):
+        return
+
+    def nextobject(self, direct=False):
+        '''
+        Yields a list of objects: keywords, literals, strings,
+        numbers, arrays and dictionaries. Arrays and dictionaries
+        are represented as Python sequence and dictionaries.
+        '''
+        while not self.results:
+            (pos, token) = self.nexttoken()
+            ##print (pos,token), (self.curtype, self.curstack)
+            if (isinstance(token, int) or
+                    isinstance(token, float) or
+                    isinstance(token, bool) or
+                    isinstance(token, str) or
+                    isinstance(token, PSLiteral)):
+                # normal token
+                self.push((pos, token))
+            elif token == KEYWORD_ARRAY_BEGIN:
+                # begin array
+                self.start_type(pos, 'a')
+            elif token == KEYWORD_ARRAY_END:
+                # end array
+                try:
+                    self.push(self.end_type('a'))
+                except PSTypeError:
+                    if STRICT: raise
+            elif token == KEYWORD_DICT_BEGIN:
+                # begin dictionary
+                self.start_type(pos, 'd')
+            elif token == KEYWORD_DICT_END:
+                # end dictionary
+                try:
+                    (pos, objs) = self.end_type('d')
+                    if len(objs) % 2 != 0:
+                        raise PSSyntaxError(
+                            'Invalid dictionary construct: %r' % objs)
+                    d = dict((literal_name(k), v) \
+                                 for (k,v) in choplist(2, objs))
+                    self.push((pos, d))
+                except PSTypeError:
+                    if STRICT: raise
+            else:
+                self.do_keyword(pos, token)
+            if self.context:
+                continue
+            else:
+                if direct:
+                    return self.pop(1)[0]
+                self.flush()
+        obj = self.results.pop(0)
+        return obj
+
+
+LITERAL_CRYPT = PSLiteralTable.intern('Crypt')
+LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl'))
+LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW'))
+LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85'))
+
+
+##  PDF Objects
+##
+class PDFObject(PSObject): pass
+
+class PDFException(PSException): pass
+class PDFTypeError(PDFException): pass
+class PDFValueError(PDFException): pass
+class PDFNotImplementedError(PSException): pass
+
+
+##  PDFObjRef
+##
+class PDFObjRef(PDFObject):
+
+    def __init__(self, doc, objid, genno):
+        if objid == 0:
+            if STRICT:
+                raise PDFValueError('PDF object id cannot be 0.')
+        self.doc = doc
+        self.objid = objid
+        self.genno = genno
+        return
+
+    def __repr__(self):
+        return '<PDFObjRef:%d %d>' % (self.objid, self.genno)
+
+    def resolve(self):
+        return self.doc.getobj(self.objid)
+
+
+# resolve
+def resolve1(x):
+    '''
+    Resolve an object. If this is an array or dictionary,
+    it may still contains some indirect objects inside.
+    '''
+    while isinstance(x, PDFObjRef):
+        x = x.resolve()
+    return x
+
+def resolve_all(x):
+    '''
+    Recursively resolve X and all the internals.
+    Make sure there is no indirect reference within the nested object.
+    This procedure might be slow.
+    '''
+    while isinstance(x, PDFObjRef):
+        x = x.resolve()
+    if isinstance(x, list):
+        x = [ resolve_all(v) for v in x ]
+    elif isinstance(x, dict):
+        for (k,v) in x.iteritems():
+            x[k] = resolve_all(v)
+    return x
+
+def decipher_all(decipher, objid, genno, x):
+    '''
+    Recursively decipher X.
+    '''
+    if isinstance(x, str):
+        return decipher(objid, genno, x)
+    decf = lambda v: decipher_all(decipher, objid, genno, v)
+    if isinstance(x, list):
+        x = [decf(v) for v in x]
+    elif isinstance(x, dict):
+        x = dict((k, decf(v)) for (k, v) in x.iteritems())
+    return x
+
+
+# Type cheking
+def int_value(x):
+    x = resolve1(x)
+    if not isinstance(x, int):
+        if STRICT:
+            raise PDFTypeError('Integer required: %r' % x)
+        return 0
+    return x
+
+def float_value(x):
+    x = resolve1(x)
+    if not isinstance(x, float):
+        if STRICT:
+            raise PDFTypeError('Float required: %r' % x)
+        return 0.0
+    return x
+
+def num_value(x):
+    x = resolve1(x)
+    if not (isinstance(x, int) or isinstance(x, float)):
+        if STRICT:
+            raise PDFTypeError('Int or Float required: %r' % x)
+        return 0
+    return x
+
+def str_value(x):
+    x = resolve1(x)
+    if not isinstance(x, str):
+        if STRICT:
+            raise PDFTypeError('String required: %r' % x)
+        return ''
+    return x
+
+def list_value(x):
+    x = resolve1(x)
+    if not (isinstance(x, list) or isinstance(x, tuple)):
+        if STRICT:
+            raise PDFTypeError('List required: %r' % x)
+        return []
+    return x
+
+def dict_value(x):
+    x = resolve1(x)
+    if not isinstance(x, dict):
+        if STRICT:
+            raise PDFTypeError('Dict required: %r' % x)
+        return {}
+    return x
+
+def stream_value(x):
+    x = resolve1(x)
+    if not isinstance(x, PDFStream):
+        if STRICT:
+            raise PDFTypeError('PDFStream required: %r' % x)
+        return PDFStream({}, '')
+    return 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
+
+
+##  PDFStream type
+class PDFStream(PDFObject):
+    def __init__(self, dic, rawdata, decipher=None):
+        length = int_value(dic.get('Length', 0))
+        eol = rawdata[length:]
+        # quick and dirty fix for false length attribute,
+        # might not work if the pdf stream parser has a problem
+        if decipher != None and decipher.__name__ == 'decrypt_aes':
+            if (len(rawdata) % 16) != 0:
+                cutdiv = len(rawdata) // 16
+                rawdata = rawdata[:16*cutdiv]
+        else:
+            if eol in ('\r', '\n', '\r\n'):
+                rawdata = rawdata[:length]
+
+        self.dic = dic
+        self.rawdata = rawdata
+        self.decipher = decipher
+        self.data = None
+        self.decdata = None
+        self.objid = None
+        self.genno = None
+        return
+
+    def set_objid(self, objid, genno):
+        self.objid = objid
+        self.genno = genno
+        return
+
+    def __repr__(self):
+        if self.rawdata:
+            return '<PDFStream(%r): raw=%d, %r>' % \
+                   (self.objid, len(self.rawdata), self.dic)
+        else:
+            return '<PDFStream(%r): data=%d, %r>' % \
+                   (self.objid, len(self.data), self.dic)
+
+    def decode(self):
+        assert self.data is None and self.rawdata is not None
+        data = self.rawdata
+        if self.decipher:
+            # Handle encryption
+            data = self.decipher(self.objid, self.genno, data)
+            if gen_xref_stm:
+                self.decdata = data # keep decrypted data
+        if 'Filter' not in self.dic:
+            self.data = data
+            self.rawdata = None
+            ##print self.dict
+            return
+        filters = self.dic['Filter']
+        if not isinstance(filters, list):
+            filters = [ filters ]
+        for f in filters:
+            if f in LITERALS_FLATE_DECODE:
+                # will get errors if the document is encrypted.
+                data = zlib.decompress(data)
+            elif f in LITERALS_LZW_DECODE:
+                data = ''.join(LZWDecoder(StringIO(data)).run())
+            elif f in LITERALS_ASCII85_DECODE:
+                data = ascii85decode(data)
+            elif f == LITERAL_CRYPT:
+                raise PDFNotImplementedError('/Crypt filter is unsupported')
+            else:
+                raise PDFNotImplementedError('Unsupported filter: %r' % f)
+            # apply predictors
+            if 'DP' in self.dic:
+                params = self.dic['DP']
+            else:
+                params = self.dic.get('DecodeParms', {})
+            if 'Predictor' in params:
+                pred = int_value(params['Predictor'])
+                if pred:
+                    if pred != 12:
+                        raise PDFNotImplementedError(
+                            'Unsupported predictor: %r' % pred)
+                    if 'Columns' not in params:
+                        raise PDFValueError(
+                            'Columns undefined for predictor=12')
+                    columns = int_value(params['Columns'])
+                    buf = ''
+                    ent0 = '\x00' * columns
+                    for i in xrange(0, len(data), columns+1):
+                        pred = data[i]
+                        ent1 = data[i+1:i+1+columns]
+                        if pred == '\x02':
+                            ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \
+                                               for (a,b) in zip(ent0,ent1))
+                        buf += ent1
+                        ent0 = ent1
+                    data = buf
+        self.data = data
+        self.rawdata = None
+        return
+
+    def get_data(self):
+        if self.data is None:
+            self.decode()
+        return self.data
+
+    def get_rawdata(self):
+        return self.rawdata
+
+    def get_decdata(self):
+        if self.decdata is not None:
+            return self.decdata
+        data = self.rawdata
+        if self.decipher and data:
+            # Handle encryption
+            data = self.decipher(self.objid, self.genno, data)
+        return data
+
+
+##  PDF Exceptions
+##
+class PDFSyntaxError(PDFException): pass
+class PDFNoValidXRef(PDFSyntaxError): pass
+class PDFEncryptionError(PDFException): pass
+class PDFPasswordIncorrect(PDFEncryptionError): pass
+
+# some predefined literals and keywords.
+LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm')
+LITERAL_XREF = PSLiteralTable.intern('XRef')
+LITERAL_PAGE = PSLiteralTable.intern('Page')
+LITERAL_PAGES = PSLiteralTable.intern('Pages')
+LITERAL_CATALOG = PSLiteralTable.intern('Catalog')
+
+
+##  XRefs
+##
+
+##  PDFXRef
+##
+class PDFXRef(object):
+
+    def __init__(self):
+        self.offsets = None
+        return
+
+    def __repr__(self):
+        return '<PDFXRef: objs=%d>' % len(self.offsets)
+
+    def objids(self):
+        return self.offsets.iterkeys()
+
+    def load(self, parser):
+        self.offsets = {}
+        while 1:
+            try:
+                (pos, line) = parser.nextline()
+            except PSEOF:
+                raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
+            if not line:
+                raise PDFNoValidXRef('Premature eof: %r' % parser)
+            if line.startswith('trailer'):
+                parser.seek(pos)
+                break
+            f = line.strip().split(' ')
+            if len(f) != 2:
+                raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
+            try:
+                (start, nobjs) = map(int, f)
+            except ValueError:
+                raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
+            for objid in xrange(start, start+nobjs):
+                try:
+                    (_, line) = parser.nextline()
+                except PSEOF:
+                    raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
+                f = line.strip().split(' ')
+                if len(f) != 3:
+                    raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
+                (pos, genno, use) = f
+                if use != 'n': continue
+                self.offsets[objid] = (int(genno), int(pos))
+        self.load_trailer(parser)
+        return
+
+    KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
+    def load_trailer(self, parser):
+        try:
+            (_,kwd) = parser.nexttoken()
+            assert kwd is self.KEYWORD_TRAILER
+            (_,dic) = parser.nextobject(direct=True)
+        except PSEOF:
+            x = parser.pop(1)
+            if not x:
+                raise PDFNoValidXRef('Unexpected EOF - file corrupted')
+            (_,dic) = x[0]
+        self.trailer = dict_value(dic)
+        return
+
+    def getpos(self, objid):
+        try:
+            (genno, pos) = self.offsets[objid]
+        except KeyError:
+            raise
+        return (None, pos)
+
+
+##  PDFXRefStream
+##
+class PDFXRefStream(object):
+
+    def __init__(self):
+        self.index = None
+        self.data = None
+        self.entlen = None
+        self.fl1 = self.fl2 = self.fl3 = None
+        return
+
+    def __repr__(self):
+        return '<PDFXRef: objids=%s>' % self.index
+
+    def objids(self):
+        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
+        (_,kwd) = parser.nexttoken()
+        (_,stream) = parser.nextobject()
+        if not isinstance(stream, PDFStream) or \
+           stream.dic['Type'] is not LITERAL_XREF:
+            raise PDFNoValidXRef('Invalid PDF stream spec.')
+        size = stream.dic['Size']
+        index = stream.dic.get('Index', (0,size))
+        self.index = zip(islice(index, 0, None, 2),
+                         islice(index, 1, None, 2))
+        (self.fl1, self.fl2, self.fl3) = stream.dic['W']
+        self.data = stream.get_data()
+        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:
+            if first <= objid  and objid < (first + size):
+                break
+            offset += size
+        else:
+            raise KeyError(objid)
+        i = self.entlen * ((objid - first) + offset)
+        ent = self.data[i:i+self.entlen]
+        f1 = nunpack(ent[:self.fl1], 1)
+        if f1 == 1:
+            pos = nunpack(ent[self.fl1:self.fl1+self.fl2])
+            genno = nunpack(ent[self.fl1+self.fl2:])
+            return (None, pos)
+        elif f1 == 2:
+            objid = nunpack(ent[self.fl1:self.fl1+self.fl2])
+            index = nunpack(ent[self.fl1+self.fl2:])
+            return (objid, index)
+        # this is a free object
+        raise KeyError(objid)
+
+
+##  PDFDocument
+##
+##  A PDFDocument object represents a PDF document.
+##  Since a PDF file is usually pretty big, normally it is not loaded
+##  at once. Rather it is parsed dynamically as processing goes.
+##  A PDF parser is associated with the document.
+##
+class PDFDocument(object):
+
+    def __init__(self):
+        self.xrefs = []
+        self.objs = {}
+        self.parsed_objs = {}
+        self.root = None
+        self.catalog = None
+        self.parser = None
+        self.encryption = None
+        self.decipher = None
+        # dictionaries for fileopen
+        self.fileopen = {}
+        self.urlresult = {}
+        self.ready = False
+        return
+
+    # set_parser(parser)
+    #   Associates the document with an (already initialized) parser object.
+    def set_parser(self, parser):
+        if self.parser: return
+        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
+        # for the document.
+        self.ready = True
+        # Retrieve the information of each header that was appended
+        # (maybe multiple times) at the end of the document.
+        self.xrefs = parser.read_xref()
+        for xref in self.xrefs:
+            trailer = xref.trailer
+            if not trailer: continue
+
+            # If there's an encryption info, remember it.
+            if 'Encrypt' in trailer:
+                #assert not self.encryption
+                try:
+                    self.encryption = (list_value(trailer['ID']),
+                                   dict_value(trailer['Encrypt']))
+                # fix for bad files
+                except:
+                    self.encryption = ('ffffffffffffffffffffffffffffffffffff',
+                                       dict_value(trailer['Encrypt']))
+            if 'Root' in trailer:
+                self.set_root(dict_value(trailer['Root']))
+                break
+        else:
+            raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
+        # The document is set to be non-ready again, until all the
+        # proper initialization (asking the password key and
+        # verifying the access permission, so on) is finished.
+        self.ready = False
+        return
+
+    # set_root(root)
+    #   Set the Root dictionary of the document.
+    #   Each PDF file must have exactly one /Root dictionary.
+    def set_root(self, root):
+        self.root = root
+        self.catalog = dict_value(self.root)
+        if self.catalog.get('Type') is not LITERAL_CATALOG:
+            if STRICT:
+                raise PDFSyntaxError('Catalog not found!')
+        return
+    # initialize(password='')
+    #   Perform the initialization with a given password.
+    #   This step is mandatory even if there's no password associated
+    #   with the document.
+    def initialize(self, password=''):
+        if not self.encryption:
+            self.is_printable = self.is_modifiable = self.is_extractable = True
+            self.ready = True
+            return
+        (docid, param) = self.encryption
+        type = literal_name(param['Filter'])
+        if type == 'Adobe.APS':
+            return self.initialize_adobe_ps(password, docid, param)
+        if type == 'Standard':
+            return self.initialize_standard(password, docid, param)
+        if type == 'EBX_HANDLER':
+            return self.initialize_ebx(password, docid, param)
+        if type == 'FOPN_fLock':
+            # remove of unnecessairy password attribute
+            return self.initialize_fopn_flock(docid, param)
+        if type == 'FOPN_foweb':
+            # remove of unnecessairy password attribute
+            return self.initialize_fopn(docid, param)
+        raise PDFEncryptionError('Unknown filter: param=%r' % param)
+
+    def initialize_adobe_ps(self, password, docid, param):
+        global KEYFILEPATH
+        self.decrypt_key = self.genkey_adobe_ps(param)
+        self.genkey = self.genkey_v4
+        self.decipher = self.decrypt_aes
+        self.ready = True
+        return
+
+    def getPrincipalKey(self, k=None, url=None, referer=None):
+            if url == None:
+                    url="ssl://edc.bibliothek-digital.de/edcws/services/urn:EDCLicenseService"
+            data1='<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SO'+\
+            'AP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http'+\
+            '://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/'+\
+            'XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns1="'+\
+            'http://edc.adobe.com/edcwebservice" xmlns:impl="http://localhost:8080/axis/s'+\
+            'ervices/urn:EDCLicenseService" xmlns:ns2="http://common.edc.adobe.com" xmlns:ns1="'+\
+            'http://ns.adobe.com/PolicyServer/ws"><SOAP-ENV:Header><EDCSecurity>&lt;wsse:Security '+\
+            'xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-'+\
+            '1.0.xsd"&gt;&lt;wsse:UsernameToken&gt;&lt;wsse:Username&gt;edc_anonymous&lt;/wsse:Username&'+\
+            'gt;&lt;wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-'+\
+            'token-profile-1.0#PasswordText"&gt;edc_anonymous&lt;/wsse:Password&gt;&lt;/wsse:UsernameToken&'+\
+            'gt;&lt;/wsse:Security&gt;</EDCSecurity><Version>7</Version><Locale>de-de</Locale></SOAP-ENV:Header>'+\
+            '<SOAP-ENV:Body><impl:synchronize><SynchronizationRequest><firstTime>1</firstTime><licenseSeqNum>0</'+\
+            'licenseSeqNum><policySeqNum>1</policySeqNum><revocationSeqNum>0</revocationSeqNum><'+\
+            'watermarkTemplateSeqNum>0</watermarkTemplateSeqNum></SynchronizationRequest></'+\
+            'impl:synchronize></SOAP-ENV:Body></SOAP-ENV:Envelope>'
+            if k not in url[:40]:
+                return None
+            #~ extract host and path:
+            host=re.compile(r'[a-zA-Z]://([^/]+)/.+', re.I).search(url).group(1)
+            urlpath=re.compile(r'[a-zA-Z]://[^/]+(/.+)', re.I).search(url).group(1)
+
+            # open a socket connection on port 80
+
+            conn = httplib.HTTPSConnection(host, 443)
+
+            #~ Headers for request
+            headers={"Accept": "*/*", "Host": host, "User-Agent": "Mozilla/3.0 (compatible; Acrobat EDC SOAP 1.0)",
+                     "Content-Type": "text/xml; charset=utf-8", "Cache-Control": "no-cache", "SOAPAction": ""}
+
+            # send data1 and headers
+            try:
+                    conn.request("POST", urlpath, data1, headers)
+            except:
+                    raise ADEPTError("Could not post request to '"+host+"'.")
+
+            # read respose
+            try:
+                    response = conn.getresponse()
+                    responsedata=response.read()
+            except:
+                    raise ADEPTError("Could not read response from '"+host+"'.")
+
+            # close connection
+            conn.close()
+
+            try:
+                    key=re.compile(r'PricipalKey"((?!<key>).)*<key[^>]*>(((?!</key>).)*)</key>', re.I).search(responsedata).group(2)
+
+            except :
+                    key=None
+            return key
+
+    def genkey_adobe_ps(self, param):
+        # nice little offline principal keys dictionary
+        principalkeys = { 'bibliothek-digital.de': 'Dzqx8McQUNd2CDzBVmtnweUxVWlqJTMqyYtiDIc4dZI='.decode('base64')}
+        for k, v in principalkeys.iteritems():
+            result = self.getPrincipalKey(k)
+            #print result
+            if result != None:
+                principalkeys[k] = result.decode('base64')
+            else:
+                raise ADEPTError("No (Online) PrincipalKey found.")
+
+        self.is_printable = self.is_modifiable = self.is_extractable = True
+##        print 'keyvalue'
+##        print len(keyvalue)
+##        print keyvalue.encode('hex')
+        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')
+        #print 'ecd rights'
+        edclist = []
+        for pair in edcdata.split('\n'):
+            edclist.append(pair)
+##        print edclist
+##        print 'edcdata decrypted'
+##        print edclist[0].decode('base64').encode('hex')
+##        print edclist[1].decode('base64').encode('hex')
+##        print edclist[2].decode('base64').encode('hex')
+##        print edclist[3].decode('base64').encode('hex')
+##        print 'offlinekey'
+##        print len(edclist[9].decode('base64'))
+##        print pdrllic
+        # principal key request
+        for key in principalkeys:
+            if key in pdrllic:
+                principalkey = principalkeys[key]
+            else:
+                raise ADEPTError('Cannot find principal key for this pdf')
+##        print 'minorversion'
+##        print int(edclist[8])
+        # fix for minor version
+##        minorversion = int(edclist[8]) - 100
+##        if minorversion < 1:
+##            minorversion = 1
+##        print int(minorversion)
+        shakey = SHA256.new()
+        shakey.update(principalkey)
+##        for i in range(0,minorversion):
+##            shakey.update(principalkey)
+        shakey = shakey.digest()
+##        shakey = SHA256.new(principalkey).digest()
+        ivector = 16 * chr(0)
+        #print shakey
+        plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
+        if plaintext[-16:] != 16 * chr(16):
+            raise ADEPTError('Offlinekey cannot be decrypted, aborting (hint: redownload pdf) ...')
+        pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
+        if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16:
+            raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
+        else:
+            cutter = -1 * ord(pdrlpol[-1])
+            #print cutter
+            pdrlpol = pdrlpol[:cutter]
+        #print plaintext.encode('hex')
+        #print 'pdrlpol'
+        #print pdrlpol
+        return plaintext[:16]
+
+    PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
+                       '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
+    # experimental aes pw support
+    def initialize_standard(self, password, docid, param):
+        # copy from a global variable
+        V = int_value(param.get('V', 0))
+        if (V <=0 or V > 4):
+            raise PDFEncryptionError('Unknown algorithm: param=%r' % param)
+        length = int_value(param.get('Length', 40)) # Key length (bits)
+        O = str_value(param['O'])
+        R = int_value(param['R']) # Revision
+        if 5 <= R:
+            raise PDFEncryptionError('Unknown revision: %r' % R)
+        U = str_value(param['U'])
+        P = int_value(param['P'])
+        try:
+            EncMetadata = str_value(param['EncryptMetadata'])
+        except:
+            EncMetadata = 'True'
+        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)
+        # Algorithm 3.2
+        password = (password+self.PASSWORD_PADDING)[:32] # 1
+        hash = hashlib.md5(password) # 2
+        hash.update(O) # 3
+        hash.update(struct.pack('<l', P)) # 4
+        hash.update(docid[0]) # 5
+        # aes special handling if metadata isn't encrypted
+        if EncMetadata == ('False' or 'false'):
+            hash.update('ffffffff'.decode('hex'))
+            # 6
+##            raise PDFNotImplementedError(
+##                'Revision 4 encryption is currently unsupported')
+        if 5 <= R:
+            # 8
+            for _ in xrange(50):
+                hash = hashlib.md5(hash.digest()[:length/8])
+        key = hash.digest()[:length/8]
+        if R == 2:
+            # Algorithm 3.4
+            u1 = ARC4.new(key).decrypt(password)
+        elif R >= 3:
+            # Algorithm 3.5
+            hash = hashlib.md5(self.PASSWORD_PADDING) # 2
+            hash.update(docid[0]) # 3
+            x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
+            for i in xrange(1,19+1):
+                k = ''.join( chr(ord(c) ^ i) for c in key )
+                x = ARC4.new(k).decrypt(x)
+            u1 = x+x # 32bytes total
+        if R == 2:
+            is_authenticated = (u1 == U)
+        else:
+            is_authenticated = (u1[:16] == U[:16])
+        if not is_authenticated:
+            raise ADEPTError('Password is not correct.')
+##            raise PDFPasswordIncorrect
+        self.decrypt_key = key
+        # genkey method
+        if V == 1 or V == 2:
+            self.genkey = self.genkey_v2
+        elif V == 3:
+            self.genkey = self.genkey_v3
+        elif V == 4:
+            self.genkey = self.genkey_v2
+         #self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
+        # rc4
+        if V != 4:
+            self.decipher = self.decipher_rc4  # XXX may be AES
+        # aes
+        elif V == 4 and Length == 128:
+            elf.decipher = self.decipher_aes
+        elif V == 4 and Length == 256:
+            raise PDFNotImplementedError('AES256 encryption is currently unsupported')
+        self.ready = True
+        return
+
+    def initialize_ebx(self, password, docid, param):
+        global KEYFILEPATH
+        self.is_printable = self.is_modifiable = self.is_extractable = True
+        # keyfile path is wrong
+        if KEYFILEPATH == False:
+            errortext = 'Cannot find adeptkey.der keyfile. Use ineptkey to generate it.'
+            raise ADEPTError(errortext)
+        with open(password, 'rb') as f:
+            keyder = f.read()
+        #    KEYFILEPATH = ''
+        key = ASN1Parser([ord(x) for x in keyder])
+        key = [bytesToNumber(key.getChild(x).value) for x in xrange(1, 4)]
+        rsa = RSA.construct(key)
+        length = int_value(param.get('Length', 0)) / 8
+        rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
+        rights = zlib.decompress(rights, -15)
+        rights = etree.fromstring(rights)
+        expr = './/{http://ns.adobe.com/adept}encryptedKey'
+        bookkey = ''.join(rights.findtext(expr)).decode('base64')
+        bookkey = rsa.decrypt(bookkey)
+        if bookkey[0] != '\x02':
+            raise ADEPTError('error decrypting book session key')
+        index = bookkey.index('\0') + 1
+        bookkey = bookkey[index:]
+        ebx_V = int_value(param.get('V', 4))
+        ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
+        # added because of the booktype / decryption book session key error
+        if ebx_V == 3:
+            V = 3
+        elif ebx_V < 4 or ebx_type < 6:
+            V = ord(bookkey[0])
+            bookkey = bookkey[1:]
+        else:
+            V = 2
+        if length and len(bookkey) != length:
+            raise ADEPTError('error decrypting book session key')
+        self.decrypt_key = bookkey
+        self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
+        self.decipher = self.decrypt_rc4
+        self.ready = True
+        return
+
+    # fileopen support
+    def initialize_fopn_flock(self, docid, param):
+        raise ADEPTError('FOPN_fLock not supported, yet ...')
+        # debug mode processing
+        global DEBUG_MODE
+        global IVERSION
+        if DEBUG_MODE == True:
+            if os.access('.',os.W_OK) == True:
+                debugfile = open('ineptpdf-'+IVERSION+'-debug.txt','w')
+            else:
+                raise ADEPTError('Cannot write debug file, current directory is not writable')
+        self.is_printable = self.is_modifiable = self.is_extractable = True
+        # get parameters and add it to the fo dictionary
+        self.fileopen['V'] = int_value(param.get('V',2))
+        # crypt base
+        (docid, param) = self.encryption
+        #rights = dict_value(param['Info'])
+        rights = param['Info']
+        #print rights
+        if DEBUG_MODE == True: debugfile.write(rights + '\n\n')
+##        for pair in rights.split(';'):
+##            try:
+##                key, value = pair.split('=',1)
+##                self.fileopen[key] = value
+##            # fix for some misconfigured INFO variables
+##            except:
+##                pass
+##        kattr = { 'SVID': 'ServiceID', 'DUID': 'DocumentID', 'I3ID': 'Ident3ID', \
+##                  'I4ID': 'Ident4ID', 'VERS': 'EncrVer', 'PRID': 'USR'}
+##        for keys in  kattr:
+##            try:
+##                self.fileopen[kattr[keys]] = self.fileopen[keys]
+##                del self.fileopen[keys]
+##            except:
+##                continue
+        # differentiate OS types
+##        sysplatform = sys.platform
+##        # if ostype is Windows
+##        if sysplatform=='win32':
+##            self.osuseragent = 'Windows NT 6.0'
+##            self.get_macaddress = self.get_win_macaddress
+##            self.fo_sethwids = self.fo_win_sethwids
+##            self.BrowserCookie = WinBrowserCookie
+##        elif sysplatform=='linux2':
+##            adeptout = 'Linux is not supported, yet.\n'
+##            raise ADEPTError(adeptout)
+##            self.osuseragent = 'Linux i686'
+##            self.get_macaddress = self.get_linux_macaddress
+##            self.fo_sethwids = self.fo_linux_sethwids
+##        else:
+##            adeptout = ''
+##            adeptout = adeptout + 'Due to various privacy violations from Apple\n'
+##            adeptout = adeptout + 'Mac OS X support is disabled by default.'
+##            raise ADEPTError(adeptout)
+##        # add static arguments for http/https request
+##        self.fo_setattributes()
+##        # add hardware specific arguments for http/https request
+##        self.fo_sethwids()
+##
+##        if 'Code' in self.urlresult:
+##            if self.fileopen['Length'] == len(self.urlresult['Code']):
+##                self.decrypt_key = self.urlresult['Code']
+##            else:
+##                self.decrypt_key = self.urlresult['Code'].decode('hex')
+##        else:
+##            raise ADEPTError('Cannot find decryption key.')
+        self.decrypt_key = 'stuff'
+        self.genkey = self.genkey_v2
+        self.decipher = self.decrypt_rc4
+        self.ready = True
+        return
+
+    def initialize_fopn(self, docid, param):
+        # debug mode processing
+        global DEBUG_MODE
+        global IVERSION
+        if DEBUG_MODE == True:
+            if os.access('.',os.W_OK) == True:
+                debugfile = open('ineptpdf-'+IVERSION+'-debug.txt','w')
+            else:
+                raise ADEPTError('Cannot write debug file, current directory is not writable')
+        self.is_printable = self.is_modifiable = self.is_extractable = True
+        # get parameters and add it to the fo dictionary
+        self.fileopen['Length'] = int_value(param.get('Length', 0)) / 8
+        self.fileopen['VEID'] = str_value(param.get('VEID'))
+        self.fileopen['BUILD'] = str_value(param.get('BUILD'))
+        self.fileopen['SVID'] = str_value(param.get('SVID'))
+        self.fileopen['DUID'] = str_value(param.get('DUID'))
+        self.fileopen['V'] = int_value(param.get('V',2))
+        # crypt base
+        rights = str_value(param.get('INFO')).decode('base64')
+        rights = self.genkey_fileopeninfo(rights)
+        if DEBUG_MODE == True: debugfile.write(rights + '\n\n')
+        for pair in rights.split(';'):
+            try:
+                key, value = pair.split('=',1)
+                self.fileopen[key] = value
+            # fix for some misconfigured INFO variables
+            except:
+                pass
+        kattr = { 'SVID': 'ServiceID', 'DUID': 'DocumentID', 'I3ID': 'Ident3ID', \
+                  'I4ID': 'Ident4ID', 'VERS': 'EncrVer', 'PRID': 'USR'}
+        for keys in  kattr:
+            # fishing some misconfigured slashs out of it
+            try:
+                self.fileopen[kattr[keys]] = urllib.quote(self.fileopen[keys],safe='')
+                del self.fileopen[keys]
+            except:
+                continue
+        # differentiate OS types
+        sysplatform = sys.platform
+        # if ostype is Windows
+        if sysplatform=='win32':
+            self.osuseragent = 'Windows NT 6.0'
+            self.get_macaddress = self.get_win_macaddress
+            self.fo_sethwids = self.fo_win_sethwids
+            self.BrowserCookie = WinBrowserCookie
+        elif sysplatform=='linux2':
+            adeptout = 'Linux is not supported, yet.\n'
+            raise ADEPTError(adeptout)
+            self.osuseragent = 'Linux i686'
+            self.get_macaddress = self.get_linux_macaddress
+            self.fo_sethwids = self.fo_linux_sethwids
+        else:
+            adeptout = ''
+            adeptout = adeptout + 'Mac OS X is not supported, yet.'
+            adeptout = adeptout + 'Read the blogs FAQs for more information'
+            raise ADEPTError(adeptout)
+        # add static arguments for http/https request
+        self.fo_setattributes()
+        # add hardware specific arguments for http/https request
+        self.fo_sethwids()
+        #if DEBUG_MODE == True: debugfile.write(self.fileopen)
+        if 'UURL' in self.fileopen:
+            buildurl = self.fileopen['UURL']
+        else:
+            buildurl = self.fileopen['PURL']
+        # fix for bad DPRM structure
+        if self.fileopen['DPRM'][0] != r'/':
+            self.fileopen['DPRM'] = r'/' + self.fileopen['DPRM']
+        # genius fix for bad server urls (IMHO)
+        if '?' in self.fileopen['DPRM']:
+            buildurl = buildurl + self.fileopen['DPRM'] + '&'
+        else:
+            buildurl = buildurl + self.fileopen['DPRM'] + '?'
+
+        # debug customization
+        #self.fileopen['Machine'] = ''
+        #self.fileopen['Disk'] = ''
+
+
+        surl = ( 'Stamp', 'Mode', 'USR', 'ServiceID', 'DocumentID',\
+                 'Ident3ID', 'Ident4ID','DocStrFmt', 'OSType', 'OSName', 'OSData', 'Language',\
+                 'LngLCID', 'LngRFC1766', 'LngISO4Char', 'Build', 'ProdVer', 'EncrVer',\
+                 'Machine', 'Disk', 'Uuid', 'PrevMach', 'PrevDisk',\
+                 'FormHFT',\
+                 'SelServer', 'AcroVersion', 'AcroProduct', 'AcroReader',\
+                 'AcroCanEdit', 'AcroPrefIDib', 'InBrowser', 'CliAppName',\
+                 'DocIsLocal', 'DocPathUrl', 'VolName', 'VolType', 'VolSN',\
+                 'FSName',  'FowpKbd', 'OSBuild',\
+                  'RequestSchema')
+
+        #settings request and special modes
+        if 'EVER' in self.fileopen and float(self.fileopen['EVER']) < 3.8:
+            self.fileopen['Mode'] = 'ICx'
+
+        origurl = buildurl
+        buildurl = buildurl + 'Request=Setting'
+        for keys in surl:
+            try:
+                buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
+            except:
+                continue
+        if DEBUG_MODE == True: debugfile.write( 'settings url:\n')
+        if DEBUG_MODE == True: debugfile.write( buildurl+'\n\n')
+        # custom user agent identification?
+        if 'AGEN' in self.fileopen:
+            useragent = self.fileopen['AGEN']
+            urllib.URLopener.version = useragent
+        # attribute doesn't exist - take the default user agent
+        else:
+            urllib.URLopener.version = self.osuseragent
+        # try to open the url
+        try:
+            u = urllib.urlopen(buildurl)
+            u.geturl()
+            result = u.read()
+        except:
+            raise ADEPTError('No internet connection or a blocking firewall!')
+##        finally:
+##            u.close()
+        # getting rid of the line feed
+        if DEBUG_MODE == True: debugfile.write('Settings'+'\n')
+        if DEBUG_MODE == True: debugfile.write(result+'\n\n')
+        #get rid of unnecessary characters
+        result = result.rstrip('\n')
+        result = result.rstrip(chr(13))
+        result = result.lstrip('\n')
+        result = result.lstrip(chr(13))
+        self.surlresult = {}
+        for pair in result.split('&'):
+            try:
+                key, value = pair.split('=',1)
+                # fix for bad server response
+                if key not in self.surlresult:
+                    self.surlresult[key] = value
+            except:
+                pass
+        if 'RequestSchema' in self.surlresult:
+            self.fileopen['RequestSchema'] = self.surlresult['RequestSchema']
+        if 'ServerSessionData' in self.surlresult:
+            self.fileopen['ServerSessionData'] = self.surlresult['ServerSessionData']
+        if 'SetScope' in self.surlresult:
+            self.fileopen['RequestSchema'] = self.surlresult['SetScope']
+        #print self.surlresult
+        if 'RetVal' in self.surlresult and 'SEMO' not in self.fileopen and(('Reason' in self.surlresult and \
+           self.surlresult['Reason'] == 'AskUnp') or ('SetTarget' in self.surlresult and\
+                                               self.surlresult['SetTarget'] == 'UnpDlg')):
+            # get user and password dialog
+            try:
+                self.gen_pw_dialog(self.surlresult['UnpUiName'], self.surlresult['UnpUiPass'],\
+                                   self.surlresult['UnpUiTitle'], self.surlresult['UnpUiOk'],\
+                                   self.surlresult['UnpUiSunk'], self.surlresult['UnpUiComm'])
+            except:
+                self.gen_pw_dialog()
+
+        # the fileopen check might not be always right because of strange server responses
+        if 'SEMO' in self.fileopen and (self.fileopen['SEMO'] == '1'\
+            or self.fileopen['SEMO'] == '2') and ('CSES' in self.fileopen and\
+                                                  self.fileopen['CSES'] != 'fileopen'):
+            # get the url name for the cookie(s)
+            if 'CURL' in self.fileopen:
+                self.surl = self.fileopen['CURL']
+            if 'CSES' in self.fileopen:
+                self.cses = self.fileopen['CSES']
+            elif 'PHOS' in self.fileopen:
+                self.surl = self.fileopen['PHOS']
+            elif 'LHOS' in self.fileopen:
+                self.surl = self.fileopen['LHOS']
+            else:
+                raise ADEPTError('unknown Cookie name.\n Check ineptpdf forum for further assistance')
+            self.pwfieldreq = 1
+            # session cookie processing
+            if self.fileopen['SEMO'] == '1':
+                cookies = self.BrowserCookie()
+                #print self.cses
+                #print self.surl
+                csession = cookies.getcookie(self.cses,self.surl)
+                if csession != None:
+                    self.fileopen['Session'] = csession
+                    self.gui = False
+                # fallback
+                else:
+                    self.pwtk = Tkinter.Tk()
+                    self.pwtk.title('Ineptpdf8')
+                    self.pwtk.minsize(150, 0)
+                    infotxt1 = 'Get the session cookie key manually (Firefox step-by-step:\n'+\
+                               'Start Firefox -> Tools -> Options -> Privacy -> Show Cookies\n'+\
+                               '-> Search for a cookie from ' + self.surl +' with the\n'+\
+                               'name ' + self.cses +' and copy paste the content field in the\n'+\
+                               'Session Content field. Remove possible spaces or new lines at the '+\
+                               'end\n (cursor must be blinking right behind the last character)'
+                    self.label0 = Tkinter.Label(self.pwtk, text=infotxt1)
+                    self.label0.pack()
+                    self.label1 = Tkinter.Label(self.pwtk, text="Session Content")
+                    self.pwfieldreq = 0
+                    self.gui = True
+            # user cookie processing
+            elif self.fileopen['SEMO'] == '2':
+                cookies = self.BrowserCookie()
+                #print self.cses
+                #print self.surl
+                name = cookies.getcookie('name',self.surl)
+                passw = cookies.getcookie('pass',self.surl)
+                if name != None or passw != None:
+                    self.fileopen['UserName'] = urllib.quote(name)
+                    self.fileopen['UserPass'] = urllib.quote(passw)
+                    self.gui = False
+                # fallback
+                else:
+                    self.pwtk = Tkinter.Tk()
+                    self.pwtk.title('Ineptpdf8')
+                    self.pwtk.minsize(150, 0)
+                    self.label1 = Tkinter.Label(self.pwtk, text="Username")
+                    infotxt1 = 'Get the user cookie keys manually (Firefox step-by-step:\n'+\
+                               'Start Firefox -> Tools -> Options -> Privacy -> Show Cookies\n'+\
+                               '-> Search for cookies from ' + self.surl +' with the\n'+\
+                               'name name in the user field and copy paste the content field in the\n'+\
+                               'username field. Do the same with the name pass in the password field).'
+                    self.label0 = Tkinter.Label(self.pwtk, text=infotxt1)
+                    self.label0.pack()
+                    self.pwfieldreq = 1
+                    self.gui = True
+##            else:
+##                self.pwtk = Tkinter.Tk()
+##                self.pwtk.title('Ineptpdf8')
+##                self.pwtk.minsize(150, 0)
+##                self.pwfieldreq = 0
+##                self.label1 = Tkinter.Label(self.pwtk, text="Username")
+##                self.pwfieldreq = 1
+##                self.gui = True
+            if self.gui == True:
+                self.un_entry = Tkinter.Entry(self.pwtk)
+                # cursor here
+                self.un_entry.focus()
+                self.label2 = Tkinter.Label(self.pwtk, text="Password")
+                self.pw_entry = Tkinter.Entry(self.pwtk, show="*")
+                self.button = Tkinter.Button(self.pwtk, text='Go for it!', command=self.fo_save_values)
+                # widget layout, stack vertical
+                self.label1.pack()
+                self.un_entry.pack()
+                # create a password label and field
+                if self.pwfieldreq == 1:
+                    self.label2.pack()
+                    self.pw_entry.pack()
+                self.button.pack()
+                self.pwtk.update()
+                # start the event loop
+                self.pwtk.mainloop()
+
+        # original request
+        # drive through tupple for building the permission url
+        burl = ( 'Stamp', 'Mode', 'USR', 'ServiceID', 'DocumentID',\
+                 'Ident3ID', 'Ident4ID','DocStrFmt', 'OSType', 'Language',\
+                 'LngLCID', 'LngRFC1766', 'LngISO4Char', 'Build', 'ProdVer', 'EncrVer',\
+                 'Machine', 'Disk', 'Uuid', 'PrevMach', 'PrevDisk', 'User', 'SaUser', 'SaSID',\
+                 # special security measures
+                 'HostIsDomain', 'PhysHostname', 'LogiHostname', 'SaRefDomain',\
+                 'FormHFT', 'UserName', 'UserPass', 'Session', \
+                 'SelServer', 'AcroVersion', 'AcroProduct', 'AcroReader',\
+                 'AcroCanEdit', 'AcroPrefIDib', 'InBrowser', 'CliAppName',\
+                 'DocIsLocal', 'DocPathUrl', 'VolName', 'VolType', 'VolSN',\
+                 'FSName', 'ServerSessionData', 'FowpKbd', 'OSBuild', \
+                 'DocumentSessionData', 'RequestSchema')
+
+        buildurl = origurl
+        buildurl = buildurl + 'Request=DocPerm'
+        for keys in burl:
+            try:
+                buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
+            except:
+                continue
+        if DEBUG_MODE == True: debugfile.write('1st url:'+'\n')
+        if DEBUG_MODE == True: debugfile.write(buildurl+'\n\n')
+        # custom user agent identification?
+        if 'AGEN' in self.fileopen:
+            useragent = self.fileopen['AGEN']
+            urllib.URLopener.version = useragent
+        # attribute doesn't exist - take the default user agent
+        else:
+            urllib.URLopener.version = self.osuseragent
+        # try to open the url
+        try:
+            u = urllib.urlopen(buildurl)
+            u.geturl()
+            result = u.read()
+        except:
+            raise ADEPTError('No internet connection or a blocking firewall!')
+##        finally:
+##            u.close()
+        # getting rid of the line feed
+        if DEBUG_MODE == True: debugfile.write('1st preresult'+'\n')
+        if DEBUG_MODE == True: debugfile.write(result+'\n\n')
+        #get rid of unnecessary characters
+        result = result.rstrip('\n')
+        result = result.rstrip(chr(13))
+        result = result.lstrip('\n')
+        result = result.lstrip(chr(13))
+        self.urlresult = {}
+        for pair in result.split('&'):
+            try:
+                key, value = pair.split('=',1)
+                self.urlresult[key] = value
+            except:
+                pass
+##        if 'RequestSchema' in self.surlresult:
+##            self.fileopen['RequestSchema'] = self.urlresult['RequestSchema']
+         #self.urlresult
+        #result[0:8] == 'RetVal=1') or (result[0:8] == 'RetVal=2'):
+        if ('RetVal' in self.urlresult and (self.urlresult['RetVal'] != '1' and \
+                                            self.urlresult['RetVal'] != '2' and \
+                                            self.urlresult['RetVal'] != 'Update' and \
+                                            self.urlresult['RetVal'] != 'Answer')):
+
+            if ('Reason' in self.urlresult and (self.urlresult['Reason'] == 'BadUserPwd'\
+                or self.urlresult['Reason'] == 'AskUnp')) or ('SwitchTo' in self.urlresult\
+                    and (self.urlresult['SwitchTo'] == 'Dialog')):
+                if 'ServerSessionData' in self.urlresult:
+                    self.fileopen['ServerSessionData'] = self.urlresult['ServerSessionData']
+                if 'DocumentSessionData' in self.urlresult:
+                    self.fileopen['DocumentSessionData'] = self.urlresult['DocumentSessionData']
+                buildurl = origurl
+                buildurl = buildurl + 'Request=DocPerm'
+                self.gen_pw_dialog()
+                # password not found - fallback
+                for keys in burl:
+                    try:
+                        buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys]
+                    except:
+                        continue
+                if DEBUG_MODE == True: debugfile.write( '2ndurl:')
+                if DEBUG_MODE == True: debugfile.write( buildurl+'\n\n')
+                # try to open the url
+                try:
+                    u = urllib.urlopen(buildurl)
+                    u.geturl()
+                    result = u.read()
+                except:
+                    raise ADEPTError('No internet connection or a blocking firewall!')
+                # getting rid of the line feed
+                if DEBUG_MODE == True: debugfile.write( '2nd preresult')
+                if DEBUG_MODE == True: debugfile.write( result+'\n\n')
+                #get rid of unnecessary characters
+                result = result.rstrip('\n')
+                result = result.rstrip(chr(13))
+                result = result.lstrip('\n')
+                result = result.lstrip(chr(13))
+                self.urlresult = {}
+                for pair in result.split('&'):
+                    try:
+                        key, value = pair.split('=',1)
+                        self.urlresult[key] = value
+                    except:
+                        pass
+        # did it work?
+        if ('RetVal' in self.urlresult and (self.urlresult['RetVal'] != '1' and \
+                                                    self.urlresult['RetVal'] != '2' and
+                                                    self.urlresult['RetVal'] != 'Update' and \
+                                                    self.urlresult['RetVal'] != 'Answer')):
+            raise ADEPTError('Decryption was not successfull.\nReason: ' + self.urlresult['Error'])
+        # fix for non-standard-conform fileopen pdfs
+##        if self.fileopen['Length'] != 5 and self.fileopen['Length'] != 16:
+##            if self.fileopen['V'] == 1:
+##                self.fileopen['Length'] = 5
+##            else:
+##                self.fileopen['Length'] = 16
+        # patch for malformed pdfs
+        #print len(self.urlresult['Code'])
+        #print self.urlresult['Code'].encode('hex')
+        if 'code' in self.urlresult:
+            self.urlresult['Code'] = self.urlresult['code']
+        if 'Code' in self.urlresult:
+            if len(self.urlresult['Code']) == 5 or len(self.urlresult['Code']) == 16:
+                self.decrypt_key = self.urlresult['Code']
+            else:
+                self.decrypt_key = self.urlresult['Code'].decode('hex')
+        else:
+            raise ADEPTError('Cannot find decryption key.')
+        self.genkey = self.genkey_v2
+        self.decipher = self.decrypt_rc4
+        self.ready = True
+        return
+
+    def gen_pw_dialog(self, Username='Username', Password='Password', Title='User/Password Authentication',\
+                      OK='Proceed', Text1='Authorization', Text2='Enter Required Data'):
+        self.pwtk = Tkinter.Tk()
+        self.pwtk.title(Title)
+        self.pwtk.minsize(150, 0)
+        self.label1 = Tkinter.Label(self.pwtk, text=Text1)
+        self.label2 = Tkinter.Label(self.pwtk, text=Text2)
+        self.label3 = Tkinter.Label(self.pwtk, text=Username)
+        self.pwfieldreq = 1
+        self.gui = True
+        self.un_entry = Tkinter.Entry(self.pwtk)
+        # cursor here
+        self.un_entry.focus()
+        self.label4 = Tkinter.Label(self.pwtk, text=Password)
+        self.pw_entry = Tkinter.Entry(self.pwtk, show="*")
+        self.button = Tkinter.Button(self.pwtk, text=OK, command=self.fo_save_values)
+        # widget layout, stack vertical
+        self.label1.pack()
+        self.label2.pack()
+        self.label3.pack()
+        self.un_entry.pack()
+        # create a password label and field
+        if self.pwfieldreq == 1:
+            self.label4.pack()
+            self.pw_entry.pack()
+        self.button.pack()
+        self.pwtk.update()
+        # start the event loop
+        self.pwtk.mainloop()
+
+    # genkey functions
+    def genkey_v2(self, objid, genno):
+        objid = struct.pack('<L', objid)[:3]
+        genno = struct.pack('<L', genno)[:2]
+        key = self.decrypt_key + objid + genno
+        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)
+        key = self.decrypt_key
+        key += objid[0] + genno[0] + objid[1] + genno[1] + objid[2] + 'sAlT'
+        hash = hashlib.md5(key)
+        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
+        return key
+
+    # aes v2 and v4 algorithm
+    def genkey_v4(self, objid, genno):
+        objid = struct.pack('<L', objid)[:3]
+        genno = struct.pack('<L', genno)[:2]
+        key = self.decrypt_key + objid + genno + 'sAlT'
+        hash = hashlib.md5(key)
+        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
+        return key
+
+    def decrypt_aes(self, objid, genno, data):
+        key = self.genkey(objid, genno)
+        ivector = data[:16]
+        data = data[16:]
+        plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
+        # remove pkcs#5 aes padding
+        cutter = -1 * ord(plaintext[-1])
+        #print cutter
+        plaintext = plaintext[:cutter]
+        return plaintext
+
+    def decrypt_aes256(self, objid, genno, data):
+        key = self.genkey(objid, genno)
+        ivector = data[:16]
+        data = data[16:]
+        plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
+        # remove pkcs#5 aes padding
+        cutter = -1 * ord(plaintext[-1])
+        #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)
+
+    # fileopen user/password dialog
+    def fo_save_values(self):
+        getout = 0
+        username = 0
+        password = 0
+        username = self.un_entry.get()
+        if self.pwfieldreq == 1:
+            password = self.pw_entry.get()
+        un_length = len(username)
+        if self.pwfieldreq == 1:
+            pw_length = len(password)
+        if (un_length != 0):
+            if self.pwfieldreq == 1:
+                if (pw_length != 0):
+                    getout = 1
+            else:
+                getout = 1
+        if getout == 1:
+            if 'SEMO' in self.fileopen and self.fileopen['SEMO'] == '1':
+                self.fileopen['Session'] = urllib.quote(username)
+            else:
+                self.fileopen['UserName'] = urllib.quote(username)
+            if self.pwfieldreq == 1:
+                self.fileopen['UserPass'] = urllib.quote(password)
+            else:
+                pass
+                #self.fileopen['UserPass'] = self.fileopen['UserName']
+            # doesn't always close the password window, who
+            # knows why (Tkinter secrets ;=))
+            self.pwtk.quit()
+
+
+    def fo_setattributes(self):
+        self.fileopen['Request']='DocPerm'
+        self.fileopen['Mode']='CNR'
+        self.fileopen['DocStrFmt']='ASCII'
+        self.fileopen['Language']='ENU'
+        self.fileopen['LngLCID']='ENU'
+        self.fileopen['LngRFC1766']='en'
+        self.fileopen['LngISO4Char']='en-us'
+        self.fileopen['ProdVer']='1.8.7.9'
+        self.fileopen['FormHFT']='Yes'
+        self.fileopen['SelServer']='Yes'
+        self.fileopen['AcroCanEdit']='Yes'
+        self.fileopen['AcroPrefIDib']='Yes'
+        self.fileopen['InBrowser']='Unk'
+        self.fileopen['CliAppName']=''
+        self.fileopen['DocIsLocal']='Yes'
+        self.fileopen['FowpKbd']='Yes'
+        self.fileopen['RequestSchema']='Default'
+
+    # get nic mac address
+    def get_linux_macaddress(self):
+        try:
+            for line in os.popen("/sbin/ifconfig"):
+                if line.find('Ether') > -1:
+                    mac = line.split()[4]
+                    break
+            return mac.replace(':','')
+        except:
+            raise ADEPTError('Cannot find MAC address. Get forum help.')
+
+    def get_win_macaddress(self):
+        try:
+            gasize = c_ulong(5000)
+            p = create_string_buffer(5000)
+            GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo
+            GetAdaptersInfo(byref(p),byref(gasize))
+            return p[0x194:0x19a].encode('hex')
+        except:
+            raise ADEPTError('Cannot find MAC address. Get forum help.')
+
+    # custom conversion 5 bytes to 8 chars method
+    def fo_convert5to8(self, edisk):
+        # byte to number/char mapping table
+        darray=[0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,\
+                0x46,0x47,0x48,0x4A,0x4B,0x4C,0x4D,0x4E,0x50,0x51,0x52,0x53,0x54,\
+                0x55,0x56,0x57,0x58,0x59,0x5A]
+        pdid = struct.pack('<I', int(edisk[0:4].encode("hex"),16))
+        pdid = int(pdid.encode("hex"),16)
+        outputhw = ''
+        # disk id processing
+        for i in range(0,6):
+            index = pdid & 0x1f
+            # shift the disk id 5 bits to the right
+            pdid = pdid >> 5
+            outputhw = outputhw + chr(darray[index])
+        pdid = (ord(edisk[4]) << 2)|pdid
+        # get the last 2 bits from the hwid + low part of the cpuid
+        for i in range(0,2):
+            index = pdid & 0x1f
+            # shift the disk id 5 bits to the right
+            pdid = pdid >> 5
+            outputhw = outputhw + chr(darray[index])
+        return outputhw
+
+    # Linux processing
+    def fo_linux_sethwids(self):
+        # linux specific attributes
+        self.fileopen['OSType']='Linux'
+        self.fileopen['AcroProduct']='AcroReader'
+        self.fileopen['AcroReader']='Yes'
+        self.fileopen['AcroVersion']='9.101'
+        self.fileopen['FSName']='ext3'
+        self.fileopen['Build']='878'
+        self.fileopen['ProdVer']='1.8.5.1'
+        self.fileopen['OSBuild']='2.6.33'
+        # write hardware keys
+        hwkey = 0
+        pmac = self.get_macaddress().decode("hex");
+        self.fileopen['Disk'] = self.fo_convert5to8(pmac[1:])
+        # get primary used default mac address
+        self.fileopen['Machine'] = self.fo_convert5to8(pmac[1:])
+        # get uuid
+        # check for reversed offline handler 6AB83F4Ah + AFh 6AB83F4Ah
+        if 'LILA' in self.fileopen:
+            pass
+        if 'Ident4ID' in self.fileopen:
+            self.fileopen['User'] = getpass.getuser()
+            self.fileopen['SaUser'] = getpass.getuser()
+            try:
+                cuser = winreg.HKEY_CURRENT_USER
+                FOW3_UUID = 'Software\\Fileopen'
+                regkey = winreg.OpenKey(cuser, FOW3_UUID)
+                userkey = winreg.QueryValueEx(regkey, 'Fowp3Uuid')[0]
+#                if self.genkey_cryptmach(userkey)[0:4] != 'ec20':
+                self.fileopen['Uuid'] = self.genkey_cryptmach(userkey)[4:]
+##                elif self.genkey_cryptmach(userkey)[0:4] != 'ec20':
+##                    self.fileopen['Uuid'] = self.genkey_cryptmach(userkey,1)[4:]
+##                else:
+            except:
+                raise ADEPTError('Cannot find FowP3Uuid file - reason might be Adobe (Reader) X.'\
+                                 'Read the FAQs for more information how to solve the problem.')
+        else:
+            self.fileopen['Uuid'] = str(uuid.uuid1())
+        # get time stamp
+        self.fileopen['Stamp'] = str(time.time())[:-3]
+        # get fileopen input pdf name + path
+        self.fileopen['DocPathUrl'] = 'file%3a%2f%2f%2f'\
+                                      + urllib.quote(os.path.normpath(INPUTFILEPATH))
+        # clear the link
+        #INPUTFILEPATH = ''
+##        # get volume name (urllib quote necessairy?) urllib.quote(
+##        self.fileopen['VolName'] = win32api.GetVolumeInformation("C:\\")[0]
+##        # get volume serial number
+##        self.fileopen['VolSN'] = str(win32api.GetVolumeInformation("C:\\")[1])
+        return
+
+    # Windows processing
+    def fo_win_sethwids(self):
+        # Windows specific attributes
+        self.fileopen['OSType']='Windows'
+        self.fileopen['OSName']='Vista'
+        self.fileopen['OSData']='Service%20Pack%204'
+        self.fileopen['AcroProduct']='Reader'
+        self.fileopen['AcroReader']='Yes'
+        self.fileopen['OSBuild']='7600'
+        self.fileopen['AcroVersion']='9.1024'
+        self.fileopen['Build']='879'
+        # write hardware keys
+        hwkey = 0
+        # get the os type and save it in ostype
+        try:
+            import win32api
+            import win32security
+            import win32file
+            import _winreg as winreg
+        except:
+            raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
+                             'Download from http://sourceforge.net/projects/pywin32/files/ ')
+        try:
+            v0 = win32api.GetVolumeInformation('C:\\')
+            v1 = win32api.GetSystemInfo()[6]
+            # fix for possible negative integer (Python problem)
+            volserial = v0[1] & 0xffffffff
+            lowcpu = v1 & 255
+            highcpu = (v1 >> 8) & 255
+            # changed to int
+            volserial = struct.pack('<I', int(volserial))
+            lowcpu   = struct.pack('B', lowcpu)
+            highcpu = struct.pack('B', highcpu)
+            encrypteddisk = volserial + lowcpu + highcpu
+            self.fileopen['Disk'] = self.fo_convert5to8(encrypteddisk)
+        except:
+            # no c system drive available empty disk attribute
+            self.fileopen['Disk'] = ''
+        # get primary used default mac address
+        pmac = self.get_macaddress().decode("hex");
+        self.fileopen['Machine'] = self.fo_convert5to8(pmac[1:])
+        if 'LIFF' in self.fileopen:
+            if 'Yes' in self.fileopen['LIFF']:
+                hostname = socket.gethostname()
+                self.fileopen['HostIsDomain']='Yes'
+                if '1' in self.fileopen['LIFF']:
+                    self.fileopen['PhysHostname']= hostname
+                    self.fileopen['LogiHostname']= hostname
+                    self.fileopen['SaRefDomain']= hostname
+        # default users
+        self.user = win32api.GetUserName().lower()
+        self.sauser = win32api.GetUserName()
+        # get uuid
+        # check for reversed offline handler
+        if 'LILA' in self.fileopen and self.fileopen['LILA'] == 'Yes':
+##            self.fileopen['User'] = win32api.GetUserName().lower()
+##            self.fileopen['SaUser'] = win32api.GetUserName()
+
+            # get sid / sasid
+            try:
+                psid = win32security.LookupAccountName("",self.sauser)[0]
+                psid = win32security.ConvertSidToStringSid(psid)
+                self.fileopen['SaSID'] = psid
+                self.fileopen['User'] = urllib.quote(self.user)
+                self.fileopen['SaUser'] = urllib.quote(self.sauser)
+            # didn't work use a generic one
+            except:
+                self.fileopen['SaSID'] = 'S-1-5-21-1380067357-584463869-1343024091-1000'
+        #if 'Ident4d' in self.fileopen or 'LILA' in self.fileopen:
+        # always calculate the right uuid
+        userkey = []
+        try:
+            cuser = winreg.HKEY_CURRENT_USER
+            FOW3_UUID = 'Software\\Fileopen'
+            regkey = winreg.OpenKey(cuser, FOW3_UUID)
+            userkey.append(winreg.QueryValueEx(regkey, 'Fowp3Uuid')[0])
+        except:
+            pass
+        try:
+            fopath = os.environ['AppData']+'\\FileOpen\\'
+            fofilename = 'Fowpmadi.txt'
+            f = open(fopath+fofilename, 'rb')
+            userkey.append(f.read()[0:40])
+            f.close()
+        except:
+            pass
+        if not userkey:
+            raise ADEPTError('Cannot find FowP3Uuid in registry or file.\n'\
+                                 +'Did Adobe (Reader) open the pdf file?')
+        cresult = self.genkey_cryptmach(userkey)
+        if cresult != False:
+            self.fileopen['Uuid'] = cresult
+        # kind of a long shot we'll see about it
+        else:
+            self.fileopen['Uuid'] = str(uuid.uuid1())
+##        else:
+##            self.fileopen['Uuid'] = str(uuid.uuid1())
+        # get time stamp
+        self.fileopen['Stamp'] = str(time.time())[:-3]
+        # get fileopen input pdf name + path
+        # print INPUTFILEPATH
+        self.fileopen['DocPathUrl'] = 'file%3a%2f%2f%2f'\
+                                      + urllib.quote(INPUTFILEPATH)
+        # determine voltype
+        voltype = ('Unknown', 'Invalid', 'Removable', 'Fixed', 'Remote', 'CDRom', 'RamDisk')
+        dletter = os.path.splitdrive(INPUTFILEPATH)[0] + '\\'
+        self.fileopen['VolType'] = voltype[win32file.GetDriveType(dletter)]
+        # get volume name (urllib quote necessairy?) urllib.quote(
+        self.fileopen['VolName'] = urllib.quote(win32api.GetVolumeInformation(dletter)[0])
+        # get volume serial number (fix for possible negative numbers)
+        self.fileopen['VolSN'] = str(win32api.GetVolumeInformation(dletter)[1])
+        # no c volume so skip it
+        self.fileopen['FSName'] = win32api.GetVolumeInformation(dletter)[4]
+        # get previous mac address or disk handling
+        userkey = []
+        try:
+            cuser = winreg.HKEY_CURRENT_USER
+            FOW3_UUID = 'Software\\Fileopen'
+            regkey = winreg.OpenKey(cuser, FOW3_UUID)
+            userkey.append(winreg.QueryValueEx(regkey, 'Fowp3Madi')[0])
+        except:
+            pass
+        try:
+            fopath = os.environ['AppData']+'\\FileOpen\\'
+            fofilename = 'Fowpmadi.txt'
+            f = open(fopath+fofilename, 'rb')
+            userkey.append(f.read()[40:])
+            f.close()
+        except:
+            pass
+        if not userkey:
+            raise ADEPTError('Cannot find FowP3Madi in registry or file.\n'\
+                             +'Did Adobe Reader open the pdf file?')
+        cresult = self.genkey_cryptmach(userkey)
+        if cresult != False:
+            machdisk = self.genkey_cryptmach(userkey)
+            machine = machdisk[:8]
+            disk = machdisk[8:]
+        # did not find the required information, false it
+        else:
+            machdisk = False
+            machine = False
+            disk = False
+        if machine != self.fileopen['Machine'] and machdisk != False:
+            self.fileopen['PrevMach'] = machine
+        if disk != self.fileopen['Disk'] and machdisk != False:
+            self.fileopen['PrevDisk'] = disk
+        return
+
+    # decryption routine for the INFO area
+    def genkey_fileopeninfo(self, data):
+        input1 = struct.pack('L', 0xa4da49de)
+        seed   = struct.pack('B', 0x82)
+        key = input1[3] + input1[2] +input1[1] +input1[0] + seed
+        hash = hashlib.md5()
+        key = hash.update(key)
+        spointer4 = struct.pack('<L', 0xec8d6c58)
+        seed = struct.pack('B', 0x07)
+        key = spointer4[3] + spointer4[2] + spointer4[1] + spointer4[0] + seed
+        key = hash.update(key)
+        md5 = hash.digest()
+        key = md5[0:10]
+        return ARC4.new(key).decrypt(data)
+
+    def genkey_cryptmach(self, data):
+        # nested subfunction
+        def genkeysub(uname, mode=False):
+            key_string = '37A4DA49DE82064939A60B1D8D7B5F0F8873B6D93E'.decode('hex')
+            m = hashlib.md5()
+            m.update(key_string[:3])
+            m.update(uname[:13]) # max 13 characters 13 - sizeof(username)
+            if (13 - len(uname)) > 0 and mode == True:
+                m.update(key_string[:(13-len(uname))])
+            md5sum = m.digest()[0:16]
+            # print md5sum.encode('hex')
+            # normal ident4id calculation
+            retval = []
+            for sdata in data:
+                retval.append(ARC4.new(md5sum).decrypt(sdata))
+            for rval in retval:
+                if rval[:4] == 'ec20':
+                    return rval[4:]
+            return False
+        # start normal execution
+        # list for username variants
+        unamevars = []
+        # fill username variants list
+        unamevars.append(self.user)
+        unamevars.append(self.user + chr(0))
+        unamevars.append(self.user.lower())
+        unamevars.append(self.user.lower() + chr(0))
+        unamevars.append(self.user.upper())
+        unamevars.append(self.user.upper() + chr(0))
+        # go through it
+        for uname in unamevars:
+            result = genkeysub(uname, True)
+            if result != False:
+              return result
+            result = genkeysub(uname)
+            if result != False:
+              return result
+        # didn't find it, return false
+        return False
+##        raise ADEPTError('Unsupported Ident4D Decryption,\n'+\
+##                             'report the bug to the ineptpdf script forum')
+
+    KEYWORD_OBJ = PSKeywordTable.intern('obj')
+
+    def getobj(self, objid):
+        if not self.ready:
+            raise PDFException('PDFDocument not initialized')
+        #assert self.xrefs
+        if objid in self.objs:
+            genno = 0
+            obj = self.objs[objid]
+        else:
+            for xref in self.xrefs:
+                try:
+                    (stmid, index) = xref.getpos(objid)
+                    break
+                except KeyError:
+                    pass
+            else:
+                #if STRICT:
+                #    raise PDFSyntaxError('Cannot locate objid=%r' % objid)
+                return None
+            if stmid:
+                if gen_xref_stm:
+                    return PDFObjStmRef(objid, stmid, index)
+# Stuff from pdfminer: extract objects from object stream
+                stream = stream_value(self.getobj(stmid))
+                if stream.dic.get('Type') is not LITERAL_OBJSTM:
+                    if STRICT:
+                        raise PDFSyntaxError('Not a stream object: %r' % stream)
+                try:
+                    n = stream.dic['N']
+                except KeyError:
+                    if STRICT:
+                        raise PDFSyntaxError('N is not defined: %r' % stream)
+                    n = 0
+
+                if stmid in self.parsed_objs:
+                    objs = self.parsed_objs[stmid]
+                else:
+                    parser = PDFObjStrmParser(stream.get_data(), self)
+                    objs = []
+                    try:
+                        while 1:
+                            (_,obj) = parser.nextobject()
+                            objs.append(obj)
+                    except PSEOF:
+                        pass
+                    self.parsed_objs[stmid] = objs
+                genno = 0
+                i = n*2+index
+                try:
+                    obj = objs[i]
+                except IndexError:
+                    raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
+                if isinstance(obj, PDFStream):
+                    obj.set_objid(objid, 0)
+###
+            else:
+                self.parser.seek(index)
+                (_,objid1) = self.parser.nexttoken() # objid
+                (_,genno) = self.parser.nexttoken() # genno
+                #assert objid1 == objid, (objid, objid1)
+                (_,kwd) = self.parser.nexttoken()
+        # #### hack around malformed pdf files
+        #        assert objid1 == objid, (objid, objid1)
+##                if objid1 != objid:
+##                    x = []
+##                    while kwd is not self.KEYWORD_OBJ:
+##                        (_,kwd) = self.parser.nexttoken()
+##                        x.append(kwd)
+##                    if x:
+##                        objid1 = x[-2]
+##                        genno = x[-1]
+##
+                if kwd is not self.KEYWORD_OBJ:
+                    raise PDFSyntaxError(
+                        'Invalid object spec: offset=%r' % index)
+                (_,obj) = self.parser.nextobject()
+                if isinstance(obj, PDFStream):
+                    obj.set_objid(objid, genno)
+                if self.decipher:
+                    obj = decipher_all(self.decipher, objid, genno, obj)
+            self.objs[objid] = obj
+        return obj
+
+# helper class for cookie retrival
+class WinBrowserCookie():
+    def __init__(self):
+        pass
+    def getcookie(self, cname, chost):
+        # check firefox db
+        fprofile =  os.environ['AppData']+r'\Mozilla\Firefox'
+        pinifile = 'profiles.ini'
+        fini = os.path.normpath(fprofile + '\\' + pinifile)
+        try:
+            with open(fini,'r') as ffini:
+                firefoxini =  ffini.read()
+        # Firefox not installed or on an USB stick
+        except:
+            return None
+        for pair in firefoxini.split('\n'):
+            try:
+                key, value = pair.split('=',1)
+                if key == 'Path':
+                    fprofile = os.path.normpath(fprofile+'//'+value+'//'+'cookies.sqlite')
+                    break
+            # asdf
+            except:
+                continue
+        if os.path.isfile(fprofile):
+            try:
+                con = sqlite3.connect(fprofile,1)
+            except:
+                raise ADEPTError('Firefox Cookie data base locked. Close Firefox and try again')
+            cur = con.cursor()
+            try:
+                cur.execute("select value from moz_cookies where name=? and host=?", (cname, chost))
+            except Exception:
+                raise ADEPTError('Firefox Cookie database is locked. Close Firefox and try again')
+            try:
+                return cur.fetchone()[0]
+            except Exception:
+                # sometimes is a dot in front of the host
+                chost = '.'+chost
+                cur.execute("select value from moz_cookies where name=? and host=?", (cname, chost))
+                try:
+                    return cur.fetchone()[0]
+                except:
+                    return None
+
+class PDFObjStmRef(object):
+    maxindex = 0
+    def __init__(self, objid, stmid, index):
+        self.objid = objid
+        self.stmid = stmid
+        self.index = index
+        if index > PDFObjStmRef.maxindex:
+            PDFObjStmRef.maxindex = index
+
+
+##  PDFParser
+##
+class PDFParser(PSStackParser):
+
+    def __init__(self, doc, fp):
+        PSStackParser.__init__(self, fp)
+        self.doc = doc
+        self.doc.set_parser(self)
+        return
+
+    def __repr__(self):
+        return '<PDFParser>'
+
+    KEYWORD_R = PSKeywordTable.intern('R')
+    KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj')
+    KEYWORD_STREAM = PSKeywordTable.intern('stream')
+    KEYWORD_XREF = PSKeywordTable.intern('xref')
+    KEYWORD_STARTXREF = PSKeywordTable.intern('startxref')
+    def do_keyword(self, pos, token):
+        if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
+            self.add_results(*self.pop(1))
+            return
+        if token is self.KEYWORD_ENDOBJ:
+            self.add_results(*self.pop(4))
+            return
+
+        if token is self.KEYWORD_R:
+            # reference to indirect object
+            try:
+                ((_,objid), (_,genno)) = self.pop(2)
+                (objid, genno) = (int(objid), int(genno))
+                obj = PDFObjRef(self.doc, objid, genno)
+                self.push((pos, obj))
+            except PSSyntaxError:
+                pass
+            return
+
+        if token is self.KEYWORD_STREAM:
+            # stream object
+            ((_,dic),) = self.pop(1)
+            dic = dict_value(dic)
+            try:
+                objlen = int_value(dic['Length'])
+            except KeyError:
+                if STRICT:
+                    raise PDFSyntaxError('/Length is undefined: %r' % dic)
+                objlen = 0
+            self.seek(pos)
+            try:
+                (_, line) = self.nextline()  # 'stream'
+            except PSEOF:
+                if STRICT:
+                    raise PDFSyntaxError('Unexpected EOF')
+                return
+            pos += len(line)
+            self.fp.seek(pos)
+            data = self.fp.read(objlen)
+            self.seek(pos+objlen)
+            while 1:
+                try:
+                    (linepos, line) = self.nextline()
+                except PSEOF:
+                    if STRICT:
+                        raise PDFSyntaxError('Unexpected EOF')
+                    break
+                if 'endstream' in line:
+                    i = line.index('endstream')
+                    objlen += i
+                    data += line[:i]
+                    break
+                objlen += len(line)
+                data += line
+            self.seek(pos+objlen)
+            obj = PDFStream(dic, data, self.doc.decipher)
+            self.push((pos, obj))
+            return
+
+        # others
+        self.push((pos, token))
+        return
+
+    def find_xref(self):
+        # search the last xref table by scanning the file backwards.
+        prev = None
+        for line in self.revreadlines():
+            line = line.strip()
+            if line == 'startxref': break
+            if line:
+                prev = line
+        else:
+            raise PDFNoValidXRef('Unexpected EOF')
+        return int(prev)
+
+    # read xref table
+    def read_xref_from(self, start, xrefs):
+        self.seek(start)
+        self.reset()
+        try:
+            (pos, token) = self.nexttoken()
+        except PSEOF:
+            raise PDFNoValidXRef('Unexpected EOF')
+        if isinstance(token, int):
+            # XRefStream: PDF-1.5
+            if GEN_XREF_STM == 1:
+                global gen_xref_stm
+                gen_xref_stm = True
+            self.seek(pos)
+            self.reset()
+            xref = PDFXRefStream()
+            xref.load(self)
+        else:
+            if token is not self.KEYWORD_XREF:
+                raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
+                                     (pos, token))
+            self.nextline()
+            xref = PDFXRef()
+            xref.load(self)
+        xrefs.append(xref)
+        trailer = xref.trailer
+        if 'XRefStm' in trailer:
+            pos = int_value(trailer['XRefStm'])
+            self.read_xref_from(pos, xrefs)
+        if 'Prev' in trailer:
+            # find previous xref
+            pos = int_value(trailer['Prev'])
+            self.read_xref_from(pos, xrefs)
+        return
+
+    # read xref tables and trailers
+    def read_xref(self):
+        xrefs = []
+        trailerpos = None
+        try:
+            pos = self.find_xref()
+            self.read_xref_from(pos, xrefs)
+        except PDFNoValidXRef:
+            # fallback
+            self.seek(0)
+            pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
+            offsets = {}
+            xref = PDFXRef()
+            while 1:
+                try:
+                    (pos, line) = self.nextline()
+                except PSEOF:
+                    break
+                if line.startswith('trailer'):
+                    trailerpos = pos # remember last trailer
+                m = pat.match(line)
+                if not m: continue
+                (objid, genno) = m.groups()
+                offsets[int(objid)] = (0, pos)
+            if not offsets: raise
+            xref.offsets = offsets
+            if trailerpos:
+                self.seek(trailerpos)
+                xref.load_trailer(self)
+                xrefs.append(xref)
+        return xrefs
+
+##  PDFObjStrmParser
+##
+class PDFObjStrmParser(PDFParser):
+
+    def __init__(self, data, doc):
+        PSStackParser.__init__(self, StringIO(data))
+        self.doc = doc
+        return
+
+    def flush(self):
+        self.add_results(*self.popall())
+        return
+
+    KEYWORD_R = KWD('R')
+    def do_keyword(self, pos, token):
+        if token is self.KEYWORD_R:
+            # reference to indirect object
+            try:
+                ((_,objid), (_,genno)) = self.pop(2)
+                (objid, genno) = (int(objid), int(genno))
+                obj = PDFObjRef(self.doc, objid, genno)
+                self.push((pos, obj))
+            except PSSyntaxError:
+                pass
+            return
+        # others
+        self.push((pos, token))
+        return
+
+###
+### My own code, for which there is none else to blame
+
+class PDFSerializer(object):
+    def __init__(self, inf, keypath):
+        global GEN_XREF_STM, gen_xref_stm
+        gen_xref_stm = GEN_XREF_STM > 1
+        self.version = inf.read(8)
+        inf.seek(0)
+        self.doc = doc = PDFDocument()
+        parser = PDFParser(doc, inf)
+        doc.initialize(keypath)
+        self.objids = objids = set()
+        for xref in reversed(doc.xrefs):
+            trailer = xref.trailer
+            for objid in xref.objids():
+                objids.add(objid)
+        trailer = dict(trailer)
+        trailer.pop('Prev', None)
+        trailer.pop('XRefStm', None)
+        if 'Encrypt' in trailer:
+            objids.remove(trailer.pop('Encrypt').objid)
+        self.trailer = trailer
+
+    def dump(self, outf):
+        self.outf = outf
+        self.write(self.version)
+        self.write('\n%\xe2\xe3\xcf\xd3\n')
+        doc = self.doc
+        objids = self.objids
+        xrefs = {}
+        maxobj = max(objids)
+        trailer = dict(self.trailer)
+        trailer['Size'] = maxobj + 1
+        for objid in objids:
+            obj = doc.getobj(objid)
+            if isinstance(obj, PDFObjStmRef):
+                xrefs[objid] = obj
+                continue
+            if obj is not None:
+                try:
+                    genno = obj.genno
+                except AttributeError:
+                    genno = 0
+                xrefs[objid] = (self.tell(), genno)
+                self.serialize_indirect(objid, obj)
+        startxref = self.tell()
+
+        if not gen_xref_stm:
+            self.write('xref\n')
+            self.write('0 %d\n' % (maxobj + 1,))
+            for objid in xrange(0, maxobj + 1):
+                if objid in xrefs:
+                    # force the genno to be 0
+                    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)
+
+        else: # Generate crossref stream.
+
+            # Calculate size of entries
+            maxoffset = max(startxref, maxobj)
+            maxindex = PDFObjStmRef.maxindex
+            fl2 = 2
+            power = 65536
+            while maxoffset >= power:
+                fl2 += 1
+                power *= 256
+            fl3 = 1
+            power = 256
+            while maxindex >= power:
+                fl3 += 1
+                power *= 256
+
+            index = []
+            first = None
+            prev = None
+            data = []
+            # Put the xrefstream's reference in itself
+            startxref = self.tell()
+            maxobj += 1
+            xrefs[maxobj] = (startxref, 0)
+            for objid in sorted(xrefs):
+                if first is None:
+                    first = objid
+                elif objid != prev + 1:
+                    index.extend((first, prev - first + 1))
+                    first = objid
+                prev = objid
+                objref = xrefs[objid]
+                if isinstance(objref, PDFObjStmRef):
+                    f1 = 2
+                    f2 = objref.stmid
+                    f3 = objref.index
+                else:
+                    f1 = 1
+                    f2 = objref[0]
+                    # 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),
+                   'Filter': LITERALS_FLATE_DECODE[0],
+                   'Root': trailer['Root'],}
+            if 'Info' in trailer:
+                dic['Info'] = trailer['Info']
+            xrefstm = PDFStream(dic, data)
+            self.serialize_indirect(maxobj, xrefstm)
+            self.write('startxref\n%d\n%%%%EOF' % startxref)
+    def write(self, data):
+        self.outf.write(data)
+        self.last = data[-1:]
+
+    def tell(self):
+        return self.outf.tell()
+
+    def escape_string(self, string):
+        string = string.replace('\\', '\\\\')
+        string = string.replace('\n', r'\n')
+        string = string.replace('(', r'\(')
+        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')
+        return string
+
+    def serialize_object(self, obj):
+        if isinstance(obj, dict):
+            # Correct malformed Mac OS resource forks for Stanza
+            if 'ResFork' in obj and 'Type' in obj and 'Subtype' not in obj \
+                   and isinstance(obj['Type'], int):
+                obj['Subtype'] = obj['Type']
+                del obj['Type']
+            # end - hope this doesn't have bad effects
+            self.write('<<')
+            for key, val in obj.items():
+                self.write('/%s' % key)
+                self.serialize_object(val)
+            self.write('>>')
+        elif isinstance(obj, list):
+            self.write('[')
+            for val in obj:
+                self.serialize_object(val)
+            self.write(']')
+        elif isinstance(obj, str):
+            self.write('(%s)' % self.escape_string(obj))
+        elif isinstance(obj, bool):
+            if self.last.isalnum():
+                self.write(' ')
+            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('%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)')
+            else:
+                data = obj.get_decdata()
+                self.serialize_object(obj.dic)
+                self.write('stream\n')
+                self.write(data)
+                self.write('\nendstream')
+        else:
+            data = str(obj)
+            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)
+        if self.last.isalnum():
+            self.write('\n')
+        self.write('endobj\n')
+
+def cli_main(argv=sys.argv):
+    progname = os.path.basename(argv[0])
+    if RSA is None:
+        print "%s: This script requires PyCrypto, which must be installed " \
+              "separately.  Read the top-of-script comment for details." % \
+              (progname,)
+        return 1
+    if len(argv) != 4:
+        print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+        return 1
+    keypath, inpath, outpath = argv[1:]
+    with open(inpath, 'rb') as inf:
+        serializer = PDFSerializer(inf, keypath)
+        # hope this will fix the 'bad file descriptor' problem
+        with open(outpath, 'wb') as outf:
+        # help construct to make sure the method runs to the end
+            serializer.dump(outf)
+    return 0
+
+
+class DecryptionDialog(Tkinter.Frame):
+    def __init__(self, root):
+        # debug mode debugging
+        global DEBUG_MODE
+        Tkinter.Frame.__init__(self, root, border=5)
+        ltext='Select file for decryption\n(Ignore Password / Key file option for Fileopen/APS PDFs)'
+        self.status = Tkinter.Label(self, text=ltext)
+        self.status.pack(fill=Tkconstants.X, expand=1)
+        body = Tkinter.Frame(self)
+        body.pack(fill=Tkconstants.X, expand=1)
+        sticky = Tkconstants.E + Tkconstants.W
+        body.grid_columnconfigure(1, weight=2)
+        Tkinter.Label(body, text='Password\nor Key file').grid(row=0)
+        self.keypath = Tkinter.Entry(body, width=30)
+        self.keypath.grid(row=0, column=1, sticky=sticky)
+        if os.path.exists('adeptkey.der'):
+            self.keypath.insert(0, 'adeptkey.der')
+        button = Tkinter.Button(body, text="...", command=self.get_keypath)
+        button.grid(row=0, column=2)
+        Tkinter.Label(body, text='Input file').grid(row=1)
+        self.inpath = Tkinter.Entry(body, width=30)
+        self.inpath.grid(row=1, column=1, sticky=sticky)
+        button = Tkinter.Button(body, text="...", command=self.get_inpath)
+        button.grid(row=1, column=2)
+        Tkinter.Label(body, text='Output file').grid(row=2)
+        self.outpath = Tkinter.Entry(body, width=30)
+        self.outpath.grid(row=2, column=1, sticky=sticky)
+        debugmode = Tkinter.Checkbutton(self, text = "Debug Mode (writable directory required)", command=self.debug_toggle, height=2, \
+                 width = 40)
+        debugmode.pack()
+        button = Tkinter.Button(body, text="...", command=self.get_outpath)
+        button.grid(row=2, column=2)
+        buttons = Tkinter.Frame(self)
+        buttons.pack()
+
+
+        botton = Tkinter.Button(
+            buttons, text="Decrypt", width=10, command=self.decrypt)
+        botton.pack(side=Tkconstants.LEFT)
+        Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+        button = Tkinter.Button(
+            buttons, text="Quit", width=10, command=self.quit)
+        button.pack(side=Tkconstants.RIGHT)
+
+
+    def get_keypath(self):
+        keypath = tkFileDialog.askopenfilename(
+            parent=None, title='Select ADEPT key file',
+            defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
+                                                ('All Files', '.*')])
+        if keypath:
+            keypath = os.path.normpath(os.path.realpath(keypath))
+            self.keypath.delete(0, Tkconstants.END)
+            self.keypath.insert(0, keypath)
+        return
+
+    def get_inpath(self):
+        inpath = tkFileDialog.askopenfilename(
+            parent=None, title='Select ADEPT or FileOpen-encrypted PDF file to decrypt',
+            defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
+                                                 ('All files', '.*')])
+        if inpath:
+            inpath = os.path.normpath(os.path.realpath(inpath))
+            self.inpath.delete(0, Tkconstants.END)
+            self.inpath.insert(0, inpath)
+        return
+
+    def debug_toggle(self):
+        global DEBUG_MODE
+        if DEBUG_MODE == False:
+            DEBUG_MODE = True
+        else:
+            DEBUG_MODE = False
+
+    def get_outpath(self):
+        outpath = tkFileDialog.asksaveasfilename(
+            parent=None, title='Select unencrypted PDF file to produce',
+            defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
+                                                 ('All files', '.*')])
+        if outpath:
+            outpath = os.path.normpath(os.path.realpath(outpath))
+            self.outpath.delete(0, Tkconstants.END)
+            self.outpath.insert(0, outpath)
+        return
+
+    def decrypt(self):
+        global INPUTFILEPATH
+        global KEYFILEPATH
+        global PASSWORD
+        keypath = self.keypath.get()
+        inpath = self.inpath.get()
+        outpath = self.outpath.get()
+        if not keypath or not os.path.exists(keypath):
+            # keyfile doesn't exist
+            KEYFILEPATH = False
+            PASSWORD = keypath
+        if not inpath or not os.path.exists(inpath):
+            self.status['text'] = 'Specified input file does not exist'
+            return
+        if not outpath:
+            self.status['text'] = 'Output file not specified'
+            return
+        if inpath == outpath:
+            self.status['text'] = 'Must have different input and output files'
+            return
+        # patch for non-ascii characters
+        INPUTFILEPATH = inpath.encode('utf-8')
+        argv = [sys.argv[0], keypath, inpath, outpath]
+        self.status['text'] = 'Processing ...'
+        try:
+            cli_main(argv)
+        except Exception, a:
+            self.status['text'] = 'Error: ' + str(a)
+            return
+        self.status['text'] = 'File successfully decrypted.\n'+\
+                              'Close this window or decrypt another pdf file.'
+        return
+
+def gui_main():
+    root = Tkinter.Tk()
+    if RSA is None:
+        root.withdraw()
+        tkMessageBox.showerror(
+            "INEPT PDF and FileOpen Decrypter",
+            "This script requires PyCrypto, which must be installed "
+            "separately.  Read the top-of-script comment for details.")
+        return 1
+    root.title('INEPT PDF Decrypter 8.4.51 (FileOpen/APS-Support)')
+    root.resizable(True, False)
+    root.minsize(370, 0)
+    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+    root.mainloop()
+    return 0
+
+
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        sys.exit(cli_main())
+    sys.exit(gui_main())
diff --git a/Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51_ReadMe.txt b/Other_Tools/Tetrachroma_FileOpen_ineptpdf/ineptpdf_8.4.51_ReadMe.txt
new file mode 100644 (file)
index 0000000..0d2e401
--- /dev/null
@@ -0,0 +1,8 @@
+ineptpdf 8.4.51
+---------------
+
+This is a version of the ineptpdf script produced by TetraChroma that can remove, on Windows, "FileOpen" DRM.
+
+No support for this script is offered at Apprentice Alf's blog.
+
+Trtrachroma's blog is http://tetrachroma.wordpress.com/
index ffb5f32c09ac1fc631515e11f1fed8557c8ec8ab..cb3dd5cd9d42d6a699a5acb4d779b5d9e3a84885 100644 (file)
@@ -1,7 +1,7 @@
 Welcome to the tools!
 =====================
 
-This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.5.3 archive.
+This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.6 archive.
 
 The is archive includes tools to remove DRM from:
 
@@ -51,7 +51,7 @@ DeDRM application for Mac OS X users: (Mac OS X 10.4 and above)
 ----------------------------------------------------------------------
 This application combines all the tools into one easy-to-use tool for Mac OS X users.
 
-Drag the "DeDRM 5.5.3.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
+Drag the "DeDRM 5.6.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
 
 To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above.
 
@@ -67,7 +67,7 @@ DeDRM application for Windows users: (Windows XP through Windows 8)
 
 This application combines all the tools into one easy-to-use tool for Windows users.
 
-Drag the DeDRM_5.5.3 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.5.3 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
+Drag the DeDRM_5.6 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.6 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
 
 To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above.