tools v5.6
This commit is contained in:
parent
602ff30b3a
commit
c23b903420
|
@ -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.
|
||||
|
|
@ -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)
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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_
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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.
|
||||
|
|
|
@ -1,420 +1,98 @@
|
|||
#!/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
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
<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>
|
||||
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
<h3>Configuration:</h3>
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
<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&N key—or migrate your existing key(s)/data from an earlier version of the plugin—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>
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
<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>
|
||||
|
||||
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>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>
|
||||
|
||||
<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>
|
||||
|
||||
class IGNOBLEError(Exception):
|
||||
pass
|
||||
<h4 style="margin-left: 1.0em;"><u>Creating New Keys:</u></h4>
|
||||
|
||||
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
|
||||
<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>
|
||||
|
||||
if iswindows:
|
||||
libcrypto = find_library('libeay32')
|
||||
else:
|
||||
libcrypto = find_library('crypto')
|
||||
<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>
|
||||
|
||||
if libcrypto is None:
|
||||
raise IGNOBLEError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
<h4 style="margin-left: 1.0em;"><u>Deleting Keys:</u></h4>
|
||||
|
||||
AES_MAXNR = 14
|
||||
<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>
|
||||
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
<h4 style="margin-left: 1.0em;"><u>Exporting Keys:</u></h4>
|
||||
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||
('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
<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>
|
||||
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
<h4 style="margin-left: 1.0em;"><u>Importing Existing Keyfiles:</u></h4>
|
||||
|
||||
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 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 <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>
|
||||
|
||||
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>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>
|
||||
|
||||
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
|
||||
<h3>Troubleshooting:</h3>
|
||||
|
||||
return AES
|
||||
<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>
|
||||
|
||||
def _load_crypto_pycrypto():
|
||||
from Crypto.Cipher import AES as _AES
|
||||
<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, key):
|
||||
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||
<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> </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):
|
||||
return self._aes.decrypt(data)
|
||||
<p> </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():
|
||||
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>
|
||||
|
|
|
@ -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 != ' ')
|
||||
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
|
||||
|
||||
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')
|
||||
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)
|
||||
|
||||
name = normalize_name(name) + '\x00'
|
||||
ccn = normalize_name(ccn) + '\x00'
|
||||
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
|
||||
|
||||
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 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)
|
||||
|
|
|
@ -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())
|
Binary file not shown.
Binary file not shown.
|
@ -1,155 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import zlib
|
||||
import zipfilerugged
|
||||
import os
|
||||
import os.path
|
||||
import getopt
|
||||
from struct import unpack
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
_FILENAME_LEN_OFFSET = 26
|
||||
_EXTRA_LEN_OFFSET = 28
|
||||
_FILENAME_OFFSET = 30
|
||||
_MAX_SIZE = 64 * 1024
|
||||
_MIMETYPE = 'application/epub+zip'
|
||||
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.'
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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())
|
||||
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
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
result = ineptepub.decryptBook(userkey, 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
|
||||
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.
|
||||
|
|
|
@ -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"
|
||||
|
|
Binary file not shown.
|
@ -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.\
|
||||
\
|
||||
\
|
||||
|
||||
|
|
|
@ -24,17 +24,17 @@
|
|||
<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>
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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__))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
from Queue import Full
|
||||
from Queue import Empty
|
||||
from multiprocessing import Process, Queue
|
||||
|
||||
__version__ = '5.5.3'
|
||||
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
|
||||
# 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)
|
||||
|
||||
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 + '" '
|
||||
# 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)
|
||||
|
||||
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
|
||||
# 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)
|
||||
|
||||
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
|
||||
# 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=sys.argv):
|
||||
apphome = os.path.dirname(sys.argv[0])
|
||||
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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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),
|
||||
|
|
|
@ -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())
|
|
@ -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())
|
|
@ -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())
|
|
@ -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)))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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__))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
-------------
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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/
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue